mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-16 11:49:43 +02:00
Phase 3 : chat streaming SSE et persistance des sessions
Backend : - db.py : SQLite (sessions + messages), schéma et CRUD - routes/sessions.py : créer/lister/ouvrir/renommer/supprimer une session - routes/chat.py : POST /api/chat, relais token par token depuis Ollama (SSE), persistance des messages, titrage auto de la session au 1er message - main.py : lifespan -> init_db, montage des nouvelles routes Frontend : - api/client.ts : APIs sessions + streamChat (parsing SSE event/data) - store : sessions, messages, état de streaming, envoi optimiste - ChatPanel : fil de conversation réel, bulles user/agent, curseur de frappe - LeftPanel : historique réel (ouvrir/supprimer, horodatage relatif) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SVay7z3y7q2gEe54ByAE6N
This commit is contained in:
1 parent
3ad37ae2c7
commit
48cbcaafaf
9 files changed
+688
-50
No files matched your search
@@ -29,6 +29,99 @@ export async function getModels(): Promise<{
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export interface Session {
|
||||
id: string;
|
||||
title: string;
|
||||
model?: string;
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
message_count?: number;
|
||||
}
|
||||
|
||||
export interface Message {
|
||||
id: string;
|
||||
session_id: string;
|
||||
role: "user" | "assistant";
|
||||
content: string;
|
||||
model?: string;
|
||||
created_at: number;
|
||||
}
|
||||
|
||||
export async function listSessions(): Promise<Session[]> {
|
||||
const res = await fetch("/api/sessions");
|
||||
return (await res.json()).sessions;
|
||||
}
|
||||
|
||||
export async function createSession(model?: string): Promise<Session> {
|
||||
const res = await fetch("/api/sessions", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ title: "Nouvelle session", model }),
|
||||
});
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function getSession(
|
||||
id: string
|
||||
): Promise<{ session: Session; messages: Message[] }> {
|
||||
const res = await fetch(`/api/sessions/${id}`);
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function deleteSession(id: string): Promise<void> {
|
||||
await fetch(`/api/sessions/${id}`, { method: "DELETE" });
|
||||
}
|
||||
|
||||
/** Envoie un message et streame la réponse de l'agent via SSE. */
|
||||
export async function streamChat(
|
||||
body: { session_id: string; content: string; model?: string },
|
||||
handlers: {
|
||||
onToken: (t: string) => void;
|
||||
onDone: (full: string) => void;
|
||||
onError: (msg: string) => void;
|
||||
}
|
||||
): Promise<void> {
|
||||
const res = await fetch("/api/chat", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
if (!res.body) {
|
||||
handlers.onError("pas de flux de réponse");
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = res.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = "";
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
const events = buffer.split("\n\n");
|
||||
buffer = events.pop() ?? "";
|
||||
|
||||
for (const block of events) {
|
||||
let event = "message";
|
||||
let data = "";
|
||||
for (const line of block.split("\n")) {
|
||||
if (line.startsWith("event: ")) event = line.slice(7).trim();
|
||||
else if (line.startsWith("data: ")) data += line.slice(6);
|
||||
}
|
||||
if (!data) continue;
|
||||
try {
|
||||
const payload = JSON.parse(data);
|
||||
if (event === "token") handlers.onToken(payload.content);
|
||||
else if (event === "done") handlers.onDone(payload.content);
|
||||
else if (event === "error") handlers.onError(payload.message);
|
||||
} catch {
|
||||
/* bloc partiel */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Télécharge un modèle en streamant la progression via SSE. */
|
||||
export async function pullModel(
|
||||
name: string,
|
||||
|
||||
Reference in new issue
Block a user