EXEPERT Brain Chat
The left-panel Chat mode uses a validated research-response contract. The
browser calls JWT-protected Supabase Edge Functions, the server calls 9Router
with a dedicated credential, and each assistant turn is persisted as
Phoenix/OpenInference-style observability data. New clients receive one bounded
exepert-research-v1 object; the original SSE transport remains only for older
clients during the rollout window.
The chat tile separates simulation context from AI routing:
- Brain Mode is the active EXEPERT brain/simulation mode.
- Model is the active AI provider/model selected from the 9Router-backed picker.
- Each assistant message keeps the provider/model that produced it, so old transcript entries remain understandable after the user changes models.
User flow
- The user opens the Chat tab in the left panel.
src/brain-ui.tssigns the visitor in anonymously when needed and provisions a playground project with the same pattern used by the Prompt Playground.fetchChatModels()loads the verified 9Router catalog through the dedicatedchat-modelsEdge Function. Refresh, stale-cache, and failure states remain distinct. The selected model is stored underexepert.brainChat.model.v1only while it remains in the server catalog.- The browser sends the current transcript, the selected
model_id, the new user message, and live brain telemetry to therun-chatEdge Function. run-chatvalidates the Supabase user token, checks project ownership, selects at most two relevant public Journal entries, calls 9Router, and validates one structured response.- The browser classifies the user's text with the local affect engine, immediately pulses the brain canvas, and renders an affect chip on the user message.
run-chatwrites asessionsrow, atracesrow, and aspansrow. The span stores both the validated JSON and a deterministic text projection.- The assistant message is classified, pulses the canvas again, and shows the provider/model footer, affect chip, and feedback controls.
- Affect records call
chat-affect, which writes syntheticaffective_stateannotations on the same assistant span. A thumbs vote callschat-feedback, which writes a Phoenix-styleuser_feedbackannotation on the assistant span.
Frontend changes
src/brain-ui.ts
brain-ui.ts owns the visible chat panel and now coordinates the real runtime:
- keeps a capped transcript in
localStorageunderexepert.brainChat.history.v1; - keeps a stable session key in
exepert.brainChat.session.v1; - keeps the selected model route in
exepert.brainChat.model.v1; - signs in anonymously if the user is not already authenticated;
- calls
ensurePlaygroundProject()and sets the active project; - loads the verified model list through
chat-modelsand exposes distinct loading, refresh, retry, stale-cache, and unavailable states; - renders the app-owned searchable model menu with public model-family groups, friendly names, variant badges, active/selected rows, click-away dismissal, and keyboard selection;
- keeps route identifiers searchable while rendering them only through the default-collapsed, non-persistent Technical names control;
- shows a live progress state while the structured answer is being built;
- safely renders non-empty Reply, Key Points, Evidence, Limitations, and Next Steps sections with Journal links restricted to known local slugs;
- adds a New chat action in the header;
- stores
trace_id,span_id,turn_id, requested route, resolved model, provider, structured-fallback state, and request metadata on assistant messages after the Edge Function returns; - renders a friendly route/resolved-model footer under assistant messages and puts exact identifiers in a collapsed technical disclosure;
- renders feedback buttons for persisted assistant messages;
- renders compact, responsive failure cards for chat route errors and keeps raw router payloads out of the transcript UI;
- classifies each user, assistant, and chat-error message through
src/affect/classifier.ts, renders affect chips, updates the Affective Field readout, and triggers emotion-colored brain pulses; - writes local and optionally LLM-refined affect annotations through
chat-affectafter the chat span is available.
The old fake response path (buildChatResponse) is no longer used. If the
request fails, the UI keeps the user's prompt in the transcript and replaces the
assistant placeholder with a .chat-error-card.
src/affect/*
The affect layer is intentionally synthetic research telemetry. It does not claim to detect a user's true emotion or diagnose mental state.
types.tsdefinesAffectAnalysis,AffectEvent,EmotionKey, andAffectStimulus.palette.tsassigns the dramatic emotion colors used by the canvas and UI.classifier.tsis the local deterministic classifier. It scores anger, fear, sadness, joy, disgust, surprise, curiosity, calm, distress, and neutral. Profanity is context-aware: "fuck yeah" routes toward high-arousal joy, while directed hostile profanity routes toward anger/distress and higher toxicity.mapping.tsmaps affect to the existing brain regions: visual, auditory, language, attention, or global. It also hashes text before persistence.client.tsposts affect events tochat-affectwith the Supabase access token.
The selected affect state is also included in the chat telemetry context as
affect_dominant, affect_intensity, affect_valence, and
affect_arousal, so the assistant can describe the visible affect layer when
asked.
src/chat/client.ts
The chat client is the browser boundary for Edge Function calls. It exports:
| Export | Purpose |
|---|---|
buildRunChatRequest() | Creates the request payload, includes the selected model_id, and caps history to the last 12 non-empty messages. |
fetchChatModels() | Loads verified model and combo metadata through chat-models, supports explicit refresh, and throws a visible error if cold discovery fails. |
normalizeChatErrorPayload() | Converts structured Edge Function errors and legacy plain message errors into compact UI-safe error objects. |
completeResearchChat() | Calls run-chat with research-v1, validates exepert-research-v1, and returns requested/route/resolved model plus observability metadata. |
parseSseLines() / streamChatCompletion() | Preserve the temporary legacy SSE transport for older deployed clients. |
submitChatFeedback() | Calls chat-feedback with the Supabase access token. |
The browser never receives the 9Router key. It only sends the user's Supabase JWT to the Edge Functions.