Files
Veskslop-t/docs/webui.md
T

148 lines
7.3 KiB
Markdown

# Web UI
The daemon serves the debug web control UI. The owner uses it to observe the account the agent
sees, read and send messages, search users, and see voice membership. The shell follows the design
handoff in `poc/WebUIMockup.zip`; several panels are roadmap placeholders.
## Run
The UI starts with the daemon. No extra command is needed.
```sh
cd poc
pnpm control:bridge
# Page
http://<host>:8789/
# API
http://<host>:8789/api/health
```
| Setting | Default | Purpose |
| --- | --- | --- |
| `WEBUI_HOST` | `0.0.0.0` | Listen address |
| `WEBUI_PORT` | `8789` | Listen port |
## Security
The UI has **no authentication**. Anyone who can reach the port controls the bot, can start and
stop the client, and can start a QR login. Bind the daemon to a private LAN or a Tailscale address
only. Do not expose the port to the public internet. See [daemon.md](daemon.md).
The MCP server stays on `127.0.0.1:8788`. Only the UI binds outside localhost.
## Page
- Plain HTML, CSS, and JavaScript in `poc/public/`. No build step and no frontend framework.
- The look follows the Nocturne design system (`poc/public/nocturne.css`) from the design handoff
in `poc/WebUIMockup.zip`. Implemented screen: **7a**, the docked three-column debug shell.
- Layout: 50px header, conditional call strip, `236px | 1fr | 306px` body, and the agent console
bar (35px collapsed, resizable drawer expanded).
- Header: wordmark, guild picker, health indicators, Takeover switch, Settings.
- Left rail: guild switcher, text channels, voice channels with inline participants, direct
messages, and the account footer.
- Center: channel header, message list, composer, and the TTS panel.
- Right column: In voice, Favourites, Open DMs & groups.
- Console: Tool calls, Events (live SSE), Transcript. The ticker shows the newest event.
- Opening a chat fetches the latest 50 messages from Discord into the client cache. The refresh
button by the channel header re-fetches. Live message events read the cache.
- Attachments render from their Discord CDN links. Images display inline; other files are link
chips. The daemon never downloads or stores attachment bytes.
- Avatars and guild icons load from the Discord CDN with a monogram fallback.
- Composer and TTS drafts persist in `localStorage` per channel. They survive re-renders, the
bridge connecting, and a page reload. A successful send clears the channel draft.
- When the bridge connects after the page loaded, the page reloads state without touching drafts.
- Roadmap gaps are shown, not hidden: TTS, transcript, tool-call tracing, favourites, and voice
join/monitor appear as marked placeholders.
## Takeover
Takeover is a read-only switch. Off, the composer is locked (a `read-only` tag sits next to the
lock) and the page cannot send. On, the composer unlocks and every manual send is written to the
action log as a `manual` entry. The choice is stored in `localStorage`. The setting is client-side
only; the API itself does not enforce it.
## Settings
The Settings button opens a slide-over with the server-side controls:
- **Core process** — state, last exit, output, Start / Stop / Restart.
- **Account login** — token file state, QR login, captcha.
- **Visible guilds** — checkboxes for the guild switcher and picker. Stored in `localStorage`;
"Show all" clears the filter.
- **Action log** — the persistent JSONL log, newest first.
## Core process
The Settings panel starts, stops, and restarts the Vesktop client. The daemon spawns the process
from the repo root and labels its output with `[core]` in the daemon log.
- The default command is `node_modules/.bin/electron .` with
`--no-sandbox --disable-gpu --disable-dev-shm-usage`.
- Override the command with `VESKSLOP_CORE_CMD` and the flags with `VESKSLOP_CORE_ARGS`.
- The spawn sets `VESKSLOP_HEADLESS=1` and points `VESKSLOP_TOKEN_FILE` at the daemon token file.
- On Linux without a `DISPLAY`, the daemon starts Xvfb automatically. `VESKSLOP_XVFB=0` disables
this. A real `DISPLAY` wins.
- Only a process started by the current daemon is managed. An externally started client shows as
`external` and cannot be stopped from the UI.
- The status shows the last exit code or signal and the last 20 output lines. A failed start is
visible on the page instead of only in the daemon log.
## Login
The Settings panel runs the same QR flow as `pnpm poc:qr`, with the QR rendered as an image.
1. Click **Start QR login**. The daemon opens the remote-auth gateway and shows the QR.
2. Scan the QR with the Discord mobile app and approve.
3. On success the token is written to the token file with mode 600. A running managed core
restarts with `VESKSLOP_FORCE_TOKEN=1` so the new account takes effect.
4. If Discord asks for a captcha, the panel shows the sitekey and a field for the hCaptcha response
token. Paste the token and submit.
The token never leaves the daemon. The API reports the account name and ID, not the token.
## API
The JSON API is the migration boundary. A future frontend calls the same routes. The page code is
replaceable.
| Method | Path | Body or query | Result |
| --- | --- | --- | --- |
| GET | `/api/health` | none | Core state, process state, owner, uptime, event count, query count |
| GET | `/api/core/status` | none | Bridge and core process state |
| POST | `/api/core/start` | `forceToken?` | Start the client |
| POST | `/api/core/stop` | none | Stop the managed client |
| POST | `/api/core/restart` | `forceToken?` | Restart the managed client |
| GET | `/api/auth` | none | Token file state and the QR session, with `qrDataUrl` when pending |
| POST | `/api/auth/qr` | none | Start a QR login session |
| POST | `/api/auth/qr/cancel` | none | Cancel the session |
| POST | `/api/auth/qr/captcha` | `key` | Submit an hCaptcha response token |
| GET | `/api/state` | none | User, guilds, and channels |
| GET | `/api/messages` | `channelId`, `limit`, `refresh?` | Messages from a channel. `refresh=1` fetches the latest from Discord and fills the client cache; otherwise cache-only. |
| POST | `/api/messages` | `channelId`, `content` | Send a message. Records a `manual` action. |
| POST | `/api/dm` | `userId` or `username`, `content` | Send a DM. Records a `manual` action. |
| GET | `/api/users` | `q`, `guildId?`, `limit?` | Search the client user cache |
| GET | `/api/dms` | `limit?` | Cached DM and group DM channels with recipients |
| GET | `/api/voice` | `userId?`, `guildId?` | One user's voice state |
| GET | `/api/voice/overview` | `guildId` | Occupied voice channels and members |
| POST | `/api/voice/join` | `channelId` | Reserved. Returns `501` |
| POST | `/api/voice/leave` | none | Reserved. Returns `501` |
| GET | `/api/logs` | `limit` | The persistent action log |
| GET | `/api/events` | none | Server-sent events |
Every response is JSON: `{ "ok": true, "result": ... }` or `{ "ok": false, "error": "..." }`. A
core validation error adds `code`.
The channel detail routes use the same pacing as the MCP tools. Sends are paced; reads take a small
gap. See [daemon.md](daemon.md).
## Not started
- Web UI authentication and user accounts.
- Voice join and leave. The UI elements exist and call the reserved routes.
- TTS playback, transcript capture, and MCP tool-call tracing.
- Favourites, presence, member counts, and message editing/deletion/reactions.
- Pagination of channel history.
- The full DM scope from the mockup: the rail always shows text, voice, and direct lists.