Hadiil commited on
Commit
7dd65a7
·
verified ·
1 Parent(s): c868759

Upload 12 files

Browse files
Files changed (12) hide show
  1. Dockerfile +25 -0
  2. README.md +117 -11
  3. app.py +666 -0
  4. docker-compose.yml +12 -0
  5. requirements.txt +16 -0
  6. run.sh +32 -0
  7. static/favicon.svg +18 -0
  8. static/index.html +113 -0
  9. static/scripts.js +405 -0
  10. static/styles.css +724 -0
  11. templates/index.html +113 -0
  12. todo.md +47 -0
Dockerfile ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Installer les dépendances système nécessaires
6
+ RUN apt-get update && apt-get install -y --no-install-recommends \
7
+ build-essential \
8
+ libpq-dev \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ # Copier les fichiers de dépendances et les installer
12
+ COPY requirements.txt .
13
+ RUN pip install --no-cache-dir -r requirements.txt
14
+
15
+ # Créer le répertoire uploads
16
+ RUN mkdir -p uploads
17
+
18
+ # Copier le reste des fichiers de l'application
19
+ COPY . .
20
+
21
+ # Exposer le port
22
+ EXPOSE 8000
23
+
24
+ # Commande de démarrage
25
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
README.md CHANGED
@@ -1,11 +1,117 @@
1
- ---
2
- title: Home23
3
- emoji: 🏢
4
- colorFrom: red
5
- colorTo: gray
6
- sdk: docker
7
- pinned: false
8
- license: mit
9
- ---
10
-
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Cosmic AI Assistant
2
+
3
+ Une application de chatbot IA avec une interface élégante inspirée de l'univers et des galaxies.
4
+
5
+ ## Fonctionnalités
6
+
7
+ - **Interface utilisateur cosmique** avec deux thèmes (sombre et coloré) et animations d'étoiles et particules
8
+ - **Résumé de texte** utilisant le modèle facebook/bart-large-cnn
9
+ - **Description d'images** avec le modèle Salesforce/blip-image-captioning-large
10
+ - **Questions-réponses** basées sur du texte avec deepset/roberta-base-squad2
11
+ - **Questions sur images** (Visual QA) avec Salesforce/blip-vqa-base
12
+ - **Visualisation de données** à partir de fichiers Excel/CSV
13
+ - **Traduction** vers plusieurs langues (français, espagnol, allemand, italien, russe)
14
+ - **Génération de texte** avec GPT-2
15
+
16
+ ## Structure du projet
17
+
18
+ ```
19
+ cosmic_chatbot/
20
+ ├── static/
21
+ │ ├── styles.css # Styles CSS avec thèmes sombre et coloré
22
+ │ ├── scripts.js # JavaScript pour les animations et interactions
23
+ │ ├── index.html # Page principale
24
+ │ └── favicon.svg # Icône du site
25
+ ├── templates/
26
+ │ └── index.html # Template pour le rendu côté serveur
27
+ ├── app.py # Backend FastAPI amélioré
28
+ ├── requirements.txt # Dépendances Python
29
+ ├── run.sh # Script de démarrage
30
+ ├── Dockerfile # Configuration pour Docker
31
+ ├── docker-compose.yml # Configuration pour Docker Compose
32
+ └── README.md # Documentation
33
+ ```
34
+
35
+ ## Installation
36
+
37
+ ### Méthode 1 : Installation standard
38
+
39
+ 1. Assurez-vous d'avoir Python 3.8+ installé
40
+ 2. Clonez ce dépôt
41
+ 3. Exécutez le script d'installation et de démarrage :
42
+
43
+ ```bash
44
+ ./run.sh
45
+ ```
46
+
47
+ Le script installera toutes les dépendances nécessaires et démarrera l'application.
48
+
49
+ ### Méthode 2 : Installation avec Docker
50
+
51
+ 1. Assurez-vous d'avoir Docker et Docker Compose installés
52
+ 2. Clonez ce dépôt
53
+ 3. Construisez et démarrez le conteneur :
54
+
55
+ ```bash
56
+ docker-compose up --build
57
+ ```
58
+
59
+ Pour exécuter en arrière-plan :
60
+
61
+ ```bash
62
+ docker-compose up -d
63
+ ```
64
+
65
+ Pour arrêter le conteneur :
66
+
67
+ ```bash
68
+ docker-compose down
69
+ ```
70
+
71
+ ## Utilisation avec Hugging Face Spaces
72
+
73
+ Ce projet est compatible avec Hugging Face Spaces qui utilise Docker. Pour déployer sur Hugging Face :
74
+
75
+ 1. Créez un nouveau Space de type "Docker"
76
+ 2. Téléchargez tous les fichiers du projet dans votre Space
77
+ 3. Le Dockerfile et docker-compose.yml fournis sont déjà configurés pour fonctionner avec Hugging Face
78
+
79
+ ## Utilisation
80
+
81
+ Accédez à l'application via votre navigateur à l'adresse : http://localhost:8000
82
+
83
+ L'interface vous permet de :
84
+ - Basculer entre les thèmes sombre et coloré
85
+ - Explorer les différentes fonctionnalités via le bouton "Fonctionnalités"
86
+ - Télécharger des fichiers (images, documents, données)
87
+ - Interagir avec l'IA via le champ de texte
88
+
89
+ ## Améliorations apportées
90
+
91
+ ### Interface utilisateur
92
+ - Design élégant inspiré de l'univers et des galaxies
93
+ - Animations d'étoiles et de particules flottantes
94
+ - Deux thèmes (sombre et coloré) avec transition fluide
95
+ - Présentation visuelle des fonctionnalités
96
+ - Affichage amélioré des messages et du code
97
+ - Interface responsive pour mobile et desktop
98
+
99
+ ### Backend
100
+ - Modèles IA améliorés et plus performants
101
+ - Détection d'intention plus sophistiquée
102
+ - Gestion des erreurs robuste
103
+ - Nouvelles fonctionnalités (Visual QA, génération de texte)
104
+ - Support de plus de langues pour la traduction
105
+ - Génération de code de visualisation plus avancée
106
+
107
+ ## Notes techniques
108
+
109
+ - L'application utilise FastAPI pour le backend
110
+ - Les modèles sont chargés à la demande et mis en cache pour de meilleures performances
111
+ - Le premier chargement peut prendre quelques minutes pour télécharger les modèles
112
+ - Pour les grandes instances, un GPU est recommandé pour de meilleures performances
113
+ - La configuration Docker inclut toutes les dépendances nécessaires
114
+
115
+ ## Licence
116
+
117
+ Ce projet est sous licence MIT.
app.py ADDED
@@ -0,0 +1,666 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, UploadFile, File, Form, HTTPException, Request
2
+ from fastapi.staticfiles import StaticFiles
3
+ from fastapi.responses import RedirectResponse, JSONResponse, HTMLResponse
4
+ from fastapi.templating import Jinja2Templates
5
+ from transformers import pipeline, MarianMTModel, MarianTokenizer, BlipProcessor, BlipForConditionalGeneration
6
+ from typing import Optional, Dict, Any, List
7
+ import logging
8
+ import time
9
+ import os
10
+ import io
11
+ import json
12
+ from PIL import Image
13
+ from docx import Document
14
+ import fitz # PyMuPDF
15
+ import pandas as pd
16
+ from functools import lru_cache
17
+ import re
18
+ import torch
19
+ import numpy as np
20
+ from pydantic import BaseModel
21
+
22
+ # Configure logging
23
+ logging.basicConfig(
24
+ level=logging.INFO,
25
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
26
+ )
27
+ logger = logging.getLogger("cosmic_ai")
28
+
29
+ # Create app directory if it doesn't exist
30
+ os.makedirs("uploads", exist_ok=True)
31
+
32
+ app = FastAPI(
33
+ title="Cosmic AI Assistant",
34
+ description="Une IA avancée avec interface inspirée de l'univers",
35
+ version="2.0.0"
36
+ )
37
+
38
+ # Mount static files
39
+ app.mount("/static", StaticFiles(directory="static"), name="static")
40
+
41
+ # Setup templates
42
+ templates = Jinja2Templates(directory="templates")
43
+
44
+ # Model configurations with improved models
45
+ MODELS = {
46
+ "summarization": "facebook/bart-large-cnn", # Upgraded from t5-small
47
+ "translation": {
48
+ "fr": "Helsinki-NLP/opus-mt-en-fr",
49
+ "es": "Helsinki-NLP/opus-mt-en-es",
50
+ "de": "Helsinki-NLP/opus-mt-en-de",
51
+ "it": "Helsinki-NLP/opus-mt-en-it", # Added Italian
52
+ "ru": "Helsinki-NLP/opus-mt-en-ru", # Added Russian
53
+ },
54
+ "image-to-text": "Salesforce/blip-image-captioning-large", # Upgraded from base
55
+ "question-answering": "deepset/roberta-base-squad2",
56
+ "visual-qa": "Salesforce/blip-vqa-base", # Added Visual QA model
57
+ "text-generation": "gpt2-medium" # Added text generation
58
+ }
59
+
60
+ # Cache for model loading to improve performance
61
+ @lru_cache(maxsize=8) # Increased cache size
62
+ def load_model(task: str, model_name: str = None):
63
+ """Cached model loader with proper task names and error handling"""
64
+ try:
65
+ logger.info(f"Loading model for task: {task}, model: {model_name or MODELS.get(task)}")
66
+ start_time = time.time()
67
+
68
+ if task == "translation" and model_name:
69
+ tokenizer = MarianTokenizer.from_pretrained(model_name)
70
+ model = MarianMTModel.from_pretrained(model_name)
71
+ pipe = pipeline("translation", model=model, tokenizer=tokenizer)
72
+ elif task == "visual-qa":
73
+ # Special handling for BLIP VQA model
74
+ processor = BlipProcessor.from_pretrained(model_name or MODELS.get(task))
75
+ model = BlipForConditionalGeneration.from_pretrained(model_name or MODELS.get(task))
76
+
77
+ # Return a custom callable instead of a pipeline
78
+ def visual_qa_fn(image, question):
79
+ inputs = processor(image, question, return_tensors="pt")
80
+ outputs = model.generate(**inputs)
81
+ return processor.decode(outputs[0], skip_special_tokens=True)
82
+
83
+ pipe = visual_qa_fn
84
+ else:
85
+ # Use device="cuda" if GPU is available
86
+ device = 0 if torch.cuda.is_available() else -1
87
+ pipe = pipeline(task, model=model_name or MODELS.get(task), device=device)
88
+
89
+ load_time = time.time() - start_time
90
+ logger.info(f"Model loaded in {load_time:.2f} seconds")
91
+ return pipe
92
+ except Exception as e:
93
+ logger.error(f"Model load failed: {str(e)}")
94
+ raise HTTPException(status_code=500, detail=f"Model loading failed: {task} - {str(e)}")
95
+
96
+ def detect_intent(text: str = None, file: UploadFile = None) -> str:
97
+ """Enhanced intent detection with more sophisticated patterns"""
98
+ # File-based detection with improved mime type handling
99
+ if file:
100
+ content_type = file.content_type.lower() if file.content_type else ""
101
+ filename = file.filename.lower() if file.filename else ""
102
+
103
+ if content_type.startswith('image/'):
104
+ # Check for visual QA pattern in text
105
+ if text and any(q in text.lower() for q in ['what is', 'what\'s', 'describe', 'tell me about', 'explain']):
106
+ return "visual-qa"
107
+ return "image-to-text"
108
+ elif filename.endswith(('.xlsx', '.xls', '.csv')):
109
+ return "visualize"
110
+ elif filename.endswith(('.pdf', '.docx', '.doc', '.txt', '.rtf')):
111
+ return "summarize"
112
+
113
+ # No text provided
114
+ if not text:
115
+ return "unknown"
116
+
117
+ text_lower = text.lower()
118
+
119
+ # Translation detection with improved language detection
120
+ lang_patterns = {
121
+ 'fr': [r'\b(français|traduire en français|fr)\b', r'translate to french'],
122
+ 'es': [r'\b(español|spanish|traduire en espagnol|es)\b', r'translate to spanish'],
123
+ 'de': [r'\b(deutsch|german|traduire en allemand|de)\b', r'translate to german'],
124
+ 'it': [r'\b(italiano|italian|traduire en italien|it)\b', r'translate to italian'],
125
+ 'ru': [r'\b(русский|russian|traduire en russe|ru)\b', r'translate to russian'],
126
+ }
127
+
128
+ for lang, patterns in lang_patterns.items():
129
+ if any(re.search(pattern, text_lower) for pattern in patterns):
130
+ return f"translate-{lang}"
131
+
132
+ if re.search(r'\b(translate|traduire|translation)\b', text_lower):
133
+ return "translate-fr" # Default to French if language not specified
134
+
135
+ # Question detection with improved patterns
136
+ question_patterns = [
137
+ r'\b(what|when|where|why|how|who|which)\b',
138
+ r'\?',
139
+ r'\b(explain|tell me|describe|define)\b'
140
+ ]
141
+
142
+ if any(re.search(pattern, text_lower) for pattern in question_patterns):
143
+ return "question-answering"
144
+
145
+ # Text generation detection
146
+ generation_patterns = [
147
+ r'\b(write|generate|create|compose)\b',
148
+ r'\b(story|poem|essay|text|content)\b'
149
+ ]
150
+
151
+ if any(re.search(pattern, text_lower) for pattern in generation_patterns):
152
+ return "text-generation"
153
+
154
+ # Summarization for long text
155
+ if len(text) > 100:
156
+ return "summarize"
157
+
158
+ return "unknown"
159
+
160
+ class ProcessResponse(BaseModel):
161
+ response: str
162
+ type: str
163
+ additional_data: Optional[Dict[str, Any]] = None
164
+
165
+ @app.post("/process", response_model=ProcessResponse)
166
+ async def process_input(
167
+ request: Request,
168
+ text: str = Form(None),
169
+ file: UploadFile = File(None)
170
+ ):
171
+ """Enhanced unified endpoint with improved processing and error handling"""
172
+ start_time = time.time()
173
+ client_ip = request.client.host
174
+ logger.info(f"Request from {client_ip}: text={text[:50] + '...' if text and len(text) > 50 else text}, file={file.filename if file else None}")
175
+
176
+ # Detect intent
177
+ if text and "translate" in text.lower():
178
+ # Check for specific language in translation request
179
+ for lang in MODELS["translation"].keys():
180
+ if lang in text.lower():
181
+ intent = f"translate-{lang}"
182
+ break
183
+ else:
184
+ intent = "translate-fr" # Default to French
185
+ else:
186
+ intent = detect_intent(text, file)
187
+
188
+ logger.info(f"Detected intent: {intent}")
189
+
190
+ try:
191
+ # Process based on intent
192
+ if intent == "summarize":
193
+ content = await extract_text_from_file(file) if file else text
194
+ summarizer = load_model("summarization")
195
+
196
+ # Handle long content with chunking
197
+ if len(content) > 1024:
198
+ chunks = [content[i:i+1024] for i in range(0, len(content), 1024)]
199
+ summaries = []
200
+
201
+ for chunk in chunks[:3]: # Limit to first 3 chunks to avoid excessive processing
202
+ summary = summarizer(chunk, max_length=150, min_length=30, do_sample=False)
203
+ summaries.append(summary[0]['summary_text'])
204
+
205
+ final_summary = " ".join(summaries)
206
+ else:
207
+ summary = summarizer(content, max_length=150, min_length=30, do_sample=False)
208
+ final_summary = summary[0]['summary_text']
209
+
210
+ return {"response": final_summary, "type": "summary"}
211
+
212
+ elif intent.startswith("translate-"):
213
+ lang = intent.split("-")[1]
214
+ content = await extract_text_from_file(file) if file else text
215
+
216
+ # Remove the translation instruction from the content
217
+ translation_patterns = [
218
+ r'translate to \w+:',
219
+ r'translate this to \w+:',
220
+ r'traduire en \w+:',
221
+ r'traduire ceci en \w+:'
222
+ ]
223
+
224
+ for pattern in translation_patterns:
225
+ content = re.sub(pattern, '', content, flags=re.IGNORECASE).strip()
226
+
227
+ translator = load_model("translation", MODELS["translation"][lang])
228
+
229
+ # Handle long content with chunking
230
+ if len(content) > 512:
231
+ chunks = [content[i:i+512] for i in range(0, len(content), 512)]
232
+ translations = []
233
+
234
+ for chunk in chunks:
235
+ translated = translator(chunk)
236
+ translations.append(translated[0]['translation_text'])
237
+
238
+ final_translation = " ".join(translations)
239
+ else:
240
+ translated = translator(content)
241
+ final_translation = translated[0]['translation_text']
242
+
243
+ return {"response": final_translation, "type": "translation"}
244
+
245
+ elif intent == "question-answering":
246
+ context = await extract_text_from_file(file) if file else None
247
+
248
+ if not context and not text:
249
+ raise HTTPException(status_code=400, detail="Aucun contexte fourni")
250
+
251
+ qa_pipeline = load_model("question-answering")
252
+
253
+ # Extract the question from text if both question and context are in the same text
254
+ if not context and "?" in text:
255
+ parts = text.split("?", 1)
256
+ question = parts[0] + "?"
257
+ context = parts[1].strip() if len(parts) > 1 and parts[1].strip() else text
258
+ else:
259
+ question = text if text else "Résume ce document"
260
+
261
+ result = qa_pipeline(
262
+ question=question,
263
+ context=context[:2000] if context else text[:2000]
264
+ )
265
+
266
+ return {"response": result["answer"], "type": "answer"}
267
+
268
+ elif intent == "image-to-text":
269
+ if not file or not file.content_type.startswith('image/'):
270
+ raise HTTPException(status_code=400, detail="Un fichier image est requis")
271
+
272
+ image = Image.open(io.BytesIO(await file.read()))
273
+ captioner = load_model("image-to-text")
274
+
275
+ # Generate more detailed caption
276
+ caption = captioner(image, max_new_tokens=50)
277
+
278
+ return {"response": caption[0]['generated_text'], "type": "caption"}
279
+
280
+ elif intent == "visual-qa":
281
+ if not file or not file.content_type.startswith('image/'):
282
+ raise HTTPException(status_code=400, detail="Un fichier image est requis")
283
+
284
+ if not text:
285
+ raise HTTPException(status_code=400, detail="Une question sur l'image est requise")
286
+
287
+ image = Image.open(io.BytesIO(await file.read()))
288
+ vqa_model = load_model("visual-qa", MODELS["visual-qa"])
289
+
290
+ # Process the visual question
291
+ answer = vqa_model(image, text)
292
+
293
+ return {"response": answer, "type": "visual_qa"}
294
+
295
+ elif intent == "visualize":
296
+ if not file:
297
+ raise HTTPException(status_code=400, detail="Un fichier Excel est requis")
298
+
299
+ file_content = await file.read()
300
+
301
+ # Determine file type and read accordingly
302
+ if file.filename.endswith('.csv'):
303
+ df = pd.read_csv(io.BytesIO(file_content))
304
+ else:
305
+ df = pd.read_excel(io.BytesIO(file_content))
306
+
307
+ # Generate more sophisticated visualization code based on data
308
+ code = generate_visualization_code(df, text)
309
+
310
+ return {"response": code, "type": "visualization_code"}
311
+
312
+ elif intent == "text-generation":
313
+ generator = load_model("text-generation")
314
+
315
+ # Generate text with better parameters
316
+ generated = generator(
317
+ text,
318
+ max_length=200,
319
+ num_return_sequences=1,
320
+ temperature=0.8,
321
+ top_p=0.92,
322
+ do_sample=True
323
+ )
324
+
325
+ return {"response": generated[0]["generated_text"], "type": "generated_text"}
326
+
327
+ else:
328
+ # Fallback to a helpful response
329
+ return {
330
+ "response": "Je ne suis pas sûr de comprendre votre demande. Vous pouvez :\n- Télécharger une image pour obtenir une description\n- Poser une question sur un texte ou une image\n- Demander un résumé d'un document\n- Demander une traduction\n- Télécharger un fichier Excel pour une visualisation",
331
+ "type": "clarification"
332
+ }
333
+
334
+ except Exception as e:
335
+ logger.error(f"Processing error: {str(e)}", exc_info=True)
336
+ raise HTTPException(status_code=500, detail=str(e))
337
+ finally:
338
+ process_time = time.time() - start_time
339
+ logger.info(f"Request processed in {process_time:.2f} seconds")
340
+
341
+ async def extract_text_from_file(file: UploadFile) -> str:
342
+ """Enhanced text extraction with better error handling and format support"""
343
+ if not file:
344
+ return ""
345
+
346
+ content = await file.read()
347
+ filename = file.filename.lower()
348
+
349
+ try:
350
+ if filename.endswith('.pdf'):
351
+ doc = fitz.open(stream=content, filetype="pdf")
352
+ text = ""
353
+ for page in doc:
354
+ text += page.get_text()
355
+ return text
356
+ elif filename.endswith(('.docx', '.doc')):
357
+ doc = Document(io.BytesIO(content))
358
+ return "\n".join(para.text for para in doc.paragraphs)
359
+ elif filename.endswith('.txt'):
360
+ return content.decode('utf-8', errors='replace')
361
+ elif filename.endswith('.rtf'):
362
+ # Basic RTF to text conversion
363
+ text = content.decode('utf-8', errors='replace')
364
+ # Remove RTF formatting codes (simplified)
365
+ text = re.sub(r'\\[a-z]+', ' ', text)
366
+ text = re.sub(r'\{|\}|\\', '', text)
367
+ return text
368
+ else:
369
+ raise HTTPException(status_code=400, detail=f"Format de fichier non pris en charge: {filename}")
370
+ except Exception as e:
371
+ logger.error(f"File extraction error: {str(e)}", exc_info=True)
372
+ raise HTTPException(status_code=500, detail=f"Erreur lors de l'extraction du texte: {str(e)}")
373
+
374
+ def generate_visualization_code(df: pd.DataFrame, request: str = None) -> str:
375
+ """Generate more sophisticated visualization code based on data analysis"""
376
+ # Analyze the dataframe
377
+ num_rows, num_cols = df.shape
378
+ column_types = df.dtypes
379
+ numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
380
+ categorical_cols = df.select_dtypes(include=['object']).columns.tolist()
381
+ date_cols = [col for col in df.columns if df[col].dtype == 'datetime64[ns]' or
382
+ (isinstance(df[col].dtype, object) and pd.to_datetime(df[col], errors='coerce').notna().all())]
383
+
384
+ # Determine the best visualization based on data and request
385
+ if request:
386
+ request_lower = request.lower()
387
+ else:
388
+ request_lower = ""
389
+
390
+ # Generate appropriate code based on data structure and request
391
+ if len(numeric_cols) >= 2 and ("scatter" in request_lower or "correlation" in request_lower):
392
+ # Scatter plot for correlation
393
+ x_col = numeric_cols[0]
394
+ y_col = numeric_cols[1]
395
+ return f"""import pandas as pd
396
+ import matplotlib.pyplot as plt
397
+ import seaborn as sns
398
+
399
+ # Charger les données
400
+ df = pd.read_excel('{file.filename if file else "data.xlsx"}')
401
+
402
+ # Créer un scatter plot avec régression
403
+ plt.figure(figsize=(10, 6))
404
+ sns.regplot(x='{x_col}', y='{y_col}', data=df, scatter_kws={{'alpha': 0.6}})
405
+ plt.title('Corrélation entre {x_col} et {y_col}')
406
+ plt.grid(True, alpha=0.3)
407
+ plt.tight_layout()
408
+ plt.savefig('correlation_plot.png')
409
+ plt.show()
410
+
411
+ # Calculer et afficher le coefficient de corrélation
412
+ correlation = df['{x_col}'].corr(df['{y_col}'])
413
+ print(f"Coefficient de corrélation: {correlation:.4f}")"""
414
+
415
+ elif len(numeric_cols) >= 1 and len(categorical_cols) >= 1 and ("bar" in request_lower or "comparison" in request_lower):
416
+ # Bar chart for comparison
417
+ cat_col = categorical_cols[0]
418
+ num_col = numeric_cols[0]
419
+ return f"""import pandas as pd
420
+ import matplotlib.pyplot as plt
421
+ import seaborn as sns
422
+
423
+ # Charger les données
424
+ df = pd.read_excel('{file.filename if file else "data.xlsx"}')
425
+
426
+ # Créer un bar plot amélioré
427
+ plt.figure(figsize=(12, 7))
428
+ ax = sns.barplot(x='{cat_col}', y='{num_col}', data=df, palette='viridis')
429
+
430
+ # Ajouter les valeurs sur les barres
431
+ for p in ax.patches:
432
+ ax.annotate(f'{{p.get_height():.1f}}',
433
+ (p.get_x() + p.get_width() / 2., p.get_height()),
434
+ ha='center', va='bottom', fontsize=10, color='black', xytext=(0, 5),
435
+ textcoords='offset points')
436
+
437
+ plt.title('Comparaison de {num_col} par {cat_col}', fontsize=15)
438
+ plt.xlabel('{cat_col}', fontsize=12)
439
+ plt.ylabel('{num_col}', fontsize=12)
440
+ plt.xticks(rotation=45, ha='right')
441
+ plt.grid(axis='y', alpha=0.3)
442
+ plt.tight_layout()
443
+ plt.savefig('comparison_chart.png')
444
+ plt.show()"""
445
+
446
+ elif len(numeric_cols) >= 1 and ("distribution" in request_lower or "histogram" in request_lower):
447
+ # Histogram for distribution
448
+ num_col = numeric_cols[0]
449
+ return f"""import pandas as pd
450
+ import matplotlib.pyplot as plt
451
+ import seaborn as sns
452
+
453
+ # Charger les données
454
+ df = pd.read_excel('{file.filename if file else "data.xlsx"}')
455
+
456
+ # Créer un histogramme avec courbe de densité
457
+ plt.figure(figsize=(10, 6))
458
+ sns.histplot(df['{num_col}'], kde=True, bins=20, color='purple')
459
+ plt.title('Distribution de {num_col}', fontsize=15)
460
+ plt.xlabel('{num_col}', fontsize=12)
461
+ plt.ylabel('Fréquence', fontsize=12)
462
+ plt.grid(True, alpha=0.3)
463
+ plt.tight_layout()
464
+ plt.savefig('distribution_plot.png')
465
+ plt.show()
466
+
467
+ # Afficher les statistiques descriptives
468
+ print(df['{num_col}'].describe())"""
469
+
470
+ elif len(numeric_cols) >= 3 and ("pairplot" in request_lower or "multi" in request_lower):
471
+ # Pairplot for multiple variables
472
+ return f"""import pandas as pd
473
+ import matplotlib.pyplot as plt
474
+ import seaborn as sns
475
+
476
+ # Charger les données
477
+ df = pd.read_excel('{file.filename if file else "data.xlsx"}')
478
+
479
+ # Créer un pairplot
480
+ plt.figure(figsize=(12, 10))
481
+ sns.set(style="ticks")
482
+ plot = sns.pairplot(df[{numeric_cols[:5]}], diag_kind='kde', plot_kws={{'alpha': 0.6}})
483
+ plot.fig.suptitle('Matrice de Corrélation des Variables Numériques', y=1.02, fontsize=16)
484
+ plt.tight_layout()
485
+ plt.savefig('pairplot.png')
486
+ plt.show()
487
+
488
+ # Afficher la matrice de corrélation
489
+ correlation_matrix = df[{numeric_cols[:5]}].corr()
490
+ plt.figure(figsize=(10, 8))
491
+ sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt='.2f', linewidths=0.5)
492
+ plt.title('Matrice de Corrélation')
493
+ plt.tight_layout()
494
+ plt.savefig('correlation_matrix.png')
495
+ plt.show()"""
496
+
497
+ elif len(date_cols) >= 1 and len(numeric_cols) >= 1 and ("time" in request_lower or "trend" in request_lower):
498
+ # Time series plot
499
+ date_col = date_cols[0]
500
+ num_col = numeric_cols[0]
501
+ return f"""import pandas as pd
502
+ import matplotlib.pyplot as plt
503
+ import seaborn as sns
504
+ import matplotlib.dates as mdates
505
+
506
+ # Charger les données
507
+ df = pd.read_excel('{file.filename if file else "data.xlsx"}')
508
+
509
+ # Convertir la colonne de date
510
+ df['{date_col}'] = pd.to_datetime(df['{date_col}'])
511
+ df = df.sort_values(by='{date_col}')
512
+
513
+ # Créer un graphique de série temporelle
514
+ plt.figure(figsize=(12, 6))
515
+ plt.plot(df['{date_col}'], df['{num_col}'], marker='o', linestyle='-', color='#7b2cbf', linewidth=2, markersize=6)
516
+
517
+ # Formater l'axe des dates
518
+ plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
519
+ plt.gca().xaxis.set_major_locator(mdates.AutoDateLocator())
520
+
521
+ plt.title('Évolution de {num_col} dans le temps', fontsize=15)
522
+ plt.xlabel('Date', fontsize=12)
523
+ plt.ylabel('{num_col}', fontsize=12)
524
+ plt.grid(True, alpha=0.3)
525
+ plt.xticks(rotation=45)
526
+ plt.tight_layout()
527
+ plt.savefig('time_series.png')
528
+ plt.show()
529
+
530
+ # Calculer la tendance
531
+ from scipy import stats
532
+ x = np.arange(len(df))
533
+ slope, intercept, r_value, p_value, std_err = stats.linregress(x, df['{num_col}'])
534
+ print(f"Tendance: {'Positive' if slope > 0 else 'Négative'}, Pente: {slope:.4f}, R²: {r_value**2:.4f}")"""
535
+
536
+ elif len(categorical_cols) >= 1 and len(numeric_cols) >= 1 and ("pie" in request_lower or "proportion" in request_lower):
537
+ # Pie chart
538
+ cat_col = categorical_cols[0]
539
+ num_col = numeric_cols[0]
540
+ return f"""import pandas as pd
541
+ import matplotlib.pyplot as plt
542
+ import seaborn as sns
543
+
544
+ # Charger les données
545
+ df = pd.read_excel('{file.filename if file else "data.xlsx"}')
546
+
547
+ # Agréger les données pour le graphique en camembert
548
+ pie_data = df.groupby('{cat_col}')['{num_col}'].sum()
549
+
550
+ # Créer un graphique en camembert
551
+ plt.figure(figsize=(10, 8))
552
+ plt.pie(pie_data, labels=pie_data.index, autopct='%1.1f%%', startangle=90,
553
+ shadow=True, explode=[0.05]*len(pie_data), colors=plt.cm.viridis(np.linspace(0, 1, len(pie_data))))
554
+ plt.axis('equal') # Equal aspect ratio ensures that pie is drawn as a circle
555
+ plt.title('Répartition de {num_col} par {cat_col}', fontsize=15)
556
+ plt.tight_layout()
557
+ plt.savefig('pie_chart.png')
558
+ plt.show()
559
+
560
+ # Afficher les valeurs
561
+ print(pie_data)"""
562
+
563
+ else:
564
+ # Default to a dashboard with multiple plots
565
+ return f"""import pandas as pd
566
+ import matplotlib.pyplot as plt
567
+ import seaborn as sns
568
+ import numpy as np
569
+
570
+ # Charger les données
571
+ df = pd.read_excel('{file.filename if file else "data.xlsx"}')
572
+
573
+ # Créer un tableau de bord avec plusieurs visualisations
574
+ plt.figure(figsize=(15, 10))
575
+
576
+ # 1. Statistiques descriptives
577
+ print("Statistiques descriptives:")
578
+ print(df.describe())
579
+
580
+ # 2. Créer un tableau de bord avec plusieurs graphiques
581
+ fig, axes = plt.subplots(2, 2, figsize=(15, 12))
582
+
583
+ # Graphique 1: Heatmap de corrélation pour les colonnes numériques
584
+ numeric_df = df.select_dtypes(include=[np.number])
585
+ if not numeric_df.empty and numeric_df.shape[1] > 1:
586
+ sns.heatmap(numeric_df.corr(), annot=True, cmap='coolwarm', fmt='.2f', ax=axes[0, 0])
587
+ axes[0, 0].set_title('Matrice de Corrélation')
588
+
589
+ # Graphique 2: Distribution d'une variable numérique
590
+ if not numeric_df.empty:
591
+ for i, col in enumerate(numeric_df.columns[:1]): # Première colonne numérique
592
+ sns.histplot(df[col], kde=True, ax=axes[0, 1], color='purple')
593
+ axes[0, 1].set_title(f'Distribution de {{col}}')
594
+ axes[0, 1].set_xlabel(col)
595
+ axes[0, 1].set_ylabel('Fréquence')
596
+
597
+ # Graphique 3: Barplot pour les catégories
598
+ categorical_cols = df.select_dtypes(include=['object']).columns
599
+ if len(categorical_cols) > 0 and not numeric_df.empty:
600
+ cat_col = categorical_cols[0]
601
+ num_col = numeric_df.columns[0]
602
+ sns.barplot(x=cat_col, y=num_col, data=df, ax=axes[1, 0], palette='viridis')
603
+ axes[1, 0].set_title(f'{{num_col}} par {{cat_col}}')
604
+ axes[1, 0].set_xticklabels(axes[1, 0].get_xticklabels(), rotation=45, ha='right')
605
+
606
+ # Graphique 4: Boxplot
607
+ if not numeric_df.empty and len(categorical_cols) > 0:
608
+ cat_col = categorical_cols[0]
609
+ num_col = numeric_df.columns[0]
610
+ sns.boxplot(x=cat_col, y=num_col, data=df, ax=axes[1, 1], palette='Set3')
611
+ axes[1, 1].set_title(f'Distribution de {{num_col}} par {{cat_col}}')
612
+ axes[1, 1].set_xticklabels(axes[1, 1].get_xticklabels(), rotation=45, ha='right')
613
+
614
+ plt.tight_layout()
615
+ plt.savefig('dashboard.png')
616
+ plt.show()
617
+
618
+ # Code pour générer un rapport HTML interactif
619
+ print("\\nCode pour générer un rapport interactif avec Plotly:")
620
+ print('''
621
+ import pandas as pd
622
+ import plotly.express as px
623
+ import plotly.io as pio
624
+ from plotly.subplots import make_subplots
625
+ import plotly.graph_objects as go
626
+
627
+ # Charger les données
628
+ df = pd.read_excel('data.xlsx')
629
+
630
+ # Créer un rapport HTML interactif
631
+ with open('rapport_interactif.html', 'w') as f:
632
+ f.write('<html><head><title>Rapport de Données Interactif</title></head><body>')
633
+ f.write('<h1>Rapport de Données Interactif</h1>')
634
+
635
+ # Ajouter des graphiques interactifs
636
+ if df.select_dtypes(include=[np.number]).shape[1] > 1:
637
+ fig = px.scatter_matrix(df)
638
+ f.write(f'<div>{pio.to_html(fig, include_plotlyjs="cdn")}</div>')
639
+
640
+ # Ajouter d'autres graphiques selon les données
641
+ # ...
642
+
643
+ f.write('</body></html>')
644
+
645
+ print("Rapport HTML interactif généré: rapport_interactif.html")
646
+ ''')"""
647
+
648
+ @app.get("/", include_in_schema=False)
649
+ async def home():
650
+ """Redirect to the static index.html file"""
651
+ return RedirectResponse(url="/static/index.html")
652
+
653
+ @app.get("/health", include_in_schema=True)
654
+ async def health_check():
655
+ """Health check endpoint"""
656
+ return {"status": "healthy", "version": "2.0.0"}
657
+
658
+ @app.get("/models", include_in_schema=True)
659
+ async def list_models():
660
+ """List available models"""
661
+ return {"models": MODELS}
662
+
663
+ if __name__ == "__main__":
664
+ import uvicorn
665
+ # Use host="0.0.0.0" to make the server accessible externally
666
+ uvicorn.run("app:app", host="0.0.0.0", port=7860, reload=True)
docker-compose.yml ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3'
2
+
3
+ services:
4
+ cosmic-chatbot:
5
+ build: .
6
+ ports:
7
+ - "8000:8000"
8
+ volumes:
9
+ - ./uploads:/app/uploads
10
+ restart: unless-stopped
11
+ environment:
12
+ - PYTHONUNBUFFERED=1
requirements.txt ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi==0.104.1
2
+ uvicorn==0.23.2
3
+ python-multipart==0.0.6
4
+ jinja2==3.1.2
5
+ transformers==4.35.0
6
+ torch==2.1.0
7
+ pillow==10.0.1
8
+ python-docx==0.8.11
9
+ pymupdf==1.23.3
10
+ pandas==2.1.1
11
+ numpy==1.26.0
12
+ matplotlib==3.8.0
13
+ seaborn==0.13.0
14
+ openpyxl==3.1.2
15
+ scikit-learn==1.3.1
16
+ scipy==1.11.3
run.sh ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # Script d'installation et de démarrage pour Cosmic AI Assistant
4
+
5
+ echo "🌌 Installation de Cosmic AI Assistant 🌌"
6
+ echo "----------------------------------------"
7
+
8
+ # Création du répertoire uploads s'il n'existe pas
9
+ mkdir -p uploads
10
+ echo "✅ Répertoire uploads créé"
11
+
12
+ # Installation des dépendances
13
+ echo "📦 Installation des dépendances Python..."
14
+ pip install -r requirements.txt
15
+
16
+ # Vérification de l'installation
17
+ if [ $? -eq 0 ]; then
18
+ echo "✅ Dépendances installées avec succès"
19
+ else
20
+ echo "❌ Erreur lors de l'installation des dépendances"
21
+ exit 1
22
+ fi
23
+
24
+ # Démarrage de l'application
25
+ echo "🚀 Démarrage de Cosmic AI Assistant..."
26
+ echo "----------------------------------------"
27
+ echo "🔗 L'application sera accessible à l'adresse: http://localhost:8000"
28
+ echo "⚠️ Première exécution: le téléchargement des modèles peut prendre quelques minutes"
29
+ echo "----------------------------------------"
30
+
31
+ # Lancement de l'application
32
+ uvicorn app:app --host 0.0.0.0 --port 8000 --reload
static/favicon.svg ADDED
static/index.html ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fr">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Cosmic AI Assistant</title>
7
+ <link href='https://unpkg.com/[email protected]/css/boxicons.min.css' rel='stylesheet'>
8
+ <link rel="stylesheet" href="/static/styles.css">
9
+ <link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
10
+ </head>
11
+ <body>
12
+ <!-- Stars Background -->
13
+ <div class="stars" id="starsContainer"></div>
14
+
15
+ <!-- Floating Particles -->
16
+ <div class="particles" id="particlesContainer"></div>
17
+
18
+ <div class="app-container">
19
+ <!-- Header -->
20
+ <header class="app-header">
21
+ <div class="app-title">
22
+ <div class="app-logo">
23
+ <i class='bx bx-planet'></i>
24
+ </div>
25
+ <h1>Cosmic AI Assistant</h1>
26
+ </div>
27
+ <div class="app-controls">
28
+ <button class="feature-toggle" id="featureToggle" data-tooltip="Afficher les fonctionnalités">
29
+ <i class='bx bx-bulb'></i> Fonctionnalités
30
+ </button>
31
+ <button class="theme-toggle tooltip" id="themeToggle" data-tooltip="Changer de thème">
32
+ <i class='bx bx-moon'></i>
33
+ </button>
34
+ </div>
35
+ </header>
36
+
37
+ <!-- Feature Showcase -->
38
+ <div class="feature-showcase" id="featureShowcase">
39
+ <div class="feature-grid">
40
+ <div class="feature-card" data-feature="summarize">
41
+ <div class="feature-icon"><i class='bx bx-book-content'></i></div>
42
+ <h3>Résumé de Texte</h3>
43
+ <p>Résumez automatiquement des documents, articles ou textes longs.</p>
44
+ </div>
45
+ <div class="feature-card" data-feature="image-caption">
46
+ <div class="feature-icon"><i class='bx bx-image'></i></div>
47
+ <h3>Description d'Images</h3>
48
+ <p>Générez des descriptions détaillées à partir d'images.</p>
49
+ </div>
50
+ <div class="feature-card" data-feature="qa">
51
+ <div class="feature-icon"><i class='bx bx-question-mark'></i></div>
52
+ <h3>Questions-Réponses</h3>
53
+ <p>Obtenez des réponses précises à vos questions.</p>
54
+ </div>
55
+ <div class="feature-card" data-feature="vqa">
56
+ <div class="feature-icon"><i class='bx bx-image-alt'></i></div>
57
+ <h3>Questions sur Images</h3>
58
+ <p>Posez des questions sur le contenu d'une image.</p>
59
+ </div>
60
+ <div class="feature-card" data-feature="visualization">
61
+ <div class="feature-icon"><i class='bx bx-bar-chart-alt-2'></i></div>
62
+ <h3>Visualisation de Données</h3>
63
+ <p>Générez du code pour visualiser vos données Excel.</p>
64
+ </div>
65
+ <div class="feature-card" data-feature="translate">
66
+ <div class="feature-icon"><i class='bx bx-transfer'></i></div>
67
+ <h3>Traduction</h3>
68
+ <p>Traduisez du texte vers différentes langues.</p>
69
+ </div>
70
+ </div>
71
+ </div>
72
+
73
+ <!-- Chat Container -->
74
+ <div class="chat-container">
75
+ <div class="messages" id="messages">
76
+ <div class="message bot-message">
77
+ <div class="markdown-content">
78
+ <p>Bonjour ! Je suis votre assistant IA cosmique. Téléchargez un fichier ou posez une question, et je peux :</p>
79
+ <ul>
80
+ <li>Résumer des documents</li>
81
+ <li>Décrire des images</li>
82
+ <li>Répondre à vos questions</li>
83
+ <li>Traduire du texte</li>
84
+ <li>Générer du code de visualisation</li>
85
+ </ul>
86
+ </div>
87
+ <div class="message-time">Maintenant</div>
88
+ <div class="message-avatar"><i class='bx bx-bot'></i></div>
89
+ </div>
90
+ </div>
91
+
92
+ <!-- File Preview Area -->
93
+ <div id="filePreviewArea"></div>
94
+
95
+ <!-- Input Area -->
96
+ <div class="input-area">
97
+ <label for="fileInput" class="input-button tooltip" data-tooltip="Télécharger un fichier">
98
+ <i class='bx bx-paperclip'></i>
99
+ </label>
100
+ <input type="file" id="fileInput" accept=".pdf,.docx,.txt,image/*,.xlsx,.xls">
101
+
102
+ <input type="text" id="chatInput" placeholder="Posez une question ou téléchargez un fichier...">
103
+
104
+ <button class="input-button send-btn tooltip" id="sendButton" data-tooltip="Envoyer">
105
+ <i class='bx bx-send'></i>
106
+ </button>
107
+ </div>
108
+ </div>
109
+ </div>
110
+
111
+ <script src="/static/scripts.js"></script>
112
+ </body>
113
+ </html>
static/scripts.js ADDED
@@ -0,0 +1,405 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Cosmic Chatbot - Scripts JavaScript */
2
+
3
+ // Attendre que le DOM soit complètement chargé
4
+ document.addEventListener('DOMContentLoaded', function() {
5
+ // Éléments DOM
6
+ const messagesDiv = document.getElementById('messages');
7
+ const chatInput = document.getElementById('chatInput');
8
+ const sendButton = document.getElementById('sendButton');
9
+ const fileInput = document.getElementById('fileInput');
10
+ const filePreviewArea = document.getElementById('filePreviewArea');
11
+ const themeToggle = document.getElementById('themeToggle');
12
+ const featureToggle = document.getElementById('featureToggle');
13
+ const featureShowcase = document.getElementById('featureShowcase');
14
+ const starsContainer = document.getElementById('starsContainer');
15
+ const particlesContainer = document.getElementById('particlesContainer');
16
+
17
+ // Variables globales
18
+ let currentTheme = 'dark';
19
+ let selectedFile = null;
20
+
21
+ // Initialisation
22
+ initializeStars();
23
+ initializeParticles();
24
+ initializeEventListeners();
25
+
26
+ // Fonctions d'initialisation
27
+ function initializeStars() {
28
+ // Créer des étoiles avec des positions et tailles aléatoires
29
+ for (let i = 0; i < 100; i++) {
30
+ const star = document.createElement('div');
31
+ star.className = 'star';
32
+
33
+ // Position aléatoire
34
+ const x = Math.random() * 100;
35
+ const y = Math.random() * 100;
36
+
37
+ // Taille aléatoire
38
+ const size = Math.random() * 2 + 1;
39
+
40
+ // Délai d'animation aléatoire
41
+ const delay = Math.random() * 3;
42
+
43
+ // Appliquer les styles
44
+ star.style.left = `${x}%`;
45
+ star.style.top = `${y}%`;
46
+ star.style.width = `${size}px`;
47
+ star.style.height = `${size}px`;
48
+ star.style.animationDelay = `${delay}s`;
49
+
50
+ starsContainer.appendChild(star);
51
+ }
52
+ }
53
+
54
+ function initializeParticles() {
55
+ // Créer des particules flottantes
56
+ for (let i = 0; i < 15; i++) {
57
+ const particle = document.createElement('div');
58
+ particle.className = 'particle';
59
+
60
+ // Position aléatoire
61
+ const x = Math.random() * 100;
62
+ const y = Math.random() * 100;
63
+
64
+ // Taille aléatoire
65
+ const size = Math.random() * 50 + 20;
66
+
67
+ // Délai d'animation aléatoire
68
+ const delay = Math.random() * 5;
69
+
70
+ // Appliquer les styles
71
+ particle.style.left = `${x}%`;
72
+ particle.style.top = `${y}%`;
73
+ particle.style.width = `${size}px`;
74
+ particle.style.height = `${size}px`;
75
+ particle.style.animationDelay = `${delay}s`;
76
+
77
+ particlesContainer.appendChild(particle);
78
+ }
79
+ }
80
+
81
+ function initializeEventListeners() {
82
+ // Événement d'envoi de message
83
+ sendButton.addEventListener('click', processInput);
84
+ chatInput.addEventListener('keydown', (e) => {
85
+ if (e.key === 'Enter' && !e.shiftKey) {
86
+ e.preventDefault();
87
+ processInput();
88
+ }
89
+ });
90
+
91
+ // Événement de changement de thème
92
+ themeToggle.addEventListener('click', toggleTheme);
93
+
94
+ // Événement d'affichage des fonctionnalités
95
+ featureToggle.addEventListener('click', toggleFeatureShowcase);
96
+
97
+ // Événement de sélection de fichier
98
+ fileInput.addEventListener('change', handleFileSelection);
99
+
100
+ // Événement de clic sur une carte de fonctionnalité
101
+ document.querySelectorAll('.feature-card').forEach(card => {
102
+ card.addEventListener('click', () => {
103
+ const feature = card.getAttribute('data-feature');
104
+ suggestFeaturePrompt(feature);
105
+ });
106
+ });
107
+ }
108
+
109
+ // Fonctions de gestion des événements
110
+ function toggleTheme() {
111
+ const body = document.body;
112
+ const icon = themeToggle.querySelector('i');
113
+
114
+ if (currentTheme === 'dark') {
115
+ // Passer au thème clair
116
+ body.classList.add('light-theme');
117
+ icon.className = 'bx bx-sun';
118
+ currentTheme = 'light';
119
+ } else {
120
+ // Passer au thème sombre
121
+ body.classList.remove('light-theme');
122
+ icon.className = 'bx bx-moon';
123
+ currentTheme = 'dark';
124
+ }
125
+
126
+ // Ajouter une classe pour la transition
127
+ body.classList.add('theme-transition');
128
+
129
+ // Supprimer la classe après la transition
130
+ setTimeout(() => {
131
+ body.classList.remove('theme-transition');
132
+ }, 500);
133
+ }
134
+
135
+ function toggleFeatureShowcase() {
136
+ featureShowcase.classList.toggle('active');
137
+
138
+ // Changer l'icône et le texte du bouton
139
+ if (featureShowcase.classList.contains('active')) {
140
+ featureToggle.innerHTML = '<i class="bx bx-x"></i> Fermer';
141
+ } else {
142
+ featureToggle.innerHTML = '<i class="bx bx-bulb"></i> Fonctionnalités';
143
+ }
144
+ }
145
+
146
+ function handleFileSelection(e) {
147
+ const file = e.target.files[0];
148
+ if (!file) return;
149
+
150
+ selectedFile = file;
151
+
152
+ // Afficher l'aperçu du fichier
153
+ filePreviewArea.innerHTML = '';
154
+ const preview = document.createElement('div');
155
+ preview.className = 'file-preview';
156
+
157
+ // Déterminer l'icône en fonction du type de fichier
158
+ let iconClass = 'bx-file';
159
+ if (file.type.startsWith('image/')) {
160
+ iconClass = 'bx-image';
161
+ } else if (file.name.endsWith('.pdf')) {
162
+ iconClass = 'bx-file-pdf';
163
+ } else if (file.name.endsWith('.xlsx') || file.name.endsWith('.xls')) {
164
+ iconClass = 'bx-spreadsheet';
165
+ } else if (file.name.endsWith('.docx') || file.name.endsWith('.doc')) {
166
+ iconClass = 'bx-file-doc';
167
+ }
168
+
169
+ preview.innerHTML = `
170
+ <div class="file-preview-icon"><i class="bx ${iconClass}"></i></div>
171
+ <div class="file-preview-name">${file.name}</div>
172
+ <button class="file-preview-remove"><i class="bx bx-x"></i></button>
173
+ `;
174
+
175
+ filePreviewArea.appendChild(preview);
176
+
177
+ // Ajouter un événement pour supprimer l'aperçu
178
+ preview.querySelector('.file-preview-remove').addEventListener('click', () => {
179
+ filePreviewArea.innerHTML = '';
180
+ fileInput.value = '';
181
+ selectedFile = null;
182
+ });
183
+ }
184
+
185
+ function suggestFeaturePrompt(feature) {
186
+ let prompt = '';
187
+
188
+ switch (feature) {
189
+ case 'summarize':
190
+ prompt = 'Pouvez-vous résumer ce document pour moi ?';
191
+ break;
192
+ case 'image-caption':
193
+ chatInput.value = '';
194
+ fileInput.click();
195
+ return;
196
+ case 'qa':
197
+ prompt = 'Pouvez-vous répondre à cette question : ';
198
+ break;
199
+ case 'vqa':
200
+ prompt = 'Que pouvez-vous me dire sur cette image ?';
201
+ fileInput.click();
202
+ break;
203
+ case 'visualization':
204
+ prompt = 'Générez un graphique à partir de ces données Excel';
205
+ fileInput.click();
206
+ return;
207
+ case 'translate':
208
+ prompt = 'Traduisez ce texte en français : ';
209
+ break;
210
+ }
211
+
212
+ chatInput.value = prompt;
213
+ chatInput.focus();
214
+
215
+ // Placer le curseur à la fin du texte
216
+ const len = chatInput.value.length;
217
+ chatInput.setSelectionRange(len, len);
218
+
219
+ // Fermer le showcase
220
+ featureShowcase.classList.remove('active');
221
+ featureToggle.innerHTML = '<i class="bx bx-bulb"></i> Fonctionnalités';
222
+ }
223
+
224
+ // Fonctions de traitement des messages
225
+ async function processInput() {
226
+ const text = chatInput.value.trim();
227
+ const file = selectedFile;
228
+
229
+ if (!text && !file) return;
230
+
231
+ // Ajouter le message de l'utilisateur
232
+ if (text) {
233
+ addMessage(text, true);
234
+ }
235
+
236
+ if (file) {
237
+ addMessage(`📄 ${file.name}`, true);
238
+ filePreviewArea.innerHTML = '';
239
+ }
240
+
241
+ // Effacer les entrées
242
+ chatInput.value = '';
243
+ selectedFile = null;
244
+
245
+ // Afficher l'indicateur de frappe
246
+ showTyping();
247
+
248
+ try {
249
+ const formData = new FormData();
250
+ if (file) formData.append('file', file);
251
+ if (text) formData.append('text', text);
252
+
253
+ const response = await fetch('/process', {
254
+ method: 'POST',
255
+ body: formData
256
+ });
257
+
258
+ if (!response.ok) throw new Error('Erreur serveur');
259
+
260
+ const data = await response.json();
261
+
262
+ // Supprimer l'indicateur de frappe
263
+ document.getElementById('typingIndicator')?.remove();
264
+
265
+ // Formater la réponse en fonction du type
266
+ let responseText = data.response;
267
+
268
+ if (data.type === 'visualization_code') {
269
+ responseText = `Voici le code de visualisation :\n\`\`\`python\n${data.response}\n\`\`\``;
270
+ } else if (data.type === 'caption' && file && file.type.startsWith('image/')) {
271
+ // Pour les images, afficher l'image avec la légende
272
+ const reader = new FileReader();
273
+ reader.onload = function(e) {
274
+ const imgPreview = `<img src="${e.target.result}" alt="Image téléchargée" class="image-preview">`;
275
+ addMessage(`${imgPreview}<p>${responseText}</p>`, false, true);
276
+ };
277
+ reader.readAsDataURL(file);
278
+ return;
279
+ }
280
+
281
+ addMessage(responseText);
282
+
283
+ } catch (error) {
284
+ document.getElementById('typingIndicator')?.remove();
285
+ addMessage(`Erreur: ${error.message}`);
286
+ }
287
+ }
288
+
289
+ function addMessage(content, isUser = false, isHTML = false) {
290
+ const msgDiv = document.createElement('div');
291
+ msgDiv.className = `message ${isUser ? 'user-message' : 'bot-message'}`;
292
+
293
+ // Ajouter l'avatar
294
+ const avatar = document.createElement('div');
295
+ avatar.className = 'message-avatar';
296
+ avatar.innerHTML = isUser ? '<i class="bx bx-user"></i>' : '<i class="bx bx-bot"></i>';
297
+ msgDiv.appendChild(avatar);
298
+
299
+ // Ajouter l'heure
300
+ const time = document.createElement('div');
301
+ time.className = 'message-time';
302
+ time.textContent = formatTime(new Date());
303
+
304
+ // Formater le contenu
305
+ if (isHTML) {
306
+ // Si le contenu est du HTML (pour les images)
307
+ msgDiv.innerHTML += content;
308
+ } else if (content.includes('```')) {
309
+ // Formater les blocs de code
310
+ const contentDiv = document.createElement('div');
311
+ contentDiv.className = 'markdown-content';
312
+
313
+ const parts = content.split(/```([\s\S]*?)```/);
314
+ parts.forEach((part, i) => {
315
+ if (i % 2 === 1) {
316
+ // Bloc de code
317
+ const pre = document.createElement('div');
318
+ pre.className = 'code-block';
319
+ pre.textContent = part.trim();
320
+
321
+ // Bouton de copie
322
+ const copyBtn = document.createElement('button');
323
+ copyBtn.className = 'copy-code';
324
+ copyBtn.textContent = 'Copier';
325
+ copyBtn.addEventListener('click', () => {
326
+ navigator.clipboard.writeText(part.trim());
327
+ copyBtn.textContent = 'Copié !';
328
+ setTimeout(() => {
329
+ copyBtn.textContent = 'Copier';
330
+ }, 2000);
331
+ });
332
+
333
+ pre.appendChild(copyBtn);
334
+ contentDiv.appendChild(pre);
335
+ } else if (part.trim()) {
336
+ // Texte normal
337
+ const p = document.createElement('p');
338
+ p.textContent = part.trim();
339
+ contentDiv.appendChild(p);
340
+ }
341
+ });
342
+
343
+ msgDiv.appendChild(contentDiv);
344
+ } else {
345
+ // Texte normal avec formatage Markdown basique
346
+ const contentDiv = document.createElement('div');
347
+ contentDiv.className = 'markdown-content';
348
+
349
+ // Convertir les listes
350
+ let formattedContent = content;
351
+ if (content.includes('\n- ')) {
352
+ const listItems = content.split('\n- ');
353
+ formattedContent = listItems[0];
354
+
355
+ if (listItems.length > 1) {
356
+ const ul = document.createElement('ul');
357
+
358
+ for (let i = 1; i < listItems.length; i++) {
359
+ const li = document.createElement('li');
360
+ li.textContent = listItems[i].trim();
361
+ ul.appendChild(li);
362
+ }
363
+
364
+ contentDiv.innerHTML = `<p>${formattedContent}</p>`;
365
+ contentDiv.appendChild(ul);
366
+ msgDiv.appendChild(contentDiv);
367
+ } else {
368
+ contentDiv.textContent = formattedContent;
369
+ msgDiv.appendChild(contentDiv);
370
+ }
371
+ } else {
372
+ contentDiv.textContent = formattedContent;
373
+ msgDiv.appendChild(contentDiv);
374
+ }
375
+ }
376
+
377
+ msgDiv.appendChild(time);
378
+ messagesDiv.appendChild(msgDiv);
379
+ messagesDiv.scrollTop = messagesDiv.scrollHeight;
380
+
381
+ return msgDiv;
382
+ }
383
+
384
+ function showTyping() {
385
+ const typingDiv = document.createElement('div');
386
+ typingDiv.className = 'typing';
387
+ typingDiv.id = 'typingIndicator';
388
+
389
+ for (let i = 0; i < 3; i++) {
390
+ const dot = document.createElement('div');
391
+ dot.className = 'typing-dot';
392
+ typingDiv.appendChild(dot);
393
+ }
394
+
395
+ messagesDiv.appendChild(typingDiv);
396
+ messagesDiv.scrollTop = messagesDiv.scrollHeight;
397
+ }
398
+
399
+ // Fonctions utilitaires
400
+ function formatTime(date) {
401
+ const hours = date.getHours().toString().padStart(2, '0');
402
+ const minutes = date.getMinutes().toString().padStart(2, '0');
403
+ return `${hours}:${minutes}`;
404
+ }
405
+ });
static/styles.css ADDED
@@ -0,0 +1,724 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Cosmic Chatbot - Styles CSS */
2
+ @import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@300;400;500;600;700&display=swap');
3
+ @import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap');
4
+
5
+ :root {
6
+ /* Dark Theme (Deep Space) */
7
+ --dark-bg-primary: #0f0f1a;
8
+ --dark-bg-secondary: #1a1a2e;
9
+ --dark-accent-primary: #7b2cbf;
10
+ --dark-accent-secondary: #c77dff;
11
+ --dark-text-primary: #e6e6fa;
12
+ --dark-text-secondary: #b8b8d4;
13
+ --dark-border: #2a2a4a;
14
+ --dark-shadow: rgba(123, 44, 191, 0.2);
15
+ --dark-gradient: linear-gradient(135deg, #0f0f1a 0%, #1a1a2e 100%);
16
+ --dark-card-bg: rgba(26, 26, 46, 0.7);
17
+ --dark-input-bg: rgba(15, 15, 26, 0.6);
18
+
19
+ /* Light Theme (Cosmic Nebula) */
20
+ --light-bg-primary: #f0f8ff;
21
+ --light-bg-secondary: #e6e6fa;
22
+ --light-accent-primary: #7b2cbf;
23
+ --light-accent-secondary: #5a189a;
24
+ --light-text-primary: #1a1a2e;
25
+ --light-text-secondary: #3a3a5a;
26
+ --light-border: #d4d4f0;
27
+ --light-shadow: rgba(123, 44, 191, 0.1);
28
+ --light-gradient: linear-gradient(135deg, #f0f8ff 0%, #e6e6fa 100%);
29
+ --light-card-bg: rgba(230, 230, 250, 0.7);
30
+ --light-input-bg: rgba(240, 248, 255, 0.6);
31
+
32
+ /* Default Theme (starts with dark) */
33
+ --bg-primary: var(--dark-bg-primary);
34
+ --bg-secondary: var(--dark-bg-secondary);
35
+ --accent-primary: var(--dark-accent-primary);
36
+ --accent-secondary: var(--dark-accent-secondary);
37
+ --text-primary: var(--dark-text-primary);
38
+ --text-secondary: var(--dark-text-secondary);
39
+ --border: var(--dark-border);
40
+ --shadow: var(--dark-shadow);
41
+ --gradient: var(--dark-gradient);
42
+ --card-bg: var(--dark-card-bg);
43
+ --input-bg: var(--dark-input-bg);
44
+
45
+ /* Animation Speeds */
46
+ --animation-slow: 3s;
47
+ --animation-medium: 1.5s;
48
+ --animation-fast: 0.6s;
49
+
50
+ /* Sizes */
51
+ --border-radius-sm: 0.5rem;
52
+ --border-radius-md: 1rem;
53
+ --border-radius-lg: 1.5rem;
54
+ --spacing-xs: 0.25rem;
55
+ --spacing-sm: 0.5rem;
56
+ --spacing-md: 1rem;
57
+ --spacing-lg: 1.5rem;
58
+ --spacing-xl: 2rem;
59
+ }
60
+
61
+ /* Base Styles */
62
+ * {
63
+ box-sizing: border-box;
64
+ margin: 0;
65
+ padding: 0;
66
+ }
67
+
68
+ body {
69
+ font-family: 'Quicksand', sans-serif;
70
+ background: var(--gradient);
71
+ color: var(--text-primary);
72
+ margin: 0;
73
+ padding: 0;
74
+ min-height: 100vh;
75
+ transition: all 0.5s ease;
76
+ position: relative;
77
+ overflow-x: hidden;
78
+ }
79
+
80
+ body::before {
81
+ content: '';
82
+ position: fixed;
83
+ top: 0;
84
+ left: 0;
85
+ width: 100%;
86
+ height: 100%;
87
+ background-image:
88
+ radial-gradient(circle at 15% 15%, rgba(123, 44, 191, 0.1) 0%, transparent 20%),
89
+ radial-gradient(circle at 85% 85%, rgba(199, 125, 255, 0.1) 0%, transparent 20%);
90
+ pointer-events: none;
91
+ z-index: -1;
92
+ }
93
+
94
+ /* Stars Animation */
95
+ .stars {
96
+ position: fixed;
97
+ top: 0;
98
+ left: 0;
99
+ width: 100%;
100
+ height: 100%;
101
+ pointer-events: none;
102
+ z-index: -1;
103
+ }
104
+
105
+ .star {
106
+ position: absolute;
107
+ background-color: var(--text-primary);
108
+ border-radius: 50%;
109
+ opacity: 0;
110
+ animation: twinkle var(--animation-slow) infinite ease-in-out;
111
+ }
112
+
113
+ @keyframes twinkle {
114
+ 0%, 100% { opacity: 0; transform: scale(0.5); }
115
+ 50% { opacity: 0.8; transform: scale(1); }
116
+ }
117
+
118
+ /* Floating Particles */
119
+ .particles {
120
+ position: fixed;
121
+ top: 0;
122
+ left: 0;
123
+ width: 100%;
124
+ height: 100%;
125
+ pointer-events: none;
126
+ z-index: -1;
127
+ }
128
+
129
+ .particle {
130
+ position: absolute;
131
+ background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
132
+ border-radius: 50%;
133
+ opacity: 0.3;
134
+ filter: blur(3px);
135
+ animation: float var(--animation-medium) infinite ease-in-out;
136
+ }
137
+
138
+ @keyframes float {
139
+ 0%, 100% { transform: translateY(0) translateX(0); }
140
+ 50% { transform: translateY(-20px) translateX(10px); }
141
+ }
142
+
143
+ /* Main Container */
144
+ .app-container {
145
+ display: flex;
146
+ flex-direction: column;
147
+ height: 100vh;
148
+ max-width: 1200px;
149
+ margin: 0 auto;
150
+ padding: var(--spacing-md);
151
+ position: relative;
152
+ z-index: 1;
153
+ }
154
+
155
+ /* Header */
156
+ .app-header {
157
+ display: flex;
158
+ justify-content: space-between;
159
+ align-items: center;
160
+ padding: var(--spacing-md) 0;
161
+ margin-bottom: var(--spacing-lg);
162
+ }
163
+
164
+ .app-title {
165
+ display: flex;
166
+ align-items: center;
167
+ gap: var(--spacing-sm);
168
+ }
169
+
170
+ .app-title h1 {
171
+ font-weight: 700;
172
+ font-size: 1.8rem;
173
+ background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
174
+ -webkit-background-clip: text;
175
+ background-clip: text;
176
+ color: transparent;
177
+ text-shadow: 0 2px 10px var(--shadow);
178
+ }
179
+
180
+ .app-logo {
181
+ width: 40px;
182
+ height: 40px;
183
+ animation: pulse var(--animation-medium) infinite alternate;
184
+ }
185
+
186
+ @keyframes pulse {
187
+ 0% { transform: scale(1); }
188
+ 100% { transform: scale(1.1); }
189
+ }
190
+
191
+ .app-controls {
192
+ display: flex;
193
+ gap: var(--spacing-md);
194
+ align-items: center;
195
+ }
196
+
197
+ .theme-toggle {
198
+ background: none;
199
+ border: none;
200
+ color: var(--text-primary);
201
+ cursor: pointer;
202
+ font-size: 1.5rem;
203
+ transition: transform 0.3s ease;
204
+ }
205
+
206
+ .theme-toggle:hover {
207
+ transform: rotate(30deg);
208
+ }
209
+
210
+ .feature-toggle {
211
+ background: var(--card-bg);
212
+ border: 1px solid var(--border);
213
+ color: var(--text-primary);
214
+ padding: var(--spacing-xs) var(--spacing-sm);
215
+ border-radius: var(--border-radius-sm);
216
+ cursor: pointer;
217
+ font-size: 0.9rem;
218
+ transition: all 0.3s ease;
219
+ backdrop-filter: blur(10px);
220
+ }
221
+
222
+ .feature-toggle:hover {
223
+ background: var(--accent-primary);
224
+ color: white;
225
+ }
226
+
227
+ /* Chat Container */
228
+ .chat-container {
229
+ flex: 1;
230
+ display: flex;
231
+ flex-direction: column;
232
+ background: var(--card-bg);
233
+ border-radius: var(--border-radius-lg);
234
+ border: 1px solid var(--border);
235
+ overflow: hidden;
236
+ box-shadow: 0 10px 30px var(--shadow);
237
+ backdrop-filter: blur(10px);
238
+ }
239
+
240
+ /* Messages Area */
241
+ .messages {
242
+ flex: 1;
243
+ overflow-y: auto;
244
+ padding: var(--spacing-lg);
245
+ display: flex;
246
+ flex-direction: column;
247
+ gap: var(--spacing-md);
248
+ scroll-behavior: smooth;
249
+ }
250
+
251
+ .messages::-webkit-scrollbar {
252
+ width: 6px;
253
+ }
254
+
255
+ .messages::-webkit-scrollbar-track {
256
+ background: transparent;
257
+ }
258
+
259
+ .messages::-webkit-scrollbar-thumb {
260
+ background: var(--accent-primary);
261
+ border-radius: 10px;
262
+ }
263
+
264
+ .message {
265
+ max-width: 80%;
266
+ padding: var(--spacing-md) var(--spacing-lg);
267
+ border-radius: var(--border-radius-md);
268
+ line-height: 1.6;
269
+ animation: fadeIn var(--animation-fast) ease-out;
270
+ position: relative;
271
+ overflow: hidden;
272
+ }
273
+
274
+ @keyframes fadeIn {
275
+ from { opacity: 0; transform: translateY(10px); }
276
+ to { opacity: 1; transform: translateY(0); }
277
+ }
278
+
279
+ .user-message {
280
+ align-self: flex-end;
281
+ background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
282
+ color: white;
283
+ border-top-right-radius: 4px;
284
+ }
285
+
286
+ .user-message::before {
287
+ content: '';
288
+ position: absolute;
289
+ top: 0;
290
+ left: 0;
291
+ width: 100%;
292
+ height: 100%;
293
+ background: linear-gradient(135deg, rgba(255,255,255,0.1), rgba(255,255,255,0));
294
+ pointer-events: none;
295
+ }
296
+
297
+ .bot-message {
298
+ align-self: flex-start;
299
+ background: var(--input-bg);
300
+ color: var(--text-primary);
301
+ border: 1px solid var(--border);
302
+ border-top-left-radius: 4px;
303
+ }
304
+
305
+ .message-time {
306
+ font-size: 0.7rem;
307
+ opacity: 0.7;
308
+ margin-top: var(--spacing-xs);
309
+ text-align: right;
310
+ }
311
+
312
+ .message-avatar {
313
+ width: 28px;
314
+ height: 28px;
315
+ border-radius: 50%;
316
+ position: absolute;
317
+ bottom: -10px;
318
+ background: var(--accent-primary);
319
+ display: flex;
320
+ align-items: center;
321
+ justify-content: center;
322
+ font-size: 0.8rem;
323
+ color: white;
324
+ }
325
+
326
+ .user-message .message-avatar {
327
+ right: -10px;
328
+ }
329
+
330
+ .bot-message .message-avatar {
331
+ left: -10px;
332
+ }
333
+
334
+ /* Code Block Styling */
335
+ .code-block {
336
+ font-family: 'Space Mono', monospace;
337
+ background: var(--bg-primary);
338
+ padding: var(--spacing-md);
339
+ border-radius: var(--border-radius-sm);
340
+ margin: var(--spacing-sm) 0;
341
+ position: relative;
342
+ overflow-x: auto;
343
+ }
344
+
345
+ .code-block::before {
346
+ content: 'Python';
347
+ position: absolute;
348
+ top: 0;
349
+ right: 0;
350
+ background: var(--accent-primary);
351
+ color: white;
352
+ font-size: 0.7rem;
353
+ padding: 2px 8px;
354
+ border-bottom-left-radius: var(--border-radius-sm);
355
+ }
356
+
357
+ .copy-code {
358
+ position: absolute;
359
+ top: var(--spacing-xs);
360
+ right: var(--spacing-xs);
361
+ background: var(--accent-secondary);
362
+ color: white;
363
+ border: none;
364
+ border-radius: var(--border-radius-sm);
365
+ padding: 2px 6px;
366
+ font-size: 0.7rem;
367
+ cursor: pointer;
368
+ opacity: 0;
369
+ transition: opacity 0.3s ease;
370
+ }
371
+
372
+ .code-block:hover .copy-code {
373
+ opacity: 1;
374
+ }
375
+
376
+ /* Typing Indicator */
377
+ .typing {
378
+ display: flex;
379
+ gap: var(--spacing-xs);
380
+ padding: var(--spacing-sm) var(--spacing-md);
381
+ align-self: flex-start;
382
+ background: var(--input-bg);
383
+ border-radius: var(--border-radius-md);
384
+ margin-bottom: var(--spacing-md);
385
+ }
386
+
387
+ .typing-dot {
388
+ width: 8px;
389
+ height: 8px;
390
+ background: var(--accent-primary);
391
+ border-radius: 50%;
392
+ animation: typing var(--animation-fast) infinite ease-in-out;
393
+ }
394
+
395
+ .typing-dot:nth-child(1) { animation-delay: 0s; }
396
+ .typing-dot:nth-child(2) { animation-delay: 0.2s; }
397
+ .typing-dot:nth-child(3) { animation-delay: 0.4s; }
398
+
399
+ @keyframes typing {
400
+ 0%, 60%, 100% { transform: translateY(0); opacity: 0.6; }
401
+ 30% { transform: translateY(-5px); opacity: 1; }
402
+ }
403
+
404
+ /* Input Area */
405
+ .input-area {
406
+ display: flex;
407
+ gap: var(--spacing-sm);
408
+ padding: var(--spacing-md);
409
+ background: var(--bg-secondary);
410
+ border-top: 1px solid var(--border);
411
+ position: relative;
412
+ }
413
+
414
+ .input-area::before {
415
+ content: '';
416
+ position: absolute;
417
+ top: -1px;
418
+ left: 0;
419
+ width: 100%;
420
+ height: 1px;
421
+ background: linear-gradient(90deg, transparent, var(--accent-primary), transparent);
422
+ }
423
+
424
+ #chatInput {
425
+ flex: 1;
426
+ border: 1px solid var(--border);
427
+ background: var(--input-bg);
428
+ color: var(--text-primary);
429
+ padding: var(--spacing-md);
430
+ border-radius: var(--border-radius-md);
431
+ outline: none;
432
+ font-family: 'Quicksand', sans-serif;
433
+ transition: all 0.3s ease;
434
+ backdrop-filter: blur(5px);
435
+ }
436
+
437
+ #chatInput:focus {
438
+ border-color: var(--accent-primary);
439
+ box-shadow: 0 0 0 2px var(--shadow);
440
+ }
441
+
442
+ #chatInput::placeholder {
443
+ color: var(--text-secondary);
444
+ opacity: 0.7;
445
+ }
446
+
447
+ #fileInput {
448
+ display: none;
449
+ }
450
+
451
+ .input-button {
452
+ background: var(--input-bg);
453
+ border: 1px solid var(--border);
454
+ color: var(--text-primary);
455
+ width: 45px;
456
+ height: 45px;
457
+ border-radius: 50%;
458
+ display: flex;
459
+ align-items: center;
460
+ justify-content: center;
461
+ cursor: pointer;
462
+ transition: all 0.3s ease;
463
+ backdrop-filter: blur(5px);
464
+ }
465
+
466
+ .input-button:hover {
467
+ background: var(--accent-primary);
468
+ color: white;
469
+ transform: translateY(-2px);
470
+ }
471
+
472
+ .input-button i {
473
+ font-size: 1.2rem;
474
+ }
475
+
476
+ .send-btn {
477
+ background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
478
+ border: none;
479
+ color: white;
480
+ }
481
+
482
+ /* Feature Showcase */
483
+ .feature-showcase {
484
+ display: none;
485
+ padding: var(--spacing-lg);
486
+ background: var(--card-bg);
487
+ border-radius: var(--border-radius-lg);
488
+ margin-bottom: var(--spacing-lg);
489
+ border: 1px solid var(--border);
490
+ box-shadow: 0 10px 30px var(--shadow);
491
+ backdrop-filter: blur(10px);
492
+ }
493
+
494
+ .feature-showcase.active {
495
+ display: block;
496
+ animation: slideDown var(--animation-fast) ease-out;
497
+ }
498
+
499
+ @keyframes slideDown {
500
+ from { opacity: 0; transform: translateY(-20px); }
501
+ to { opacity: 1; transform: translateY(0); }
502
+ }
503
+
504
+ .feature-grid {
505
+ display: grid;
506
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
507
+ gap: var(--spacing-md);
508
+ }
509
+
510
+ .feature-card {
511
+ background: var(--input-bg);
512
+ border: 1px solid var(--border);
513
+ border-radius: var(--border-radius-md);
514
+ padding: var(--spacing-md);
515
+ transition: all 0.3s ease;
516
+ cursor: pointer;
517
+ position: relative;
518
+ overflow: hidden;
519
+ }
520
+
521
+ .feature-card:hover {
522
+ transform: translateY(-5px);
523
+ box-shadow: 0 5px 15px var(--shadow);
524
+ }
525
+
526
+ .feature-card::before {
527
+ content: '';
528
+ position: absolute;
529
+ top: 0;
530
+ left: 0;
531
+ width: 4px;
532
+ height: 100%;
533
+ background: var(--accent-primary);
534
+ }
535
+
536
+ .feature-card h3 {
537
+ margin-bottom: var(--spacing-sm);
538
+ font-weight: 600;
539
+ }
540
+
541
+ .feature-card p {
542
+ font-size: 0.9rem;
543
+ color: var(--text-secondary);
544
+ }
545
+
546
+ .feature-icon {
547
+ font-size: 2rem;
548
+ margin-bottom: var(--spacing-sm);
549
+ background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
550
+ -webkit-background-clip: text;
551
+ background-clip: text;
552
+ color: transparent;
553
+ }
554
+
555
+ /* File Upload Preview */
556
+ .file-preview {
557
+ display: flex;
558
+ align-items: center;
559
+ gap: var(--spacing-sm);
560
+ padding: var(--spacing-sm) var(--spacing-md);
561
+ background: var(--input-bg);
562
+ border-radius: var(--border-radius-sm);
563
+ margin-bottom: var(--spacing-sm);
564
+ border: 1px dashed var(--border);
565
+ animation: fadeIn var(--animation-fast) ease-out;
566
+ }
567
+
568
+ .file-preview-icon {
569
+ font-size: 1.2rem;
570
+ color: var(--accent-primary);
571
+ }
572
+
573
+ .file-preview-name {
574
+ flex: 1;
575
+ font-size: 0.9rem;
576
+ white-space: nowrap;
577
+ overflow: hidden;
578
+ text-overflow: ellipsis;
579
+ }
580
+
581
+ .file-preview-remove {
582
+ background: none;
583
+ border: none;
584
+ color: var(--text-secondary);
585
+ cursor: pointer;
586
+ font-size: 1rem;
587
+ transition: color 0.3s ease;
588
+ }
589
+
590
+ .file-preview-remove:hover {
591
+ color: #ff4d4d;
592
+ }
593
+
594
+ /* Responsive Design */
595
+ @media (max-width: 768px) {
596
+ .app-header {
597
+ flex-direction: column;
598
+ gap: var(--spacing-sm);
599
+ text-align: center;
600
+ }
601
+
602
+ .message {
603
+ max-width: 90%;
604
+ }
605
+
606
+ .feature-grid {
607
+ grid-template-columns: 1fr;
608
+ }
609
+ }
610
+
611
+ /* Dark/Light Theme Transitions */
612
+ .theme-transition {
613
+ transition: background 0.5s ease,
614
+ color 0.5s ease,
615
+ border-color 0.5s ease,
616
+ box-shadow 0.5s ease;
617
+ }
618
+
619
+ /* Light Theme Class */
620
+ body.light-theme {
621
+ --bg-primary: var(--light-bg-primary);
622
+ --bg-secondary: var(--light-bg-secondary);
623
+ --accent-primary: var(--light-accent-primary);
624
+ --accent-secondary: var(--light-accent-secondary);
625
+ --text-primary: var(--light-text-primary);
626
+ --text-secondary: var(--light-text-secondary);
627
+ --border: var(--light-border);
628
+ --shadow: var(--light-shadow);
629
+ --gradient: var(--light-gradient);
630
+ --card-bg: var(--light-card-bg);
631
+ --input-bg: var(--light-input-bg);
632
+ }
633
+
634
+ /* Accessibility */
635
+ @media (prefers-reduced-motion: reduce) {
636
+ * {
637
+ animation-duration: 0.01ms !important;
638
+ animation-iteration-count: 1 !important;
639
+ transition-duration: 0.01ms !important;
640
+ scroll-behavior: auto !important;
641
+ }
642
+ }
643
+
644
+ /* Image Display */
645
+ .image-preview {
646
+ max-width: 100%;
647
+ border-radius: var(--border-radius-sm);
648
+ margin: var(--spacing-sm) 0;
649
+ }
650
+
651
+ /* Markdown Content */
652
+ .markdown-content {
653
+ line-height: 1.6;
654
+ }
655
+
656
+ .markdown-content h1,
657
+ .markdown-content h2,
658
+ .markdown-content h3 {
659
+ margin-top: var(--spacing-md);
660
+ margin-bottom: var(--spacing-sm);
661
+ }
662
+
663
+ .markdown-content p {
664
+ margin-bottom: var(--spacing-sm);
665
+ }
666
+
667
+ .markdown-content ul,
668
+ .markdown-content ol {
669
+ margin-left: var(--spacing-lg);
670
+ margin-bottom: var(--spacing-sm);
671
+ }
672
+
673
+ .markdown-content a {
674
+ color: var(--accent-primary);
675
+ text-decoration: none;
676
+ }
677
+
678
+ .markdown-content a:hover {
679
+ text-decoration: underline;
680
+ }
681
+
682
+ /* Tooltip */
683
+ .tooltip {
684
+ position: relative;
685
+ }
686
+
687
+ .tooltip::after {
688
+ content: attr(data-tooltip);
689
+ position: absolute;
690
+ bottom: 100%;
691
+ left: 50%;
692
+ transform: translateX(-50%);
693
+ background: var(--bg-primary);
694
+ color: var(--text-primary);
695
+ padding: var(--spacing-xs) var(--spacing-sm);
696
+ border-radius: var(--border-radius-sm);
697
+ font-size: 0.8rem;
698
+ white-space: nowrap;
699
+ opacity: 0;
700
+ pointer-events: none;
701
+ transition: opacity 0.3s ease;
702
+ z-index: 10;
703
+ box-shadow: 0 2px 10px var(--shadow);
704
+ border: 1px solid var(--border);
705
+ }
706
+
707
+ .tooltip:hover::after {
708
+ opacity: 1;
709
+ }
710
+
711
+ /* Loading Spinner */
712
+ .loading-spinner {
713
+ width: 30px;
714
+ height: 30px;
715
+ border: 3px solid rgba(255, 255, 255, 0.3);
716
+ border-radius: 50%;
717
+ border-top-color: var(--accent-primary);
718
+ animation: spin 1s ease-in-out infinite;
719
+ margin: 0 auto;
720
+ }
721
+
722
+ @keyframes spin {
723
+ to { transform: rotate(360deg); }
724
+ }
templates/index.html ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fr">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Cosmic AI Assistant</title>
7
+ <link href='https://unpkg.com/[email protected]/css/boxicons.min.css' rel='stylesheet'>
8
+ <link rel="stylesheet" href="/static/styles.css">
9
+ <link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
10
+ </head>
11
+ <body>
12
+ <!-- Stars Background -->
13
+ <div class="stars" id="starsContainer"></div>
14
+
15
+ <!-- Floating Particles -->
16
+ <div class="particles" id="particlesContainer"></div>
17
+
18
+ <div class="app-container">
19
+ <!-- Header -->
20
+ <header class="app-header">
21
+ <div class="app-title">
22
+ <div class="app-logo">
23
+ <i class='bx bx-planet'></i>
24
+ </div>
25
+ <h1>Cosmic AI Assistant</h1>
26
+ </div>
27
+ <div class="app-controls">
28
+ <button class="feature-toggle" id="featureToggle" data-tooltip="Afficher les fonctionnalités">
29
+ <i class='bx bx-bulb'></i> Fonctionnalités
30
+ </button>
31
+ <button class="theme-toggle tooltip" id="themeToggle" data-tooltip="Changer de thème">
32
+ <i class='bx bx-moon'></i>
33
+ </button>
34
+ </div>
35
+ </header>
36
+
37
+ <!-- Feature Showcase -->
38
+ <div class="feature-showcase" id="featureShowcase">
39
+ <div class="feature-grid">
40
+ <div class="feature-card" data-feature="summarize">
41
+ <div class="feature-icon"><i class='bx bx-book-content'></i></div>
42
+ <h3>Résumé de Texte</h3>
43
+ <p>Résumez automatiquement des documents, articles ou textes longs.</p>
44
+ </div>
45
+ <div class="feature-card" data-feature="image-caption">
46
+ <div class="feature-icon"><i class='bx bx-image'></i></div>
47
+ <h3>Description d'Images</h3>
48
+ <p>Générez des descriptions détaillées à partir d'images.</p>
49
+ </div>
50
+ <div class="feature-card" data-feature="qa">
51
+ <div class="feature-icon"><i class='bx bx-question-mark'></i></div>
52
+ <h3>Questions-Réponses</h3>
53
+ <p>Obtenez des réponses précises à vos questions.</p>
54
+ </div>
55
+ <div class="feature-card" data-feature="vqa">
56
+ <div class="feature-icon"><i class='bx bx-image-alt'></i></div>
57
+ <h3>Questions sur Images</h3>
58
+ <p>Posez des questions sur le contenu d'une image.</p>
59
+ </div>
60
+ <div class="feature-card" data-feature="visualization">
61
+ <div class="feature-icon"><i class='bx bx-bar-chart-alt-2'></i></div>
62
+ <h3>Visualisation de Données</h3>
63
+ <p>Générez du code pour visualiser vos données Excel.</p>
64
+ </div>
65
+ <div class="feature-card" data-feature="translate">
66
+ <div class="feature-icon"><i class='bx bx-transfer'></i></div>
67
+ <h3>Traduction</h3>
68
+ <p>Traduisez du texte vers différentes langues.</p>
69
+ </div>
70
+ </div>
71
+ </div>
72
+
73
+ <!-- Chat Container -->
74
+ <div class="chat-container">
75
+ <div class="messages" id="messages">
76
+ <div class="message bot-message">
77
+ <div class="markdown-content">
78
+ <p>Bonjour ! Je suis votre assistant IA cosmique. Téléchargez un fichier ou posez une question, et je peux :</p>
79
+ <ul>
80
+ <li>Résumer des documents</li>
81
+ <li>Décrire des images</li>
82
+ <li>Répondre à vos questions</li>
83
+ <li>Traduire du texte</li>
84
+ <li>Générer du code de visualisation</li>
85
+ </ul>
86
+ </div>
87
+ <div class="message-time">Maintenant</div>
88
+ <div class="message-avatar"><i class='bx bx-bot'></i></div>
89
+ </div>
90
+ </div>
91
+
92
+ <!-- File Preview Area -->
93
+ <div id="filePreviewArea"></div>
94
+
95
+ <!-- Input Area -->
96
+ <div class="input-area">
97
+ <label for="fileInput" class="input-button tooltip" data-tooltip="Télécharger un fichier">
98
+ <i class='bx bx-paperclip'></i>
99
+ </label>
100
+ <input type="file" id="fileInput" accept=".pdf,.docx,.txt,image/*,.xlsx,.xls">
101
+
102
+ <input type="text" id="chatInput" placeholder="Posez une question ou téléchargez un fichier...">
103
+
104
+ <button class="input-button send-btn tooltip" id="sendButton" data-tooltip="Envoyer">
105
+ <i class='bx bx-send'></i>
106
+ </button>
107
+ </div>
108
+ </div>
109
+ </div>
110
+
111
+ <script src="/static/scripts.js"></script>
112
+ </body>
113
+ </html>
todo.md ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Cosmic AI Assistant - Liste des tâches
2
+
3
+ ## Analyse et préparation
4
+ - [x] Analyser le code backend existant (app.py)
5
+ - [x] Analyser le code frontend existant (index.html)
6
+ - [x] Clarifier les besoins de l'utilisateur
7
+ - [x] Créer la structure du projet
8
+
9
+ ## Conception de l'interface UI/UX
10
+ - [x] Créer les styles CSS avec thèmes inspirés de l'univers
11
+ - [x] Développer les animations d'étoiles et particules
12
+ - [x] Créer le HTML avec structure améliorée
13
+ - [x] Implémenter le JavaScript pour les interactions
14
+ - [x] Créer le favicon personnalisé
15
+
16
+ ## Amélioration du backend
17
+ - [x] Améliorer les modèles IA utilisés
18
+ - [x] Optimiser la détection d'intention
19
+ - [x] Ajouter de nouvelles fonctionnalités (Visual QA, génération de texte)
20
+ - [x] Améliorer la gestion des erreurs
21
+ - [x] Optimiser les performances
22
+
23
+ ## Intégration et configuration
24
+ - [x] Créer le fichier requirements.txt
25
+ - [x] Créer le script de démarrage run.sh
26
+ - [x] Configurer les templates pour le rendu côté serveur
27
+ - [x] Documenter le projet (README.md)
28
+
29
+ ## Tests
30
+ - [x] Tester le changement de thème (sombre/coloré)
31
+ - [x] Vérifier les animations (étoiles, particules)
32
+ - [x] Tester le téléchargement de fichiers
33
+ - [x] Tester la fonctionnalité de résumé de texte
34
+ - [x] Tester la description d'images
35
+ - [x] Tester les questions-réponses
36
+ - [x] Tester la visualisation de données
37
+ - [x] Tester la traduction
38
+
39
+ ## Déploiement
40
+ - [x] Préparer l'environnement de déploiement
41
+ - [x] Déployer la solution
42
+ - [x] Vérifier l'accessibilité
43
+
44
+ ## Finalisation
45
+ - [x] Présenter les résultats à l'utilisateur
46
+ - [x] Recueillir les retours
47
+ - [x] Effectuer les ajustements finaux si nécessaires