Skip to content

2.2 chat

wiki[bot] edited this page Aug 23, 2026 · 3 revisions

2.2. The Chat Experience

The Chat area is the reason 3F exists: a complete, streaming, multi-modal conversation UI that shows its work. Component root: src/components/chat/.

Layout

┌─ conversation-header ─ model, session, conversation controls (rename/delete/pin) ─┐
│                                                               │
│  exchange-list                    ┌─ right-panel ────────────┐│
│   ├ chat-exchange (user)          │  session metadata,       ││
│   ├ chat-exchange (assistant)     │  attachments, tools,     ││
│   │   └ exchange-content          │  playlist                ││
│   │       ├ reasoning-area        └──────────────────────────┘│
│   │       └ assistant-response (structured)                   │
│   └ pending-indicator (streaming state)                       │
├─ prompt-action-bar ─ input, images, submit, cancel, think ────┤
└─ toolbar · floating-player · floating-playlist (media) ────────┘

Exchanges

An exchange is one user request + the assistant's full response cycle, keyed by requestId and persisted per session/conversation (HarnessConversation.content). The exchange-list renders the history; each chat-exchange shows:

  • the user prompt with its attached images;
  • the assistant-response, rendered from the harness's structured schemas — comparisons, describe/OCR blocks, summaries, articles, product/news cards, image/video galleries, and stock-market answers render as UI elements, not raw prose; foreign-language articles/videos appear in an international coverage aside so they augment rather than displace primary-language content.

Custom scroll experience

The exchange list supports two scroll modes, switchable per conversation from the SysCtl chat-navigation section and persisted in localStorage (app store):

  • Carousel (mode: 'carousel') — a vertical carousel of full-height sections (vertical-carousel/). Each exchange section is exactly one viewport tall; the container snaps between them and scrolling crossfades the outgoing section into the incoming one (useCarouselBlend computes per-slide opacity from scroll position, so the blend reads as a seamless cut rather than a hard jump). useVerticalCarousel owns the auto-scroll/pin behavior: a new prompt snaps to the newest section, the bottom pin releases once the first response content arrives so the user can read from the top, and a manual scroll-away is preserved until the response finishes.
  • Native (mode: 'native') — a continuous scroll container with variable-height sections (useNativeScroll): auto-scroll-to-bottom while the user is at the bottom, active-section highlight, and scrollToSection for history-click navigation.

Both modes share ScrollableExchangeList (the container that renders either variant) and CarouselSection (one section = user exchange + its assistant exchanges). Switching modes preserves the section the user was reading instead of jumping to the bottom. The chat column is composed by ChatMainColumn; empty states render ExchangeEmptyState / NoConversationPanel.

Stock-market answers

stockmarketitem / stockmarketlist responses render with a D3 chart suite (templates/stockmarket-response/d3-charts/): D3UnifiedStockChart (OHLC/heatmap/HLC-area with crosshair tooltips, zoom, and range controls) and D3StackedAreaChart. Charts fetch cached end-of-day history from GET /api/v1/stock-data/history (see 1.1), paginate via use-stock-history-pager, and size their range controls from the coverage endpoint. The previous lightweight-charts implementation was removed.

Gallery carousel

Image galleries with more than a couple of items render as a carousel (sections/gallery-section/carousel/): a scroll-snap track with prev/next buttons, active-index tracking from scroll position, and a header. AssistantCarousel orchestrates the track; CarouselContent/CarouselHeader render it.

Reasoning areas

Models with thinking support stream their chain-of-thought as a distinct phase. The reasoning-area renders it collapsed-but-inspectable inside the exchange — you can watch the model think without letting it dominate the answer. use-chat-think wires the think parameter so thinking is a per-conversation choice.

Conversation controls

The conversation header manages the thread's lifecycle directly:

  • Rename — inline title editing (ConversationTitleEditor).
  • Delete — remove the conversation and its uploaded objects.
  • Pin / Unpin — toggle a conversation between temporary and persistent (ConversationHeaderActions), so important threads survive session cleanup while throwaway ones stay ephemeral.

Streaming UX

  • Tokens land on the Socket.IO room as text/stream payloads (see 1.4) and append live; pending-indicator reflects the harness phase rather than a spinner-of-doom.
  • use-frozen-read-snapshot + use-read-tracker freeze the reading viewport while content grows, and release it precisely — no scroll fights mid-stream.
  • Cancel posts /api/v1/harness/cancel and waits for cancel_result — in-flight generation actually stops (see 1.2).

Input

The prompt action bar handles multimodal input: text plus one or more images (validated before upload), numCtx awareness (use-chat-context-size shows how much of the window the current thread consumes — the feature many proprietary chats hide), model picker fed by the live catalogue, and submit/cancel that maps 1:1 to the harness lifecycle.

SysCtl, DLQ, PProc interplay

Chat is not isolated: model + preprocessing + provider overrides configured in SysCtl apply to the next request; failed jobs surface in DLQ where they can be repaired and re-instated; PProc previews exactly which image variants the server will compute from your uploads.

Debuggability

components/debug and the debug store mirror the raw socket/API traffic. Every exchange can be traced client-side from submission through room events to the persisted record — the dashboard treats observability as a user feature.

Clone this wiki locally