Phase 16 — Web UI

Replace the PySide6 layer with a browser-based frontend served by the existing FastAPI gateway. The Python backend — agents, tools, services, ZMQ bus — is untouched.

Motivation

LoCAL2 is heading toward being shared with others. The current PySide6 UI has real barriers: Qt dependency friction, a chat layout that doesn't match what people expect from frontier models, context and compaction as separate windows, no settings page. Every successful local LLM tool that's been shared widely (Open WebUI, Jan, AnythingLLM) is browser-based. The LoCAL2 backend is the differentiator — the bus architecture, CriticAgent, score-weighted memory, reward loop. The frontend should be as frictionless as possible to install and use.

Install story: git clone + pip install -r requirements.txtpython run_local.py → open localhost:8080

What We're Not Doing

Architecture

Browser └── WebSocket /ws/chat/{session_id} ──────────────────────────────────┐ └── REST /api/settings, /api/sessions, /api/feedback │ └── Static / (React build served by FastAPI) │ ▼ src/local/api/gateway.py (FastAPI) ZMQ Bus (existing) ├── WebSocket handler │ │ publishes query.received ──────────────────────────────▶ │ │ subscribes generation.thinking ◀──────────────────────── │ │ subscribes tool.activity.* ◀─────────────────────────── │ │ subscribes response.generation ◀─────────────────────── │ │ streams WS events → browser │ ├── Settings endpoints (read/write config YAML) │ ├── Sessions endpoints (list/get/delete) │ └── Serves frontend/dist/ as static files │ │ GeneratorAgent ◀────── query.received ────────────────────────── │ CriticAgent ──────▶ critique.result ──────────────────────── │ MemoryAgent ◀────── response.generation ─────────────────────┘ (all agents unchanged)

WebSocket Event Protocol

The gateway translates ZMQ bus events into a typed WebSocket event stream. The browser receives a sequence of JSON objects for each query:

// Client → server (one message per query)
{ "query": "...", "attachments": [...], "session_id": "..." }

// Server → client (stream)
{ "type": "thinking_chunk", "chunk": "...", "query_id": "..." }
{ "type": "tool_start",  "tool": "web_search", "args": {...},    "query_id": "..." }
{ "type": "tool_result", "tool": "web_search", "result": "...", "query_id": "..." }
{ "type": "response",    "answer": "...", "thinking": "...",
  "tool_calls": [...], "session_id": "...", "query_id": "...", "prompt_tokens": 0 }
{ "type": "error",       "message": "..." }
{ "type": "critique",    "score": 4, "feedback": "...", "query_id": "..." }

The WebSocket stays open for the session lifetime. Subsequent queries on the same connection reuse the same subscription. A separate /ws/bus/{session_id} endpoint streams all bus events for developer mode (state machine transitions, raw tool activity).

Frontend Stack

React + Vite + TypeScript — wider contributor familiarity than Svelte, strong ecosystem for markdown rendering and streaming UI. Component library: shadcn/ui (Tailwind-based, copy-paste components, no runtime dependency). Markdown: react-markdown + remark-gfm + highlight.js.

Milestones

16.1 — WebSocket Gateway

backend

Extend src/local/api/gateway.py with two WebSocket endpoints and new REST endpoints for settings and sessions. No frontend work yet — verify with wscat or a minimal HTML test page.

New endpoints

Files

16.2 — Frontend Scaffold

frontend

Create the React+Vite project, wire the WebSocket client, and implement a minimal chat loop: send query → stream thinking → display response. No styling polish yet; this is about proving the end-to-end data path works.

Files

16.3 — Chat Interface

frontend

The primary chat experience. Should feel like a frontier model UI: conversation in the main panel, thinking tokens as inline collapsible blocks, tool calls as compact activity chips, response rendered as markdown.

Components

Key UX decisions

16.4 — Settings Page

frontendbackend

A proper settings UI backed by the /api/settings endpoints. Reads current YAML config, renders editable form fields, writes back on save. No direct YAML file editing required.

Settings sections

SectionFields
Generatormodel, temperature, num_ctx, system_prompt, max_tool_iterations
Criticmodel, temperature, grade_timeout, rubric (textarea)
Memorymodel, collection names, score_weight, recency_weight
Web Searchprovider, api_key (masked), max_results
Locationoverride toggle, city/region/country fields

Settings changes take effect on next query (ConfigManager invalidation already handles hot-reload for tools that watch CONFIG_NAME).

16.5 — Conversation Navigator

frontend

Left sidebar listing past sessions from /api/sessions. Clicking a session loads its history into the chat view. New session button clears and starts fresh. Mirrors the existing ConversationsWindow but inline in the main layout.

16.6 — Developer Mode (Observability)

frontend

A toggleable developer panel that surfaces LoCAL2's observability layer — the differentiating features that no other local LLM frontend has. Hidden by default for end users; one click to reveal for researchers and developers.

Dev panel tabs

Uses the /ws/bus stream — no new backend logic required. The frontend filters and routes events to the appropriate panel.

File Map

FileStatusNotes
src/local/api/gateway.pymodifiedAdd WS endpoints, settings/sessions REST, static serving
src/local/api/ws_bridge.pynewZMQ→WebSocket event translation + bus subscription management
src/local/api/settings_api.pynewConfig read/write with validation
run_local.pymodified--api mode auto-opens browser; default port 8080
frontend/newReact + Vite + TypeScript project
frontend/src/hooks/newuseWebSocket, useChatStream, useSettings, useSessions
frontend/src/components/newMessageList, ThinkingBlock, ToolCallChip, TokenGauge, SettingsForm, DevPanel, SessionNav
src/local/ui/keptPySide6 UI unchanged in Phase 16; removed in Phase 17

What PySide6 Had — Web Equivalent

PySide6 windowWeb equivalentWhere
Main chat windowMessageList + MessageInputMain panel
ThinkingWindowThinkingBlock (inline, collapsible)Inline in message
ToolWindowToolCallChip + Dev panel Tools tabInline chip + Dev mode
CriticWindowCritiqueBar + Dev panel Critic tabUnder message + Dev mode
GeneratorWindowDev panel Generator tabDev mode
MemoryWindowDev panel Memory tabDev mode
ConversationsWindowSession navigator sidebarLeft sidebar
DocumentsWindowSettings → Library sectionSettings page
Context gauge + Compact buttonTokenGauge in chat headerChat header
YAML config editing (none)Settings pageSettings page (new)

Run Modes After Phase 16

# Web UI (default, opens browser)
python run_local.py

# Web UI headless (no browser pop, useful for servers)
python run_local.py --headless

# Legacy PySide6 (during transition)
python run_local.py --desktop

# Web UI + API exposed (for external clients)
python run_local.py --api-port 8080

Non-Goals for Phase 16

Acceptance Criteria