feat: add default variant support in settings and UI components
This commit is contained in:
@@ -276,6 +276,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({ className }) => {
|
||||
currentModelId,
|
||||
currentVariant,
|
||||
currentAgentName,
|
||||
settingsDefaultVariant,
|
||||
setProvider,
|
||||
setModel,
|
||||
setCurrentVariant,
|
||||
@@ -865,7 +866,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({ className }) => {
|
||||
}, [currentAgentName, currentSessionId, getAgentModelForSession, tryApplyModelSelection, agents, contextHydrated]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!contextHydrated || !currentSessionId || !currentAgentName) {
|
||||
if (!contextHydrated || !currentAgentName) {
|
||||
setCurrentVariant(undefined);
|
||||
return;
|
||||
}
|
||||
@@ -875,6 +876,28 @@ export const ModelControls: React.FC<ModelControlsProps> = ({ className }) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (availableVariants.length === 0) {
|
||||
setCurrentVariant(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentVariant && !availableVariants.includes(currentVariant)) {
|
||||
setCurrentVariant(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
// Draft state (no session yet): seed from settings default, but don't override
|
||||
// user selection while drafting.
|
||||
if (!currentSessionId) {
|
||||
if (!currentVariant) {
|
||||
const desired = settingsDefaultVariant && availableVariants.includes(settingsDefaultVariant)
|
||||
? settingsDefaultVariant
|
||||
: undefined;
|
||||
setCurrentVariant(desired);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const savedVariant = getAgentModelVariantForSession(
|
||||
currentSessionId,
|
||||
currentAgentName,
|
||||
@@ -882,12 +905,11 @@ export const ModelControls: React.FC<ModelControlsProps> = ({ className }) => {
|
||||
currentModelId,
|
||||
);
|
||||
|
||||
if (savedVariant && !availableVariants.includes(savedVariant)) {
|
||||
setCurrentVariant(undefined);
|
||||
return;
|
||||
}
|
||||
const resolvedSaved = savedVariant && availableVariants.includes(savedVariant)
|
||||
? savedVariant
|
||||
: undefined;
|
||||
|
||||
setCurrentVariant(savedVariant);
|
||||
setCurrentVariant(resolvedSaved);
|
||||
}, [
|
||||
availableVariants,
|
||||
contextHydrated,
|
||||
@@ -895,8 +917,10 @@ export const ModelControls: React.FC<ModelControlsProps> = ({ className }) => {
|
||||
currentAgentName,
|
||||
currentProviderId,
|
||||
currentModelId,
|
||||
currentVariant,
|
||||
getAgentModelVariantForSession,
|
||||
setCurrentVariant,
|
||||
settingsDefaultVariant,
|
||||
]);
|
||||
|
||||
const handleVariantSelect = React.useCallback((variant: string | undefined) => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { RiInformationLine } from '@remixicon/react';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import { ModelSelector } from '@/components/sections/agents/ModelSelector';
|
||||
import { AgentSelector } from '@/components/sections/commands/AgentSelector';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { updateDesktopSettings } from '@/lib/persistence';
|
||||
import { isDesktopRuntime, getDesktopSettings } from '@/lib/desktop';
|
||||
import { useConfigStore } from '@/stores/useConfigStore';
|
||||
@@ -13,13 +14,16 @@ export const DefaultsSettings: React.FC = () => {
|
||||
const setProvider = useConfigStore((state) => state.setProvider);
|
||||
const setModel = useConfigStore((state) => state.setModel);
|
||||
const setAgent = useConfigStore((state) => state.setAgent);
|
||||
const setCurrentVariant = useConfigStore((state) => state.setCurrentVariant);
|
||||
const setSettingsDefaultModel = useConfigStore((state) => state.setSettingsDefaultModel);
|
||||
const setSettingsDefaultVariant = useConfigStore((state) => state.setSettingsDefaultVariant);
|
||||
const setSettingsDefaultAgent = useConfigStore((state) => state.setSettingsDefaultAgent);
|
||||
const settingsAutoCreateWorktree = useConfigStore((state) => state.settingsAutoCreateWorktree);
|
||||
const setSettingsAutoCreateWorktree = useConfigStore((state) => state.setSettingsAutoCreateWorktree);
|
||||
const providers = useConfigStore((state) => state.providers);
|
||||
|
||||
const [defaultModel, setDefaultModel] = React.useState<string | undefined>();
|
||||
const [defaultVariant, setDefaultVariant] = React.useState<string | undefined>();
|
||||
const [defaultAgent, setDefaultAgent] = React.useState<string | undefined>();
|
||||
const [isLoading, setIsLoading] = React.useState(true);
|
||||
|
||||
@@ -35,7 +39,7 @@ export const DefaultsSettings: React.FC = () => {
|
||||
React.useEffect(() => {
|
||||
const loadSettings = async () => {
|
||||
try {
|
||||
let data: { defaultModel?: string; defaultAgent?: string } | null = null;
|
||||
let data: { defaultModel?: string; defaultVariant?: string; defaultAgent?: string } | null = null;
|
||||
|
||||
// 1. Desktop runtime (Tauri)
|
||||
if (isDesktopRuntime()) {
|
||||
@@ -50,6 +54,7 @@ export const DefaultsSettings: React.FC = () => {
|
||||
if (settings) {
|
||||
data = {
|
||||
defaultModel: typeof settings.defaultModel === 'string' ? settings.defaultModel : undefined,
|
||||
defaultVariant: typeof (settings as Record<string, unknown>).defaultVariant === 'string' ? ((settings as Record<string, unknown>).defaultVariant as string) : undefined,
|
||||
defaultAgent: typeof settings.defaultAgent === 'string' ? settings.defaultAgent : undefined,
|
||||
};
|
||||
}
|
||||
@@ -71,8 +76,13 @@ export const DefaultsSettings: React.FC = () => {
|
||||
}
|
||||
|
||||
if (data) {
|
||||
setDefaultModel(data.defaultModel);
|
||||
setDefaultAgent(data.defaultAgent);
|
||||
const model = typeof data.defaultModel === 'string' && data.defaultModel.trim().length > 0 ? data.defaultModel.trim() : undefined;
|
||||
const variant = typeof data.defaultVariant === 'string' && data.defaultVariant.trim().length > 0 ? data.defaultVariant.trim() : undefined;
|
||||
const agent = typeof data.defaultAgent === 'string' && data.defaultAgent.trim().length > 0 ? data.defaultAgent.trim() : undefined;
|
||||
|
||||
setDefaultModel(model);
|
||||
setDefaultVariant(variant);
|
||||
setDefaultAgent(agent);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to load defaults settings:', error);
|
||||
@@ -87,6 +97,11 @@ export const DefaultsSettings: React.FC = () => {
|
||||
const newValue = providerId && modelId ? `${providerId}/${modelId}` : undefined;
|
||||
setDefaultModel(newValue);
|
||||
|
||||
// Reset variant when model changes (model-specific)
|
||||
setDefaultVariant(undefined);
|
||||
setSettingsDefaultVariant(undefined);
|
||||
setCurrentVariant(undefined);
|
||||
|
||||
// Update config store settings default (used by setAgent logic)
|
||||
setSettingsDefaultModel(newValue);
|
||||
|
||||
@@ -102,11 +117,29 @@ export const DefaultsSettings: React.FC = () => {
|
||||
try {
|
||||
await updateDesktopSettings({
|
||||
defaultModel: newValue ?? '',
|
||||
defaultVariant: '',
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn('Failed to save default model:', error);
|
||||
}
|
||||
}, [providers, setProvider, setModel, setSettingsDefaultModel]);
|
||||
}, [providers, setCurrentVariant, setProvider, setModel, setSettingsDefaultModel, setSettingsDefaultVariant]);
|
||||
|
||||
const DEFAULT_VARIANT_VALUE = '__default__';
|
||||
|
||||
const handleVariantChange = React.useCallback(async (variant: string) => {
|
||||
const newValue = variant === DEFAULT_VARIANT_VALUE ? undefined : (variant || undefined);
|
||||
setDefaultVariant(newValue);
|
||||
setSettingsDefaultVariant(newValue);
|
||||
setCurrentVariant(newValue);
|
||||
|
||||
try {
|
||||
await updateDesktopSettings({
|
||||
defaultVariant: newValue ?? '',
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn('Failed to save default variant:', error);
|
||||
}
|
||||
}, [setCurrentVariant, setSettingsDefaultVariant]);
|
||||
|
||||
const handleAgentChange = React.useCallback(async (agentName: string) => {
|
||||
const newValue = agentName || undefined;
|
||||
@@ -129,6 +162,31 @@ export const DefaultsSettings: React.FC = () => {
|
||||
}
|
||||
}, [setAgent, setSettingsDefaultAgent]);
|
||||
|
||||
const availableVariants = React.useMemo(() => {
|
||||
const provider = providers.find((p) => p.id === parsedModel.providerId);
|
||||
const model = provider?.models.find((m: Record<string, unknown>) => (m as { id?: string }).id === parsedModel.modelId) as
|
||||
| { variants?: Record<string, unknown> }
|
||||
| undefined;
|
||||
const variants = model?.variants;
|
||||
if (!variants) {
|
||||
return [];
|
||||
}
|
||||
return Object.keys(variants);
|
||||
}, [parsedModel.modelId, parsedModel.providerId, providers]);
|
||||
|
||||
const supportsVariants = availableVariants.length > 0;
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!supportsVariants && defaultVariant) {
|
||||
setDefaultVariant(undefined);
|
||||
setSettingsDefaultVariant(undefined);
|
||||
setCurrentVariant(undefined);
|
||||
updateDesktopSettings({ defaultVariant: '' }).catch(() => {
|
||||
// best effort
|
||||
});
|
||||
}
|
||||
}, [defaultVariant, setCurrentVariant, setSettingsDefaultVariant, supportsVariants]);
|
||||
|
||||
const handleAutoWorktreeChange = React.useCallback(async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const enabled = e.target.checked;
|
||||
setSettingsAutoCreateWorktree(enabled);
|
||||
@@ -161,29 +219,53 @@ export const DefaultsSettings: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label className="typography-ui-label text-muted-foreground">Default model</label>
|
||||
<ModelSelector
|
||||
providerId={parsedModel.providerId}
|
||||
modelId={parsedModel.modelId}
|
||||
onChange={handleModelChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label className="typography-ui-label text-muted-foreground">Default model</label>
|
||||
<ModelSelector
|
||||
providerId={parsedModel.providerId}
|
||||
modelId={parsedModel.modelId}
|
||||
onChange={handleModelChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label className="typography-ui-label text-muted-foreground">Default agent</label>
|
||||
<AgentSelector
|
||||
agentName={defaultAgent || ''}
|
||||
onChange={handleAgentChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{supportsVariants && (
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label className="typography-ui-label text-muted-foreground">Default thinking</label>
|
||||
<Select value={defaultVariant ?? DEFAULT_VARIANT_VALUE} onValueChange={handleVariantChange}>
|
||||
<SelectTrigger className="w-auto max-w-xs typography-meta text-foreground">
|
||||
<SelectValue placeholder="Thinking" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={DEFAULT_VARIANT_VALUE} className="pr-2 [&>span:first-child]:hidden">Default</SelectItem>
|
||||
{availableVariants.map((variant) => (
|
||||
<SelectItem key={variant} value={variant} className="pr-2 [&>span:first-child]:hidden">
|
||||
{variant}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label className="typography-ui-label text-muted-foreground">Default agent</label>
|
||||
<AgentSelector
|
||||
agentName={defaultAgent || ''}
|
||||
onChange={handleAgentChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{(defaultModel || defaultAgent) && (
|
||||
<div className="typography-meta text-muted-foreground">
|
||||
New sessions will start with:{' '}
|
||||
{defaultModel && <span className="text-foreground">{defaultModel}</span>}
|
||||
{defaultModel && (
|
||||
<span className="text-foreground">
|
||||
{defaultModel}
|
||||
{supportsVariants ? ` (${defaultVariant ?? 'default'})` : ''}
|
||||
</span>
|
||||
)}
|
||||
{defaultModel && defaultAgent && ' / '}
|
||||
{defaultAgent && <span className="text-foreground">{defaultAgent}</span>}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user