feat(webui): show the actual fallback model (#5017)
This commit is contained in:
@@ -357,6 +357,30 @@ describe("NanobotClient", () => {
|
||||
expect(handler).toHaveBeenCalledWith("openai/gpt-4.1", "fast");
|
||||
});
|
||||
|
||||
it("dispatches turn model updates to the active chat", () => {
|
||||
const client = new NanobotClient({
|
||||
url: "ws://test",
|
||||
reconnect: false,
|
||||
socketFactory: (url) => new FakeSocket(url) as unknown as WebSocket,
|
||||
});
|
||||
const chatHandler = vi.fn();
|
||||
client.onChat("chat-a", chatHandler);
|
||||
client.connect();
|
||||
lastSocket().fakeOpen();
|
||||
|
||||
lastSocket().fakeMessage({
|
||||
event: "turn_model_updated",
|
||||
chat_id: "chat-a",
|
||||
model_name: "deepseek/deepseek-chat",
|
||||
});
|
||||
|
||||
expect(chatHandler).toHaveBeenCalledWith({
|
||||
event: "turn_model_updated",
|
||||
chat_id: "chat-a",
|
||||
model_name: "deepseek/deepseek-chat",
|
||||
});
|
||||
});
|
||||
|
||||
it("dispatches session updates globally", () => {
|
||||
const client = new NanobotClient({
|
||||
url: "ws://test",
|
||||
|
||||
@@ -14,13 +14,23 @@ const HERO_GREETING_PATTERN =
|
||||
function makeClient() {
|
||||
const errorHandlers = new Set<(err: { kind: string }) => void>();
|
||||
const chatHandlers = new Map<string, Set<(ev: import("@/lib/types").InboundEvent) => void>>();
|
||||
const runtimeModelHandlers = new Set<
|
||||
(modelName: string | null, modelPreset?: string | null) => void
|
||||
>();
|
||||
const sessionUpdateHandlers = new Set<(chatId: string, scope?: string) => void>();
|
||||
const goalStateByChatId = new Map<string, import("@/lib/types").GoalStateWsPayload>();
|
||||
return {
|
||||
status: "open" as const,
|
||||
defaultChatId: null as string | null,
|
||||
onStatus: () => () => {},
|
||||
onRuntimeModelUpdate: () => () => {},
|
||||
onRuntimeModelUpdate: (
|
||||
handler: (modelName: string | null, modelPreset?: string | null) => void,
|
||||
) => {
|
||||
runtimeModelHandlers.add(handler);
|
||||
return () => {
|
||||
runtimeModelHandlers.delete(handler);
|
||||
};
|
||||
},
|
||||
getRunStartedAt: () => null,
|
||||
getGoalState: (chatId: string) => goalStateByChatId.get(chatId),
|
||||
onChat: (chatId: string, handler: (ev: import("@/lib/types").InboundEvent) => void) => {
|
||||
@@ -55,6 +65,9 @@ function makeClient() {
|
||||
}
|
||||
for (const h of chatHandlers.get(chatId) ?? []) h(ev);
|
||||
},
|
||||
_emitRuntimeModelUpdate(modelName: string | null, modelPreset?: string | null) {
|
||||
for (const h of runtimeModelHandlers) h(modelName, modelPreset);
|
||||
},
|
||||
_emitSessionUpdate(chatId: string, scope?: string) {
|
||||
for (const h of sessionUpdateHandlers) h(chatId, scope);
|
||||
},
|
||||
@@ -411,6 +424,66 @@ describe("ThreadShell", () => {
|
||||
expect(screen.queryByRole("button", { name: "Model not configured" })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("highlights the configured model badge without replacing the preset label", async () => {
|
||||
const client = makeClient();
|
||||
render(wrap(
|
||||
client,
|
||||
<ThreadShell
|
||||
session={session("fallback-model")}
|
||||
title="Fallback model"
|
||||
onToggleSidebar={() => {}}
|
||||
settingsSnapshot={modelSettings("openai-codex/gpt-5.5", "openai_codex")}
|
||||
/>,
|
||||
"openai-codex/gpt-5.5",
|
||||
));
|
||||
|
||||
expect(await screen.findByText("gpt-5.5")).toBeInTheDocument();
|
||||
const configuredBadge = screen.getByTestId("composer-model-logo-openai_codex").parentElement;
|
||||
expect(configuredBadge).not.toBeNull();
|
||||
expect(configuredBadge).toHaveClass("composer-model-badge");
|
||||
expect(configuredBadge).not.toHaveAttribute("data-fallback");
|
||||
|
||||
act(() => {
|
||||
client._emitChat("fallback-model", {
|
||||
event: "turn_model_updated",
|
||||
chat_id: "fallback-model",
|
||||
model_name: "deepseek/deepseek-chat",
|
||||
});
|
||||
});
|
||||
|
||||
const logo = screen.getByTestId("composer-model-logo-openai_codex");
|
||||
const badge = logo.parentElement;
|
||||
expect(badge).not.toBeNull();
|
||||
expect(badge).toBe(configuredBadge);
|
||||
expect(screen.getByText("gpt-5.5")).toBeInTheDocument();
|
||||
expect(screen.queryByText("deepseek-chat")).not.toBeInTheDocument();
|
||||
expect(badge).toHaveAttribute("data-fallback", "true");
|
||||
expect(badge).toHaveAttribute(
|
||||
"title",
|
||||
"deepseek/deepseek-chat",
|
||||
);
|
||||
expect(logo).not.toHaveAttribute("data-fallback");
|
||||
|
||||
act(() => {
|
||||
client._emitChat("fallback-model", {
|
||||
event: "turn_end",
|
||||
chat_id: "fallback-model",
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByTestId("composer-model-logo-openai_codex").parentElement,
|
||||
).not.toHaveAttribute("data-fallback");
|
||||
});
|
||||
expect(
|
||||
screen.getByTestId("composer-model-logo-openai_codex").parentElement,
|
||||
).toHaveAttribute("title", "gpt-5.5 · OpenAI Codex");
|
||||
expect(
|
||||
screen.getByTestId("composer-model-logo-openai_codex").parentElement,
|
||||
).toBe(badge);
|
||||
});
|
||||
|
||||
it("opens model settings from the unconfigured model badge", async () => {
|
||||
const client = makeClient();
|
||||
const settings = modelSettings("openai-codex/gpt-5.1-codex", "openai_codex");
|
||||
|
||||
Reference in New Issue
Block a user