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.
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.txt→python run_local.py→ openlocalhost:8080
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/dist/frontend/dist/ at / via StaticFilesvite dev proxies /ws and /api to the Python serverExtend 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.
WS /ws/chat/{session_id} — query/response streamWS /ws/bus/{session_id} — raw bus event stream (developer mode)GET /api/settings — returns all YAML configs as JSONPOST /api/settings/{name} — writes a config YAML (validates keys)GET /api/sessions — lists sessions from ConversationServiceGET /api/sessions/{id} — returns session message historyDELETE /api/sessions/{id} — deletes a sessionPOST /api/sessions/{id}/compact — triggers compactionPOST /api/feedback — publishes user.feedback to bus (completes Phase 4)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.
frontend/ directory with Vite projectuseWebSocket hook wrapping the WS connection with reconnect logicuseChatStream hook accumulating event stream into message statefrontend/dist/; run_local.py --api launches both--api mode also serves frontendThe 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.
/api/sessions/{id}/compactA 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.
| Section | Fields |
|---|---|
| Generator | model, temperature, num_ctx, system_prompt, max_tool_iterations |
| Critic | model, temperature, grade_timeout, rubric (textarea) |
| Memory | model, collection names, score_weight, recency_weight |
| Web Search | provider, api_key (masked), max_results |
| Location | override toggle, city/region/country fields |
Settings changes take effect on next query (ConfigManager invalidation already handles hot-reload for tools that watch CONFIG_NAME).
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.
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.
/ws/bus); filterable by subject prefixUses the /ws/bus stream — no new backend logic required. The frontend filters and routes events to the appropriate panel.
| File | Status | Notes |
|---|---|---|
src/local/api/gateway.py | modified | Add WS endpoints, settings/sessions REST, static serving |
src/local/api/ws_bridge.py | new | ZMQ→WebSocket event translation + bus subscription management |
src/local/api/settings_api.py | new | Config read/write with validation |
run_local.py | modified | --api mode auto-opens browser; default port 8080 |
frontend/ | new | React + Vite + TypeScript project |
frontend/src/hooks/ | new | useWebSocket, useChatStream, useSettings, useSessions |
frontend/src/components/ | new | MessageList, ThinkingBlock, ToolCallChip, TokenGauge, SettingsForm, DevPanel, SessionNav |
src/local/ui/ | kept | PySide6 UI unchanged in Phase 16; removed in Phase 17 |
| PySide6 window | Web equivalent | Where |
|---|---|---|
| Main chat window | MessageList + MessageInput | Main panel |
| ThinkingWindow | ThinkingBlock (inline, collapsible) | Inline in message |
| ToolWindow | ToolCallChip + Dev panel Tools tab | Inline chip + Dev mode |
| CriticWindow | CritiqueBar + Dev panel Critic tab | Under message + Dev mode |
| GeneratorWindow | Dev panel Generator tab | Dev mode |
| MemoryWindow | Dev panel Memory tab | Dev mode |
| ConversationsWindow | Session navigator sidebar | Left sidebar |
| DocumentsWindow | Settings → Library section | Settings page |
| Context gauge + Compact button | TokenGauge in chat header | Chat header |
| YAML config editing (none) | Settings page | Settings page (new) |
# 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
python run_local.py opens the browser automatically on first run