feat(phase4): permanent cache-control & asset versioning, canonical nabil stored_session_id & resume, dsh session persistence fix, dsh dialogue/files toggle, and collapsible conv sidebar

This commit is contained in:
bolbol
2026-08-20 23:51:08 +01:00
parent e40fe71f5c
commit 9c8f292d0f
6 changed files with 150 additions and 36 deletions
+27 -6
View File
@@ -114,7 +114,7 @@ async def list_universe_conversations(universe_id: str) -> List[Dict[str, Any]]:
for s in backend_sessions:
sid = str(s.get("id") or s.get("session_id") or "")
if sid:
stitle = s.get("title") or s.get("display_name") or f"Session {sid[:8]}"
stitle = s.get("title") or s.get("display_name") or f"Session {sid[:15]}"
spinned = 1 if s.get("pinned") else 0
started_at = s.get("started_at")
c_at = int(started_at * 1000) if isinstance(started_at, (int, float)) and started_at < 1e11 else int(time.time() * 1000)
@@ -171,9 +171,10 @@ async def create_universe_conversation(universe_id: str, title: Optional[str] =
data = json.loads(raw)
if data.get("id") == req_id and "result" in data:
res = data["result"]
session_key = res.get("session_id") or res.get("stored_session_id")
# Prioritize durable stored_session_id (format YYYYMMDD_HHMMSS_xxxxxx) over ephemeral handle
session_key = res.get("stored_session_id") or res.get("session_id")
break
except Exception as e:
except Exception:
# Fallback if WS fails
session_key = f"nabil_{uuid.uuid4().hex[:12]}"
@@ -234,7 +235,6 @@ async def get_conversation_messages(universe_id: str, session_key: str) -> List[
async def stream_chat_messages(universe_id: str, session_key: str, message: str) -> AsyncGenerator[str, None]:
"""Streams chat chunks using Server-Sent Events (SSE)."""
# Touch conversation in SQLite and update title if first message
existing = get_conversation(universe_id, session_key)
if existing and existing.get("title") in ("Nouvelle conversation", "Untitled", None):
summary_title = (message[:38] + "...") if len(message) > 38 else message
@@ -272,13 +272,35 @@ async def stream_chat_messages(universe_id: str, session_key: str, message: str)
ping_interval=20,
ping_timeout=20
) as ws:
# 1. First resume/activate the session to get the active live handle
resume_req_id = f"res-{uuid.uuid4().hex[:8]}"
await ws.send(json.dumps({
"jsonrpc": "2.0",
"id": resume_req_id,
"method": "session.resume",
"params": {"session_id": session_key}
}))
active_sid = session_key
try:
while True:
raw_res = await asyncio.wait_for(ws.recv(), timeout=4.0)
data_res = json.loads(raw_res)
if data_res.get("id") == resume_req_id:
if "result" in data_res:
active_sid = data_res["result"].get("session_id") or session_key
break
except Exception:
pass
# 2. Submit prompt with active_sid
req_id = f"prompt-{uuid.uuid4().hex[:8]}"
send_payload = {
"jsonrpc": "2.0",
"id": req_id,
"method": "prompt.submit",
"params": {
"session_id": session_key,
"session_id": active_sid,
"text": message
}
}
@@ -300,7 +322,6 @@ async def stream_chat_messages(universe_id: str, session_key: str, message: str)
yield f"event: chunk\ndata: {json.dumps({'text': accumulated_text, 'chunk': delta, 'fullReplace': True})}\n\n"
elif ev_type in ("reasoning.delta", "thinking.delta"):
delta = payload.get("text", "")
# We can stream reasoning as needed
elif ev_type in ("message.finish", "session.idle", "reasoning.available") or data.get("method") in ("chat.done", "session.done"):
yield f"event: done\ndata: {json.dumps({'state': 'complete', 'text': accumulated_text})}\n\n"
break