mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-05-20 08:32:25 +00:00
fix(webui): sync model badge after preset switch
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
b61c6304c3
commit
c92345bbb1
@ -1471,6 +1471,9 @@ class WebSocketChannel(BaseChannel):
|
|||||||
payload["kind"] = "tool_hint"
|
payload["kind"] = "tool_hint"
|
||||||
elif msg.metadata.get("_progress"):
|
elif msg.metadata.get("_progress"):
|
||||||
payload["kind"] = "progress"
|
payload["kind"] = "progress"
|
||||||
|
webui_model_name = msg.metadata.get("_webui_model_name")
|
||||||
|
if isinstance(webui_model_name, str) and webui_model_name.strip():
|
||||||
|
payload["model_name"] = webui_model_name.strip()
|
||||||
raw = json.dumps(payload, ensure_ascii=False)
|
raw = json.dumps(payload, ensure_ascii=False)
|
||||||
for connection in conns:
|
for connection in conns:
|
||||||
await self._safe_send_to(connection, raw, label=" ")
|
await self._safe_send_to(connection, raw, label=" ")
|
||||||
|
|||||||
@ -225,7 +225,7 @@ async def cmd_model(ctx: CommandContext) -> OutboundMessage:
|
|||||||
channel=ctx.msg.channel,
|
channel=ctx.msg.channel,
|
||||||
chat_id=ctx.msg.chat_id,
|
chat_id=ctx.msg.chat_id,
|
||||||
content=_model_command_status(loop),
|
content=_model_command_status(loop),
|
||||||
metadata=metadata,
|
metadata={**metadata, "_webui_model_name": loop.model},
|
||||||
)
|
)
|
||||||
|
|
||||||
parts = args.split()
|
parts = args.split()
|
||||||
@ -264,7 +264,7 @@ async def cmd_model(ctx: CommandContext) -> OutboundMessage:
|
|||||||
channel=ctx.msg.channel,
|
channel=ctx.msg.channel,
|
||||||
chat_id=ctx.msg.chat_id,
|
chat_id=ctx.msg.chat_id,
|
||||||
content="\n".join(lines),
|
content="\n".join(lines),
|
||||||
metadata=metadata,
|
metadata={**metadata, "_webui_model_name": loop.model},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -229,6 +229,26 @@ async def test_send_delivers_json_message_with_media_and_reply() -> None:
|
|||||||
assert payload["buttons"] == [["Yes", "No"]]
|
assert payload["buttons"] == [["Yes", "No"]]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_send_includes_webui_model_name_metadata() -> None:
|
||||||
|
bus = MagicMock()
|
||||||
|
channel = WebSocketChannel({"enabled": True, "allowFrom": ["*"]}, bus)
|
||||||
|
mock_ws = AsyncMock()
|
||||||
|
channel._attach(mock_ws, "chat-1")
|
||||||
|
|
||||||
|
await channel.send(
|
||||||
|
OutboundMessage(
|
||||||
|
channel="websocket",
|
||||||
|
chat_id="chat-1",
|
||||||
|
content="switched",
|
||||||
|
metadata={"_webui_model_name": "openai/gpt-4.1"},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
payload = json.loads(mock_ws.send.call_args[0][0])
|
||||||
|
assert payload["model_name"] == "openai/gpt-4.1"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_send_stages_external_media_as_signed_url(monkeypatch, tmp_path) -> None:
|
async def test_send_stages_external_media_as_signed_url(monkeypatch, tmp_path) -> None:
|
||||||
bus = MagicMock()
|
bus = MagicMock()
|
||||||
|
|||||||
@ -64,7 +64,8 @@ async def test_model_command_lists_current_and_available_presets(tmp_path) -> No
|
|||||||
assert "Active preset: `(none)`" in out.content
|
assert "Active preset: `(none)`" in out.content
|
||||||
assert "`default`" in out.content
|
assert "`default`" in out.content
|
||||||
assert "`fast`" in out.content
|
assert "`fast`" in out.content
|
||||||
assert out.metadata == {"render_as": "text"}
|
assert out.metadata["render_as"] == "text"
|
||||||
|
assert out.metadata["_webui_model_name"] == "base-model"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@ -75,6 +76,7 @@ async def test_model_command_switches_preset(tmp_path) -> None:
|
|||||||
|
|
||||||
assert "Switched model preset to `fast`." in out.content
|
assert "Switched model preset to `fast`." in out.content
|
||||||
assert "Model: `openai/gpt-4.1`" in out.content
|
assert "Model: `openai/gpt-4.1`" in out.content
|
||||||
|
assert out.metadata["_webui_model_name"] == "openai/gpt-4.1"
|
||||||
assert loop.model_preset == "fast"
|
assert loop.model_preset == "fast"
|
||||||
assert loop.model == "openai/gpt-4.1"
|
assert loop.model == "openai/gpt-4.1"
|
||||||
assert loop.subagents.model == "openai/gpt-4.1"
|
assert loop.subagents.model == "openai/gpt-4.1"
|
||||||
@ -90,6 +92,7 @@ async def test_model_command_switches_back_to_default(tmp_path) -> None:
|
|||||||
out = await cmd_model(_ctx(loop, "/model default", args="default"))
|
out = await cmd_model(_ctx(loop, "/model default", args="default"))
|
||||||
|
|
||||||
assert "Switched model preset to `default`." in out.content
|
assert "Switched model preset to `default`." in out.content
|
||||||
|
assert out.metadata["_webui_model_name"] == "base-model"
|
||||||
assert loop.model_preset == "default"
|
assert loop.model_preset == "default"
|
||||||
assert loop.model == "base-model"
|
assert loop.model == "base-model"
|
||||||
assert loop.context_window_tokens == 1000
|
assert loop.context_window_tokens == 1000
|
||||||
|
|||||||
@ -492,6 +492,7 @@ function Shell({ onModelNameChange, onLogout }: { onModelNameChange: (modelName:
|
|||||||
onNewChat={onNewChat}
|
onNewChat={onNewChat}
|
||||||
onCreateChat={onCreateChat}
|
onCreateChat={onCreateChat}
|
||||||
onTurnEnd={onTurnEnd}
|
onTurnEnd={onTurnEnd}
|
||||||
|
onModelNameChange={onModelNameChange}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
onToggleTheme={toggle}
|
onToggleTheme={toggle}
|
||||||
hideSidebarToggleOnDesktop={desktopSidebarOpen}
|
hideSidebarToggleOnDesktop={desktopSidebarOpen}
|
||||||
|
|||||||
@ -32,6 +32,7 @@ interface ThreadShellProps {
|
|||||||
onNewChat?: () => void;
|
onNewChat?: () => void;
|
||||||
onCreateChat?: () => Promise<string | null>;
|
onCreateChat?: () => Promise<string | null>;
|
||||||
onTurnEnd?: () => void;
|
onTurnEnd?: () => void;
|
||||||
|
onModelNameChange?: (modelName: string | null) => void;
|
||||||
theme?: "light" | "dark";
|
theme?: "light" | "dark";
|
||||||
onToggleTheme?: () => void;
|
onToggleTheme?: () => void;
|
||||||
hideSidebarToggleOnDesktop?: boolean;
|
hideSidebarToggleOnDesktop?: boolean;
|
||||||
@ -75,6 +76,7 @@ export function ThreadShell({
|
|||||||
onToggleSidebar,
|
onToggleSidebar,
|
||||||
onCreateChat,
|
onCreateChat,
|
||||||
onTurnEnd,
|
onTurnEnd,
|
||||||
|
onModelNameChange,
|
||||||
theme = "light",
|
theme = "light",
|
||||||
onToggleTheme = () => {},
|
onToggleTheme = () => {},
|
||||||
hideSidebarToggleOnDesktop = false,
|
hideSidebarToggleOnDesktop = false,
|
||||||
@ -103,7 +105,7 @@ export function ThreadShell({
|
|||||||
setMessages,
|
setMessages,
|
||||||
streamError,
|
streamError,
|
||||||
dismissStreamError,
|
dismissStreamError,
|
||||||
} = useNanobotStream(chatId, initial, hasPendingToolCalls, onTurnEnd);
|
} = useNanobotStream(chatId, initial, hasPendingToolCalls, onTurnEnd, onModelNameChange);
|
||||||
const showHeroComposer = messages.length === 0 && !loading;
|
const showHeroComposer = messages.length === 0 && !loading;
|
||||||
const pendingAsk = useMemo(() => {
|
const pendingAsk = useMemo(() => {
|
||||||
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
||||||
|
|||||||
@ -44,6 +44,7 @@ export function useNanobotStream(
|
|||||||
initialMessages: UIMessage[] = [],
|
initialMessages: UIMessage[] = [],
|
||||||
hasPendingToolCalls = false,
|
hasPendingToolCalls = false,
|
||||||
onTurnEnd?: () => void,
|
onTurnEnd?: () => void,
|
||||||
|
onModelNameChange?: (modelName: string | null) => void,
|
||||||
): {
|
): {
|
||||||
messages: UIMessage[];
|
messages: UIMessage[];
|
||||||
isStreaming: boolean;
|
isStreaming: boolean;
|
||||||
@ -181,6 +182,9 @@ export function useNanobotStream(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ev.event === "message") {
|
if (ev.event === "message") {
|
||||||
|
if (ev.model_name !== undefined) {
|
||||||
|
onModelNameChange?.(ev.model_name || null);
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
suppressStreamUntilTurnEndRef.current &&
|
suppressStreamUntilTurnEndRef.current &&
|
||||||
(ev.kind === "tool_hint" || ev.kind === "progress")
|
(ev.kind === "tool_hint" || ev.kind === "progress")
|
||||||
|
|||||||
@ -147,6 +147,8 @@ export type InboundEvent =
|
|||||||
/** Present when the frame is an agent breadcrumb (e.g. tool hint,
|
/** Present when the frame is an agent breadcrumb (e.g. tool hint,
|
||||||
* generic progress line) rather than a conversational reply. */
|
* generic progress line) rather than a conversational reply. */
|
||||||
kind?: "tool_hint" | "progress";
|
kind?: "tool_hint" | "progress";
|
||||||
|
/** Runtime model name after commands like `/model fast` update it. */
|
||||||
|
model_name?: string | null;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
event: "delta";
|
event: "delta";
|
||||||
|
|||||||
@ -134,6 +134,28 @@ describe("useNanobotStream", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("reports runtime model name updates from message frames", () => {
|
||||||
|
const fake = fakeClient();
|
||||||
|
const onModelNameChange = vi.fn();
|
||||||
|
renderHook(
|
||||||
|
() => useNanobotStream("chat-model", EMPTY_MESSAGES, false, undefined, onModelNameChange),
|
||||||
|
{
|
||||||
|
wrapper: wrap(fake.client),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
fake.emit("chat-model", {
|
||||||
|
event: "message",
|
||||||
|
chat_id: "chat-model",
|
||||||
|
text: "Switched model preset to `fast`.",
|
||||||
|
model_name: "openai/gpt-4.1",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(onModelNameChange).toHaveBeenCalledWith("openai/gpt-4.1");
|
||||||
|
});
|
||||||
|
|
||||||
it("suppresses redundant stream confirmation after assistant media", () => {
|
it("suppresses redundant stream confirmation after assistant media", () => {
|
||||||
const fake = fakeClient();
|
const fake = fakeClient();
|
||||||
const { result } = renderHook(() => useNanobotStream("chat-img-result", EMPTY_MESSAGES), {
|
const { result } = renderHook(() => useNanobotStream("chat-img-result", EMPTY_MESSAGES), {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user