merge main to pick up German locale for custom provider keys

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
This commit is contained in:
Cursor Agent
2026-08-03 08:53:06 +00:00
co-authored by Serhii Dziupin
233 changed files with 19087 additions and 1831 deletions
@@ -23,9 +23,17 @@ interface ModelSelectorProps {
onChange: (providerId: string, modelId: string) => void;
className?: string;
allowedProviderIds?: string[];
isModelAllowed?: (providerId: string, modelId: string) => boolean;
placeholder?: string;
tooltipsEnabled?: boolean;
dropdownPortalToBody?: boolean;
/**
* Drop the model name and the chevron, leaving the provider logo. For
* headers that run out of room before they run out of controls — the logo
* still says which provider is answering, which is the part a glance is
* usually after.
*/
compact?: boolean;
}
export const ModelSelector: React.FC<ModelSelectorProps> = ({
@@ -34,9 +42,11 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
onChange,
className,
allowedProviderIds,
isModelAllowed,
placeholder,
tooltipsEnabled = true,
dropdownPortalToBody = false,
compact = false,
}) => {
const { t } = useI18n();
const { isReady, isUnavailable } = useOpenCodeReadiness();
@@ -115,6 +125,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
selectedModel={selectedModel}
hiddenModels={hiddenModels}
allowedProviderIds={allowedProviderIds}
isModelAllowed={isModelAllowed}
includeNotSelected
onSelectNone={handleSelectNone}
onEscape={closePicker}
@@ -166,26 +177,35 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
return (
<DropdownMenu open={isReady && isDropdownOpen} onOpenChange={isReady ? setIsDropdownOpen : undefined}>
<DropdownMenuTrigger asChild>
<div className={cn(
dropdownTriggerVariants({ size: 'sm' }),
'min-w-0 w-fit',
!isReady && 'opacity-60 cursor-not-allowed',
className,
)}>
<div
className={cn(
dropdownTriggerVariants({ size: 'sm' }),
'min-w-0 w-fit',
!isReady && 'opacity-60 cursor-not-allowed',
className,
)}
// The name is gone from the trigger, so it has to stay
// reachable somewhere.
title={compact && isReady ? triggerLabel : undefined}
>
{!isReady ? (
<>
<Icon name="loader-4" className="h-3.5 w-3.5 animate-spin text-muted-foreground flex-shrink-0" />
<span className="typography-ui-label font-normal whitespace-nowrap text-muted-foreground">
{isUnavailable ? t('common.unavailable') : t('common.loading')}
</span>
{!compact && (
<span className="typography-ui-label font-normal whitespace-nowrap text-muted-foreground">
{isUnavailable ? t('common.unavailable') : t('common.loading')}
</span>
)}
</>
) : (
<>
{providerId ? <ProviderLogo providerId={providerId} className="h-3.5 w-3.5 flex-shrink-0" /> : <Icon name="pencil-ai" className="h-3.5 w-3.5 flex-shrink-0 text-muted-foreground" />}
<span className="typography-ui-label min-w-0 flex-1 truncate text-left font-normal text-foreground">{triggerLabel}</span>
{!compact && (
<span className="typography-ui-label min-w-0 flex-1 truncate text-left font-normal text-foreground">{triggerLabel}</span>
)}
</>
)}
<Icon name="arrow-down-s" className="h-4 w-4 flex-shrink-0 text-muted-foreground/50" />
{!compact && <Icon name="arrow-down-s" className="h-4 w-4 flex-shrink-0 text-muted-foreground/50" />}
</div>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-[min(380px,calc(100vw-2rem))] p-0 flex flex-col" align="start" portalToBody={dropdownPortalToBody}>
@@ -45,6 +45,7 @@ export const DefaultsSettings: React.FC = () => {
const showDeletionDialog = useUIStore((state) => state.showDeletionDialog);
const setShowDeletionDialog = useUIStore((state) => state.setShowDeletionDialog);
const providers = useConfigStore((state) => state.providers);
const modelsMetadata = useConfigStore((state) => state.modelsMetadata);
const [defaultModel, setDefaultModel] = React.useState<string | undefined>();
const [defaultVariant, setDefaultVariant] = React.useState<string | undefined>();
@@ -52,6 +53,7 @@ export const DefaultsSettings: React.FC = () => {
const [smallModelUseDefault, setSmallModelUseDefault] = React.useState(true);
const [smallModelOverride, setSmallModelOverride] = React.useState<string | undefined>();
const [smallModelProviders, setSmallModelProviders] = React.useState<string[] | undefined>();
const [walkthroughModelOverride, setWalkthroughModelOverride] = React.useState<string | undefined>();
const [isLoading, setIsLoading] = React.useState(true);
const parsedModel = React.useMemo(() => getDisplayModel(defaultModel), [defaultModel]);
@@ -65,6 +67,7 @@ export const DefaultsSettings: React.FC = () => {
defaultAgent?: string;
smallModelUseDefault?: boolean;
smallModelOverride?: string;
walkthroughModelOverride?: string;
} | null = null;
if (!data) {
@@ -84,6 +87,8 @@ export const DefaultsSettings: React.FC = () => {
defaultAgent: typeof settings.defaultAgent === 'string' ? settings.defaultAgent : undefined,
smallModelUseDefault: typeof raw.smallModelUseDefault === 'boolean' ? raw.smallModelUseDefault : undefined,
smallModelOverride: typeof raw.smallModelOverride === 'string' ? raw.smallModelOverride : undefined,
walkthroughModelOverride:
typeof raw.walkthroughModelOverride === 'string' ? raw.walkthroughModelOverride : undefined,
};
}
} catch {
@@ -123,6 +128,9 @@ export const DefaultsSettings: React.FC = () => {
if (typeof data.smallModelOverride === 'string' && data.smallModelOverride.trim()) {
setSmallModelOverride(data.smallModelOverride.trim());
}
if (typeof data.walkthroughModelOverride === 'string' && data.walkthroughModelOverride.trim()) {
setWalkthroughModelOverride(data.walkthroughModelOverride.trim());
}
}
} catch (error) {
console.warn('Failed to load defaults settings:', error);
@@ -236,10 +244,43 @@ export const DefaultsSettings: React.FC = () => {
[]
);
const handleWalkthroughModelOverrideChange = React.useCallback(
async (providerId: string, modelId: string) => {
const newValue = providerId && modelId ? `${providerId}/${modelId}` : undefined;
setWalkthroughModelOverride(newValue);
try {
// Clearing the picker is how the user goes back to the small model, so
// an empty value is a real choice rather than a no-op.
await updateDesktopSettings({ walkthroughModelOverride: newValue ?? '' });
} catch (error) {
console.warn('Failed to save walkthrough model override:', error);
}
},
[]
);
// The walkthrough cannot work at all without schema-shaped output, so models
// the catalog says cannot do it are hidden rather than offered and then
// refused. A missing capability is not a "no": roughly half the catalog omits
// the field, and those models usually work.
const isStructuredOutputCapable = React.useCallback(
(providerId: string, modelId: string) =>
modelsMetadata.get(`${providerId}/${modelId}`)?.structured_output !== false,
[modelsMetadata]
);
const parsedSmallModel = React.useMemo(() => getDisplayModel(smallModelOverride), [smallModelOverride]);
const parsedWalkthroughModel = React.useMemo(
() => getDisplayModel(walkthroughModelOverride),
[walkthroughModelOverride]
);
React.useEffect(() => {
if (smallModelUseDefault || smallModelProviders !== undefined) return;
// Both pickers filter by the same authenticated-provider list, so either
// one being open is reason enough to fetch it.
// Both pickers filter by the same authenticated-provider list, and the
// walkthrough picker is always visible, so this is always worth fetching.
if (smallModelProviders !== undefined) return;
let cancelled = false;
(async () => {
try {
@@ -256,7 +297,7 @@ export const DefaultsSettings: React.FC = () => {
return () => {
cancelled = true;
};
}, [smallModelUseDefault, smallModelProviders]);
}, [smallModelProviders]);
const availableVariants = React.useMemo(() => {
if (!parsedModel.providerId || !parsedModel.modelId) return [];
@@ -396,6 +437,32 @@ export const DefaultsSettings: React.FC = () => {
/>
</SettingsFieldRow>
) : null}
<SettingsInset className={SETTINGS_OPTION_STACK_CLASS}>
<div className="flex items-center gap-1.5">
<SettingsGroupTitle>
{t('settings.openchamber.defaults.walkthroughModel.title')}
</SettingsGroupTitle>
<SettingsInfoHint>
{t('settings.openchamber.defaults.walkthroughModel.description')}
</SettingsInfoHint>
</div>
<SettingsFieldRow
settingsItem="sessions.walkthrough-model"
label={t('settings.openchamber.defaults.walkthroughModel.overrideModel')}
>
<ModelSelector
providerId={parsedWalkthroughModel.providerId}
modelId={parsedWalkthroughModel.modelId}
onChange={handleWalkthroughModelOverrideChange}
allowedProviderIds={smallModelProviders}
isModelAllowed={isStructuredOutputCapable}
placeholder={t('settings.openchamber.defaults.walkthroughModel.usesSmallModel')}
className={SETTINGS_CUSTOM_TRIGGER_CLASS}
/>
</SettingsFieldRow>
</SettingsInset>
</div>
</div>
</SettingsSection>