import { useState, useEffect, useMemo } from 'react';
import { toast } from 'sonner';
import { Terminal, Eye, Trash2 } from 'lucide-react';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Button } from '@/components/ui/button';
import { Textarea } from '@/components/ui/textarea';
import { Slider } from '@/components/ui/slider';
import { Switch } from '@/components/ui/switch';
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '@/components/ui/accordion';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Card } from '@/components/Card';
import { useSettings } from '@/state/useSettings';
import { useUserState } from '@/state/useUserState';
import {
useClaudeModels,
useOpenCodeModels,
useVisibleClaudeModels,
useVisibleOpenCodeModels,
} from '@/state/useModels';
import type { UserSettings } from '@/state/types/user-settings';
export const AISettings = () => {
const [mainTab, setMainTab] = useUserState('ai-settings-tab', 'chat-defaults');
return (
Chat Defaults
Model Visibility
);
};
const ChatDefaultsSection = () => {
const { settings, saveSettings } = useSettings();
const claudeModels = useVisibleClaudeModels();
const openCodeModels = useVisibleOpenCodeModels();
const [isSaving, setIsSaving] = useState(false);
const [model, setModel] = useState(settings.chat.defaultModel);
const [systemPrompt, setSystemPrompt] = useState(settings.chat.systemPrompt);
const [temperature, setTemperature] = useState(settings.chat.temperature);
const [defaultPwd, setDefaultPwd] = useState(settings.chat.defaultPwd);
useEffect(() => {
setModel(settings.chat.defaultModel);
setSystemPrompt(settings.chat.systemPrompt);
setTemperature(settings.chat.temperature);
setDefaultPwd(settings.chat.defaultPwd);
}, [settings]);
const allModels = useMemo(
() => [
...claudeModels.map((m) => ({ ...m, provider: 'Claude' })),
...openCodeModels.map((m) => ({ ...m, provider: m.provider ?? 'OpenCode' })),
],
[claudeModels, openCodeModels],
);
const handleSave = async () => {
if (isSaving) return;
setIsSaving(true);
try {
const isOpenCode = openCodeModels.some((m) => m.id === model);
const defaultProvider = isOpenCode ? ('opencode' as const) : ('claude' as const);
const updated: UserSettings = {
...settings,
chat: { defaultProvider, defaultModel: model, systemPrompt, temperature, defaultPwd },
};
await saveSettings(updated);
toast.success('Chat defaults saved');
} catch {
toast.error('Failed to save settings');
} finally {
setIsSaving(false);
}
};
return (
);
};
const ModelVisibilitySection = () => {
const { settings, saveSettings } = useSettings();
const claudeModels = useClaudeModels();
const openCodeModels = useOpenCodeModels();
const [subTab, setSubTab] = useUserState('ai-settings-visibility-tab', 'opencode');
const enabledModels = settings.ai?.enabledModels ?? [];
const enabledProviders = settings.ai?.enabledProviders ?? [];
const toggleModel = async (key: string) => {
const isEnabled = enabledModels.includes(key);
const newEnabled = isEnabled ? enabledModels.filter((id) => id !== key) : [...enabledModels, key];
await saveSettings({ ...settings, ai: { ...settings.ai, enabledModels: newEnabled } });
};
const ocGroups = useMemo(() => {
const groups: Record = {};
for (const m of openCodeModels) {
const provider = m.provider ?? 'Other';
if (!groups[provider]) groups[provider] = [];
groups[provider].push({ id: m.id, name: m.name });
}
return Object.entries(groups)
.sort(([a], [b]) => a.localeCompare(b))
.map(([provider, models]) => ({ provider, models: models.sort((a, b) => a.name.localeCompare(b.name)) }));
}, [openCodeModels]);
const [addingProvider, setAddingProvider] = useState(false);
const [selectedNewProvider, setSelectedNewProvider] = useState('');
const disabledProviders = ocGroups.filter((g) => !enabledProviders.includes(g.provider));
const handleEnableProvider = async () => {
if (!selectedNewProvider) return;
await saveSettings({
...settings,
ai: { ...settings.ai, enabledProviders: [...enabledProviders, selectedNewProvider] },
});
setAddingProvider(false);
setSelectedNewProvider('');
};
const handleRemoveProvider = async (provider: string) => {
await saveSettings({
...settings,
ai: { ...settings.ai, enabledProviders: enabledProviders.filter((p) => p !== provider) },
});
};
return (
OpenCode
Claude
{claudeModels.map((m) => (
))}
{addingProvider ? (
<>
>
) : (
)}
{ocGroups.length === 0 ? (
No OpenCode models available.
) : (
)}
);
};
type ProviderGroup = { provider: string; models: { id: string; name: string }[] };
type OpenCodeProviderListProps = {
groups: ProviderGroup[];
enabledProviders: string[];
enabledModels: string[];
onToggleModel: (key: string) => void;
onRemoveProvider: (provider: string) => void;
};
const OpenCodeProviderList = ({
groups,
enabledProviders,
enabledModels,
onToggleModel,
onRemoveProvider,
}: OpenCodeProviderListProps) => {
const [openProvider, setOpenProvider] = useUserState('ai-settings-oc-accordion', '');
const enabled = groups.filter((g) => enabledProviders.includes(g.provider));
if (enabled.length === 0) {
return No providers enabled.
;
}
return (
setOpenProvider(v ?? '')}>
{enabled.map((g) => (
{g.provider}
{g.models.length}
{[...g.models]
.sort((a, b) => {
const aEnabled = enabledModels.includes(`${g.provider}:${a.id}`);
const bEnabled = enabledModels.includes(`${g.provider}:${b.id}`);
if (aEnabled !== bEnabled) return aEnabled ? -1 : 1;
return a.name.localeCompare(b.name);
})
.map((m) => {
const key = `${g.provider}:${m.id}`;
return (
);
})}
))}
);
};