mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-31 08:13:11 +03:00
fix(webui): only highlight actual model fallbacks
This commit is contained in:
@@ -100,6 +100,7 @@ class TurnModelUpdatedEvent(OutboundEvent):
|
|||||||
model: str
|
model: str
|
||||||
model_preset: str | None = None
|
model_preset: str | None = None
|
||||||
context_window_tokens: int | None = None
|
context_window_tokens: int | None = None
|
||||||
|
is_fallback: bool = False
|
||||||
|
|
||||||
|
|
||||||
def outbound_message_for_event(
|
def outbound_message_for_event(
|
||||||
|
|||||||
@@ -1685,6 +1685,7 @@ class WebSocketChannel(BaseChannel):
|
|||||||
model_name=event.model,
|
model_name=event.model,
|
||||||
model_preset=event.model_preset,
|
model_preset=event.model_preset,
|
||||||
context_window_tokens=event.context_window_tokens,
|
context_window_tokens=event.context_window_tokens,
|
||||||
|
is_fallback=event.is_fallback,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
if isinstance(event, UserInputEvent):
|
if isinstance(event, UserInputEvent):
|
||||||
@@ -2102,6 +2103,7 @@ class WebSocketChannel(BaseChannel):
|
|||||||
model_name: Any,
|
model_name: Any,
|
||||||
model_preset: Any = None,
|
model_preset: Any = None,
|
||||||
context_window_tokens: Any = None,
|
context_window_tokens: Any = None,
|
||||||
|
is_fallback: Any = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Notify one chat's subscribers which model is handling its current request."""
|
"""Notify one chat's subscribers which model is handling its current request."""
|
||||||
conns = list(self._subs.get(chat_id, ()))
|
conns = list(self._subs.get(chat_id, ()))
|
||||||
@@ -2115,6 +2117,7 @@ class WebSocketChannel(BaseChannel):
|
|||||||
"event": "turn_model_updated",
|
"event": "turn_model_updated",
|
||||||
"chat_id": chat_id,
|
"chat_id": chat_id,
|
||||||
"model_name": model_name.strip(),
|
"model_name": model_name.strip(),
|
||||||
|
"is_fallback": is_fallback is True,
|
||||||
}
|
}
|
||||||
if isinstance(model_preset, str) and model_preset.strip():
|
if isinstance(model_preset, str) and model_preset.strip():
|
||||||
body["model_preset"] = model_preset.strip()
|
body["model_preset"] = model_preset.strip()
|
||||||
|
|||||||
@@ -2061,6 +2061,7 @@ async def test_send_scopes_turn_model_updates_to_the_subscribed_chat() -> None:
|
|||||||
model="deepseek/deepseek-chat",
|
model="deepseek/deepseek-chat",
|
||||||
model_preset="Deep Research",
|
model_preset="Deep Research",
|
||||||
context_window_tokens=128_000,
|
context_window_tokens=128_000,
|
||||||
|
is_fallback=True,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -2072,6 +2073,7 @@ async def test_send_scopes_turn_model_updates_to_the_subscribed_chat() -> None:
|
|||||||
"model_name": "deepseek/deepseek-chat",
|
"model_name": "deepseek/deepseek-chat",
|
||||||
"model_preset": "Deep Research",
|
"model_preset": "Deep Research",
|
||||||
"context_window_tokens": 128_000,
|
"context_window_tokens": 128_000,
|
||||||
|
"is_fallback": True,
|
||||||
}
|
}
|
||||||
chat_two.send.assert_not_awaited()
|
chat_two.send.assert_not_awaited()
|
||||||
|
|
||||||
|
|||||||
@@ -495,6 +495,7 @@ def build_webui_fallback_model_observer(bus: MessageBus) -> FallbackModelObserve
|
|||||||
if context.runtime is not None
|
if context.runtime is not None
|
||||||
else None
|
else None
|
||||||
),
|
),
|
||||||
|
is_fallback=True,
|
||||||
),
|
),
|
||||||
metadata=context.metadata,
|
metadata=context.metadata,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -179,6 +179,7 @@ async def test_fallback_model_is_scoped_to_its_websocket_chat() -> None:
|
|||||||
assert isinstance(outbound.event, TurnModelUpdatedEvent)
|
assert isinstance(outbound.event, TurnModelUpdatedEvent)
|
||||||
assert outbound.event.model == "deepseek/deepseek-chat"
|
assert outbound.event.model == "deepseek/deepseek-chat"
|
||||||
assert outbound.event.model_preset == "Deep Research"
|
assert outbound.event.model_preset == "Deep Research"
|
||||||
|
assert outbound.event.is_fallback is True
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@@ -218,6 +219,7 @@ async def test_admitted_runtime_publishes_chat_scoped_model_and_preset(tmp_path)
|
|||||||
assert isinstance(outbound.event, TurnModelUpdatedEvent)
|
assert isinstance(outbound.event, TurnModelUpdatedEvent)
|
||||||
assert outbound.event.model == "openai-codex/gpt-5.6"
|
assert outbound.event.model == "openai-codex/gpt-5.6"
|
||||||
assert outbound.event.model_preset == "Codex"
|
assert outbound.event.model_preset == "Codex"
|
||||||
|
assert outbound.event.is_fallback is False
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
|||||||
@@ -959,7 +959,7 @@ export function ThreadShell({
|
|||||||
setFallbackModelName(null);
|
setFallbackModelName(null);
|
||||||
return client.onChat(chatId, (event) => {
|
return client.onChat(chatId, (event) => {
|
||||||
if (event.event !== "turn_model_updated") return;
|
if (event.event !== "turn_model_updated") return;
|
||||||
setFallbackModelName(event.model_name);
|
setFallbackModelName(event.is_fallback ? event.model_name : null);
|
||||||
});
|
});
|
||||||
}, [chatId, client]);
|
}, [chatId, client]);
|
||||||
|
|
||||||
|
|||||||
@@ -1313,6 +1313,7 @@ export type InboundEvent =
|
|||||||
chat_id: string;
|
chat_id: string;
|
||||||
model_name: string;
|
model_name: string;
|
||||||
model_preset?: string | null;
|
model_preset?: string | null;
|
||||||
|
is_fallback?: boolean;
|
||||||
}
|
}
|
||||||
| ({
|
| ({
|
||||||
event: "turn_end";
|
event: "turn_end";
|
||||||
|
|||||||
@@ -1724,6 +1724,7 @@ describe("NanobotClient", () => {
|
|||||||
chat_id: "chat-a",
|
chat_id: "chat-a",
|
||||||
model_name: "deepseek/deepseek-chat",
|
model_name: "deepseek/deepseek-chat",
|
||||||
model_preset: "Deep Research",
|
model_preset: "Deep Research",
|
||||||
|
is_fallback: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(chatHandler).toHaveBeenCalledWith({
|
expect(chatHandler).toHaveBeenCalledWith({
|
||||||
@@ -1731,6 +1732,7 @@ describe("NanobotClient", () => {
|
|||||||
chat_id: "chat-a",
|
chat_id: "chat-a",
|
||||||
model_name: "deepseek/deepseek-chat",
|
model_name: "deepseek/deepseek-chat",
|
||||||
model_preset: "Deep Research",
|
model_preset: "Deep Research",
|
||||||
|
is_fallback: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -696,7 +696,7 @@ describe("ThreadShell", () => {
|
|||||||
expect(screen.queryByRole("button", { name: "Model not configured" })).not.toBeInTheDocument();
|
expect(screen.queryByRole("button", { name: "Model not configured" })).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("highlights the configured model badge without replacing the preset label", async () => {
|
it("only highlights fallback model updates without replacing the preset label", async () => {
|
||||||
const client = makeClient();
|
const client = makeClient();
|
||||||
render(wrap(
|
render(wrap(
|
||||||
client,
|
client,
|
||||||
@@ -715,11 +715,23 @@ describe("ThreadShell", () => {
|
|||||||
expect(configuredBadge).toHaveClass("composer-model-badge");
|
expect(configuredBadge).toHaveClass("composer-model-badge");
|
||||||
expect(configuredBadge).not.toHaveAttribute("data-fallback");
|
expect(configuredBadge).not.toHaveAttribute("data-fallback");
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
client._emitChat("fallback-model", {
|
||||||
|
event: "turn_model_updated",
|
||||||
|
chat_id: "fallback-model",
|
||||||
|
model_name: "openai-codex/gpt-5.5",
|
||||||
|
is_fallback: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(configuredBadge).not.toHaveAttribute("data-fallback");
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
client._emitChat("fallback-model", {
|
client._emitChat("fallback-model", {
|
||||||
event: "turn_model_updated",
|
event: "turn_model_updated",
|
||||||
chat_id: "fallback-model",
|
chat_id: "fallback-model",
|
||||||
model_name: "deepseek/deepseek-chat",
|
model_name: "deepseek/deepseek-chat",
|
||||||
|
is_fallback: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user