Skip to content

Commit 7ddf958

Browse files
authored
🤖 fix: keep model display pretty with mux-gateway (#1104)
Fixes a UI regression where enabling mux-gateway caused the bottom-left model selector to show an ugly raw string (e.g. "Openai/gpt 4o") instead of the pretty display name. - Use canonical `baseModel` for display components (ModelSelector + ModelSettings) - Add a Storybook interaction regression test: App/Chat/ModelSelectorPrettyWithGateway Tests: - make static-check - make storybook-build - make test-storybook _Generated with `mux`_
1 parent b320c87 commit 7ddf958

File tree

8 files changed

+89
-10
lines changed

8 files changed

+89
-10
lines changed

.storybook/mocks/orpc.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import type { APIClient } from "@/browser/contexts/API";
77
import type { FrontendWorkspaceMetadata } from "@/common/types/workspace";
88
import type { ProjectConfig } from "@/node/config";
9-
import type { WorkspaceChatMessage } from "@/common/orpc/types";
9+
import type { WorkspaceChatMessage, ProvidersConfigMap } from "@/common/orpc/types";
1010
import type { ChatStats } from "@/common/types/chatStats";
1111
import { DEFAULT_RUNTIME_CONFIG } from "@/common/constants/workspace";
1212
import { createAsyncMessageQueue } from "@/common/utils/asyncMessageQueue";
@@ -22,7 +22,7 @@ export interface MockORPCClientOptions {
2222
script: string
2323
) => Promise<{ success: true; output: string; exitCode: number; wall_duration_ms: number }>;
2424
/** Provider configuration (API keys, base URLs, etc.) */
25-
providersConfig?: Record<string, { apiKeySet: boolean; baseUrl?: string; models?: string[] }>;
25+
providersConfig?: ProvidersConfigMap;
2626
/** List of available provider names */
2727
providersList?: string[];
2828
/** Mock for projects.remove - return error string to simulate failure */

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,10 +246,10 @@ static-check: lint typecheck fmt-check check-eager-imports check-bench-agent che
246246
check-bench-agent: ## Verify terminal-bench agent configuration and imports
247247
@./scripts/check-bench-agent.sh
248248

249-
lint: node_modules/.installed ## Run ESLint (typecheck runs in separate target)
249+
lint: node_modules/.installed src/version.ts ## Run ESLint (typecheck runs in separate target)
250250
@./scripts/lint.sh
251251

252-
lint-fix: node_modules/.installed ## Run linter with --fix
252+
lint-fix: node_modules/.installed src/version.ts ## Run linter with --fix
253253
@./scripts/lint.sh --fix
254254

255255
ifeq ($(OS),Windows_NT)

eslint.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ export default defineConfig([
217217
files: ["src/**/*.{ts,tsx}"],
218218
languageOptions: {
219219
parserOptions: {
220-
project: ["./tsconfig.json", "./tsconfig.main.json"],
220+
project: ["./tsconfig.json"],
221221
tsconfigRootDir: import.meta.dirname,
222222
},
223223
globals: {

src/browser/components/ChatInput/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1534,7 +1534,7 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
15341534
>
15351535
<ModelSelector
15361536
ref={modelSelectorRef}
1537-
value={preferredModel}
1537+
value={baseModel}
15381538
onChange={setPreferredModel}
15391539
recentModels={recentModels}
15401540
onComplete={() => inputRef.current?.focus()}
@@ -1576,7 +1576,7 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
15761576
</div>
15771577

15781578
<div className="ml-4 flex items-center" data-component="ModelSettingsGroup">
1579-
<ModelSettings model={preferredModel || ""} />
1579+
<ModelSettings model={baseModel || ""} />
15801580
</div>
15811581

15821582
{preferredModel && (

src/browser/components/ModelSelector.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,12 @@ export const ModelSelector = forwardRef<ModelSelectorRef, ModelSelectorProps>(
208208
{gatewayActive && (
209209
<Tooltip>
210210
<TooltipTrigger asChild>
211-
<GatewayIcon className="text-accent h-3 w-3 shrink-0" active />
211+
<GatewayIcon
212+
className="text-accent h-3 w-3 shrink-0"
213+
active
214+
role="img"
215+
aria-label="Using Mux Gateway"
216+
/>
212217
</TooltipTrigger>
213218
<TooltipContent align="center">Using Mux Gateway</TooltipContent>
214219
</Tooltip>

src/browser/stories/App.chat.stories.tsx

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import {
1414
createStatusTool,
1515
createGenericTool,
1616
} from "./mockFactory";
17+
import { updatePersistedState } from "@/browser/hooks/usePersistedState";
18+
import { getModelKey } from "@/common/constants/storage";
1719
import { setupSimpleChatStory, setupStreamingChatStory } from "./storyHelpers";
1820
import { within, userEvent, waitFor } from "@storybook/test";
1921

@@ -498,3 +500,74 @@ export const ModeHelpTooltip: AppStory = {
498500
},
499501
},
500502
};
503+
504+
/**
505+
* Model selector pretty display with mux-gateway enabled.
506+
*
507+
* Regression test: when gateway is enabled, `useSendMessageOptions().model` becomes
508+
* `mux-gateway:provider/model`, but the UI should still display the canonical
509+
* provider:model form (e.g. GPT-4o, not \"Openai/gpt 4o\").
510+
*/
511+
export const ModelSelectorPrettyWithGateway: AppStory = {
512+
render: () => (
513+
<AppWithMocks
514+
setup={() => {
515+
const workspaceId = "ws-gateway-model";
516+
const baseModel = "openai:gpt-4o";
517+
518+
// Ensure the gateway transform actually kicks in (so the regression would reproduce).
519+
updatePersistedState(getModelKey(workspaceId), baseModel);
520+
updatePersistedState("gateway-enabled", true);
521+
updatePersistedState("gateway-available", true);
522+
updatePersistedState("gateway-models", [baseModel]);
523+
524+
return setupSimpleChatStory({
525+
workspaceId,
526+
messages: [],
527+
providersConfig: {
528+
"mux-gateway": { apiKeySet: false, couponCodeSet: true },
529+
},
530+
});
531+
}}
532+
/>
533+
),
534+
play: async ({ canvasElement }: { canvasElement: HTMLElement }) => {
535+
const canvas = within(canvasElement);
536+
537+
// Wait for chat input to mount.
538+
await canvas.findAllByText("Exec", {}, { timeout: 10000 });
539+
540+
// With gateway enabled, we should still display the *pretty* model name.
541+
await waitFor(() => {
542+
canvas.getByText("GPT-4o");
543+
});
544+
545+
// The buggy rendering (mux-gateway:openai/gpt-4o) shows up as "Openai/gpt 4o".
546+
const ugly = canvas.queryByText("Openai/gpt 4o");
547+
if (ugly) {
548+
throw new Error(`Unexpected gateway-formatted model label: ${ugly.textContent ?? "(empty)"}`);
549+
}
550+
551+
// Sanity check that the gateway indicator exists.
552+
const gatewayIndicator = canvasElement.querySelector('[aria-label="Using Mux Gateway"]');
553+
if (!gatewayIndicator) throw new Error("Gateway indicator not found");
554+
555+
// Hover to prove the gateway tooltip is wired up (and keep it visible for snapshot).
556+
await userEvent.hover(gatewayIndicator);
557+
await waitFor(
558+
() => {
559+
const tooltip = document.querySelector('[role="tooltip"]');
560+
if (!tooltip) throw new Error("Tooltip not visible");
561+
},
562+
{ timeout: 2000, interval: 50 }
563+
);
564+
},
565+
parameters: {
566+
docs: {
567+
description: {
568+
story:
569+
"Verifies the bottom-left model selector stays pretty (e.g. GPT-4o) even when mux-gateway routing is enabled.",
570+
},
571+
},
572+
},
573+
};

src/browser/stories/storyHelpers.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
*/
77

88
import type { FrontendWorkspaceMetadata } from "@/common/types/workspace";
9-
import type { WorkspaceChatMessage, ChatMuxMessage } from "@/common/orpc/types";
9+
import type { WorkspaceChatMessage, ChatMuxMessage, ProvidersConfigMap } from "@/common/orpc/types";
1010
import type { APIClient } from "@/browser/contexts/API";
1111
import {
1212
SELECTED_WORKSPACE_KEY,
@@ -153,7 +153,7 @@ export interface SimpleChatSetupOptions {
153153
projectName?: string;
154154
messages: ChatMuxMessage[];
155155
gitStatus?: GitStatusFixture;
156-
providersConfig?: Record<string, { apiKeySet: boolean; baseUrl?: string; models?: string[] }>;
156+
providersConfig?: ProvidersConfigMap;
157157
backgroundProcesses?: BackgroundProcessFixture[];
158158
}
159159

tsconfig.main.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"excludeFiles": ["**/*.d.ts.map"]
1313
},
1414
"include": [
15+
"src/version.ts",
1516
"src/cli/index.ts",
1617
"src/cli/server.ts",
1718
"src/desktop/**/*",

0 commit comments

Comments
 (0)