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 @@
+
+
+
@@ -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
};
}