mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-21 22:18:13 +02:00
Phase 4 : boucle agentique, outils fichiers et aperçu live
Backend : - tools.py : read_file / write_file / list_dir confinés au workspace (anti-traversal) - agent.py : boucle de tool-calling itérative au-dessus d'Ollama, génère des événements (token, tool_call, tool_result, final) - routes/chat.py : pilote run_agent, relaie les événements en SSE, persiste la réponse finale + le récap des outils (meta) - routes/files.py : arborescence et contenu du workspace (confiné) - db.py : colonne meta sur messages (+ migration douce), list_messages_for_model Frontend : - ToolCard : carte d'appel d'outil dans le fil (statut en cours/terminé/échec) - streamChat : événements tool_call / tool_result - store : streamTools, fileTree, preview (ouverture auto d'un HTML écrit) - LeftPanel : arborescence réelle du workspace, clic -> aperçu - PreviewPanel : rendu HTML en iframe + onglet Code - ChatPanel : rendu des outils dans les bulles agent (streaming + persisté) Tests manuels : outils + confinement, boucle agent (mock Ollama), chat HTTP de bout en bout (SSE, persistance meta, fichiers écrits, confinement route). 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
48cbcaafaf
commit
b6bf4edf97
13 files changed
+677
-80
No files matched your search
+33
-6
@@ -5,6 +5,7 @@ sont rapides ; un verrou protège l'accès concurrent depuis les routes async.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import sqlite3
|
||||
import threading
|
||||
@@ -42,12 +43,17 @@ def init_db() -> None:
|
||||
role TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
model TEXT,
|
||||
meta TEXT,
|
||||
created_at REAL NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_messages_session
|
||||
ON messages(session_id, created_at);
|
||||
"""
|
||||
)
|
||||
# Migration douce : ajoute la colonne meta aux bases antérieures.
|
||||
cols = {r["name"] for r in conn.execute("PRAGMA table_info(messages)")}
|
||||
if "meta" not in cols:
|
||||
conn.execute("ALTER TABLE messages ADD COLUMN meta TEXT")
|
||||
|
||||
|
||||
def _now() -> float:
|
||||
@@ -110,20 +116,33 @@ def touch_session(sid: str) -> None:
|
||||
|
||||
|
||||
# ── Messages ─────────────────────────────────────────────────────────────
|
||||
def add_message(sid: str, role: str, content: str, model: str | None) -> dict:
|
||||
def add_message(
|
||||
sid: str,
|
||||
role: str,
|
||||
content: str,
|
||||
model: str | None,
|
||||
meta: dict | None = None,
|
||||
) -> dict:
|
||||
mid = uuid.uuid4().hex
|
||||
now = _now()
|
||||
meta_json = json.dumps(meta, ensure_ascii=False) if meta else None
|
||||
with _LOCK, _connect() as conn:
|
||||
conn.execute(
|
||||
"INSERT INTO messages (id, session_id, role, content, model, created_at)"
|
||||
" VALUES (?, ?, ?, ?, ?, ?)",
|
||||
(mid, sid, role, content, model, now),
|
||||
"INSERT INTO messages (id, session_id, role, content, model, meta, created_at)"
|
||||
" VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
(mid, sid, role, content, model, meta_json, now),
|
||||
)
|
||||
conn.execute(
|
||||
"UPDATE sessions SET updated_at = ? WHERE id = ?", (now, sid)
|
||||
)
|
||||
return {"id": mid, "session_id": sid, "role": role, "content": content,
|
||||
"model": model, "created_at": now}
|
||||
"model": model, "meta": meta, "created_at": now}
|
||||
|
||||
|
||||
def _row_to_message(row: sqlite3.Row) -> dict:
|
||||
msg = dict(row)
|
||||
msg["meta"] = json.loads(msg["meta"]) if msg.get("meta") else None
|
||||
return msg
|
||||
|
||||
|
||||
def list_messages(sid: str) -> list[dict]:
|
||||
@@ -132,4 +151,12 @@ def list_messages(sid: str) -> list[dict]:
|
||||
"SELECT * FROM messages WHERE session_id = ? ORDER BY created_at",
|
||||
(sid,),
|
||||
).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
return [_row_to_message(r) for r in rows]
|
||||
|
||||
|
||||
def list_messages_for_model(sid: str) -> list[dict]:
|
||||
"""Historique épuré (role/content) destiné au contexte du modèle."""
|
||||
return [
|
||||
{"role": m["role"], "content": m["content"]}
|
||||
for m in list_messages(sid)
|
||||
]
|
||||
Reference in new issue
Block a user