Files
agent/docs/web-ui.md
highperfocused 089bd7bd48
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m15s
Add HTTP gateway with streaming chat and multi-client conversation mapping (#1)
Reviewed-on: MoA/agent#1
Co-authored-by: highperfocused <highperfocused@pm.me>
Co-committed-by: highperfocused <highperfocused@pm.me>
2026-03-12 14:47:45 +01:00

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 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 <id>
  • 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.


For full gateway/API details, see:

  • docs/gateway.md