Spaces:
Sleeping
Sleeping
Upload 12 files
Browse files- Dockerfile +25 -0
- README.md +117 -11
- app.py +666 -0
- docker-compose.yml +12 -0
- requirements.txt +16 -0
- run.sh +32 -0
- static/favicon.svg +18 -0
- static/index.html +113 -0
- static/scripts.js +405 -0
- static/styles.css +724 -0
- templates/index.html +113 -0
- 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 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|