feat(webui): refine output timeline and model controls (#4108)

* feat(webui): refine output timeline and composer queue

* feat(webui): add provider model picker

* fix(webui): polish model settings and heartbeat checks

* chore: keep heartbeat changes out of webui pr

* refactor(webui): isolate settings routes

* fix(providers): align minimax anthropic test

* fix(providers): keep minimax anthropic base sdk-compatible

* fix(providers): normalize anthropic base urls
This commit is contained in:
Xubin Ren
2026-05-30 23:45:26 +08:00
committed by GitHub
parent b2e43955e3
commit 3dcf511c84
65 changed files with 4526 additions and 1428 deletions
+3 -3
View File
@@ -148,7 +148,7 @@ async def test_cli_apps_routes_require_token_and_return_payload(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setattr(
"nanobot.channels.websocket.cli_apps_payload",
"nanobot.webui.settings_routes.cli_apps_payload",
lambda: {
"apps": [
{
@@ -173,7 +173,7 @@ async def test_cli_apps_routes_require_token_and_return_payload(
},
)
monkeypatch.setattr(
"nanobot.channels.websocket.cli_apps_action",
"nanobot.webui.settings_routes.cli_apps_action",
lambda action, query: {
"apps": [],
"installed_count": 1,
@@ -280,7 +280,7 @@ async def test_mcp_presets_routes_require_token_and_return_payload(
return {"ok": True, "message": "MCP config reloaded.", "requires_restart": False}
monkeypatch.setattr(
"nanobot.channels.websocket.request_mcp_reload",
"nanobot.webui.settings_routes.request_mcp_reload",
_hot_reload,
)
channel = _ch(bus, session_manager=_seed_session(tmp_path), port=29913)
@@ -453,6 +453,35 @@ async def test_media_route_degrades_non_image_to_octet_stream(
assert resp.headers.get("x-content-type-options") == "nosniff"
@pytest.mark.asyncio
async def test_media_route_serves_svg_with_strict_csp(
bus: MagicMock, tmp_path: Path
) -> None:
"""Generated SVG can preview as an image without becoming executable HTML."""
media = tmp_path / "media"
media.mkdir()
target = media / "chart.svg"
target.write_text("<svg xmlns='http://www.w3.org/2000/svg'><script>alert(1)</script></svg>")
channel = _ch(bus, port=29928)
with patch("nanobot.channels.websocket.get_media_dir", return_value=media):
url_path = channel._sign_media_path(target)
assert url_path is not None
server_task = asyncio.create_task(channel.start())
await asyncio.sleep(0.3)
try:
resp = await _http_get(f"http://127.0.0.1:29928{url_path}")
finally:
await channel.stop()
await server_task
assert resp.status_code == 200
assert resp.headers["content-type"].startswith("image/svg+xml")
assert resp.headers.get("x-content-type-options") == "nosniff"
assert "default-src 'none'" in resp.headers.get("content-security-policy", "")
assert "sandbox" in resp.headers.get("content-security-policy", "")
# ---------------------------------------------------------------------------
# /api/sessions/<key>/messages: media_urls hydration on session read
# ---------------------------------------------------------------------------