# Web UI: how it works This document explains the built-in browser UI served by the gateway. Source file: - `src/gateway/web-ui.ts` --- ## Overview The Web UI is a single HTML page returned by `GET /` (when enabled). It is intentionally simple: - plain HTML/CSS/JS (no framework) - sends requests to `/v1/chat/stream` - renders streamed assistant text in real time - stores and reuses `conversationId` in `localStorage` --- ## Availability The UI route is controlled by `GATEWAY_ENABLE_WEB_UI`: - `true` (default): `GET /` returns UI - `false`: `GET /` returns `404` with `{ "error": "Web UI disabled" }` If `GATEWAY_AUTH_TOKEN` is enabled, `GET /` also requires an `Authorization` header, because auth is global in the gateway. --- ## UI sections ### 1) Session/header card - **Conversation ID input** (`#conversationId`) - if empty, server auto-creates one during first message - persisted locally under `pi_gateway_conversation_id` - **Auth token input** (`#token`) - optional bearer token included in API requests from the page - this affects `fetch` calls only; it does not add auth headers to the initial page load ### 2) Messages card - container `#messages` - each message is appended as a `.msg.user` or `.msg.assistant` block - text is rendered as plain text (`textContent`), not Markdown/HTML ### 3) Composer card - textarea `#message` - status text `#status` - buttons: - `Send` - `New session` --- ## Local state The page keeps only minimal browser-side state: - `conversationId` in input + local storage - rendered message list in DOM - current request state via button disabled/enabled Storage key: - `pi_gateway_conversation_id` On load, if this key exists, it pre-fills the conversation input. --- ## Send flow When user presses **Send** (or Cmd/Ctrl + Enter): 1. Trim textarea value; ignore empty input. 2. Disable `Send` and `New session` buttons. 3. Append user message bubble. 4. Append empty assistant bubble. 5. Build payload: - required: `message` - optional: `conversationId` (if input non-empty) 6. POST to `/v1/chat/stream` with JSON body. 7. Parse SSE stream incrementally. 8. Update assistant bubble and status based on events. 9. Re-enable buttons when request finishes/fails. --- ## SSE event handling in UI Handled events: - `assistant_text_delta` - appends `data.delta` to assistant message bubble - `done` - reads `data.conversationId` - updates conversation input - writes `pi_gateway_conversation_id` - status becomes `Done • conversation ` - `error` - status becomes error text - writes fallback error into assistant bubble if empty Other event types are currently ignored by the UI. --- ## New session button behavior Clicking **New session**: - does nothing if a request is currently streaming (`Send` disabled) - clears conversation ID input - removes `pi_gateway_conversation_id` from local storage - clears rendered message list - sets status to `New session ready` - focuses the message textarea This starts a fresh client-side chat thread. The next send will create a new conversation on the server. --- ## Keyboard shortcut In the message textarea: - `Cmd + Enter` (macOS) or `Ctrl + Enter` (Windows/Linux) - triggers the same send flow as the Send button --- ## Limitations Current UI is intentionally minimal: - no server-side message history loading - no cancel/abort button for in-flight response - no rendering for tool events/thinking events - no Markdown formatting - no multi-conversation sidebar It is best used as a lightweight test/debug interface. --- ## Related API docs For full gateway/API details, see: - `docs/gateway.md`