pierreguillou commited on
Commit
577dea7
·
verified ·
1 Parent(s): 07a8e96

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +401 -0
app.py ADDED
@@ -0,0 +1,401 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ import time
4
+ import requests
5
+ from datetime import datetime
6
+ from langchain_openai import ChatOpenAI
7
+ from langchain_anthropic import ChatAnthropic
8
+ from langchain_google_genai import ChatGoogleGenerativeAI
9
+ from langchain_core.messages import HumanMessage
10
+ import PyPDF2
11
+ import docx
12
+ import pandas as pd
13
+ from pptx import Presentation
14
+ import io
15
+ import tempfile
16
+ from urllib.parse import urlparse
17
+ import re
18
+
19
+ # Configuration des modèles
20
+ MODELS = {
21
+ "Gemini 2.5 Flash (Google AI)": {
22
+ "provider": "Google AI",
23
+ "class": ChatGoogleGenerativeAI,
24
+ "model_name": "gemini-2.0-flash-exp",
25
+ "default_api": True
26
+ },
27
+ "ChatGPT 5 (OpenAI)": {
28
+ "provider": "OpenAI",
29
+ "class": ChatOpenAI,
30
+ "model_name": "gpt-4o",
31
+ "default_api": False
32
+ },
33
+ "Claude Sonnet 4 (Anthropic)": {
34
+ "provider": "Anthropic",
35
+ "class": ChatAnthropic,
36
+ "model_name": "claude-3-5-sonnet-20241022",
37
+ "default_api": False
38
+ },
39
+ "Gemini 2.5 Pro (Google AI)": {
40
+ "provider": "Google AI",
41
+ "class": ChatGoogleGenerativeAI,
42
+ "model_name": "gemini-2.0-flash-exp",
43
+ "default_api": False
44
+ }
45
+ }
46
+
47
+ # API par défaut pour Gemini 2.5 Flash
48
+ DEFAULT_GEMINI_API = os.getenv("GOOGLE_API_KEY", "")
49
+
50
+ def extract_text_from_file(file):
51
+ """Extrait le texte d'un fichier uploadé"""
52
+ if file is None:
53
+ return ""
54
+
55
+ file_extension = os.path.splitext(file.name)[1].lower()
56
+
57
+ try:
58
+ if file_extension == '.pdf':
59
+ with open(file.name, 'rb') as f:
60
+ reader = PyPDF2.PdfReader(f)
61
+ text = ""
62
+ for page in reader.pages:
63
+ text += page.extract_text() + "\n"
64
+ return text
65
+
66
+ elif file_extension == '.docx':
67
+ doc = docx.Document(file.name)
68
+ text = ""
69
+ for paragraph in doc.paragraphs:
70
+ text += paragraph.text + "\n"
71
+ return text
72
+
73
+ elif file_extension == '.txt':
74
+ with open(file.name, 'r', encoding='utf-8') as f:
75
+ return f.read()
76
+
77
+ elif file_extension in ['.xlsx', '.xls']:
78
+ df = pd.read_excel(file.name)
79
+ return df.to_string()
80
+
81
+ elif file_extension == '.pptx':
82
+ prs = Presentation(file.name)
83
+ text = ""
84
+ for slide in prs.slides:
85
+ for shape in slide.shapes:
86
+ if hasattr(shape, "text"):
87
+ text += shape.text + "\n"
88
+ return text
89
+
90
+ else:
91
+ return "Format de fichier non supporté"
92
+
93
+ except Exception as e:
94
+ return f"Erreur lors de la lecture du fichier: {str(e)}"
95
+
96
+ def extract_text_from_url(url):
97
+ """Extrait le texte d'une URL"""
98
+ try:
99
+ response = requests.get(url, timeout=10)
100
+ response.raise_for_status()
101
+
102
+ # Simple extraction du contenu textuel
103
+ content = response.text
104
+ # Suppression basique des balises HTML
105
+ content = re.sub(r'<[^>]+>', '', content)
106
+ content = re.sub(r'\s+', ' ', content).strip()
107
+
108
+ return content[:10000] # Limite à 10k caractères
109
+
110
+ except Exception as e:
111
+ return f"Erreur lors de la récupération de l'URL: {str(e)}"
112
+
113
+ def get_document_content(text_input, url_input, file_input):
114
+ """Récupère le contenu du document selon la source"""
115
+ if text_input.strip():
116
+ return text_input.strip()
117
+ elif url_input.strip():
118
+ return extract_text_from_url(url_input.strip())
119
+ elif file_input is not None:
120
+ return extract_text_from_file(file_input)
121
+ else:
122
+ return ""
123
+
124
+ def create_llm_instance(model_name, api_key):
125
+ """Crée une instance du modèle LLM"""
126
+ model_config = MODELS[model_name]
127
+
128
+ if model_config["provider"] == "OpenAI":
129
+ return model_config["class"](
130
+ model=model_config["model_name"],
131
+ api_key=api_key,
132
+ temperature=0.7
133
+ )
134
+ elif model_config["provider"] == "Anthropic":
135
+ return model_config["class"](
136
+ model=model_config["model_name"],
137
+ api_key=api_key,
138
+ temperature=0.7
139
+ )
140
+ elif model_config["provider"] == "Google AI":
141
+ api_to_use = api_key if api_key else DEFAULT_GEMINI_API
142
+ return model_config["class"](
143
+ model=model_config["model_name"],
144
+ google_api_key=api_to_use,
145
+ temperature=0.7
146
+ )
147
+
148
+ def generate_html(model_name, api_key, text_input, url_input, file_input):
149
+ """Génère le fichier HTML éducatif"""
150
+ start_time = time.time()
151
+
152
+ # Validation des entrées
153
+ if model_name != "Gemini 2.5 Flash (Google AI)" and not api_key.strip():
154
+ return None, "❌ Erreur: Veuillez fournir une clé API pour ce modèle.", 0
155
+
156
+ document_content = get_document_content(text_input, url_input, file_input)
157
+ if not document_content:
158
+ return None, "❌ Erreur: Veuillez fournir un document (texte, URL ou fichier).", 0
159
+
160
+ try:
161
+ # Création de l'instance LLM
162
+ llm = create_llm_instance(model_name, api_key)
163
+
164
+ # Lecture du prompt template
165
+ with open("creation_educational_html_from_any_document_18082025.txt", "r", encoding="utf-8") as f:
166
+ prompt_template = f.read()
167
+
168
+ # Remplacement des variables
169
+ model_config = MODELS[model_name]
170
+ prompt = prompt_template.format(
171
+ model_name=model_config["model_name"],
172
+ provider_name=model_config["provider"],
173
+ document=document_content
174
+ )
175
+
176
+ # Génération du contenu
177
+ message = HumanMessage(content=prompt)
178
+ response = llm.invoke([message])
179
+
180
+ html_content = response.content
181
+
182
+ # Calcul du temps de génération
183
+ generation_time = time.time() - start_time
184
+
185
+ # Sauvegarde du fichier HTML
186
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
187
+ filename = f"document_educatif_{timestamp}.html"
188
+
189
+ with open(filename, "w", encoding="utf-8") as f:
190
+ f.write(html_content)
191
+
192
+ success_message = f"✅ Fichier HTML généré avec succès en {generation_time:.2f} secondes!"
193
+
194
+ return filename, success_message, generation_time
195
+
196
+ except Exception as e:
197
+ error_message = f"❌ Erreur lors de la génération: {str(e)}"
198
+ return None, error_message, 0
199
+
200
+ def reset_form():
201
+ """Remet à zéro le formulaire"""
202
+ return (
203
+ "Gemini 2.5 Flash (Google AI)", # model_name
204
+ "", # api_key
205
+ "", # text_input
206
+ "", # url_input
207
+ None, # file_input
208
+ "", # status_message
209
+ None, # html_file
210
+ "" # html_preview
211
+ )
212
+
213
+ def update_api_info(model_name):
214
+ """Met à jour les informations sur l'API selon le modèle sélectionné"""
215
+ if model_name == "Gemini 2.5 Flash (Google AI)":
216
+ return gr.update(
217
+ label="Clé API (optionnelle)",
218
+ placeholder="API gratuite disponible jusqu'à épuisement, ou utilisez votre propre clé",
219
+ info="💡 Une API gratuite est déjà configurée pour ce modèle. Vous pouvez utiliser votre propre clé si vous le souhaitez."
220
+ )
221
+ else:
222
+ return gr.update(
223
+ label="Clé API (obligatoire)",
224
+ placeholder="Entrez votre clé API",
225
+ info="🔑 Clé API requise pour ce modèle"
226
+ )
227
+
228
+ # Interface Gradio
229
+ with gr.Blocks(
230
+ title="EduHTML Creator - Générateur de contenu éducatif HTML",
231
+ theme=gr.themes.Soft(),
232
+ css="""
233
+ .main-container {
234
+ max-width: 1200px;
235
+ margin: 0 auto;
236
+ padding: 20px;
237
+ }
238
+ .header {
239
+ text-align: center;
240
+ margin-bottom: 30px;
241
+ padding: 30px;
242
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
243
+ border-radius: 15px;
244
+ color: white;
245
+ }
246
+ .form-section {
247
+ background: white;
248
+ padding: 25px;
249
+ border-radius: 15px;
250
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
251
+ margin-bottom: 20px;
252
+ }
253
+ .apple-button {
254
+ background: #007AFF;
255
+ color: white;
256
+ border: none;
257
+ border-radius: 8px;
258
+ padding: 12px 24px;
259
+ font-weight: 500;
260
+ transition: all 0.3s ease;
261
+ }
262
+ .apple-button:hover {
263
+ background: #0056CC;
264
+ transform: translateY(-1px);
265
+ }
266
+ .reset-button {
267
+ background: #FF3B30;
268
+ color: white;
269
+ border: none;
270
+ border-radius: 8px;
271
+ padding: 12px 24px;
272
+ font-weight: 500;
273
+ }
274
+ .status-success {
275
+ color: #34C759;
276
+ font-weight: 500;
277
+ }
278
+ .status-error {
279
+ color: #FF3B30;
280
+ font-weight: 500;
281
+ }
282
+ """
283
+ ) as app:
284
+
285
+ gr.HTML("""
286
+ <div class="header">
287
+ <h1>🎓 EduHTML Creator</h1>
288
+ <p style="font-size: 18px; margin: 10px 0;">Transformez n'importe quel document en contenu éducatif HTML interactif</p>
289
+ <p style="font-size: 14px; opacity: 0.9;">
290
+ Cette application utilise l'intelligence artificielle pour créer des pages HTML éducatives élégantes et interactives
291
+ à partir de vos documents. Le design s'inspire du style Apple pour une expérience utilisateur premium.
292
+ L'objectif éducatif est de faciliter l'apprentissage grâce à la structuration, l'interactivité et la visualisation
293
+ des informations clés de vos documents originaux.
294
+ </p>
295
+ </div>
296
+ """)
297
+
298
+ with gr.Row():
299
+ with gr.Column(scale=1):
300
+ gr.HTML("<div class='form-section'>")
301
+
302
+ # Sélection du modèle
303
+ model_dropdown = gr.Dropdown(
304
+ choices=list(MODELS.keys()),
305
+ value="Gemini 2.5 Flash (Google AI)",
306
+ label="🤖 Modèle LLM",
307
+ info="Choisissez le modèle d'IA à utiliser"
308
+ )
309
+
310
+ # Champ API
311
+ api_input = gr.Textbox(
312
+ label="Clé API (optionnelle)",
313
+ placeholder="API gratuite disponible jusqu'à épuisement, ou utilisez votre propre clé",
314
+ info="💡 Une API gratuite est déjà configurée pour ce modèle. Vous pouvez utiliser votre propre clé si vous le souhaitez.",
315
+ type="password"
316
+ )
317
+
318
+ gr.HTML("</div>")
319
+
320
+ gr.HTML("<div class='form-section'>")
321
+ gr.HTML("<h3>📄 Source du document</h3>")
322
+
323
+ # Entrées de document
324
+ text_input = gr.Textbox(
325
+ label="Texte copié/collé",
326
+ placeholder="Collez votre texte ici...",
327
+ lines=5
328
+ )
329
+
330
+ url_input = gr.Textbox(
331
+ label="Lien Web",
332
+ placeholder="https://exemple.com/article"
333
+ )
334
+
335
+ file_input = gr.File(
336
+ label="Fichier",
337
+ file_types=[".pdf", ".txt", ".docx", ".xlsx", ".xls", ".pptx"]
338
+ )
339
+
340
+ gr.HTML("</div>")
341
+
342
+ # Boutons
343
+ with gr.Row():
344
+ submit_btn = gr.Button(
345
+ "🚀 Générer le HTML",
346
+ variant="primary",
347
+ elem_classes=["apple-button"]
348
+ )
349
+ reset_btn = gr.Button(
350
+ "🔄 Reset",
351
+ elem_classes=["reset-button"]
352
+ )
353
+
354
+ with gr.Column(scale=1):
355
+ # Statut et résultats
356
+ status_output = gr.HTML(label="Statut")
357
+
358
+ # Fichier téléchargeable
359
+ html_file_output = gr.File(
360
+ label="📥 Fichier HTML téléchargeable",
361
+ visible=False
362
+ )
363
+
364
+ # Prévisualisation
365
+ html_preview = gr.HTML(
366
+ label="👀 Prévisualisation",
367
+ visible=False
368
+ )
369
+
370
+ # Événements
371
+ model_dropdown.change(
372
+ fn=update_api_info,
373
+ inputs=[model_dropdown],
374
+ outputs=[api_input]
375
+ )
376
+
377
+ submit_btn.click(
378
+ fn=generate_html,
379
+ inputs=[model_dropdown, api_input, text_input, url_input, file_input],
380
+ outputs=[html_file_output, status_output, gr.State()]
381
+ ).then(
382
+ fn=lambda file, status, time_taken: (
383
+ gr.update(visible=file is not None),
384
+ status,
385
+ gr.update(visible=file is not None, value=open(file, 'r', encoding='utf-8').read() if file else "")
386
+ ),
387
+ inputs=[html_file_output, status_output, gr.State()],
388
+ outputs=[html_file_output, status_output, html_preview]
389
+ )
390
+
391
+ reset_btn.click(
392
+ fn=reset_form,
393
+ outputs=[model_dropdown, api_input, text_input, url_input, file_input, status_output, html_file_output, html_preview]
394
+ )
395
+
396
+ if __name__ == "__main__":
397
+ app.launch(
398
+ server_name="0.0.0.0",
399
+ server_port=7860,
400
+ share=True
401
+ )