fix: improve fallback model names
Shows readable model names when provider data is still loading Centralizes model display fallback logic Covers fallback formatting with tests
This commit is contained in:
@@ -25,6 +25,7 @@ import { filterVisibleParts, normalizeParts } from './message/partUtils';
|
||||
import { normalizeUserDisplayParts } from './message/normalizeUserDisplayParts';
|
||||
import { flattenAssistantTextParts } from '@/lib/messages/messageText';
|
||||
import { isLikelyProviderAuthFailure, PROVIDER_AUTH_FAILURE_MESSAGE } from '@/lib/messages/providerAuthError';
|
||||
import { getProviderModelDisplayName } from '@/lib/modelDisplay';
|
||||
import { lazyWithChunkRecovery } from '@/lib/chunkLoadRecovery';
|
||||
import type { TurnGroupingContext } from './lib/turns/types';
|
||||
import { copyTextToClipboard } from '@/lib/clipboard';
|
||||
@@ -168,7 +169,7 @@ const ChatMessage: React.FC<ChatMessageProps> = ({
|
||||
streamPerfCount('ui.chat_message.render.streaming');
|
||||
}
|
||||
|
||||
const providers = useConfigStore.getState().providers;
|
||||
const providers = useConfigStore((state) => state.providers);
|
||||
const { showReasoningTraces, stickyUserHeader, chatRenderMode, showExpandedBashTools, showExpandedEditTools } = useUIStore(
|
||||
useShallow((state) => ({
|
||||
showReasoningTraces: state.showReasoningTraces,
|
||||
@@ -363,17 +364,10 @@ const ChatMessage: React.FC<ChatMessageProps> = ({
|
||||
const modelName = React.useMemo(() => {
|
||||
if (isUser) return undefined;
|
||||
|
||||
if (providerID && modelID && providers.length > 0) {
|
||||
const provider = providers.find((p) => p.id === providerID);
|
||||
if (provider?.models && Array.isArray(provider.models)) {
|
||||
const model = provider.models.find((m: Record<string, unknown>) => (m as Record<string, unknown>).id === modelID);
|
||||
const modelObj = model as Record<string, unknown> | undefined;
|
||||
const name = modelObj?.name;
|
||||
return typeof name === 'string' ? name : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
const provider = providerID && providers.length > 0
|
||||
? providers.find((p) => p.id === providerID)
|
||||
: undefined;
|
||||
return getProviderModelDisplayName(provider, modelID) || undefined;
|
||||
}, [isUser, providerID, modelID, providers]);
|
||||
|
||||
const modelHasVariants = React.useMemo(() => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useConfigStore } from '@/stores/useConfigStore';
|
||||
import { getModelDisplayName } from './mobileControlsUtils';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
|
||||
interface MobileModelButtonProps {
|
||||
onOpenModel: () => void;
|
||||
@@ -9,10 +10,11 @@ interface MobileModelButtonProps {
|
||||
}
|
||||
|
||||
export const MobileModelButton: React.FC<MobileModelButtonProps> = ({ onOpenModel, className }) => {
|
||||
const { t } = useI18n();
|
||||
const currentModelId = useConfigStore((state) => state.currentModelId);
|
||||
const getCurrentProvider = useConfigStore((state) => state.getCurrentProvider);
|
||||
const currentProvider = getCurrentProvider();
|
||||
const modelLabel = getModelDisplayName(currentProvider, currentModelId);
|
||||
const modelLabel = getModelDisplayName(currentProvider, currentModelId, t('chat.modelControls.selectModel'));
|
||||
|
||||
return (
|
||||
<button
|
||||
|
||||
@@ -22,6 +22,7 @@ import { isDesktopShell } from '@/lib/desktop';
|
||||
import { getAgentColor } from '@/lib/agentColors';
|
||||
import { useDeviceInfo } from '@/lib/device';
|
||||
import { mergeModelMetadataWithLiveModel } from '@/lib/modelMetadata';
|
||||
import { getModelDisplayName as getSharedModelDisplayName } from '@/lib/modelDisplay';
|
||||
import { getEditModeColors } from '@/lib/permissions/editModeColors';
|
||||
import { cn, fuzzyMatch } from '@/lib/utils';
|
||||
import { useContextStore } from '@/stores/contextStore';
|
||||
@@ -1258,12 +1259,8 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const getModelDisplayName = (model: ProviderModel | undefined) => {
|
||||
const name = (typeof model?.name === 'string' ? model.name : (typeof model?.id === 'string' ? model.id : ''));
|
||||
if (name.length > 40) {
|
||||
return name.substring(0, 37) + '...';
|
||||
}
|
||||
return name;
|
||||
const getModelDisplayName = (model: ProviderModel | undefined, fallbackModelId?: string) => {
|
||||
return getSharedModelDisplayName(model, fallbackModelId, { maxLength: 40 });
|
||||
};
|
||||
|
||||
const getProviderDisplayName = () => {
|
||||
@@ -1272,10 +1269,9 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
};
|
||||
|
||||
const getCurrentModelDisplayName = () => {
|
||||
if (!currentProviderId || !currentModelId) return 'Not selected';
|
||||
if (models.length === 0) return 'Not selected';
|
||||
if (!currentModelId) return t('chat.modelControls.selectModel');
|
||||
const currentModel = models.find((m: ProviderModel) => m.id === currentModelId);
|
||||
return getModelDisplayName(currentModel);
|
||||
return getModelDisplayName(currentModel, currentModelId) || t('chat.modelControls.selectModel');
|
||||
};
|
||||
|
||||
const currentModelDisplayName = getCurrentModelDisplayName();
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useConfigStore } from '@/stores/useConfigStore';
|
||||
import { useSessionUIStore } from '@/sync/session-ui-store';
|
||||
import { useContextStore } from '@/stores/contextStore';
|
||||
import { formatEffortLabel, getAgentDisplayName, getModelDisplayName } from './mobileControlsUtils';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
|
||||
const STATUS_CHIP_STYLE = {
|
||||
height: '28px',
|
||||
@@ -17,6 +18,7 @@ interface StatusChipProps {
|
||||
}
|
||||
|
||||
export const StatusChip: React.FC<StatusChipProps> = ({ onClick, className }) => {
|
||||
const { t } = useI18n();
|
||||
const currentModelId = useConfigStore((state) => state.currentModelId);
|
||||
const currentVariant = useConfigStore((state) => state.currentVariant);
|
||||
const currentAgentName = useConfigStore((state) => state.currentAgentName);
|
||||
@@ -32,7 +34,7 @@ export const StatusChip: React.FC<StatusChipProps> = ({ onClick, className }) =>
|
||||
const uiAgentName = currentSessionId ? (sessionAgentName || currentAgentName) : currentAgentName;
|
||||
const agentLabel = getAgentDisplayName(agents, uiAgentName);
|
||||
const currentProvider = getCurrentProvider();
|
||||
const modelLabel = getModelDisplayName(currentProvider, currentModelId);
|
||||
const modelLabel = getModelDisplayName(currentProvider, currentModelId, t('chat.modelControls.selectModel'));
|
||||
const hasEffort = getCurrentModelVariants().length > 0;
|
||||
const effortLabel = hasEffort ? formatEffortLabel(currentVariant) : null;
|
||||
const fullLabel = [agentLabel, modelLabel, effortLabel].filter(Boolean).join(' · ');
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Agent } from '@opencode-ai/sdk/v2';
|
||||
import { getProviderModelDisplayName, type DisplayProvider } from '@/lib/modelDisplay';
|
||||
|
||||
export type MobileControlsPanel = 'model' | 'agent' | 'variant' | null;
|
||||
|
||||
@@ -36,24 +37,12 @@ export const getAgentDisplayName = (agents: Agent[], agentName?: string) => {
|
||||
return fallbackAgent ? capitalizeLabel(fallbackAgent.name) : 'Select agent';
|
||||
};
|
||||
|
||||
type ProviderModel = { id?: string; name?: string };
|
||||
|
||||
export const getModelDisplayName = (
|
||||
provider: { models?: ProviderModel[] } | undefined,
|
||||
provider: DisplayProvider,
|
||||
modelId: string | undefined,
|
||||
fallbackLabel = '',
|
||||
) => {
|
||||
if (!provider || !modelId) {
|
||||
return 'Not selected';
|
||||
}
|
||||
const models = Array.isArray(provider.models) ? provider.models : [];
|
||||
const model = models.find((entry) => entry.id === modelId);
|
||||
if (typeof model?.name === 'string' && model.name.trim().length > 0) {
|
||||
return model.name;
|
||||
}
|
||||
if (typeof model?.id === 'string' && model.id.trim().length > 0) {
|
||||
return model.id;
|
||||
}
|
||||
return modelId;
|
||||
return getProviderModelDisplayName(provider, modelId, { fallbackLabel });
|
||||
};
|
||||
|
||||
export const formatEffortLabel = (variant?: string) => {
|
||||
|
||||
@@ -15,6 +15,7 @@ import { ProviderLogo } from '@/components/ui/ProviderLogo';
|
||||
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import { mergeModelMetadataWithLiveModel } from '@/lib/modelMetadata';
|
||||
import { getModelDisplayName as getSharedModelDisplayName } from '@/lib/modelDisplay';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { ModelMetadata } from '@/types';
|
||||
|
||||
@@ -58,10 +59,7 @@ const CURRENCY_FORMATTER = new Intl.NumberFormat('en-US', {
|
||||
});
|
||||
|
||||
const getModelDisplayName = (model: Record<string, unknown>) => {
|
||||
const name = model?.name || model?.id || '';
|
||||
const nameStr = String(name);
|
||||
if (nameStr.length > 40) return `${nameStr.substring(0, 37)}...`;
|
||||
return nameStr;
|
||||
return getSharedModelDisplayName(model, undefined, { maxLength: 40 });
|
||||
};
|
||||
|
||||
const formatModelContextTokens = (value?: number | null) => {
|
||||
|
||||
Reference in New Issue
Block a user