chore:show provider or custom name in chat model picker labels (#1115)

This commit is contained in:
paisley
2026-06-11 18:27:07 +08:00
committed by GitHub
parent d246860ef1
commit dd92cf400b
4 changed files with 72 additions and 12 deletions
+25 -1
View File
@@ -48,6 +48,27 @@ export function formatModelRefLabel(modelRef: string | null | undefined): string
return parsed?.modelId || (modelRef || '').trim() || 'Model';
}
export function formatProviderDisplayName(
account: ProviderAccount,
vendorMap: Map<string, ProviderVendorInfo>,
): string {
if (account.vendorId === 'custom' || account.vendorId === 'ollama') {
return account.label.trim() || account.vendorId;
}
const vendor = vendorMap.get(account.vendorId);
return vendor?.name || account.label.trim() || account.vendorId;
}
export function formatConfiguredModelLabel(
modelId: string,
account: ProviderAccount,
vendorMap: Map<string, ProviderVendorInfo>,
): string {
const providerName = formatProviderDisplayName(account, vendorMap);
return `${modelId} (${providerName})`;
}
export function toModelOptionTestId(label: string): string {
return label.replace(/[^a-zA-Z0-9_-]+/g, '-');
}
@@ -108,10 +129,13 @@ export function buildRuntimeProviderOptions(
export function buildConfiguredModelOptions(
providerAccounts: ProviderAccount[],
providerStatuses: ProviderWithKeyInfo[],
providerVendors: ProviderVendorInfo[],
providerDefaultAccountId: string | null,
): ConfiguredModelOption[] {
const safeAccounts = Array.isArray(providerAccounts) ? providerAccounts : [];
const safeStatuses = Array.isArray(providerStatuses) ? providerStatuses : [];
const safeVendors = Array.isArray(providerVendors) ? providerVendors : [];
const vendorMap = new Map<string, ProviderVendorInfo>(safeVendors.map((vendor) => [vendor.id, vendor]));
const statusById = new Map<string, ProviderWithKeyInfo>(safeStatuses.map((status) => [status.id, status]));
const entries = safeAccounts
.filter((account) => account.enabled && account.model?.trim() && hasConfiguredProviderCredentials(account, statusById))
@@ -132,7 +156,7 @@ export function buildConfiguredModelOptions(
if (deduped.has(modelRef)) continue;
deduped.set(modelRef, {
modelRef,
label: modelId,
label: formatConfiguredModelLabel(modelId, account, vendorMap),
runtimeProviderKey,
accountId: account.id,
});
+12 -3
View File
@@ -218,6 +218,7 @@ export function ChatInput({ onSend, onStop, disabled = false, sending = false }:
const providerAccounts = useProviderStore((s) => s.accounts);
const providerStatuses = useProviderStore((s) => s.statuses);
const providerDefaultAccountId = useProviderStore((s) => s.defaultAccountId);
const providerVendors = useProviderStore((s) => s.vendors);
const refreshProviderSnapshot = useProviderStore((s) => s.refreshProviderSnapshot);
const currentAgentId = useChatStore((s) => s.currentAgentId);
const currentAgent = useMemo(
@@ -229,11 +230,19 @@ export function ChatInput({ onSend, onStop, disabled = false, sending = false }:
[currentAgent, currentAgentId],
);
const modelOptions = useMemo(
() => buildConfiguredModelOptions(providerAccounts, providerStatuses, providerDefaultAccountId),
[providerAccounts, providerDefaultAccountId, providerStatuses],
() => buildConfiguredModelOptions(
providerAccounts,
providerStatuses,
providerVendors,
providerDefaultAccountId,
),
[providerAccounts, providerDefaultAccountId, providerStatuses, providerVendors],
);
const effectiveModelRef = optimisticModelRef || currentAgent?.modelRef || defaultModelRef || modelOptions[0]?.modelRef || null;
const currentModelLabel = formatModelRefLabel(effectiveModelRef);
const currentModelLabel = useMemo(() => {
const matchedOption = modelOptions.find((option) => option.modelRef === effectiveModelRef);
return matchedOption?.label || formatModelRefLabel(effectiveModelRef);
}, [effectiveModelRef, modelOptions]);
const mentionableAgents = useMemo(
() => (agents ?? []).filter((agent) => agent.id !== currentAgentId),
[agents, currentAgentId],
+4 -4
View File
@@ -162,12 +162,12 @@ test.describe('ClawX chat model picker', () => {
win?.webContents.send('gateway:status-changed', { state: 'running', port: 18789, pid: 12345, gatewayReady: true });
});
await expect(page.getByTestId('chat-model-picker-button')).toContainText('model-alpha');
await expect(page.getByTestId('chat-model-picker-button')).toContainText('model-alpha (Alpha)');
await page.getByTestId('chat-model-picker-button').click();
await expect(page.getByTestId('chat-model-picker-menu')).toBeVisible();
await expect(page.getByTestId('chat-model-picker-menu')).toContainText('provider/model-beta');
await page.getByTestId('chat-model-picker-menu').getByRole('button', { name: 'provider/model-beta' }).click();
await expect(page.getByTestId('chat-model-picker-button')).toContainText('provider/model-beta');
await expect(page.getByTestId('chat-model-picker-menu')).toContainText('provider/model-beta (Beta)');
await page.getByTestId('chat-model-picker-menu').getByRole('button', { name: 'provider/model-beta (Beta)' }).click();
await expect(page.getByTestId('chat-model-picker-button')).toContainText('provider/model-beta (Beta)');
const requests = await app.evaluate(() => (
(globalThis as typeof globalThis & { __chatModelPickerRequests?: Array<{ path: string; method: string; body: unknown }> }).__chatModelPickerRequests ?? []
+31 -4
View File
@@ -1,10 +1,12 @@
import { describe, expect, it } from 'vitest';
import {
buildConfiguredModelOptions,
formatConfiguredModelLabel,
formatModelRefLabel,
formatProviderDisplayName,
resolveRuntimeProviderKey,
} from '../../src/lib/model-options';
import type { ProviderAccount, ProviderWithKeyInfo } from '../../src/lib/providers';
import type { ProviderAccount, ProviderVendorInfo, ProviderWithKeyInfo } from '../../src/lib/providers';
const now = '2026-04-28T00:00:00.000Z';
@@ -36,12 +38,34 @@ function status(id: string, hasKey = true): ProviderWithKeyInfo {
} as ProviderWithKeyInfo;
}
const vendors: ProviderVendorInfo[] = [
{
id: 'openai',
name: 'OpenAI',
icon: '💚',
placeholder: 'sk-proj-...',
model: 'GPT',
requiresApiKey: true,
category: 'official',
supportedAuthModes: ['api_key', 'oauth_browser'],
defaultAuthMode: 'api_key',
supportsMultipleAccounts: true,
},
];
describe('model option helpers', () => {
it('formats model refs using only the text after the provider prefix', () => {
expect(formatModelRefLabel('openrouter/openai/gpt-5.5')).toBe('openai/gpt-5.5');
expect(formatModelRefLabel('custom-alpha1234/model-alpha')).toBe('model-alpha');
});
it('formats provider display names using custom labels or vendor names', () => {
const vendorMap = new Map(vendors.map((vendor) => [vendor.id, vendor]));
expect(formatProviderDisplayName(account({ vendorId: 'custom', label: 'Alpha' }), vendorMap)).toBe('Alpha');
expect(formatProviderDisplayName(account({ vendorId: 'openai', label: 'OpenAI' }), vendorMap)).toBe('OpenAI');
expect(formatConfiguredModelLabel('gpt-5.5', account({ vendorId: 'openai', label: 'OpenAI' }), vendorMap)).toBe('gpt-5.5 (OpenAI)');
});
it('builds one configured custom model option per account', () => {
const options = buildConfiguredModelOptions(
[
@@ -49,19 +73,20 @@ describe('model option helpers', () => {
account({ id: 'beta5678', label: 'Beta', model: 'provider/model-beta', updatedAt: '2026-04-02T00:00:00.000Z' }),
],
[status('alpha1234'), status('beta5678')],
vendors,
'alpha1234',
);
expect(options).toEqual([
{
modelRef: 'custom-alpha123/model-alpha',
label: 'model-alpha',
label: 'model-alpha (Alpha)',
runtimeProviderKey: 'custom-alpha123',
accountId: 'alpha1234',
},
{
modelRef: 'custom-beta5678/provider/model-beta',
label: 'provider/model-beta',
label: 'provider/model-beta (Beta)',
runtimeProviderKey: 'custom-beta5678',
accountId: 'beta5678',
},
@@ -76,12 +101,13 @@ describe('model option helpers', () => {
account({ id: 'delta3456', label: 'Delta', model: 'model-delta' }),
],
[status('gamma9012'), status('delta3456', false)],
vendors,
null,
);
expect(options).toHaveLength(1);
expect(options[0].modelRef).toBe('custom-gamma901/model-gamma');
expect(options[0].label).toBe('model-gamma');
expect(options[0].label).toBe('model-gamma (Alpha)');
});
it('treats malformed provider snapshots as empty options', () => {
@@ -89,6 +115,7 @@ describe('model option helpers', () => {
buildConfiguredModelOptions(
{} as ProviderAccount[],
{} as ProviderWithKeyInfo[],
{} as ProviderVendorInfo[],
null,
),
).toEqual([]);