feat: move default agent configuration to Agent Settings (vibe-kanban) (#1453)
This commit is contained in:
committed by
GitHub
parent
e28e25720a
commit
45d0359118
@@ -40,8 +40,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"taskExecution": {
|
"taskExecution": {
|
||||||
"title": "Task Execution",
|
"title": "Default Coding Agent",
|
||||||
"description": "Configure how tasks are executed and processed.",
|
"description": "Choose the default coding agent for tasks.",
|
||||||
"executor": {
|
"executor": {
|
||||||
"label": "Default Agent Configuration",
|
"label": "Default Agent Configuration",
|
||||||
"placeholder": "Select profile",
|
"placeholder": "Select profile",
|
||||||
|
|||||||
@@ -40,8 +40,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"taskExecution": {
|
"taskExecution": {
|
||||||
"title": "Ejecución de Tareas",
|
"title": "Agente de Código Predeterminado",
|
||||||
"description": "Configura cómo se ejecutan y procesan las tareas.",
|
"description": "Elige el agente de código predeterminado para las tareas.",
|
||||||
"executor": {
|
"executor": {
|
||||||
"label": "Configuración predeterminada del Agente",
|
"label": "Configuración predeterminada del Agente",
|
||||||
"placeholder": "Seleccionar perfil",
|
"placeholder": "Seleccionar perfil",
|
||||||
|
|||||||
@@ -40,8 +40,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"taskExecution": {
|
"taskExecution": {
|
||||||
"title": "タスク実行",
|
"title": "デフォルトコーディングエージェント",
|
||||||
"description": "タスクの実行と処理方法を設定します。",
|
"description": "タスクのデフォルトコーディングエージェントを選択します。",
|
||||||
"executor": {
|
"executor": {
|
||||||
"label": "デフォルトエージェント設定",
|
"label": "デフォルトエージェント設定",
|
||||||
"placeholder": "プロファイルを選択",
|
"placeholder": "プロファイルを選択",
|
||||||
|
|||||||
@@ -40,8 +40,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"taskExecution": {
|
"taskExecution": {
|
||||||
"title": "작업 실행",
|
"title": "기본 코딩 에이전트",
|
||||||
"description": "작업이 실행되고 처리되는 방식을 구성하세요.",
|
"description": "작업의 기본 코딩 에이전트를 선택하세요.",
|
||||||
"executor": {
|
"executor": {
|
||||||
"label": "기본 에이전트 구성",
|
"label": "기본 에이전트 구성",
|
||||||
"placeholder": "프로필 선택",
|
"placeholder": "프로필 선택",
|
||||||
|
|||||||
@@ -40,8 +40,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"taskExecution": {
|
"taskExecution": {
|
||||||
"title": "任务执行",
|
"title": "默认编码代理",
|
||||||
"description": "配置任务的执行和处理方式。",
|
"description": "选择任务的默认编码代理。",
|
||||||
"executor": {
|
"executor": {
|
||||||
"label": "默认代理配置",
|
"label": "默认代理配置",
|
||||||
"placeholder": "选择配置文件",
|
"placeholder": "选择配置文件",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { cloneDeep, isEqual } from 'lodash';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -15,23 +16,35 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/components/ui/select';
|
} from '@/components/ui/select';
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from '@/components/ui/dropdown-menu';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||||
import { Checkbox } from '@/components/ui/checkbox';
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
import { JSONEditor } from '@/components/ui/json-editor';
|
import { JSONEditor } from '@/components/ui/json-editor';
|
||||||
import { Loader2 } from 'lucide-react';
|
import { ChevronDown, Loader2 } from 'lucide-react';
|
||||||
|
|
||||||
import { ExecutorConfigForm } from '@/components/ExecutorConfigForm';
|
import { ExecutorConfigForm } from '@/components/ExecutorConfigForm';
|
||||||
import { useProfiles } from '@/hooks/useProfiles';
|
import { useProfiles } from '@/hooks/useProfiles';
|
||||||
import { useUserSystem } from '@/components/ConfigProvider';
|
import { useUserSystem } from '@/components/ConfigProvider';
|
||||||
import { CreateConfigurationDialog } from '@/components/dialogs/settings/CreateConfigurationDialog';
|
import { CreateConfigurationDialog } from '@/components/dialogs/settings/CreateConfigurationDialog';
|
||||||
import { DeleteConfigurationDialog } from '@/components/dialogs/settings/DeleteConfigurationDialog';
|
import { DeleteConfigurationDialog } from '@/components/dialogs/settings/DeleteConfigurationDialog';
|
||||||
import type { BaseCodingAgent, ExecutorConfigs } from 'shared/types';
|
import { useAgentAvailability } from '@/hooks/useAgentAvailability';
|
||||||
|
import { AgentAvailabilityIndicator } from '@/components/AgentAvailabilityIndicator';
|
||||||
|
import type {
|
||||||
|
BaseCodingAgent,
|
||||||
|
ExecutorConfigs,
|
||||||
|
ExecutorProfileId,
|
||||||
|
} from 'shared/types';
|
||||||
|
|
||||||
type ExecutorsMap = Record<string, Record<string, Record<string, unknown>>>;
|
type ExecutorsMap = Record<string, Record<string, Record<string, unknown>>>;
|
||||||
|
|
||||||
export function AgentSettings() {
|
export function AgentSettings() {
|
||||||
const { t } = useTranslation('settings');
|
const { t } = useTranslation(['settings', 'common']);
|
||||||
// Use profiles hook for server state
|
// Use profiles hook for server state
|
||||||
const {
|
const {
|
||||||
profilesContent: serverProfilesContent,
|
profilesContent: serverProfilesContent,
|
||||||
@@ -42,7 +55,8 @@ export function AgentSettings() {
|
|||||||
save: saveProfiles,
|
save: saveProfiles,
|
||||||
} = useProfiles();
|
} = useProfiles();
|
||||||
|
|
||||||
const { reloadSystem } = useUserSystem();
|
const { config, updateAndSaveConfig, profiles, reloadSystem } =
|
||||||
|
useUserSystem();
|
||||||
|
|
||||||
// Local editor state (draft that may differ from server)
|
// Local editor state (draft that may differ from server)
|
||||||
const [localProfilesContent, setLocalProfilesContent] = useState('');
|
const [localProfilesContent, setLocalProfilesContent] = useState('');
|
||||||
@@ -59,6 +73,17 @@ export function AgentSettings() {
|
|||||||
useState<ExecutorConfigs | null>(null);
|
useState<ExecutorConfigs | null>(null);
|
||||||
const [isDirty, setIsDirty] = useState(false);
|
const [isDirty, setIsDirty] = useState(false);
|
||||||
|
|
||||||
|
// Default executor profile state
|
||||||
|
const [executorDraft, setExecutorDraft] = useState<ExecutorProfileId | null>(
|
||||||
|
() => (config?.executor_profile ? cloneDeep(config.executor_profile) : null)
|
||||||
|
);
|
||||||
|
const [executorSaving, setExecutorSaving] = useState(false);
|
||||||
|
const [executorSuccess, setExecutorSuccess] = useState(false);
|
||||||
|
const [executorError, setExecutorError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// Check agent availability when draft executor changes
|
||||||
|
const agentAvailability = useAgentAvailability(executorDraft?.executor);
|
||||||
|
|
||||||
// Sync server state to local state when not dirty
|
// Sync server state to local state when not dirty
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isDirty && serverProfilesContent) {
|
if (!isDirty && serverProfilesContent) {
|
||||||
@@ -74,6 +99,50 @@ export function AgentSettings() {
|
|||||||
}
|
}
|
||||||
}, [serverProfilesContent, isDirty]);
|
}, [serverProfilesContent, isDirty]);
|
||||||
|
|
||||||
|
// Check if executor draft differs from saved config
|
||||||
|
const executorDirty =
|
||||||
|
executorDraft && config?.executor_profile
|
||||||
|
? !isEqual(executorDraft, config.executor_profile)
|
||||||
|
: false;
|
||||||
|
|
||||||
|
// Sync executor draft when config changes (only if not dirty)
|
||||||
|
useEffect(() => {
|
||||||
|
if (config?.executor_profile) {
|
||||||
|
setExecutorDraft((currentDraft) => {
|
||||||
|
// Only update if draft matches the old config (not dirty)
|
||||||
|
if (!currentDraft || isEqual(currentDraft, config.executor_profile)) {
|
||||||
|
return cloneDeep(config.executor_profile);
|
||||||
|
}
|
||||||
|
return currentDraft;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [config?.executor_profile]);
|
||||||
|
|
||||||
|
// Update executor draft
|
||||||
|
const updateExecutorDraft = (newProfile: ExecutorProfileId) => {
|
||||||
|
setExecutorDraft(newProfile);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Save executor profile
|
||||||
|
const handleSaveExecutorProfile = async () => {
|
||||||
|
if (!executorDraft || !config) return;
|
||||||
|
|
||||||
|
setExecutorSaving(true);
|
||||||
|
setExecutorError(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await updateAndSaveConfig({ executor_profile: executorDraft });
|
||||||
|
setExecutorSuccess(true);
|
||||||
|
setTimeout(() => setExecutorSuccess(false), 3000);
|
||||||
|
reloadSystem();
|
||||||
|
} catch (err) {
|
||||||
|
setExecutorError(t('settings.general.save.error'));
|
||||||
|
console.error('Error saving executor profile:', err);
|
||||||
|
} finally {
|
||||||
|
setExecutorSaving(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Sync raw profiles with parsed profiles
|
// Sync raw profiles with parsed profiles
|
||||||
const syncRawProfiles = (profiles: unknown) => {
|
const syncRawProfiles = (profiles: unknown) => {
|
||||||
setLocalProfilesContent(JSON.stringify(profiles, null, 2));
|
setLocalProfilesContent(JSON.stringify(profiles, null, 2));
|
||||||
@@ -382,6 +451,153 @@ export function AgentSettings() {
|
|||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{executorError && (
|
||||||
|
<Alert variant="destructive">
|
||||||
|
<AlertDescription>{executorError}</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{executorSuccess && (
|
||||||
|
<Alert variant="success">
|
||||||
|
<AlertDescription className="font-medium">
|
||||||
|
{t('settings.general.save.success')}
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>{t('settings.general.taskExecution.title')}</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
{t('settings.general.taskExecution.description')}
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="executor">
|
||||||
|
{t('settings.general.taskExecution.executor.label')}
|
||||||
|
</Label>
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
<Select
|
||||||
|
value={executorDraft?.executor ?? ''}
|
||||||
|
onValueChange={(value: string) => {
|
||||||
|
const variants = profiles?.[value];
|
||||||
|
const keepCurrentVariant =
|
||||||
|
variants &&
|
||||||
|
executorDraft?.variant &&
|
||||||
|
variants[executorDraft.variant];
|
||||||
|
|
||||||
|
const newProfile: ExecutorProfileId = {
|
||||||
|
executor: value as BaseCodingAgent,
|
||||||
|
variant: keepCurrentVariant ? executorDraft!.variant : null,
|
||||||
|
};
|
||||||
|
updateExecutorDraft(newProfile);
|
||||||
|
}}
|
||||||
|
disabled={!profiles}
|
||||||
|
>
|
||||||
|
<SelectTrigger id="executor">
|
||||||
|
<SelectValue
|
||||||
|
placeholder={t(
|
||||||
|
'settings.general.taskExecution.executor.placeholder'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{profiles &&
|
||||||
|
Object.entries(profiles)
|
||||||
|
.sort((a, b) => a[0].localeCompare(b[0]))
|
||||||
|
.map(([profileKey]) => (
|
||||||
|
<SelectItem key={profileKey} value={profileKey}>
|
||||||
|
{profileKey}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
{/* Show variant selector if selected profile has variants */}
|
||||||
|
{(() => {
|
||||||
|
const currentProfileVariant = executorDraft;
|
||||||
|
const selectedProfile =
|
||||||
|
profiles?.[currentProfileVariant?.executor || ''];
|
||||||
|
const hasVariants =
|
||||||
|
selectedProfile && Object.keys(selectedProfile).length > 0;
|
||||||
|
|
||||||
|
if (hasVariants) {
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="w-full h-10 px-2 flex items-center justify-between"
|
||||||
|
>
|
||||||
|
<span className="text-sm truncate flex-1 text-left">
|
||||||
|
{currentProfileVariant?.variant ||
|
||||||
|
t('settings.general.taskExecution.defaultLabel')}
|
||||||
|
</span>
|
||||||
|
<ChevronDown className="h-4 w-4 ml-1 flex-shrink-0" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent>
|
||||||
|
{Object.entries(selectedProfile).map(
|
||||||
|
([variantLabel]) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
key={variantLabel}
|
||||||
|
onClick={() => {
|
||||||
|
const newProfile: ExecutorProfileId = {
|
||||||
|
executor: currentProfileVariant!.executor,
|
||||||
|
variant: variantLabel,
|
||||||
|
};
|
||||||
|
updateExecutorDraft(newProfile);
|
||||||
|
}}
|
||||||
|
className={
|
||||||
|
currentProfileVariant?.variant === variantLabel
|
||||||
|
? 'bg-accent'
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{variantLabel}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
);
|
||||||
|
} else if (selectedProfile) {
|
||||||
|
// Show disabled button when profile exists but has no variants
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="w-full h-10 px-2 flex items-center justify-between"
|
||||||
|
disabled
|
||||||
|
>
|
||||||
|
<span className="text-sm truncate flex-1 text-left">
|
||||||
|
{t('settings.general.taskExecution.defaultLabel')}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
<AgentAvailabilityIndicator availability={agentAvailability} />
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{t('settings.general.taskExecution.executor.helper')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button
|
||||||
|
onClick={handleSaveExecutorProfile}
|
||||||
|
disabled={!executorDirty || executorSaving}
|
||||||
|
>
|
||||||
|
{executorSaving && (
|
||||||
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||||
|
)}
|
||||||
|
{t('common:buttons.save')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>{t('settings.agents.title')}</CardTitle>
|
<CardTitle>{t('settings.agents.title')}</CardTitle>
|
||||||
|
|||||||
@@ -16,32 +16,17 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/components/ui/select';
|
} from '@/components/ui/select';
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from '@/components/ui/dropdown-menu';
|
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||||
import { Checkbox } from '@/components/ui/checkbox';
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
import { ChevronDown, Loader2, Volume2 } from 'lucide-react';
|
import { Loader2, Volume2 } from 'lucide-react';
|
||||||
import {
|
import { EditorType, SoundFile, ThemeMode, UiLanguage } from 'shared/types';
|
||||||
BaseCodingAgent,
|
|
||||||
EditorType,
|
|
||||||
ExecutorProfileId,
|
|
||||||
SoundFile,
|
|
||||||
ThemeMode,
|
|
||||||
UiLanguage,
|
|
||||||
} from 'shared/types';
|
|
||||||
import { getLanguageOptions } from '@/i18n/languages';
|
import { getLanguageOptions } from '@/i18n/languages';
|
||||||
|
|
||||||
import { toPrettyCase } from '@/utils/string';
|
import { toPrettyCase } from '@/utils/string';
|
||||||
import { useEditorAvailability } from '@/hooks/useEditorAvailability';
|
import { useEditorAvailability } from '@/hooks/useEditorAvailability';
|
||||||
import { EditorAvailabilityIndicator } from '@/components/EditorAvailabilityIndicator';
|
import { EditorAvailabilityIndicator } from '@/components/EditorAvailabilityIndicator';
|
||||||
import { useAgentAvailability } from '@/hooks/useAgentAvailability';
|
|
||||||
import { AgentAvailabilityIndicator } from '@/components/AgentAvailabilityIndicator';
|
|
||||||
import { useTheme } from '@/components/ThemeProvider';
|
import { useTheme } from '@/components/ThemeProvider';
|
||||||
import { useUserSystem } from '@/components/ConfigProvider';
|
import { useUserSystem } from '@/components/ConfigProvider';
|
||||||
import { TagManager } from '@/components/TagManager';
|
import { TagManager } from '@/components/TagManager';
|
||||||
@@ -60,7 +45,6 @@ export function GeneralSettings() {
|
|||||||
config,
|
config,
|
||||||
loading,
|
loading,
|
||||||
updateAndSaveConfig, // Use this on Save
|
updateAndSaveConfig, // Use this on Save
|
||||||
profiles,
|
|
||||||
} = useUserSystem();
|
} = useUserSystem();
|
||||||
|
|
||||||
// Draft state management
|
// Draft state management
|
||||||
@@ -77,11 +61,6 @@ export function GeneralSettings() {
|
|||||||
// Check editor availability when draft editor changes
|
// Check editor availability when draft editor changes
|
||||||
const editorAvailability = useEditorAvailability(draft?.editor.editor_type);
|
const editorAvailability = useEditorAvailability(draft?.editor.editor_type);
|
||||||
|
|
||||||
// Check agent availability when draft executor changes
|
|
||||||
const agentAvailability = useAgentAvailability(
|
|
||||||
draft?.executor_profile?.executor
|
|
||||||
);
|
|
||||||
|
|
||||||
const validateBranchPrefix = useCallback(
|
const validateBranchPrefix = useCallback(
|
||||||
(prefix: string): string | null => {
|
(prefix: string): string | null => {
|
||||||
if (!prefix) return null; // empty allowed
|
if (!prefix) return null; // empty allowed
|
||||||
@@ -299,134 +278,6 @@ export function GeneralSettings() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>{t('settings.general.taskExecution.title')}</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
{t('settings.general.taskExecution.description')}
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="executor">
|
|
||||||
{t('settings.general.taskExecution.executor.label')}
|
|
||||||
</Label>
|
|
||||||
<div className="grid grid-cols-2 gap-2">
|
|
||||||
<Select
|
|
||||||
value={draft?.executor_profile?.executor ?? ''}
|
|
||||||
onValueChange={(value: string) => {
|
|
||||||
const variants = profiles?.[value];
|
|
||||||
const keepCurrentVariant =
|
|
||||||
variants &&
|
|
||||||
draft?.executor_profile?.variant &&
|
|
||||||
variants[draft.executor_profile.variant];
|
|
||||||
|
|
||||||
const newProfile: ExecutorProfileId = {
|
|
||||||
executor: value as BaseCodingAgent,
|
|
||||||
variant: keepCurrentVariant
|
|
||||||
? draft!.executor_profile!.variant
|
|
||||||
: null,
|
|
||||||
};
|
|
||||||
updateDraft({
|
|
||||||
executor_profile: newProfile,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
disabled={!profiles}
|
|
||||||
>
|
|
||||||
<SelectTrigger id="executor">
|
|
||||||
<SelectValue
|
|
||||||
placeholder={t(
|
|
||||||
'settings.general.taskExecution.executor.placeholder'
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{profiles &&
|
|
||||||
Object.entries(profiles)
|
|
||||||
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
||||||
.map(([profileKey]) => (
|
|
||||||
<SelectItem key={profileKey} value={profileKey}>
|
|
||||||
{profileKey}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
|
|
||||||
{/* Show variant selector if selected profile has variants */}
|
|
||||||
{(() => {
|
|
||||||
const currentProfileVariant = draft?.executor_profile;
|
|
||||||
const selectedProfile =
|
|
||||||
profiles?.[currentProfileVariant?.executor || ''];
|
|
||||||
const hasVariants =
|
|
||||||
selectedProfile && Object.keys(selectedProfile).length > 0;
|
|
||||||
|
|
||||||
if (hasVariants) {
|
|
||||||
return (
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
className="w-full h-10 px-2 flex items-center justify-between"
|
|
||||||
>
|
|
||||||
<span className="text-sm truncate flex-1 text-left">
|
|
||||||
{currentProfileVariant?.variant ||
|
|
||||||
t('settings.general.taskExecution.defaultLabel')}
|
|
||||||
</span>
|
|
||||||
<ChevronDown className="h-4 w-4 ml-1 flex-shrink-0" />
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent>
|
|
||||||
{Object.entries(selectedProfile).map(
|
|
||||||
([variantLabel]) => (
|
|
||||||
<DropdownMenuItem
|
|
||||||
key={variantLabel}
|
|
||||||
onClick={() => {
|
|
||||||
const newProfile: ExecutorProfileId = {
|
|
||||||
executor: currentProfileVariant!.executor,
|
|
||||||
variant: variantLabel,
|
|
||||||
};
|
|
||||||
updateDraft({
|
|
||||||
executor_profile: newProfile,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
className={
|
|
||||||
currentProfileVariant?.variant === variantLabel
|
|
||||||
? 'bg-accent'
|
|
||||||
: ''
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{variantLabel}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
);
|
|
||||||
} else if (selectedProfile) {
|
|
||||||
// Show disabled button when profile exists but has no variants
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
className="w-full h-10 px-2 flex items-center justify-between"
|
|
||||||
disabled
|
|
||||||
>
|
|
||||||
<span className="text-sm truncate flex-1 text-left">
|
|
||||||
{t('settings.general.taskExecution.defaultLabel')}
|
|
||||||
</span>
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
})()}
|
|
||||||
</div>
|
|
||||||
<AgentAvailabilityIndicator availability={agentAvailability} />
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
{t('settings.general.taskExecution.executor.helper')}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>{t('settings.general.editor.title')}</CardTitle>
|
<CardTitle>{t('settings.general.editor.title')}</CardTitle>
|
||||||
|
|||||||
Reference in New Issue
Block a user