fix: intégration de la route API de renommage des projets dans main.py
Build and Push Docker Image / build-and-push (push) Successful in 41s
Build and Push Docker Image / build-and-push (push) Successful in 41s
This commit is contained in:
1 parent
3226886190
commit
8dd8d65b42
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 .database import engine, Base, get_db
|
||||||
from .models import User, Project, Message
|
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 .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
|
# Création des tables dans la base de données
|
||||||
Base.metadata.create_all(bind=engine)
|
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)
|
db.refresh(new_project)
|
||||||
return 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)
|
@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)):
|
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."""
|
"""Bascule le statut épinglé (is_pinned) d'un projet."""
|
||||||
|
|||||||
+4
-1
@@ -41,4 +41,7 @@ class MessageResponse(MessageBase):
|
|||||||
project_id: int
|
project_id: int
|
||||||
|
|
||||||
class Config:
|
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>
|
<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>
|
<span class="truncate">{{ project.title }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="hidden group-hover:flex items-center gap-1 flex-shrink-0">
|
<div class="hidden group-hover:flex items-center gap-1 flex-shrink-0 bg-gray-800 pl-2 rounded">
|
||||||
<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="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="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>
|
<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>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -71,9 +72,10 @@
|
|||||||
<i data-lucide="message-square" class="w-4 h-4 text-gray-500 flex-shrink-0"></i>
|
<i data-lucide="message-square" class="w-4 h-4 text-gray-500 flex-shrink-0"></i>
|
||||||
<span class="truncate">{{ project.title }}</span>
|
<span class="truncate">{{ project.title }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="hidden group-hover:flex items-center gap-1 flex-shrink-0">
|
<div class="hidden group-hover:flex items-center gap-1 flex-shrink-0 bg-gray-800 pl-2 rounded">
|
||||||
<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="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="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>
|
<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>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -97,17 +99,20 @@
|
|||||||
<span class="font-bold text-lg neon-text">AETHAS38</span>
|
<span class="font-bold text-lg neon-text">AETHAS38</span>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</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">
|
<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">
|
<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>
|
<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>
|
<p class="text-gray-500 text-sm text-center">Sélectionnez ou créez un projet pour commencer.</p>
|
||||||
</div>
|
</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-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>
|
<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>
|
||||||
<div class="text-gray-200 whitespace-pre-wrap text-sm leading-relaxed">{{ msg.content }}</div>
|
<div class="text-gray-200 whitespace-pre-wrap text-sm leading-relaxed">{{ msg.content }}</div>
|
||||||
</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>
|
</div>
|
||||||
|
|
||||||
<!-- Saisie du Prompt -->
|
<!-- Saisie du Prompt -->
|
||||||
<div class="p-4 md:p-6 w-full max-w-4xl mx-auto">
|
<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">
|
<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
|
<textarea
|
||||||
v-model="prompt"
|
v-model="prompt"
|
||||||
@input="autoResize"
|
@input="autoResize"
|
||||||
@keydown.enter.exact.prevent="sendMessage"
|
@keydown.enter.exact.prevent="sendMessage"
|
||||||
ref="promptTextarea"
|
ref="promptTextarea"
|
||||||
:disabled="!activeProject"
|
:disabled="!activeProject || isAiThinking"
|
||||||
rows="1"
|
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"
|
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>
|
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>
|
</div>
|
||||||
<p class="text-center text-xs text-gray-500 mt-3">AETHAS38 peut faire des erreurs. Vérifiez toujours les informations importantes.</p>
|
<p class="text-center text-xs text-gray-500 mt-3">AETHAS38 peut faire des erreurs. Vérifiez toujours les informations importantes.</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -154,19 +172,18 @@
|
|||||||
const activeProject = ref(null);
|
const activeProject = ref(null);
|
||||||
const messages = ref([]);
|
const messages = ref([]);
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
|
const isAiThinking = ref(false);
|
||||||
|
|
||||||
const pinnedProjects = computed(() => projects.value.filter(p => p.is_pinned));
|
const pinnedProjects = computed(() => projects.value.filter(p => p.is_pinned));
|
||||||
const recentProjects = 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 toggleSidebar = () => { isSidebarOpen.value = !isSidebarOpen.value; };
|
||||||
|
|
||||||
const autoResize = () => {
|
const autoResize = () => {
|
||||||
const el = promptTextarea.value;
|
const el = promptTextarea.value;
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
el.style.height = 'auto';
|
el.style.height = 'auto';
|
||||||
el.style.height = el.scrollHeight + 'px';
|
el.style.height = el.scrollHeight + 'px';
|
||||||
};
|
};
|
||||||
|
|
||||||
const scrollToBottom = () => {
|
const scrollToBottom = () => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
const container = document.getElementById('chat-container');
|
const container = document.getElementById('chat-container');
|
||||||
@@ -181,7 +198,7 @@
|
|||||||
projects.value = await response.json();
|
projects.value = await response.json();
|
||||||
nextTick(() => lucide.createIcons());
|
nextTick(() => lucide.createIcons());
|
||||||
}
|
}
|
||||||
} catch (error) { console.error("Erreur", error); }
|
} catch (error) { console.error(error); }
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectProject = async (project) => {
|
const selectProject = async (project) => {
|
||||||
@@ -194,7 +211,7 @@
|
|||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
nextTick(() => lucide.createIcons());
|
nextTick(() => lucide.createIcons());
|
||||||
}
|
}
|
||||||
} catch (error) { console.error("Erreur chargement messages", error); }
|
} catch (error) { console.error(error); }
|
||||||
};
|
};
|
||||||
|
|
||||||
const createNewProject = async () => {
|
const createNewProject = async () => {
|
||||||
@@ -210,33 +227,63 @@
|
|||||||
projects.value.unshift(newProject);
|
projects.value.unshift(newProject);
|
||||||
selectProject(newProject);
|
selectProject(newProject);
|
||||||
}
|
}
|
||||||
} catch (error) { console.error("Erreur", error); }
|
} catch (error) { console.error(error); }
|
||||||
finally { isLoading.value = false; }
|
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 () => {
|
const sendMessage = async () => {
|
||||||
if (!prompt.value.trim() || !activeProject.value) return;
|
if (!prompt.value.trim() || !activeProject.value) return;
|
||||||
const userText = prompt.value.trim();
|
const userText = prompt.value.trim();
|
||||||
prompt.value = "";
|
prompt.value = "";
|
||||||
autoResize();
|
autoResize();
|
||||||
|
|
||||||
// Ajout optimiste à l'UI
|
|
||||||
messages.value.push({ id: Date.now(), role: 'user', content: userText });
|
messages.value.push({ id: Date.now(), role: 'user', content: userText });
|
||||||
|
isAiThinking.value = true;
|
||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
nextTick(() => lucide.createIcons());
|
nextTick(() => lucide.createIcons());
|
||||||
|
|
||||||
// Sauvegarde en DB
|
|
||||||
try {
|
try {
|
||||||
await fetch(`/api/projects/${activeProject.value.id}/messages`, {
|
await fetch(`/api/projects/${activeProject.value.id}/messages`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ role: 'user', content: userText })
|
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 {
|
try {
|
||||||
const response = await fetch(`/api/projects/${projectId}/pin`, { method: 'PUT' });
|
const response = await fetch(`/api/projects/${projectId}/pin`, { method: 'PUT' });
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
@@ -244,10 +291,10 @@
|
|||||||
const index = projects.value.findIndex(p => p.id === projectId);
|
const index = projects.value.findIndex(p => p.id === projectId);
|
||||||
if (index !== -1) projects.value[index] = updatedProject;
|
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;
|
if (!confirm("Voulez-vous vraiment supprimer ce projet ?")) return;
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/projects/${projectId}`, { method: 'DELETE' });
|
const response = await fetch(`/api/projects/${projectId}`, { method: 'DELETE' });
|
||||||
@@ -255,7 +302,7 @@
|
|||||||
projects.value = projects.value.filter(p => p.id !== projectId);
|
projects.value = projects.value.filter(p => p.id !== projectId);
|
||||||
if (activeProject.value && activeProject.value.id === projectId) activeProject.value = null;
|
if (activeProject.value && activeProject.value.id === projectId) activeProject.value = null;
|
||||||
}
|
}
|
||||||
} catch (error) { console.error("Erreur de suppression", error); }
|
} catch (error) { console.error(error); }
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -265,8 +312,8 @@
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
username, isSidebarOpen, toggleSidebar, prompt, autoResize, promptTextarea,
|
username, isSidebarOpen, toggleSidebar, prompt, autoResize, promptTextarea,
|
||||||
projects, pinnedProjects, recentProjects, activeProject, messages,
|
projects, pinnedProjects, recentProjects, activeProject, messages, isAiThinking,
|
||||||
createNewProject, selectProject, sendMessage, isLoading, togglePin, deleteProject
|
createNewProject, selectProject, renameProject, sendMessage, isLoading, togglePin, deleteProject
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}).mount('#app');
|
}).mount('#app');
|
||||||
|
|||||||
Reference in new issue
Block a user