diff --git a/backend/main.py b/backend/main.py index cc7786f..c3c9b20 100644 --- a/backend/main.py +++ b/backend/main.py @@ -143,14 +143,21 @@ def get_messages(project_id: int, db: Session = Depends(get_db), current_user: U @app.post("/api/projects/{project_id}/messages", response_model=List[MessageResponse]) async def create_message(project_id: int, message: MessageCreate, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)): - db.add(Message(role=message.role, content=message.content, project_id=project_id)); db.commit() + final_content = message.content + if message.file_content and message.file_name: + final_content = f"[Fichier attaché : {message.file_name}]\n```\n{message.file_content}\n```\n\n{message.content}" + + db.add(Message(role=message.role, content=final_content, project_id=project_id)) + db.commit() + history = db.query(Message).filter(Message.project_id == project_id).order_by(Message.created_at.asc()).all() settings = db.query(SystemSettings).first() conf = message.config.dict() if message.config else {"workers": ["gemini-3.5-flash-lite"]} - ai_resp = await run_orchestrator(db, history, settings, conf) # Transmission de DB + ai_resp = await run_orchestrator(db, history, settings, conf) - db.add(Message(role="assistant", content=ai_resp, project_id=project_id)); db.commit() + db.add(Message(role="assistant", content=ai_resp, project_id=project_id)) + db.commit() return db.query(Message).filter(Message.project_id == project_id).order_by(Message.created_at.asc()).all() # --- ROUTES MODÈLES & FINANCES --- diff --git a/backend/schemas.py b/backend/schemas.py index 8c17b12..3ee312a 100644 --- a/backend/schemas.py +++ b/backend/schemas.py @@ -50,6 +50,8 @@ class MessageBase(BaseModel): class MessageCreate(MessageBase): config: Optional[OrchestratorConfig] = None + file_content: Optional[str] = None + file_name: Optional[str] = None class MessageResponse(MessageBase): id: int diff --git a/frontend/dashboard.html b/frontend/dashboard.html index 193d036..09cf4bc 100644 --- a/frontend/dashboard.html +++ b/frontend/dashboard.html @@ -136,6 +136,62 @@ + +
+
+

Configuration Système

+ +
+ + + +
+ +
+
+

Personnalisation du Profil

+
+ +
{{ user.username.charAt(0).toUpperCase() }}
+
+ +
+
+
+
+

Sécurité

+
{{ settingsMessage }}
+
+ + + + +
+
+
+ +
+
+

Outils Administrateur

+
+ + Exporter DB Modèles (JSON) +
+
+
+ +
+
+

Espace Réservé Super-Admin

+

Gestion globale du système.

+
+
+
+
+
@@ -243,7 +299,7 @@
- @@ -253,7 +309,7 @@
- @@ -267,7 +323,7 @@
@@ -276,7 +332,7 @@
@@ -285,7 +341,7 @@
@@ -294,7 +350,7 @@
@@ -303,7 +359,7 @@ @@ -312,7 +368,7 @@ @@ -321,16 +377,25 @@ - +
- - + + + + + + + +
@@ -369,10 +434,15 @@ const filterDomain = ref(""); const filterPricing = ref(""); + const colPricing = ref(""); + const colDomain = ref(""); + const workers = ref(["gemini-3.5-flash-lite", "", "", "", ""]); const selectedPrompter = ref("gemini-3.5-flash-lite"); const selectedConcatenator = ref("gemini-3.5-flash-lite"); + const attachedFile = ref(null); + const pinnedProjects = computed(() => projects.value.filter(p => p.is_pinned)); const recentProjects = computed(() => projects.value.filter(p => !p.is_pinned)); @@ -387,15 +457,16 @@ }); }); - const filteredBarModels = computed(() => { + const getFilteredModelsForCol = (pricingFilter, domainFilter, currentSelectedId) => { return modelInfo.models.filter(m => { - const matchDomain = filterDomain.value === "" || m.domain === filterDomain.value; + if (m.model_id === currentSelectedId) return true; + const matchDomain = domainFilter === "" || m.domain === domainFilter; let matchPrice = true; - if (filterPricing.value === 'free') matchPrice = m.is_free; - if (filterPricing.value === 'paid') matchPrice = !m.is_free; + if (pricingFilter === 'free') matchPrice = m.is_free; + if (pricingFilter === 'paid') matchPrice = !m.is_free; return matchDomain && matchPrice; }); - }); + }; const formatDate = (isoString) => { if (!isoString) return "Jamais"; @@ -444,6 +515,34 @@ event.target.value = null; }; + const handleFileUpload = (event) => { + const file = event.target.files[0]; + if (!file) return; + processFile(file); + event.target.value = null; + }; + + const handlePaste = (event) => { + const items = (event.clipboardData || event.originalEvent.clipboardData).items; + for (let index in items) { + let item = items[index]; + if (item.kind === 'file') { + let file = item.getAsFile(); + if (file) processFile(file); + } + } + }; + + const processFile = (file) => { + const reader = new FileReader(); + reader.onload = (e) => { + attachedFile.value = { name: file.name, content: e.target.result }; + }; + reader.readAsText(file); + }; + + const removeFile = () => { attachedFile.value = null; }; + const exportProject = (format) => { if(activeProject.value) window.open(`/api/projects/${activeProject.value.id}/export?format=${format}`, '_blank'); }; const copyToClipboard = async (text) => { try { await navigator.clipboard.writeText(text); alert("Copié !"); } catch (e) {} }; const downloadFile = (content, hint) => { let fname = hint ? hint.trim() : 'code.txt'; if (!fname.includes('.')) fname += '.txt'; const blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = fname; a.click(); URL.revokeObjectURL(url); }; @@ -456,23 +555,33 @@ const sendMessage = async () => { const activeWorkers = workers.value.filter(w => w && w.trim() !== ""); - if (!prompt.value.trim() || !activeProject.value || activeWorkers.length === 0) { - alert("Veuillez sélectionner au moins un travailleur."); + if ((!prompt.value.trim() && !attachedFile.value) || !activeProject.value || activeWorkers.length === 0) { + alert("Veuillez sélectionner au moins un travailleur et rédiger ou joindre un contenu."); return; } - const userText = prompt.value.trim(); prompt.value = ""; autoResize(); - messages.value.push({ id: Date.now(), role: 'user', content: userText }); - isAiThinking.value = true; scrollToBottom(); + const userText = prompt.value.trim(); + prompt.value = ""; + autoResize(); + + messages.value.push({ id: Date.now(), role: 'user', content: userText + (attachedFile.value ? `\n[Fichier joint: ${attachedFile.value.name}]` : "") }); + isAiThinking.value = true; + scrollToBottom(); const payload = { role: 'user', - content: userText, + content: userText || "Analyse ce fichier joint.", config: { workers: activeWorkers, prompter: selectedPrompter.value, concatenator: selectedConcatenator.value - } + }, + file_content: attachedFile.value ? attachedFile.value.content : null, + file_name: attachedFile.value ? attachedFile.value.name : null }; + + // DESTRUCTION IMMÉDIATE DU FICHIER CÔTÉ FRONTEND APRÈS ENVOI + attachedFile.value = null; + try { const response = await fetch(`/api/projects/${activeProject.value.id}/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (response.ok) { @@ -504,8 +613,9 @@ projects, pinnedProjects, recentProjects, activeProject, messages, isAiThinking, createNewProject, selectProject, renameProject, sendMessage, isLoading, togglePin, deleteProject, isSettingsModalOpen, openSettings, closeSettings, passForm, settingsMessage, isSavingSettings, changePassword, uploadAvatar, - modelInfo, financesList, filterSearch, filterDomain, filterPricing, filteredModels, filteredBarModels, formatDate, syncModels, isSyncing, - workers, selectedPrompter, selectedConcatenator, + modelInfo, financesList, filterSearch, filterDomain, filterPricing, filteredModels, getFilteredModelsForCol, formatDate, syncModels, isSyncing, + workers, selectedPrompter, selectedConcatenator, colPricing, colDomain, + attachedFile, handleFileUpload, handlePaste, removeFile, exportProject, copyToClipboard, downloadFile, downloadMessage, parseMessage }; }