Spaces:
Runtime error
Runtime error
Commit History
-
Saved responses: store just the pair, not the full threadβ
webmuppet +1: Claude Opus 4.7 (1M context)Previously save_response captured st.session_state.messages[:i+1] β every message leading up to the saved Q&A. Clicking a saved response restored that entire thread into the chat, which contradicted the 'one save per question' mental model the user wanted. New shape: each saved record holds messages_pair (2 entries: user q, assistant a) and history_pair (the same exchange in the LLM-history shape). The single result dict sits flat on the record. Restoring loads just those two messages into a fresh conversation_id β new questions fork from that pair as the only context. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>baa1903 -
Saved Responses: per-Q&A bookmarks with fork-on-clickβ
webmuppet +1: Claude Opus 4.7 (1M context)Replaces the per-message .md download with an in-app save that bookmarks that Q&A pair into a new sidebar section. Clicking a saved response forks the conversation β restores the snapshot context under a fresh conversation_id, leaving the original conversation untouched. The sidebar now has three vocabularies: - Save (in-app, Q&A pair β sidebar) - Saved Responses (the section holding them) - Download saved responses (aggregate β single .md file) - Recent conversations (renamed from Past conversations) Save is idempotent on (conversation_id, msg_index) β re-clicking the same Q&A is a no-op; the button renders 'Saved β' (disabled) once saved. Storage lives in a separate localStorage key (hmc-saved-responses) so conversation schema changes don't risk corrupting bookmarks. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>2260b35
-
Fix past-conversation delete (zombie bug) + title ellipsisβ
webmuppet +1: Claude Opus 4.7 (1M context)Two bugs fixed: 1. Delete didn't actually remove from localStorage. Root cause was the streamlit-js-eval st.rerun() race: calling st.rerun() inside the click handler aborted the script before the component was sent to the frontend, so the JS that writes to localStorage never executed. UI looked correct (cache mutation) but on browser refresh the deleted conversation came back from localStorage as a 'zombie'. Fix: deferred-write pattern. queue_delete() stashes the id on session_state, click handler reruns for UI, and the next render calls process_pending() at the top β which runs the actual delete + write inside a render that completes normally. The component sends to the frontend, JS executes, localStorage updates. 2. Past-conversation titles were hard-cut without ellipsis. text-overflow:ellipsis was on the button, but Streamlit wraps button labels in an inner div/p β the overflowing text node was the inner element, not the button. Moved β¦71d5674 -
Fix persistence: bypass streamlit-js-eval's broken set_local_storageβ
webmuppet +1: Claude Opus 4.7 (1M context)streamlit-js-eval 1.0.0 builds set_local_storage's JS as: localStorage.setItem('key', '<your-value>') β single-quoted, no escaping. Any apostrophe in the value (very common in compliance answers: 'I'll', 'you're', 'doesn't') breaks the JS string literal, raising 'Uncaught SyntaxError: missing ) after argument list' inside the iframe. The save silently fails; nothing lands in localStorage. Confirmed via console log from the user's session β every save was emitting a SyntaxError on the apostrophe in the assistant's response. Workaround: bypass the helper, call streamlit_js_eval directly, build the JS with json.dumps(json.dumps(value)) β the inner dumps produces our payload, the outer dumps wraps it as a JS-safe string literal with all special characters escaped. This was the root cause of all the persistence failures in this session, not the bootstrap_load cache logic and not the iframe hiding. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>59d97a8 -
Fix persistence: don't cache first-render None as 'loaded'β
webmuppet +1: Claude Opus 4.7 (1M context)Conversations weren't persisting across refreshes because of a race in bootstrap_load: streamlit-js-eval's get_local_storage() returns None on the first render (component is still mounting); Streamlit then auto-reruns and the second render returns the actual stored value. Old code cached the None response as []. Next render saw _CACHE_KEY already in session_state, short-circuited, and never re-read β so the real value (which was sitting in localStorage) never made it into the cache. Fix: only populate the cache when the response is non-None. First-render None is treated as 'still loading' and we let the next render re-attempt. Empty string is the actual 'no data stored' signal and is cached as []. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>314ef46 -
Add browser-local conversation persistenceβ
webmuppet +1: Claude Opus 4.7 (1M context)New feature: past conversations persist to browser localStorage and appear in the sidebar so users can return to prior compliance Q&A in the same browser. Implementation: - New module src/persistence.py wraps streamlit-js-eval to read/write a single localStorage key 'hmc-conversations' containing a JSON array of conversation objects (id, title, created_at, last_active_at, messages, history, results). Capped at 20 most recent by last_active_at. - App boot: persistence.bootstrap_load() runs once per session, cached in session_state thereafter. - conversation_id allocated when user submits first message in a fresh session, persisted across renders, cleared by 'New conversation', overwritten on restore. - Save fires after each assistant response β after st.write_stream returns and session_state is fully updated. No st.rerun() between state mutation and save, so no race condition. - Sidebar 'Past conversations' section: title button + β delete per row via st.columns([5,β¦26a2c61