XQ commited on
Commit
b95bdcd
Β·
1 Parent(s): 20b4d6f

Update chat memory

Browse files
Files changed (3) hide show
  1. requirements.txt +1 -0
  2. src/api/routes.py +9 -1
  3. 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
- # Per-browser session ID (persisted via cookie, falls back to session_state)
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
  # ---------------------------------------------------------------------------