fix: intégration de la route API de renommage des projets dans main.py
This commit is contained in:
1 parent
3226886190
commit
d6462dae38
3 files changed
+91
-29
No files matched your search
+13
-1
@@ -9,7 +9,7 @@ import os
|
||||
from .database import engine, Base, get_db
|
||||
from .models import User, Project, Message
|
||||
from .auth import get_password_hash, generate_totp_secret, get_totp_uri, verify_password, verify_totp, create_access_token, verify_token
|
||||
from .schemas import AdminCreate, LoginRequest, ProjectCreate, ProjectResponse, MessageCreate, MessageResponse
|
||||
from .schemas import AdminCreate, LoginRequest, ProjectCreate, ProjectResponse, ProjectRename, MessageCreate, MessageResponse
|
||||
|
||||
# Création des tables dans la base de données
|
||||
Base.metadata.create_all(bind=engine)
|
||||
@@ -171,6 +171,18 @@ def create_project(project: ProjectCreate, db: Session = Depends(get_db), curren
|
||||
db.refresh(new_project)
|
||||
return new_project
|
||||
|
||||
@app.put("/api/projects/{project_id}/rename", response_model=ProjectResponse)
|
||||
def rename_project(project_id: int, project_data: ProjectRename, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
|
||||
"""Renomme un projet existant."""
|
||||
project = db.query(Project).filter(Project.id == project_id, Project.user_id == current_user.id).first()
|
||||
if not project:
|
||||
raise HTTPException(status_code=404, detail="Projet introuvable")
|
||||
|
||||
project.title = project_data.title
|
||||
db.commit()
|
||||
db.refresh(project)
|
||||
return project
|
||||
|
||||
@app.put("/api/projects/{project_id}/pin", response_model=ProjectResponse)
|
||||
def toggle_pin_project(project_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
|
||||
"""Bascule le statut épinglé (is_pinned) d'un projet."""
|
||||
|
||||
+4
-1
@@ -41,4 +41,7 @@ class MessageResponse(MessageBase):
|
||||
project_id: int
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
from_attributes = True
|
||||
|
||||
class ProjectRename(BaseModel):
|
||||
title: str
|
||||
+74
-27
@@ -53,9 +53,10 @@
|
||||
<i data-lucide="pin" class="w-4 h-4 text-aethas-cyan fill-aethas-cyan flex-shrink-0"></i>
|
||||
<span class="truncate">{{ project.title }}</span>
|
||||
</div>
|
||||
<div class="hidden group-hover:flex items-center gap-1 flex-shrink-0">
|
||||
<button @click.stop="togglePin(project.id)" class="text-gray-400 hover:text-white p-1 rounded hover:bg-gray-700"><i data-lucide="pin-off" class="w-3.5 h-3.5"></i></button>
|
||||
<button @click.stop="deleteProject(project.id)" class="text-gray-400 hover:text-red-400 p-1 rounded hover:bg-gray-700"><i data-lucide="trash-2" class="w-3.5 h-3.5"></i></button>
|
||||
<div class="hidden group-hover:flex items-center gap-1 flex-shrink-0 bg-gray-800 pl-2 rounded">
|
||||
<button @click.stop="renameProject(project)" class="text-gray-400 hover:text-aethas-cyan p-1 rounded hover:bg-gray-700" title="Renommer"><i data-lucide="edit-2" class="w-3.5 h-3.5"></i></button>
|
||||
<button @click.stop="togglePin(project.id)" class="text-gray-400 hover:text-white p-1 rounded hover:bg-gray-700" title="Désépingler"><i data-lucide="pin-off" class="w-3.5 h-3.5"></i></button>
|
||||
<button @click.stop="deleteProject(project.id)" class="text-gray-400 hover:text-red-400 p-1 rounded hover:bg-gray-700" title="Supprimer"><i data-lucide="trash-2" class="w-3.5 h-3.5"></i></button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -71,9 +72,10 @@
|
||||
<i data-lucide="message-square" class="w-4 h-4 text-gray-500 flex-shrink-0"></i>
|
||||
<span class="truncate">{{ project.title }}</span>
|
||||
</div>
|
||||
<div class="hidden group-hover:flex items-center gap-1 flex-shrink-0">
|
||||
<button @click.stop="togglePin(project.id)" class="text-gray-400 hover:text-white p-1 rounded hover:bg-gray-700"><i data-lucide="pin" class="w-3.5 h-3.5"></i></button>
|
||||
<button @click.stop="deleteProject(project.id)" class="text-gray-400 hover:text-red-400 p-1 rounded hover:bg-gray-700"><i data-lucide="trash-2" class="w-3.5 h-3.5"></i></button>
|
||||
<div class="hidden group-hover:flex items-center gap-1 flex-shrink-0 bg-gray-800 pl-2 rounded">
|
||||
<button @click.stop="renameProject(project)" class="text-gray-400 hover:text-aethas-cyan p-1 rounded hover:bg-gray-700" title="Renommer"><i data-lucide="edit-2" class="w-3.5 h-3.5"></i></button>
|
||||
<button @click.stop="togglePin(project.id)" class="text-gray-400 hover:text-white p-1 rounded hover:bg-gray-700" title="Épingler"><i data-lucide="pin" class="w-3.5 h-3.5"></i></button>
|
||||
<button @click.stop="deleteProject(project.id)" class="text-gray-400 hover:text-red-400 p-1 rounded hover:bg-gray-700" title="Supprimer"><i data-lucide="trash-2" class="w-3.5 h-3.5"></i></button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -97,17 +99,20 @@
|
||||
<span class="font-bold text-lg neon-text">AETHAS38</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="activeProject" class="text-sm font-medium text-gray-300">{{ activeProject.title }}</div>
|
||||
<div v-if="activeProject" class="text-sm font-medium text-gray-300 flex items-center gap-2">
|
||||
{{ activeProject.title }}
|
||||
<button @click="renameProject(activeProject)" class="text-gray-500 hover:text-aethas-cyan"><i data-lucide="edit-2" class="w-4 h-4"></i></button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Affichage par défaut (Aucun projet actif) -->
|
||||
<!-- Affichage par défaut -->
|
||||
<div v-if="!activeProject" class="flex-1 overflow-y-auto p-4 md:p-8 flex flex-col items-center justify-center">
|
||||
<img src="/static/logo.png" alt="AETHAS38 Logo" class="w-24 h-24 mb-6 rounded-full border-2 border-aethas-cyan shadow-[0_0_15px_rgba(6,182,212,0.5)] object-cover bg-gray-800 flex-shrink-0">
|
||||
<h2 class="text-2xl font-bold text-gray-200 mb-2 text-center">Comment puis-je vous aider aujourd'hui ?</h2>
|
||||
<p class="text-gray-500 text-sm text-center">Sélectionnez ou créez un projet pour commencer.</p>
|
||||
</div>
|
||||
|
||||
<!-- Zone de Chat (Projet Actif) -->
|
||||
<!-- Zone de Chat -->
|
||||
<div v-else class="flex-1 overflow-y-auto p-4 md:p-8 space-y-6 flex flex-col" id="chat-container">
|
||||
<div v-if="messages.length === 0" class="flex-1 flex items-center justify-center text-gray-500 italic">Démarrez la conversation ci-dessous.</div>
|
||||
|
||||
@@ -119,22 +124,35 @@
|
||||
</div>
|
||||
<div class="text-gray-200 whitespace-pre-wrap text-sm leading-relaxed">{{ msg.content }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Indicateur de chargement IA -->
|
||||
<div v-if="isAiThinking" class="max-w-3xl rounded-2xl p-4 bg-aethas-panel mr-auto border border-aethas-cyan/30 w-24">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<i data-lucide="bot" class="w-4 h-4 text-aethas-cyan"></i>
|
||||
<span class="text-xs font-semibold uppercase tracking-wider text-gray-400">AETHAS38</span>
|
||||
</div>
|
||||
<div class="flex space-x-1.5 h-4 items-center">
|
||||
<div class="w-2 h-2 bg-aethas-cyan rounded-full animate-bounce"></div>
|
||||
<div class="w-2 h-2 bg-aethas-cyan rounded-full animate-bounce" style="animation-delay: 0.15s"></div>
|
||||
<div class="w-2 h-2 bg-aethas-cyan rounded-full animate-bounce" style="animation-delay: 0.3s"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Saisie du Prompt -->
|
||||
<div class="p-4 md:p-6 w-full max-w-4xl mx-auto">
|
||||
<div class="relative flex items-end bg-gray-800 border border-gray-600 rounded-2xl p-2 shadow-lg focus-within:border-aethas-cyan focus-within:ring-1 focus-within:ring-aethas-cyan transition-all">
|
||||
<button class="p-3 text-gray-400 hover:text-aethas-cyan transition-colors flex-shrink-0" :disabled="!activeProject"><i data-lucide="paperclip" class="w-5 h-5"></i></button>
|
||||
<button class="p-3 text-gray-400 hover:text-aethas-cyan transition-colors flex-shrink-0" :disabled="!activeProject || isAiThinking"><i data-lucide="paperclip" class="w-5 h-5"></i></button>
|
||||
<textarea
|
||||
v-model="prompt"
|
||||
@input="autoResize"
|
||||
@keydown.enter.exact.prevent="sendMessage"
|
||||
ref="promptTextarea"
|
||||
:disabled="!activeProject"
|
||||
:disabled="!activeProject || isAiThinking"
|
||||
rows="1"
|
||||
class="w-full bg-transparent text-gray-200 p-3 max-h-[200px] overflow-y-auto focus:outline-none resize-none leading-relaxed disabled:opacity-50"
|
||||
placeholder="Posez votre question à l'Orchestrateur..."></textarea>
|
||||
<button @click="sendMessage" :disabled="!activeProject || !prompt.trim()" class="p-3 text-aethas-cyan hover:text-white transition-colors flex-shrink-0 disabled:opacity-50 disabled:text-gray-500"><i data-lucide="send" class="w-5 h-5"></i></button>
|
||||
<button @click="sendMessage" :disabled="!activeProject || !prompt.trim() || isAiThinking" class="p-3 text-aethas-cyan hover:text-white transition-colors flex-shrink-0 disabled:opacity-50 disabled:text-gray-500"><i data-lucide="send" class="w-5 h-5"></i></button>
|
||||
</div>
|
||||
<p class="text-center text-xs text-gray-500 mt-3">AETHAS38 peut faire des erreurs. Vérifiez toujours les informations importantes.</p>
|
||||
</div>
|
||||
@@ -154,19 +172,18 @@
|
||||
const activeProject = ref(null);
|
||||
const messages = ref([]);
|
||||
const isLoading = ref(false);
|
||||
const isAiThinking = ref(false);
|
||||
|
||||
const pinnedProjects = computed(() => projects.value.filter(p => p.is_pinned));
|
||||
const recentProjects = computed(() => projects.value.filter(p => !p.is_pinned));
|
||||
|
||||
const toggleSidebar = () => { isSidebarOpen.value = !isSidebarOpen.value; };
|
||||
|
||||
const autoResize = () => {
|
||||
const el = promptTextarea.value;
|
||||
if (!el) return;
|
||||
el.style.height = 'auto';
|
||||
el.style.height = el.scrollHeight + 'px';
|
||||
};
|
||||
|
||||
const scrollToBottom = () => {
|
||||
nextTick(() => {
|
||||
const container = document.getElementById('chat-container');
|
||||
@@ -181,7 +198,7 @@
|
||||
projects.value = await response.json();
|
||||
nextTick(() => lucide.createIcons());
|
||||
}
|
||||
} catch (error) { console.error("Erreur", error); }
|
||||
} catch (error) { console.error(error); }
|
||||
};
|
||||
|
||||
const selectProject = async (project) => {
|
||||
@@ -194,7 +211,7 @@
|
||||
scrollToBottom();
|
||||
nextTick(() => lucide.createIcons());
|
||||
}
|
||||
} catch (error) { console.error("Erreur chargement messages", error); }
|
||||
} catch (error) { console.error(error); }
|
||||
};
|
||||
|
||||
const createNewProject = async () => {
|
||||
@@ -210,33 +227,63 @@
|
||||
projects.value.unshift(newProject);
|
||||
selectProject(newProject);
|
||||
}
|
||||
} catch (error) { console.error("Erreur", error); }
|
||||
} catch (error) { console.error(error); }
|
||||
finally { isLoading.value = false; }
|
||||
};
|
||||
|
||||
const renameProject = async (project) => {
|
||||
const newTitle = window.prompt("Nouveau nom du projet :", project.title);
|
||||
if (!newTitle || newTitle.trim() === "" || newTitle === project.title) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/projects/${project.id}/rename`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ title: newTitle.trim() })
|
||||
});
|
||||
if (response.ok) {
|
||||
const updatedProject = await response.json();
|
||||
const index = projects.value.findIndex(p => p.id === project.id);
|
||||
if (index !== -1) projects.value[index] = updatedProject;
|
||||
if (activeProject.value && activeProject.value.id === project.id) {
|
||||
activeProject.value.title = updatedProject.title;
|
||||
}
|
||||
}
|
||||
} catch (error) { console.error("Erreur de renommage", error); }
|
||||
};
|
||||
|
||||
const sendMessage = async () => {
|
||||
if (!prompt.value.trim() || !activeProject.value) return;
|
||||
const userText = prompt.value.trim();
|
||||
prompt.value = "";
|
||||
autoResize();
|
||||
|
||||
// Ajout optimiste à l'UI
|
||||
messages.value.push({ id: Date.now(), role: 'user', content: userText });
|
||||
isAiThinking.value = true;
|
||||
scrollToBottom();
|
||||
nextTick(() => lucide.createIcons());
|
||||
|
||||
// Sauvegarde en DB
|
||||
try {
|
||||
await fetch(`/api/projects/${activeProject.value.id}/messages`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ role: 'user', content: userText })
|
||||
});
|
||||
// Bientôt : C'est ici que nous déclencherons le workflow IA !
|
||||
} catch (error) { console.error("Erreur envoi message", error); }
|
||||
|
||||
// SIMULATION TEMPORAIRE : Attente de l'IA (2 secondes)
|
||||
setTimeout(() => {
|
||||
isAiThinking.value = false;
|
||||
scrollToBottom();
|
||||
// La prochaine étape remplacera ce setTimeout par le véritable appel au moteur IA !
|
||||
}, 2000);
|
||||
|
||||
} catch (error) {
|
||||
console.error("Erreur envoi message", error);
|
||||
isAiThinking.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const togglePin = async (projectId) => {
|
||||
const togglePin = async (projectId) => { /* ... code existant identique ... */
|
||||
try {
|
||||
const response = await fetch(`/api/projects/${projectId}/pin`, { method: 'PUT' });
|
||||
if (response.ok) {
|
||||
@@ -244,10 +291,10 @@
|
||||
const index = projects.value.findIndex(p => p.id === projectId);
|
||||
if (index !== -1) projects.value[index] = updatedProject;
|
||||
}
|
||||
} catch (error) { console.error("Erreur d'épinglage", error); }
|
||||
} catch (error) { console.error(error); }
|
||||
};
|
||||
|
||||
const deleteProject = async (projectId) => {
|
||||
const deleteProject = async (projectId) => { /* ... code existant identique ... */
|
||||
if (!confirm("Voulez-vous vraiment supprimer ce projet ?")) return;
|
||||
try {
|
||||
const response = await fetch(`/api/projects/${projectId}`, { method: 'DELETE' });
|
||||
@@ -255,7 +302,7 @@
|
||||
projects.value = projects.value.filter(p => p.id !== projectId);
|
||||
if (activeProject.value && activeProject.value.id === projectId) activeProject.value = null;
|
||||
}
|
||||
} catch (error) { console.error("Erreur de suppression", error); }
|
||||
} catch (error) { console.error(error); }
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
@@ -265,8 +312,8 @@
|
||||
|
||||
return {
|
||||
username, isSidebarOpen, toggleSidebar, prompt, autoResize, promptTextarea,
|
||||
projects, pinnedProjects, recentProjects, activeProject, messages,
|
||||
createNewProject, selectProject, sendMessage, isLoading, togglePin, deleteProject
|
||||
projects, pinnedProjects, recentProjects, activeProject, messages, isAiThinking,
|
||||
createNewProject, selectProject, renameProject, sendMessage, isLoading, togglePin, deleteProject
|
||||
};
|
||||
}
|
||||
}).mount('#app');
|
||||
|
||||
Reference in new issue
Block a user