Frontend — React UI, stores, streaming & theming
http://localhost:8765.1. Layout (frontend/src/App.tsx)
BootLoader ──(health check /api/health)──▶ App
App
├── Sidebar (conversations, theme toggle, workspace toggle)
├── main
│ ├── ChatArea (messages, streaming answer, statuses, thinking,
│ │ tool calls, citations)
│ ├── ApprovalBar (human-in-the-loop approve/reject, when pending)
│ └── InputBar (send, attach files, folder pick, drag-and-drop)
└── Right panel (one at a time)
├── SourcesPanel (sources, pipeline dashboard, execution log,
│ working set, memo export)
├── WorkspacePanel (discovered folder files)
└── PipelineEditor (visual pipeline canvas, 560px panel)
Plus: CommandPalette (Cmd/Ctrl+K), SourceViewerModal (citation detail)
- Boot gates the whole UI on
/api/healthsucceeding (BootLoader shows loading/error with retry). - Keyboard shortcuts: keys 1–5 select sources, arrows navigate, Esc closes modals, Cmd/Ctrl+K toggles the command palette.
2. Chat flow (App.handleSend)
- Ensure a conversation exists; append the user message; start streaming.
- If
pendingFilesexist →uploadFile()each (sequential, with status events), collectuploadedPaths/uploadedNames; if the user typed nothing, the query becomes a default batch-scan prompt ("Scan these documents for compliance issues: …"). streamChat({query, mode, top_k, conversation_id, attached_file_paths, attached_filenames})— an async generator parsing SSE lines.- Event switch:
meta— ignoredintent— status line with icon + confidencestatus→addStreamingStatusthinking→appendStreamingThinking(chain-of-thought)tool_call/tool_result→ streaming tool cardsplan→ plan result line (the routing directive, humanized)node_start/node_complete→ execution log (pipeline nodes)sources→setSourcesinterrupted→setApprovalPending(shows ApprovalBar)resumed→ clear approval, continuetoken→appendStreamingTokendone→finalizeStreamingMessage(sources, execNodes)
- If the stream ends without
doneand no approval is pending, finalize anyway (interrupt fallback).
handleResume(approved) calls resumeChat(threadId, response, 5) and feeds the same event pipeline.
3. State management (frontend/src/store/chatStore.ts)
Zustand store, persisted per project: the storage key is freebuff-chat-storage plus a per-project hash suffix (freebuff-chat-storage:<project-hash>, re-pointed via projectStorage.ts when the workspace changes), and the same data is also saved server-side to the project's workspace/history.json (only conversations + activeConversationId + starred messages are persisted; streaming state is ephemeral; Date objects are rehydrated via rehydrateDates).
Key state:
conversations[],activeConversationId,messages[],sources[]- Streaming:
streamingAnswer,streamingNodes,streamingStatuses,streamingThinking,streamingToolCalls,isStreaming,streamStartedAt - HITL:
approvalPending,approvalThreadId,approvalDescription,approvalToolName,approvalStartedAt,lastApprovalLatencyMs - Actions: create/select/delete conversation (auto-titles from the first user message), addMessage, all streaming append/update actions,
finalizeStreamingMessage(computesPipelineMetrics: totalMs, approvalLatencyMs, retryCount, nodeTimings; stores thinking/statuses/ toolCalls on the message),setApprovalPending/clearApprovalPending,getLiveMetrics.
4. API layer (frontend/src/services/api.ts)
API_BASE_URL = 'http://localhost:8765'api.healthCheck(),api.chat(),api.getDocuments()uploadFile(file)→ FormData to/api/upload(requires an active workspace)streamChat(req)/resumeChat(threadId, response, topK)— SSE parsers yielding typedStreamEvents- Workspace:
setWorkspace,getWorkspace,clearWorkspace,streamWorkspaceIngest(SSE withingest_start/ingest_progress/ingest_done/ingest_cancelled/ingest_error),cancelWorkspaceIngest,getWorkspaceWatchStatus,getWorkspaceHistory,saveWorkspaceHistory - GPU profile:
getGpuProfile,saveGpuProfile - Scan:
fetchPlaybooks,fetchDocuments,fetchFlags,resolveFlag,streamScan(documentId, playbookId),streamScanUpload(file, playbookId)
5. Right-panel components
SourcesPanel (components/Sources/SourcesPanel.tsx, 360px)
- PipelineDashboard — metrics for recent messages (queries, avg time, approval latency, retry count).
- PipelineExecution — the execution log of the current/last query (nodes, statuses, elapsed ms).
- SourceItem list — each source shows file, page, section, score; click opens the SourceViewerModal.
- WorkingSetPanel — pinned/selected sources.
- Memo export —
buildMemoFromCurrentConversation()→ copies a legal memorandum to the clipboard ("Memo" button).
WorkspacePanel
Shows files discovered from a selected folder (supported document extensions only, capped at 50) with per-file remove and clear-all.
ScanPanel (components/Scan/ScanPanel.tsx)
- Document + playbook selectors; "Start Scan" (50MB upload cap).
- Live progress bar (current/total, phase, %), streaming statuses.
- Result summary card (chunks, classified, flags, timing).
- Flags list with severity colors/icons and resolve/dismiss/escalate (optimistic update, revert on failure).
- Auto-scans an attached file once when opened with
initialFile.
Chat components (components/Chat/)
ChatArea, Message, StreamingMessage (react-markdown + GFM), SkeletonMessage, TypingIndicator, InputBar (attachment chip list, drag- and-drop), ApprovalBar, ScanFlagsCard (compliance flags on the answer), ConfidenceMeter, AnswerTransparency, InlineCitation, BootLoader, ScanProgressBar.
6. Streaming UX
- Tokens append live into
streamingAnswer; the streaming message card shows statuses, chain-of-thought, tool calls with results, and inline citations. - Node statuses (
node_start/node_complete) drive the execution log and the pipeline editor's live highlighting. - On
done, the message is finalized and stored with its sources, execution log, metrics, thinking, statuses, and tool calls.
7. Theming (light & dark)
- Default is light parchment;
useThemetoggleshtml.lighton the root element (persisted). - All colors are CSS variables in
index.css(--bg-primary,--bg-secondary,--bg-card,--text-primary/secondary/muted,--accent/brass family,--border-color,--text-info,--severity-warn). - An
html.light"bridge" remaps Tailwind utility classes (text-gray-*,text-white, severity colors, placeholder, hover variants) so components styled for dark mode stay readable on parchment. No opacity-variant color classes remain; white text only appears on accent/gradient/amber backgrounds. - Fonts: Newsreader (display/serif headings), Plus Jakarta Sans (UI), JetBrains Mono (labels).
8. Tooling & scripts
frontend/package.json:dev(vite),build(tsc && vite build),lint,typecheck(tsc --noEmit).- Root
package.json:devruns the Electron app (frontend/electron), which boots the backend and Vite itself;build/package/package:win|mac|linuxvia electron-builder.