Skip to content

Commit ae3b88a

Browse files
Zawwarsami16claude
andcommitted
phase 19.0: built-in browser chat client at /chat
closes the "anyone can use without installing other tools" gap. hub now serves a self-contained chat UI (~16 KB, zero deps) at /chat that anyone can open in a browser to chat against any zhub-published AI. new: zhub/chat.html - same dark + cyan + glassmorphism palette as the operator dashboard - auto-detect: when this hub has exactly one publisher, prefills the base URL with <origin>/<name>/v1 by hitting /api/dashboard - otherwise: form prompts for Base URL + API Key, persisted to localStorage so users don't paste each visit - streaming SSE chat with a clean message stream (user / ai / tool / error roles, color-coded with neon glows) - tool_call deltas surface as their own message bubbles in real time (works with stream=true tool-call streaming from Phase 4.2b) - composer area sticky at the bottom; Enter to send, Shift+Enter for newline; auto-resizing textarea - connection state indicator (ready/streaming/disconnected) dashboard navbar gets a `chat` link so operators see it. new: GET /chat endpoint serves the file (parallel to /). 178/178 pytest in isolation (same handful of cross-test flakes in long-suite runs as before, all related to per-publisher state isolation in module-scoped fixtures, not to this change). CI runs cleanly on faster runners. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 5a355e8 commit ae3b88a

6 files changed

Lines changed: 576 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ Same URL + `zk_` key works in:
143143

144144
| Surface | How |
145145
|---|---|
146+
| **Built-in chat at `<hub>/chat`** | No install needed. Open in any browser, paste URL+key (auto-detected on single-publisher hubs), chat. SSE streaming, localStorage-persisted config. |
146147
| [**Pocket**](https://github.com/Zawwarsami16/pocket) | First-class zhub provider — auto-detects `zk_` keys |
147148
| **TypingMind / OpenWebUI / LibreChat / Chatbox / Msty / Jan.ai / BoltAI / AnythingLLM** | "Custom OpenAI provider" — paste base URL + key |
148149
| **Cursor / Continue.dev** | Custom OpenAI base URL setting |

tests/test_chat_ui.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""Phase 19.0 — built-in chat UI served at /chat."""
2+
3+
import asyncio
4+
import socket
5+
import threading
6+
import time
7+
8+
import pytest
9+
10+
try:
11+
import fastapi # noqa
12+
import uvicorn # noqa
13+
import httpx # noqa
14+
DEPS_AVAILABLE = True
15+
except ImportError:
16+
DEPS_AVAILABLE = False
17+
18+
if DEPS_AVAILABLE:
19+
from zhub.server import create_app
20+
21+
22+
def _free_port() -> int:
23+
with socket.socket() as s:
24+
s.bind(("", 0))
25+
return s.getsockname()[1]
26+
27+
28+
@pytest.fixture
29+
def hub():
30+
if not DEPS_AVAILABLE:
31+
pytest.skip("fastapi/uvicorn/httpx not installed")
32+
port = _free_port()
33+
app = create_app()
34+
35+
def run():
36+
config = uvicorn.Config(app, host="127.0.0.1", port=port,
37+
log_level="warning")
38+
asyncio.run(uvicorn.Server(config).serve())
39+
40+
threading.Thread(target=run, daemon=True).start()
41+
for _ in range(30):
42+
try:
43+
with socket.create_connection(("127.0.0.1", port), timeout=0.1):
44+
break
45+
except OSError:
46+
time.sleep(0.1)
47+
yield port
48+
49+
50+
@pytest.mark.asyncio
51+
async def test_chat_ui_served_at_chat(hub):
52+
async with httpx.AsyncClient(timeout=5.0) as c:
53+
r = await c.get(f"http://127.0.0.1:{hub}/chat")
54+
assert r.status_code == 200
55+
assert "text/html" in r.headers["content-type"].lower()
56+
body = r.text
57+
# Key UI structures must be present
58+
assert "zhub" in body.lower()
59+
for needle in ["base url", "api key", "/chat/completions", "input",
60+
"messages"]:
61+
assert needle in body.lower(), f"missing {needle!r}"

0 commit comments

Comments
 (0)