Spaces:
Running
Running
XQ commited on
Commit Β·
b95bdcd
1
Parent(s): 20b4d6f
Update chat memory
Browse files- requirements.txt +1 -0
- src/api/routes.py +9 -1
- src/ui/app.py +44 -5
requirements.txt
CHANGED
|
@@ -35,6 +35,7 @@ pytest-asyncio==1.3.0
|
|
| 35 |
|
| 36 |
# Frontend
|
| 37 |
streamlit==1.56.0
|
|
|
|
| 38 |
requests==2.33.1
|
| 39 |
|
| 40 |
# Utilities
|
|
|
|
| 35 |
|
| 36 |
# Frontend
|
| 37 |
streamlit==1.56.0
|
| 38 |
+
extra-streamlit-components==0.1.81
|
| 39 |
requests==2.33.1
|
| 40 |
|
| 41 |
# Utilities
|
src/api/routes.py
CHANGED
|
@@ -327,17 +327,25 @@ async def query_stream(request: QueryRequest) -> StreamingResponse:
|
|
| 327 |
stream_kwargs["memory"] = session_memory
|
| 328 |
for event in _query_router.route_stream(**stream_kwargs):
|
| 329 |
event_queue.put(event)
|
| 330 |
-
# Persist turn to SQLite when streaming completes
|
|
|
|
|
|
|
|
|
|
|
|
|
| 331 |
if (
|
| 332 |
event.get("step") == "done"
|
| 333 |
and request.session_id
|
| 334 |
and _session_store is not None
|
| 335 |
):
|
| 336 |
result = event.get("result", {})
|
|
|
|
|
|
|
|
|
|
| 337 |
_session_store.persist_turn(
|
| 338 |
request.session_id,
|
| 339 |
request.question,
|
| 340 |
result.get("answer", ""),
|
|
|
|
| 341 |
)
|
| 342 |
except Exception as exc:
|
| 343 |
logger.error("Stream query failed: %s", exc, exc_info=True)
|
|
|
|
| 327 |
stream_kwargs["memory"] = session_memory
|
| 328 |
for event in _query_router.route_stream(**stream_kwargs):
|
| 329 |
event_queue.put(event)
|
| 330 |
+
# Persist turn to SQLite when streaming completes.
|
| 331 |
+
# The router has already added the turn (with sources) to the
|
| 332 |
+
# in-memory ConversationMemory before yielding `done`, so we
|
| 333 |
+
# read sources back from there to keep the SQLite copy
|
| 334 |
+
# consistent with the in-memory cache across restarts.
|
| 335 |
if (
|
| 336 |
event.get("step") == "done"
|
| 337 |
and request.session_id
|
| 338 |
and _session_store is not None
|
| 339 |
):
|
| 340 |
result = event.get("result", {})
|
| 341 |
+
persisted_sources = (
|
| 342 |
+
session_memory.last_sources() if session_memory else []
|
| 343 |
+
)
|
| 344 |
_session_store.persist_turn(
|
| 345 |
request.session_id,
|
| 346 |
request.question,
|
| 347 |
result.get("answer", ""),
|
| 348 |
+
persisted_sources,
|
| 349 |
)
|
| 350 |
except Exception as exc:
|
| 351 |
logger.error("Stream query failed: %s", exc, exc_info=True)
|
src/ui/app.py
CHANGED
|
@@ -4,22 +4,22 @@ Calls the FastAPI backend at http://localhost:8000.
|
|
| 4 |
Single-page document search interface with clean sans-serif design.
|
| 5 |
"""
|
| 6 |
|
|
|
|
| 7 |
import html
|
| 8 |
import json
|
| 9 |
import os
|
| 10 |
import random
|
| 11 |
import uuid
|
| 12 |
|
|
|
|
| 13 |
import streamlit as st
|
| 14 |
import requests
|
| 15 |
|
| 16 |
API_BASE = os.environ.get("API_BASE_URL", "http://localhost:8000")
|
| 17 |
|
| 18 |
-
# -
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
if "session_id" not in st.session_state:
|
| 22 |
-
st.session_state["session_id"] = str(uuid.uuid4())
|
| 23 |
|
| 24 |
# ---------------------------------------------------------------------------
|
| 25 |
# Example questions β drawn from the documents in docs/
|
|
@@ -223,6 +223,45 @@ st.set_page_config(
|
|
| 223 |
|
| 224 |
st.markdown('<meta name="robots" content="noindex, nofollow">', unsafe_allow_html=True)
|
| 225 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
# ---------------------------------------------------------------------------
|
| 227 |
# Analytics β Umami Cloud
|
| 228 |
# ---------------------------------------------------------------------------
|
|
|
|
| 4 |
Single-page document search interface with clean sans-serif design.
|
| 5 |
"""
|
| 6 |
|
| 7 |
+
import datetime
|
| 8 |
import html
|
| 9 |
import json
|
| 10 |
import os
|
| 11 |
import random
|
| 12 |
import uuid
|
| 13 |
|
| 14 |
+
import extra_streamlit_components as stx
|
| 15 |
import streamlit as st
|
| 16 |
import requests
|
| 17 |
|
| 18 |
API_BASE = os.environ.get("API_BASE_URL", "http://localhost:8000")
|
| 19 |
|
| 20 |
+
# Cookie name used to persist the per-browser session ID across page reloads.
|
| 21 |
+
_SESSION_COOKIE_NAME = "kuda_session_id"
|
| 22 |
+
_SESSION_COOKIE_TTL_DAYS = 30
|
|
|
|
|
|
|
| 23 |
|
| 24 |
# ---------------------------------------------------------------------------
|
| 25 |
# Example questions β drawn from the documents in docs/
|
|
|
|
| 223 |
|
| 224 |
st.markdown('<meta name="robots" content="noindex, nofollow">', unsafe_allow_html=True)
|
| 225 |
|
| 226 |
+
# ---------------------------------------------------------------------------
|
| 227 |
+
# Per-browser session ID β persisted in a cookie so chat history survives
|
| 228 |
+
# page refreshes. Falls back to a freshly generated UUID if the cookie is
|
| 229 |
+
# not yet readable (first visit, or before the JS component has initialised).
|
| 230 |
+
# ---------------------------------------------------------------------------
|
| 231 |
+
@st.cache_resource
|
| 232 |
+
def _get_cookie_manager() -> stx.CookieManager:
|
| 233 |
+
"""Return a singleton CookieManager (cached across reruns)."""
|
| 234 |
+
return stx.CookieManager(key="kuda_cookie_manager")
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
_cookie_manager = _get_cookie_manager()
|
| 238 |
+
_cookies = _cookie_manager.get_all()
|
| 239 |
+
|
| 240 |
+
# CookieManager loads cookies asynchronously via a JS component. On the very
|
| 241 |
+
# first script run after a page load, get_all() returns None because the
|
| 242 |
+
# component has not yet reported back. Stop here and wait for the rerun the
|
| 243 |
+
# component triggers once it delivers the browser's cookies β otherwise we
|
| 244 |
+
# would always see "no cookie" on first render and overwrite any existing
|
| 245 |
+
# session_id with a fresh UUID.
|
| 246 |
+
if _cookies is None:
|
| 247 |
+
st.stop()
|
| 248 |
+
|
| 249 |
+
_existing_sid = _cookies.get(_SESSION_COOKIE_NAME)
|
| 250 |
+
if _existing_sid:
|
| 251 |
+
# Cookie present β reuse it so the backend can find prior turns.
|
| 252 |
+
st.session_state["session_id"] = _existing_sid
|
| 253 |
+
elif "session_id" not in st.session_state:
|
| 254 |
+
# No cookie yet β mint a fresh ID and persist it for next reload.
|
| 255 |
+
new_sid = str(uuid.uuid4())
|
| 256 |
+
st.session_state["session_id"] = new_sid
|
| 257 |
+
_cookie_manager.set(
|
| 258 |
+
_SESSION_COOKIE_NAME,
|
| 259 |
+
new_sid,
|
| 260 |
+
expires_at=datetime.datetime.now()
|
| 261 |
+
+ datetime.timedelta(days=_SESSION_COOKIE_TTL_DAYS),
|
| 262 |
+
key="kuda_set_session_cookie",
|
| 263 |
+
)
|
| 264 |
+
|
| 265 |
# ---------------------------------------------------------------------------
|
| 266 |
# Analytics β Umami Cloud
|
| 267 |
# ---------------------------------------------------------------------------
|