from typing import Literal, NotRequired, TypedDict import streamlit as st from utils.helpers import session_key PendingChatAction = Literal["new_user_prompt", "regenerate_after_edit"] class ChatMessage(TypedDict): role: str content: str _contrast: NotRequired[object] _needs_contrast: NotRequired[bool] class ChatState(TypedDict): messages: list[ChatMessage] persona_id: str | None prompt_mode: str def chat_session_key(model_name: str, dataset_source: str) -> str: """Build the session-state key for a chat conversation. A model/backend switch changes *how* the next turn is generated, not which conversation the user is looking at. Keeping the model out of the key means toggling local/remote execution (or selecting another model) no longer makes an existing thread appear to vanish behind a fresh empty state. ``model_name`` stays in the signature for call-site compatibility and to make the intent explicit where chat state is requested. """ _ = model_name return session_key("chat_state", dataset_source) def default_chat_state() -> ChatState: return { "messages": [], "persona_id": None, "prompt_mode": "templated", } def reset_chat_context_state( state: ChatState, persona_id: str, prompt_mode: str, *ui_keys: str, ) -> None: """Reset one chat context and clear any related widget state.""" state["messages"] = [] state["persona_id"] = persona_id state["prompt_mode"] = prompt_mode for key in ui_keys: st.session_state.pop(key, None) def get_chat_state(model_name: str, dataset_source: str) -> ChatState: """Return the mutable chat state for the active context.""" key = chat_session_key(model_name, dataset_source) return st.session_state.setdefault(key, default_chat_state())