Reduce React Doctor diagnostics in ModelControls (#1265)
* fix: reduce React Doctor diagnostics in ModelControls (69/88) * Move EditModeIcon and IconBadge to module scope to prevent remount on every render EditModeIcon and IconBadge were defined as React.FC inside ModelControls function body, producing a new component type reference on every render and forcing React to unmount/remount subtrees. Moves both to module scope; EditModeIcon no longer closes over editToggleIconClass since all callers already pass an explicit className prop.
This commit is contained in:
committed by
GitHub
parent
5e3afa3aa7
commit
8056fae275
@@ -191,19 +191,19 @@ const getModalityIcons = (metadata: ModelMetadata | undefined, direction: 'input
|
||||
|
||||
const uniqueValues = Array.from(new Set(modalityList.map((item) => normalizeModality(item))));
|
||||
|
||||
return uniqueValues
|
||||
.map((modality) => {
|
||||
const definition = MODALITY_ICON_MAP[modality];
|
||||
if (!definition) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
key: modality,
|
||||
icon: definition.icon,
|
||||
label: definition.label,
|
||||
} satisfies ModalityIcon;
|
||||
})
|
||||
.filter((entry): entry is ModalityIcon => Boolean(entry));
|
||||
const result: ModalityIcon[] = [];
|
||||
for (const modality of uniqueValues) {
|
||||
const definition = MODALITY_ICON_MAP[modality];
|
||||
if (!definition) {
|
||||
continue;
|
||||
}
|
||||
result.push({
|
||||
key: modality,
|
||||
icon: definition.icon,
|
||||
label: definition.label,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const COMPACT_NUMBER_FORMATTER = new Intl.NumberFormat('en-US', {
|
||||
@@ -220,8 +220,45 @@ const CURRENCY_FORMATTER = new Intl.NumberFormat('en-US', {
|
||||
minimumFractionDigits: 2,
|
||||
});
|
||||
|
||||
const KNOWLEDGE_DATE_FORMATTER = new Intl.DateTimeFormat('en-US', { month: 'short', year: 'numeric' });
|
||||
|
||||
const DATE_FORMATTER = new Intl.DateTimeFormat('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
});
|
||||
|
||||
const ADD_PROVIDER_ID = '__add_provider__';
|
||||
|
||||
const IconBadge: React.FC<{ iconName: IconComponent; label: string }> = ({ iconName, label }) => (
|
||||
<span
|
||||
className="flex size-5 items-center justify-center rounded-xl bg-muted/60 text-muted-foreground"
|
||||
title={label}
|
||||
aria-label={label}
|
||||
role="img"
|
||||
>
|
||||
<Icon name={iconName} className="size-3.5" />
|
||||
</span>
|
||||
);
|
||||
|
||||
const EditModeIcon: React.FC<{ mode: EditPermissionMode; className?: string }> = ({ mode, className }) => {
|
||||
const combinedClassName = cn(className, 'flex-shrink-0');
|
||||
const modeColors = getEditModeColors(mode);
|
||||
const iconColor = modeColors ? modeColors.text : 'var(--foreground)';
|
||||
const iconStyle = { color: iconColor };
|
||||
|
||||
if (mode === 'full') {
|
||||
return <Icon name="pencil-ai" className={combinedClassName} style={iconStyle} />;
|
||||
}
|
||||
if (mode === 'allow') {
|
||||
return <Icon name="checkbox-circle" className={combinedClassName} style={iconStyle} />;
|
||||
}
|
||||
if (mode === 'deny') {
|
||||
return <Icon name="close-circle" className={combinedClassName} style={iconStyle} />;
|
||||
}
|
||||
return <Icon name="question" className={combinedClassName} style={iconStyle} />;
|
||||
};
|
||||
|
||||
const formatTokens = (value?: number | null) => {
|
||||
if (typeof value !== 'number' || Number.isNaN(value)) {
|
||||
return '—';
|
||||
@@ -266,11 +303,13 @@ const formatCompactPrice = (metadata?: ModelMetadata): string | null => {
|
||||
};
|
||||
|
||||
const getCapabilityIcons = (metadata?: ModelMetadata) => {
|
||||
return CAPABILITY_DEFINITIONS.filter((definition) => definition.isActive(metadata)).map((definition) => ({
|
||||
key: definition.key,
|
||||
icon: definition.icon,
|
||||
label: definition.label,
|
||||
}));
|
||||
const result: { key: string; icon: IconComponent; label: string }[] = [];
|
||||
for (const definition of CAPABILITY_DEFINITIONS) {
|
||||
if (definition.isActive(metadata)) {
|
||||
result.push({ key: definition.key, icon: definition.icon, label: definition.label });
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const formatKnowledge = (knowledge?: string) => {
|
||||
@@ -284,7 +323,7 @@ const formatKnowledge = (knowledge?: string) => {
|
||||
const monthIndex = Number.parseInt(match[2], 10) - 1;
|
||||
const knowledgeDate = new Date(Date.UTC(year, monthIndex, 1));
|
||||
if (!Number.isNaN(knowledgeDate.getTime())) {
|
||||
return new Intl.DateTimeFormat('en-US', { month: 'short', year: 'numeric' }).format(knowledgeDate);
|
||||
return KNOWLEDGE_DATE_FORMATTER.format(knowledgeDate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,11 +340,7 @@ const formatDate = (value?: string) => {
|
||||
return value;
|
||||
}
|
||||
|
||||
return new Intl.DateTimeFormat('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
}).format(parsedDate);
|
||||
return DATE_FORMATTER.format(parsedDate);
|
||||
};
|
||||
|
||||
interface ModelControlsProps {
|
||||
@@ -394,10 +429,16 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
const setSettingsDialogOpen = useUIStore((state) => state.setSettingsDialogOpen);
|
||||
const setSettingsPage = useUIStore((state) => state.setSettingsPage);
|
||||
const hiddenModels = useUIStore((state) => state.hiddenModels);
|
||||
const collapsedProviderSet = React.useMemo(
|
||||
() => new Set(collapsedModelProviders.map((providerId) => providerId.trim()).filter(Boolean)),
|
||||
[collapsedModelProviders]
|
||||
);
|
||||
const collapsedProviderSet = React.useMemo(() => {
|
||||
const result = new Set<string>();
|
||||
for (const providerId of collapsedModelProviders) {
|
||||
const trimmed = providerId.trim();
|
||||
if (trimmed) {
|
||||
result.add(trimmed);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}, [collapsedModelProviders]);
|
||||
|
||||
// Separate state for agent selector to avoid conflict with model selector
|
||||
const [isAgentSelectorOpen, setIsAgentSelectorOpen] = React.useState(false);
|
||||
@@ -473,13 +514,10 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
React.useEffect(() => {
|
||||
if (activeMobilePanel !== 'model') {
|
||||
setMobileModelQuery('');
|
||||
setExpandedMobileModelKey(null);
|
||||
}
|
||||
}, [activeMobilePanel]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setExpandedMobileModelKey(null);
|
||||
}, [mobileModelQuery]);
|
||||
|
||||
// Handle model selector close behavior (separate from agent selector)
|
||||
const prevModelSelectorOpenRef = React.useRef(isModelSelectorOpen);
|
||||
React.useEffect(() => {
|
||||
@@ -552,44 +590,28 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
|
||||
const sizeVariant: 'mobile' | 'vscode' | 'default' = isMobile ? 'mobile' : isVSCodeRuntime ? 'vscode' : 'default';
|
||||
const buttonHeight = sizeVariant === 'mobile' ? 'h-9' : sizeVariant === 'vscode' ? 'h-6' : 'h-8';
|
||||
const editToggleIconClass = sizeVariant === 'mobile' ? 'h-5 w-5' : sizeVariant === 'vscode' ? 'h-4 w-4' : 'h-4 w-4';
|
||||
const controlIconSize = sizeVariant === 'mobile' ? 'h-5 w-5' : sizeVariant === 'vscode' ? 'h-4 w-4' : 'h-4 w-4';
|
||||
const controlIconSize = sizeVariant === 'mobile' ? 'size-5' : sizeVariant === 'vscode' ? 'size-4' : 'size-4';
|
||||
const controlTextSize = isCompact ? 'typography-micro' : 'typography-meta';
|
||||
const inlineGapClass = sizeVariant === 'mobile' ? 'gap-x-1' : sizeVariant === 'vscode' ? 'gap-x-2' : 'gap-x-3';
|
||||
const renderEditModeIcon = React.useCallback((mode: EditPermissionMode, iconClass = editToggleIconClass) => {
|
||||
const combinedClassName = cn(iconClass, 'flex-shrink-0');
|
||||
const modeColors = getEditModeColors(mode);
|
||||
const iconColor = modeColors ? modeColors.text : 'var(--foreground)';
|
||||
const iconStyle = { color: iconColor };
|
||||
|
||||
if (mode === 'full') {
|
||||
return <Icon name="pencil-ai" className={combinedClassName} style={iconStyle} />;
|
||||
}
|
||||
if (mode === 'allow') {
|
||||
return <Icon name="checkbox-circle" className={combinedClassName} style={iconStyle} />;
|
||||
}
|
||||
if (mode === 'deny') {
|
||||
return <Icon name="close-circle" className={combinedClassName} style={iconStyle} />;
|
||||
}
|
||||
return <Icon name="question" className={combinedClassName} style={iconStyle} />;
|
||||
}, [editToggleIconClass]);
|
||||
|
||||
const currentProvider = getCurrentProvider();
|
||||
const models = Array.isArray(currentProvider?.models) ? currentProvider.models : [];
|
||||
|
||||
const visibleProviders = React.useMemo(() => {
|
||||
return providers
|
||||
.map((provider) => {
|
||||
const providerModels = Array.isArray(provider.models) ? provider.models : [];
|
||||
const visibleModels = providerModels.filter((model: ProviderModel) => {
|
||||
const modelId = typeof model?.id === 'string' ? model.id : '';
|
||||
return !hiddenModels.some(
|
||||
(item) => item.providerID === String(provider.id) && item.modelID === modelId
|
||||
);
|
||||
});
|
||||
return { ...provider, models: visibleModels };
|
||||
})
|
||||
.filter((provider) => provider.models.length > 0);
|
||||
const result: typeof providers = [];
|
||||
for (const provider of providers) {
|
||||
const providerModels = Array.isArray(provider.models) ? provider.models : [];
|
||||
const visibleModels = providerModels.filter((model: ProviderModel) => {
|
||||
const modelId = typeof model?.id === 'string' ? model.id : '';
|
||||
return !hiddenModels.some(
|
||||
(item) => item.providerID === String(provider.id) && item.modelID === modelId
|
||||
);
|
||||
});
|
||||
if (visibleModels.length > 0) {
|
||||
result.push({ ...provider, models: visibleModels });
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}, [providers, hiddenModels]);
|
||||
|
||||
const normalizeModelSearchValue = React.useCallback((value: string) => {
|
||||
@@ -632,11 +654,12 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
if (!normalizedQuery) return true;
|
||||
return matchesModelSearch(modelName, normalizedQuery) || matchesModelSearch(providerName, normalizedQuery);
|
||||
};
|
||||
const providersById = new Map(providers.map((p) => [p.id, p]));
|
||||
|
||||
let flatIndex = 0;
|
||||
|
||||
for (const { model, providerID, modelID } of favoriteModelsList) {
|
||||
const provider = providers.find((entry) => entry.id === providerID);
|
||||
const provider = providersById.get(providerID);
|
||||
const providerName = provider?.name || providerID;
|
||||
const modelName = getModelDisplayName(model);
|
||||
if (!matchesQuery(modelName, providerName)) {
|
||||
@@ -649,7 +672,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
}
|
||||
|
||||
for (const { model, providerID, modelID } of recentModelsList) {
|
||||
const provider = providers.find((entry) => entry.id === providerID);
|
||||
const provider = providersById.get(providerID);
|
||||
const providerName = provider?.name || providerID;
|
||||
const modelName = getModelDisplayName(model);
|
||||
if (!matchesQuery(modelName, providerName)) {
|
||||
@@ -1123,6 +1146,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
if (!contextHydrated) {
|
||||
return;
|
||||
}
|
||||
const abortController = new AbortController();
|
||||
|
||||
const handleAgentSwitch = async () => {
|
||||
try {
|
||||
@@ -1130,7 +1154,17 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
prevAgentNameRef.current = currentAgentName;
|
||||
|
||||
if (currentAgentName && currentSessionId) {
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
await new Promise<void>((resolve) => {
|
||||
const timer = setTimeout(resolve, 50);
|
||||
abortController.signal.addEventListener('abort', () => {
|
||||
clearTimeout(timer);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
if (abortController.signal.aborted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const persistedChoice = getAgentModelForSession(currentSessionId, currentAgentName);
|
||||
|
||||
@@ -1152,6 +1186,10 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
};
|
||||
|
||||
handleAgentSwitch();
|
||||
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
}, [currentAgentName, currentSessionId, getAgentModelForSession, tryApplyModelSelection, contextHydrated]);
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -1335,18 +1373,6 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
return name.charAt(0).toUpperCase() + name.slice(1);
|
||||
};
|
||||
|
||||
const renderIconBadge = (iconName: IconComponent, label: string, key: string) => (
|
||||
<span
|
||||
key={key}
|
||||
className="flex h-5 w-5 items-center justify-center rounded-xl bg-muted/60 text-muted-foreground"
|
||||
title={label}
|
||||
aria-label={label}
|
||||
role="img"
|
||||
>
|
||||
<Icon name={iconName} className="h-3.5 w-3.5" />
|
||||
</span>
|
||||
);
|
||||
|
||||
const toggleMobileProviderExpansion = React.useCallback((providerId: string) => {
|
||||
setExpandedMobileProviders((prev) => {
|
||||
const next = new Set(prev);
|
||||
@@ -1405,7 +1431,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{currentCapabilityIcons.map(({ key, icon, label }) => (
|
||||
<div key={key} className="flex items-center gap-1.5">
|
||||
{renderIconBadge(icon, label, `cap-${key}`)}
|
||||
<IconBadge key={`cap-${key}`} iconName={icon} label={label} />
|
||||
<span className="typography-meta text-foreground">{label}</span>
|
||||
</div>
|
||||
))}
|
||||
@@ -1422,7 +1448,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="typography-meta text-muted-foreground/80 w-12">{t('chat.modelControls.input')}</span>
|
||||
<div className="flex gap-1">
|
||||
{inputModalityIcons.map(({ key, icon, label }) => renderIconBadge(icon, `${label} input`, `input-${key}`))}
|
||||
{inputModalityIcons.map(({ key, icon, label }) => <IconBadge key={`input-${key}`} iconName={icon} label={`${label} input`} />)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -1430,7 +1456,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="typography-meta text-muted-foreground/80 w-12">{t('chat.modelControls.output')}</span>
|
||||
<div className="flex gap-1">
|
||||
{outputModalityIcons.map(({ key, icon, label }) => renderIconBadge(icon, `${label} output`, `output-${key}`))}
|
||||
{outputModalityIcons.map(({ key, icon, label }) => <IconBadge key={`output-${key}`} iconName={icon} label={`${label} output`} />)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -1560,7 +1586,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="typography-meta text-muted-foreground/80">{t('chat.modelControls.edit')}</span>
|
||||
<div className="flex items-center gap-1.5">
|
||||
{renderEditModeIcon(editPermissionSummary.mode, 'h-3.5 w-3.5')}
|
||||
<EditModeIcon mode={editPermissionSummary.mode} className="size-3.5" />
|
||||
<span className="typography-meta font-medium text-foreground">
|
||||
{editPermissionSummary.label}
|
||||
</span>
|
||||
@@ -1569,7 +1595,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="typography-meta text-muted-foreground/80">{t('chat.modelControls.bash')}</span>
|
||||
<div className="flex items-center gap-1.5">
|
||||
{renderEditModeIcon(bashPermissionSummary.mode, 'h-3.5 w-3.5')}
|
||||
<EditModeIcon mode={bashPermissionSummary.mode} className="size-3.5" />
|
||||
<span className="typography-meta font-medium text-foreground">
|
||||
{bashPermissionSummary.label}
|
||||
</span>
|
||||
@@ -1578,7 +1604,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="typography-meta text-muted-foreground/80">{t('chat.modelControls.webFetch')}</span>
|
||||
<div className="flex items-center gap-1.5">
|
||||
{renderEditModeIcon(webfetchPermissionSummary.mode, 'h-3.5 w-3.5')}
|
||||
<EditModeIcon mode={webfetchPermissionSummary.mode} className="size-3.5" />
|
||||
<span className="typography-meta font-medium text-foreground">
|
||||
{webfetchPermissionSummary.label}
|
||||
</span>
|
||||
@@ -1592,7 +1618,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
<div className="rounded-xl border border-border/40 bg-sidebar/30 px-2 py-1.5">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="typography-meta text-muted-foreground/80">{t('chat.modelControls.customPrompt')}</span>
|
||||
<Icon name="checkbox-circle" className="h-4 w-4 text-foreground" />
|
||||
<Icon name="checkbox-circle" className="size-4 text-foreground" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -1623,26 +1649,28 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
|| matchesModelSearch(providerName, normalizedQuery);
|
||||
});
|
||||
|
||||
const filteredProviders = visibleProviders
|
||||
.map((provider) => {
|
||||
const providerModels = Array.isArray(provider.models) ? provider.models : [];
|
||||
const matchesProvider = normalizedQuery.length === 0
|
||||
? true
|
||||
: matchesModelSearch(provider.name, normalizedQuery) || matchesModelSearch(provider.id, normalizedQuery);
|
||||
const matchingModels = normalizedQuery.length === 0
|
||||
? providerModels
|
||||
: providerModels.filter((model: ProviderModel) => {
|
||||
const name = getModelDisplayName(model);
|
||||
const id = typeof model.id === 'string' ? model.id : '';
|
||||
return matchesModelSearch(name, normalizedQuery) || matchesModelSearch(id, normalizedQuery);
|
||||
});
|
||||
return {
|
||||
provider,
|
||||
providerModels: matchesProvider && normalizedQuery.length > 0 ? providerModels : matchingModels,
|
||||
matchesProvider,
|
||||
};
|
||||
})
|
||||
.filter(({ matchesProvider, providerModels }) => matchesProvider || providerModels.length > 0);
|
||||
const filteredProviders: {
|
||||
provider: (typeof visibleProviders)[number];
|
||||
providerModels: ProviderModel[];
|
||||
matchesProvider: boolean;
|
||||
}[] = [];
|
||||
for (const provider of visibleProviders) {
|
||||
const providerModels = Array.isArray(provider.models) ? provider.models : [];
|
||||
const matchesProvider = normalizedQuery.length === 0
|
||||
? true
|
||||
: matchesModelSearch(provider.name, normalizedQuery) || matchesModelSearch(provider.id, normalizedQuery);
|
||||
const matchingModels = normalizedQuery.length === 0
|
||||
? providerModels
|
||||
: providerModels.filter((model: ProviderModel) => {
|
||||
const name = getModelDisplayName(model);
|
||||
const id = typeof model.id === 'string' ? model.id : '';
|
||||
return matchesModelSearch(name, normalizedQuery) || matchesModelSearch(id, normalizedQuery);
|
||||
});
|
||||
const resolvedModels = matchesProvider && normalizedQuery.length > 0 ? providerModels : matchingModels;
|
||||
if (matchesProvider || resolvedModels.length > 0) {
|
||||
filteredProviders.push({ provider, providerModels: resolvedModels, matchesProvider });
|
||||
}
|
||||
}
|
||||
|
||||
const focusMobileComposer = () => {
|
||||
requestAnimationFrame(() => {
|
||||
@@ -1724,14 +1752,14 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
)}
|
||||
>
|
||||
{showProviderLogo ? (
|
||||
<ProviderLogo providerId={providerId} className="mt-0.5 h-3.5 w-3.5 flex-shrink-0" />
|
||||
<ProviderLogo providerId={providerId} className="mt-0.5 size-3.5 flex-shrink-0" />
|
||||
) : null}
|
||||
<div className="flex min-w-0 flex-1 flex-col gap-0.5">
|
||||
<div className="flex min-w-0 items-start gap-2">
|
||||
<span className="typography-meta font-medium text-foreground truncate">
|
||||
{getModelDisplayName(model)}
|
||||
</span>
|
||||
{isSelected ? <Icon name="check" className="mt-0.5 h-4 w-4 flex-shrink-0 text-primary" /> : null}
|
||||
{isSelected ? <Icon name="check" className="mt-0.5 size-4 flex-shrink-0 text-primary" /> : null}
|
||||
</div>
|
||||
{contextText || indicatorIcons.length > 0 ? (
|
||||
<div className="flex min-w-0 items-center gap-1.5 overflow-hidden typography-micro text-muted-foreground">
|
||||
@@ -1748,11 +1776,11 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
{indicatorIcons.map(({ key, icon: iconName, label }) => (
|
||||
<span
|
||||
key={`meta-${providerId}-${modelId}-${key}`}
|
||||
className="flex h-4 w-4 flex-shrink-0 items-center justify-center text-muted-foreground"
|
||||
className="flex size-4 flex-shrink-0 items-center justify-center text-muted-foreground"
|
||||
title={label}
|
||||
aria-label={label}
|
||||
>
|
||||
<Icon name={iconName} className="h-3 w-3" />
|
||||
<Icon name={iconName} className="size-3" />
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
@@ -1770,7 +1798,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
aria-label={isExpanded ? t('chat.modelControls.hideThinkingModes') : t('chat.modelControls.showThinkingModes')}
|
||||
>
|
||||
<span className="whitespace-nowrap">{variantLabel}</span>
|
||||
{isExpanded ? <Icon name="arrow-down-s" className="h-3.5 w-3.5" /> : <Icon name="arrow-right-s" className="h-3.5 w-3.5" />}
|
||||
{isExpanded ? <Icon name="arrow-down-s" className="size-3.5" /> : <Icon name="arrow-right-s" className="size-3.5" />}
|
||||
</button>
|
||||
) : null}
|
||||
<div className="flex flex-shrink-0 items-start gap-1.5">
|
||||
@@ -1782,7 +1810,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
toggleFavoriteModel(providerId, modelId);
|
||||
}}
|
||||
className={cn(
|
||||
'model-favorite-button flex h-5 w-5 items-center justify-center hover:text-primary/80 flex-shrink-0',
|
||||
'model-favorite-button flex size-5 items-center justify-center hover:text-primary/80 flex-shrink-0',
|
||||
isFavoriteModel(providerId, modelId) ? 'text-primary' : 'text-muted-foreground'
|
||||
)}
|
||||
aria-label={isFavoriteModel(providerId, modelId)
|
||||
@@ -1793,15 +1821,15 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
: t('chat.modelControls.addToFavorites')}
|
||||
>
|
||||
{isFavoriteModel(providerId, modelId) ? (
|
||||
<Icon name="star-fill" className="h-4 w-4" />
|
||||
<Icon name="star-fill" className="size-4" />
|
||||
) : (
|
||||
<Icon name="star" className="h-4 w-4" />
|
||||
<Icon name="star" className="size-4" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{isExpanded && hasVariants ? (
|
||||
<div className="border-t border-border/30 px-2 py-2">
|
||||
<div className="border-t border-border/30 p-2">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{inlineVariantOptions.map((variantOption) => {
|
||||
const isVariantSelected = variantOption === resolvedVariant || (!variantOption && !resolvedVariant);
|
||||
@@ -1850,21 +1878,27 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
<div className="flex flex-col gap-2">
|
||||
<div>
|
||||
<div className="relative">
|
||||
<Icon name="search" className="absolute left-2 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
|
||||
<Icon name="search" className="absolute left-2 top-1/2 -translate-y-1/2 size-3.5 text-muted-foreground" />
|
||||
<Input
|
||||
value={mobileModelQuery}
|
||||
onChange={(event) => setMobileModelQuery(event.target.value)}
|
||||
onChange={(event) => {
|
||||
setMobileModelQuery(event.target.value);
|
||||
setExpandedMobileModelKey(null);
|
||||
}}
|
||||
placeholder={t('chat.modelControls.searchProvidersOrModels')}
|
||||
className="pl-7 h-9 rounded-xl border-border/40 bg-[var(--surface-elevated)] typography-meta"
|
||||
/>
|
||||
{mobileModelQuery && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setMobileModelQuery('')}
|
||||
onClick={() => {
|
||||
setMobileModelQuery('');
|
||||
setExpandedMobileModelKey(null);
|
||||
}}
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
||||
aria-label={t('chat.modelControls.clearSearch')}
|
||||
>
|
||||
<Icon name="close-circle" className="h-4 w-4" />
|
||||
<Icon name="close-circle" className="size-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -1880,7 +1914,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
{filteredFavorites.length > 0 && (
|
||||
<div className="rounded-xl border border-border/40 bg-[var(--surface-elevated)] overflow-hidden">
|
||||
<div className="px-2 py-1.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider">
|
||||
<Icon name="star-fill" className="h-3 w-3 inline-block mr-1.5 text-primary" />
|
||||
<Icon name="star-fill" className="size-3 inline-block mr-1.5 text-primary" />
|
||||
{t('chat.modelControls.favorites')}
|
||||
</div>
|
||||
<div className="flex flex-col border-t border-border/30">
|
||||
@@ -1898,7 +1932,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
{filteredRecents.length > 0 && (
|
||||
<div className="rounded-xl border border-border/40 bg-[var(--surface-elevated)] overflow-hidden">
|
||||
<div className="px-2 py-1.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider">
|
||||
<Icon name="time" className="h-3 w-3 inline-block mr-1.5" />
|
||||
<Icon name="time" className="size-3 inline-block mr-1.5" />
|
||||
{t('chat.modelControls.recent')}
|
||||
</div>
|
||||
<div className="flex flex-col border-t border-border/30">
|
||||
@@ -1936,7 +1970,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
<div className="flex items-center gap-2">
|
||||
<ProviderLogo
|
||||
providerId={provider.id}
|
||||
className="h-3.5 w-3.5"
|
||||
className="size-3.5"
|
||||
/>
|
||||
<span className="typography-meta font-medium text-foreground">
|
||||
{provider.name}
|
||||
@@ -1946,9 +1980,9 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
)}
|
||||
</div>
|
||||
{isExpanded ? (
|
||||
<Icon name="arrow-down-s" className="h-3 w-3 text-muted-foreground" />
|
||||
<Icon name="arrow-down-s" className="size-3 text-muted-foreground" />
|
||||
) : (
|
||||
<Icon name="arrow-right-s" className="h-3 w-3 text-muted-foreground" />
|
||||
<Icon name="arrow-right-s" className="size-3 text-muted-foreground" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
@@ -2012,7 +2046,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
onClick={handleBack}
|
||||
className="flex items-center gap-1 rounded-lg px-1.5 py-1 typography-meta text-muted-foreground hover:bg-interactive-hover"
|
||||
>
|
||||
<Icon name="arrow-go-back" className="h-4 w-4" />
|
||||
<Icon name="arrow-go-back" className="size-4" />
|
||||
<span>{t('onboarding.common.actions.back')}</span>
|
||||
</button>
|
||||
<h2 className="typography-ui-label font-semibold text-foreground">{t('chat.modelControls.thinking')}</h2>
|
||||
@@ -2031,7 +2065,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
onClick={() => handleSelect(undefined)}
|
||||
>
|
||||
<span className="typography-meta font-medium text-foreground">{t('chat.modelControls.default')}</span>
|
||||
{isDefault && <Icon name="check" className="h-4 w-4 text-primary flex-shrink-0" />}
|
||||
{isDefault && <Icon name="check" className="size-4 text-primary flex-shrink-0" />}
|
||||
</button>
|
||||
|
||||
{targetVariants.map((variant) => {
|
||||
@@ -2050,7 +2084,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
onClick={() => handleSelect(variant)}
|
||||
>
|
||||
<span className="typography-meta font-medium text-foreground">{label}</span>
|
||||
{selected && <Icon name="check" className="h-4 w-4 text-primary flex-shrink-0" />}
|
||||
{selected && <Icon name="check" className="size-4 text-primary flex-shrink-0" />}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
@@ -2089,7 +2123,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
onClick={() => handleAgentChange(agent.name)}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={cn('h-2.5 w-2.5 rounded-full flex-shrink-0', agentColor.class)} />
|
||||
<div className={cn('size-2.5 rounded-full flex-shrink-0', agentColor.class)} />
|
||||
<span
|
||||
className="typography-ui-label font-semibold"
|
||||
style={isSelected ? { color: `var(${agentColor.var})` } : undefined}
|
||||
@@ -2097,7 +2131,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
{capitalizeAgentName(agent.name)}
|
||||
</span>
|
||||
{isSelected && (
|
||||
<Icon name="check" className="h-4 w-4 text-primary ml-auto flex-shrink-0" />
|
||||
<Icon name="check" className="size-4 text-primary ml-auto flex-shrink-0" />
|
||||
)}
|
||||
</div>
|
||||
{agent.description && (
|
||||
@@ -2128,7 +2162,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
<div className="flex flex-wrap items-center gap-1.5">
|
||||
{currentCapabilityIcons.length > 0 ? (
|
||||
currentCapabilityIcons.map(({ key, icon, label }) =>
|
||||
renderIconBadge(icon, label, `cap-${key}`)
|
||||
<IconBadge key={`cap-${key}`} iconName={icon} label={label} />
|
||||
)
|
||||
) : (
|
||||
<span className="typography-meta text-muted-foreground">{t('chat.modelControls.modeValue.none')}</span>
|
||||
@@ -2143,9 +2177,9 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
<div className="flex items-center gap-1.5">
|
||||
{inputModalityIcons.length > 0
|
||||
? inputModalityIcons.map(({ key, icon, label }) =>
|
||||
renderIconBadge(icon, `${label} input`, `input-${key}`)
|
||||
<IconBadge key={`input-${key}`} iconName={icon} label={`${label} input`} />
|
||||
)
|
||||
: <span className="typography-meta text-muted-foreground">—</span>}
|
||||
: <span className="typography-meta text-muted-foreground">-</span>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
@@ -2153,9 +2187,9 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
<div className="flex items-center gap-1.5">
|
||||
{outputModalityIcons.length > 0
|
||||
? outputModalityIcons.map(({ key, icon, label }) =>
|
||||
renderIconBadge(icon, `${label} output`, `output-${key}`)
|
||||
<IconBadge key={`output-${key}`} iconName={icon} label={`${label} output`} />
|
||||
)
|
||||
: <span className="typography-meta text-muted-foreground">—</span>}
|
||||
: <span className="typography-meta text-muted-foreground">-</span>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2266,12 +2300,12 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
{indicatorIcons.map(({ id, icon: iconName, label }) => (
|
||||
<span
|
||||
key={id}
|
||||
className="flex h-3.5 w-3.5 items-center justify-center text-muted-foreground"
|
||||
className="flex size-3.5 items-center justify-center text-muted-foreground"
|
||||
aria-label={label}
|
||||
role="img"
|
||||
title={label}
|
||||
>
|
||||
<Icon name={iconName} className="h-2.5 w-2.5" />
|
||||
<Icon name={iconName} className="size-2.5" />
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
@@ -2308,11 +2342,19 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
<div
|
||||
key={`${keyPrefix}-${providerID}-${modelID}`}
|
||||
ref={(el) => { modelItemRefs.current[flatIndex] = el; }}
|
||||
role="option"
|
||||
aria-selected={isSelected}
|
||||
className={cn(
|
||||
"typography-meta group flex items-center gap-2 px-2 py-1.5 rounded-md cursor-pointer",
|
||||
isHighlighted ? "bg-interactive-selection" : "hover:bg-interactive-hover/50"
|
||||
)}
|
||||
onClick={() => handleProviderAndModelChange(providerID, modelID)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
handleProviderAndModelChange(providerID, modelID);
|
||||
}
|
||||
}}
|
||||
onMouseEnter={handlePointerActivity}
|
||||
onMouseMove={handlePointerActivity}
|
||||
>
|
||||
@@ -2326,16 +2368,16 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}}
|
||||
className="model-favorite-drag-handle flex h-4 w-4 flex-shrink-0 items-center justify-center text-muted-foreground hover:text-foreground"
|
||||
className="model-favorite-drag-handle flex size-4 flex-shrink-0 items-center justify-center text-muted-foreground hover:text-foreground"
|
||||
aria-label={t('chat.modelControls.reorderFavoriteAria')}
|
||||
title={t('chat.modelControls.reorderFavoriteTitle')}
|
||||
>
|
||||
<Icon name="draggable" className="h-3.5 w-3.5" />
|
||||
<Icon name="draggable" className="size-3.5" />
|
||||
</button>
|
||||
) : null}
|
||||
<div className="flex items-center gap-1.5 flex-1 min-w-0">
|
||||
{showProviderLogo && (
|
||||
<ProviderLogo providerId={providerID} className="h-3.5 w-3.5 flex-shrink-0" />
|
||||
<ProviderLogo providerId={providerID} className="size-3.5 flex-shrink-0" />
|
||||
)}
|
||||
<span className="font-medium truncate">
|
||||
{getModelDisplayName(model)}
|
||||
@@ -2370,7 +2412,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
</div>
|
||||
) : null}
|
||||
{isSelected && (
|
||||
<Icon name="check" className="h-4 w-4 text-primary" />
|
||||
<Icon name="check" className="size-4 text-primary" />
|
||||
)}
|
||||
<button
|
||||
onClick={(e) => {
|
||||
@@ -2379,7 +2421,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
toggleFavoriteModel(providerID, modelID);
|
||||
}}
|
||||
className={cn(
|
||||
"model-favorite-button flex h-4 w-4 items-center justify-center hover:text-primary/80",
|
||||
"model-favorite-button flex size-4 items-center justify-center hover:text-primary/80",
|
||||
isFavorite ? "text-primary" : "text-muted-foreground"
|
||||
)}
|
||||
aria-label={isFavorite
|
||||
@@ -2390,9 +2432,9 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
: t('chat.modelControls.addToFavorites')}
|
||||
>
|
||||
{isFavorite ? (
|
||||
<Icon name="star-fill" className="h-3.5 w-3.5" />
|
||||
<Icon name="star-fill" className="size-3.5" />
|
||||
) : (
|
||||
<Icon name="star" className="h-3.5 w-3.5" />
|
||||
<Icon name="star" className="size-3.5" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
@@ -2429,16 +2471,17 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
return filterByQuery(modelName, providerName, desktopModelQuery);
|
||||
});
|
||||
|
||||
const filteredProviders = visibleProviders
|
||||
.map((provider) => {
|
||||
const providerModels = Array.isArray(provider.models) ? provider.models : [];
|
||||
const filteredModels = providerModels.filter((model: ProviderModel) => {
|
||||
const modelName = getModelDisplayName(model);
|
||||
return filterByQuery(modelName, provider.name || provider.id || '', desktopModelQuery);
|
||||
});
|
||||
return { ...provider, models: filteredModels };
|
||||
})
|
||||
.filter((provider) => provider.models.length > 0);
|
||||
const filteredProviders: typeof visibleProviders = [];
|
||||
for (const provider of visibleProviders) {
|
||||
const providerModels = Array.isArray(provider.models) ? provider.models : [];
|
||||
const filteredModels = providerModels.filter((model: ProviderModel) => {
|
||||
const modelName = getModelDisplayName(model);
|
||||
return filterByQuery(modelName, provider.name || provider.id || '', desktopModelQuery);
|
||||
});
|
||||
if (filteredModels.length > 0) {
|
||||
filteredProviders.push({ ...provider, models: filteredModels });
|
||||
}
|
||||
}
|
||||
|
||||
const providerSections = filteredProviders.map((provider) => {
|
||||
const providerId = typeof provider.id === 'string' ? provider.id : '';
|
||||
@@ -2457,9 +2500,12 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
filteredRecents.length > 0 ||
|
||||
filteredProviders.length > 0;
|
||||
|
||||
const filteredProviderIds = filteredProviders
|
||||
.map((provider) => (typeof provider.id === 'string' ? provider.id : ''))
|
||||
.filter(Boolean);
|
||||
const filteredProviderIds: string[] = [];
|
||||
for (const provider of filteredProviders) {
|
||||
if (typeof provider.id === 'string' && provider.id) {
|
||||
filteredProviderIds.push(provider.id);
|
||||
}
|
||||
}
|
||||
|
||||
const favoriteModelLookup = new Map(
|
||||
filteredFavorites.map(({ providerID, modelID }) => [buildModelRefKey(providerID, modelID), { providerID, modelID }])
|
||||
@@ -2687,7 +2733,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
{/* Search Input */}
|
||||
<div className="p-2 border-b border-border/40">
|
||||
<div className="relative">
|
||||
<Icon name="search" className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
|
||||
<Icon name="search" className="absolute left-2.5 top-1/2 -translate-y-1/2 size-3.5 text-muted-foreground" />
|
||||
<Input
|
||||
type="text"
|
||||
placeholder={t('chat.modelControls.searchModels')}
|
||||
@@ -2695,7 +2741,6 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
onChange={(e) => setDesktopModelQuery(e.target.value)}
|
||||
onKeyDown={handleModelKeyDown}
|
||||
className="pl-8 h-8 typography-meta"
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2718,8 +2763,8 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
}}
|
||||
className="typography-meta group flex items-center gap-1 rounded-md px-2 py-1.5 cursor-pointer hover:bg-interactive-hover/50"
|
||||
>
|
||||
<span className="flex h-4 w-4 items-center justify-center text-muted-foreground">
|
||||
<Icon name="add" className="h-4 w-4 -mr-0.5" />
|
||||
<span className="flex size-4 items-center justify-center text-muted-foreground">
|
||||
<Icon name="add" className="size-4 -mr-0.5" />
|
||||
</span>
|
||||
<span className="font-medium text-foreground">{t('chat.modelControls.addNewProvider')}</span>
|
||||
</div>
|
||||
@@ -2738,7 +2783,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
<DropdownMenuLabel
|
||||
className="typography-micro font-semibold text-muted-foreground uppercase tracking-wider flex items-center gap-2 -mx-1 px-3 py-1.5 border-b border-border/30"
|
||||
>
|
||||
<Icon name="star-fill" className="h-4 w-4 text-primary" />
|
||||
<Icon name="star-fill" className="size-4 text-primary" />
|
||||
{t('chat.modelControls.favorites')}
|
||||
</DropdownMenuLabel>
|
||||
{favoriteSortingEnabled ? (
|
||||
@@ -2788,7 +2833,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
<DropdownMenuLabel
|
||||
className="typography-micro font-semibold text-muted-foreground uppercase tracking-wider flex items-center gap-2 -mx-1 px-3 py-1.5 border-b border-border/30"
|
||||
>
|
||||
<Icon name="time" className="h-4 w-4" />
|
||||
<Icon name="time" className="size-4" />
|
||||
{t('chat.modelControls.recent')}
|
||||
</DropdownMenuLabel>
|
||||
{filteredRecents.map(({ model, providerID, modelID }) => {
|
||||
@@ -2849,14 +2894,14 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<ProviderLogo
|
||||
providerId={provider.id}
|
||||
className="h-4 w-4 flex-shrink-0"
|
||||
className="size-4 flex-shrink-0"
|
||||
/>
|
||||
<span className="min-w-0 truncate">{provider.name}</span>
|
||||
<span className="flex h-4 w-4 flex-shrink-0 items-center justify-center text-muted-foreground">
|
||||
<span className="flex size-4 flex-shrink-0 items-center justify-center text-muted-foreground">
|
||||
{isExpanded ? (
|
||||
<Icon name="arrow-down-s" className="h-4 w-4" />
|
||||
<Icon name="arrow-down-s" className="size-4" />
|
||||
) : (
|
||||
<Icon name="arrow-right-s" className="h-4 w-4" />
|
||||
<Icon name="arrow-right-s" className="size-4" />
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
@@ -3023,7 +3068,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="typography-meta text-muted-foreground/80 w-16">{t('chat.modelControls.edit')}</span>
|
||||
<div className="flex items-center gap-1.5">
|
||||
{renderEditModeIcon(editPermissionSummary.mode, 'h-3.5 w-3.5')}
|
||||
<EditModeIcon mode={editPermissionSummary.mode} className="size-3.5" />
|
||||
<span className="typography-meta font-medium text-foreground w-12">
|
||||
{editPermissionSummary.label}
|
||||
</span>
|
||||
@@ -3032,7 +3077,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="typography-meta text-muted-foreground/80 w-16">{t('chat.modelControls.bash')}</span>
|
||||
<div className="flex items-center gap-1.5">
|
||||
{renderEditModeIcon(bashPermissionSummary.mode, 'h-3.5 w-3.5')}
|
||||
<EditModeIcon mode={bashPermissionSummary.mode} className="size-3.5" />
|
||||
<span className="typography-meta font-medium text-foreground w-12">
|
||||
{bashPermissionSummary.label}
|
||||
</span>
|
||||
@@ -3041,7 +3086,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="typography-meta text-muted-foreground/80 w-16">{t('chat.modelControls.webFetch')}</span>
|
||||
<div className="flex items-center gap-1.5">
|
||||
{renderEditModeIcon(webfetchPermissionSummary.mode, 'h-3.5 w-3.5')}
|
||||
<EditModeIcon mode={webfetchPermissionSummary.mode} className="size-3.5" />
|
||||
<span className="typography-meta font-medium text-foreground w-12">
|
||||
{webfetchPermissionSummary.label}
|
||||
</span>
|
||||
@@ -3052,7 +3097,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
{hasCustomPrompt && (
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<span className="typography-meta text-muted-foreground/80">{t('chat.modelControls.customPrompt')}</span>
|
||||
<Icon name="checkbox-circle" className="h-4 w-4 text-foreground" />
|
||||
<Icon name="checkbox-circle" className="size-4 text-foreground" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -3125,7 +3170,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
<DropdownMenuItem className="typography-meta" onSelect={() => handleVariantSelect(undefined)}>
|
||||
<div className="flex items-center justify-between gap-2 w-full min-w-0">
|
||||
<span className="typography-meta font-medium text-foreground truncate min-w-0">{t('chat.modelControls.default')}</span>
|
||||
{isDefault && <Icon name="check" className="h-4 w-4 text-primary flex-shrink-0" />}
|
||||
{isDefault && <Icon name="check" className="size-4 text-primary flex-shrink-0" />}
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
{availableVariants.length > 0 && <DropdownMenuSeparator />}
|
||||
@@ -3140,7 +3185,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2 w-full min-w-0">
|
||||
<span className="typography-meta font-medium text-foreground truncate min-w-0">{label}</span>
|
||||
{selected && <Icon name="check" className="h-4 w-4 text-primary flex-shrink-0" />}
|
||||
{selected && <Icon name="check" className="size-4 text-primary flex-shrink-0" />}
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
@@ -3213,7 +3258,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
<DropdownMenuContent align="end" alignOffset={-40} className="w-[min(280px,calc(100vw-2rem))] p-0 flex flex-col">
|
||||
<div className="p-2 border-b border-border/40">
|
||||
<div className="relative">
|
||||
<Icon name="search" className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
|
||||
<Icon name="search" className="absolute left-2.5 top-1/2 -translate-y-1/2 size-3.5 text-muted-foreground" />
|
||||
<Input
|
||||
type="text"
|
||||
placeholder={t('chat.modelControls.searchAgents')}
|
||||
@@ -3223,7 +3268,6 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
e.stopPropagation();
|
||||
}}
|
||||
className="pl-8 h-8 typography-meta"
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -3236,7 +3280,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
|
||||
onSelect={() => handleAgentChange(defaultAgentName)}
|
||||
>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Icon name="arrow-go-back" className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
<Icon name="arrow-go-back" className="size-3.5 text-muted-foreground" />
|
||||
<span className="font-medium">{t('chat.modelControls.resetToDefault')}</span>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
|
||||
Reference in New Issue
Block a user