Reviewed-on: MoA/agent#1 Co-authored-by: highperfocused <highperfocused@pm.me> Co-committed-by: highperfocused <highperfocused@pm.me>
3.6 KiB
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
conversationIdinlocalStorage
Availability
The UI route is controlled by GATEWAY_ENABLE_WEB_UI:
true(default):GET /returns UIfalse:GET /returns404with{ "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
fetchcalls only; it does not add auth headers to the initial page load
2) Messages card
- container
#messages - each message is appended as a
.msg.useror.msg.assistantblock - text is rendered as plain text (
textContent), not Markdown/HTML
3) Composer card
- textarea
#message - status text
#status - buttons:
SendNew session
Local state
The page keeps only minimal browser-side state:
conversationIdin 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):
- Trim textarea value; ignore empty input.
- Disable
SendandNew sessionbuttons. - Append user message bubble.
- Append empty assistant bubble.
- Build payload:
- required:
message - optional:
conversationId(if input non-empty)
- required:
- POST to
/v1/chat/streamwith JSON body. - Parse SSE stream incrementally.
- Update assistant bubble and status based on events.
- Re-enable buttons when request finishes/fails.
SSE event handling in UI
Handled events:
assistant_text_delta- appends
data.deltato assistant message bubble
- appends
done- reads
data.conversationId - updates conversation input
- writes
pi_gateway_conversation_id - status becomes
Done • conversation <id>
- reads
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 (
Senddisabled) - clears conversation ID input
- removes
pi_gateway_conversation_idfrom 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) orCtrl + 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