Skip to content

feat(dashboard): add chat interface via API server proxy - #9903

Open
SHL0MS wants to merge 1 commit into
NousResearch:mainfrom
SHL0MS:feat/dashboard-chat
Open

feat(dashboard): add chat interface via API server proxy#9903
SHL0MS wants to merge 1 commit into
NousResearch:mainfrom
SHL0MS:feat/dashboard-chat

Conversation

@SHL0MS

@SHL0MS SHL0MS commented Apr 14, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a Chat tab to the dashboard. Proxies to the gateway's existing API server (/v1/chat/completions) — no new agent lifecycle code, no new dependencies.

Backend — 2 endpoints, 110 lines

POST /api/chat/send — SSE streaming proxy to localhost:8642/v1/chat/completions

  • Forwards messages + optional X-Hermes-Session-Id for session continuity
  • Passes API_SERVER_KEY to the API server when set
  • Handles empty stream (agent internal errors): detects finish_reason without content, falls back to a non-streaming request to surface the actual error message
  • Respects API_SERVER_PORT env var

GET /api/chat/status — health check, hits the API server's /health endpoint

Both are protected by the existing auth middleware (#9800).

Frontend — ChatPage.tsx, 212 lines

  • SSE stream parsing (OpenAI-compatible choices[0].delta.content chunks)
  • Markdown rendering via existing Markdown component
  • Session continuity (stores session ID from first response, sends on subsequent messages)
  • Auto-scroll to bottom on new content
  • Auto-resize textarea, Enter to send, Shift+Enter for newline
  • When gateway isn't running: "Chat unavailable" with hermes gateway start instruction
  • i18n: English + Chinese translations

Requires

The gateway must be running with the api_server platform enabled:

# ~/.hermes/config.yaml
platforms:
  api_server:
    enabled: true

Then hermes gateway start. The Chat tab detects this automatically and shows setup instructions if not configured.

Files changed

File Lines What
hermes_cli/web_server.py +110 Chat proxy endpoints
web/src/pages/ChatPage.tsx +212 Chat UI (new file)
web/src/App.tsx +4 Add Chat route + nav item
web/src/lib/api.ts +4 checkChatStatus() method
web/src/i18n/types.ts +9 Chat string types
web/src/i18n/en.ts +8 English strings
web/src/i18n/zh.ts +8 Chinese strings

Adds a Chat tab to the dashboard that proxies to the gateway's existing
API server (/v1/chat/completions) with SSE streaming.

Backend (web_server.py):
- POST /api/chat/send — proxies to API server with streaming, falls
  back to non-streaming request when stream returns empty content
  (surfaces agent errors instead of hanging)
- GET /api/chat/status — health check for API server availability

Frontend (ChatPage.tsx):
- SSE stream parsing (OpenAI-compatible chunks)
- Markdown rendering for assistant responses
- Session continuity via X-Hermes-Session-Id header
- Auto-scroll, auto-resize textarea, Enter to send
- Unavailable state with setup instructions when gateway not running

Requires the gateway running with api_server platform enabled.
i18n: en + zh translations for chat UI strings.
@alt-glitch alt-glitch mentioned this pull request Apr 22, 2026
11 tasks
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/gateway Gateway runner, session dispatch, delivery labels Apr 26, 2026
@PaulPricop2

Copy link
Copy Markdown

Field note from building a similar thing externally, in case it's useful for this design.

I built a per-user auth bridge in front of api_server for a third-party integration (a control-room UI, not the built-in dashboard), and initially planned to reuse the dashboard's own OIDC/PKCE session the way this issue proposes — a dashboard route gated by the real auth middleware, forwarding to api_server with the key attached server-side.

Ran into gated_auth_middleware being a no-op whenever the dashboard is bound to loopback (should_require_auth() treats loopback as "local-only, trusted operator" by design). Confirmed live: completed a real OIDC login against a self-hosted IdP, landed back on the dashboard signed in — but GET /api/auth/me still returned 401 immediately after, since request.state.session never gets populated on that code path when auth_required is False. For any integration that's also running on the same loopback interface as the dashboard (which seems like the common self-hosted case), reusing the dashboard's session this way only actually works once the dashboard itself is bound non-loopback — a real network-exposure decision, not something you'd want to force just to let a local integration authenticate.

Worked around it by having the integration act as its own independent OIDC/PKCE relying party — its own registered IdP client, its own login/callback routes, no dependency on the dashboard's session or gated_auth_middleware at all. Sidesteps the loopback behavior entirely since it never goes through that code path. Live-tested end to end (real login, real session, real proxied request with the key held server-side only, confirmed the key never reaches client code).

Might be worth a note in this design (or wherever the dashboard-chat-proxy pattern gets documented) that for integrations that aren't the dashboard's own same-origin UI, an independent-client-per-integration pattern avoids the loopback gate entirely, rather than assuming the dashboard's own auth_required/session state is always the right thing to piggyback on.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P3 Low — cosmetic, nice to have type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants