import { useState, useEffect, useMemo, useCallback, type DragEvent } from 'react'; import { toast } from 'sonner'; import { Terminal, Eye, Bot, Settings, X, Plus } 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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { useQueryClient } from '@tanstack/react-query'; import type { LayoutNode, PanelComponents } from '@/components/Workspace'; import { WorkspaceLayout } from '@/components/Workspace'; import { usePanelChannel } from 'hooks/usePanelChannel'; import { TerminalView } from 'apps/Terminal'; import { appRegistry } from '../Workspaces/app-registry'; import { createSettingsPanelComponents, type SettingsSectionGroup } from './SettingsPanel'; import { useSettings } from '@/state/useSettings'; import { useUserState } from '@/state/useUserState'; import { usePiMonoModels, useVisiblePiMonoModels } from '@/state/useModels'; import type { UserSettings } from '@/state/types/user-settings'; import { AIHarnessesSection } from './ServerSettings/AIHarnessesSection'; import { RUN_COMMAND_CHANNEL, type RunCommandState } from './ServerSettings/run-command-channel'; const groups: SettingsSectionGroup[] = [ { label: 'AI', icon: Bot, sections: [ { key: 'ai-harnesses', icon: Terminal, title: 'Providers', description: 'Remote and local AI providers', content: }, { key: 'model-visibility', icon: Eye, title: 'Models', description: 'Enable or disable models', content: }, { key: 'chat-defaults', icon: Terminal, title: 'Chat Defaults', description: 'Model, prompt, and temperature', content: }, ], }, ]; const { Sidebar, Content } = createSettingsPanelComponents({ globalKey: 'SYSTEM_SETTINGS_SELECTED', sidebarIcon: Settings, sidebarLabel: 'System', groups, }); const baseLayout: LayoutNode = { type: 'group', id: 'system-root', direction: 'horizontal', children: [ { node: { type: 'panel', id: 'system-left', appType: null }, size: 20 }, { node: { type: 'panel', id: 'system-right', appType: null }, size: 80 }, ], }; const splitLayout: LayoutNode = { type: 'group', id: 'system-root', direction: 'horizontal', children: [ { node: { type: 'panel', id: 'system-left', appType: null }, size: 20 }, { node: { type: 'group', id: 'system-right-group', direction: 'vertical', children: [ { node: { type: 'panel', id: 'system-right', appType: null }, size: 50 }, { node: { type: 'panel', id: 'system-terminal', appType: null }, size: 50 }, ], }, size: 80, }, ], }; const SystemTerminalPanel = () => { const queryClient = useQueryClient(); const [state, setState] = usePanelChannel(RUN_COMMAND_CHANNEL, null); const [session, setSession] = useState<{ id: string; command: string } | null>(null); useEffect(() => { if (state && (!session || session.command !== state.command)) { setSession({ id: `run-cmd-${Date.now()}`, command: state.command }); } else if (!state) { setSession(null); } }, [state]); const close = () => setState(null); const onCommandDone = (exitCode: number, output: string) => { if (state) { for (const key of state.refetchKeys) { queryClient.invalidateQueries({ queryKey: [key] }); } } if (exitCode === 0) { toast.success('Command completed successfully'); } else { toast.error(output || `Command failed with exit code ${exitCode}`, { duration: 8000 }); } setState(null); }; if (!state || !session) return null; return (
Run Command
); }; export const SystemSettings = () => { const [runCommand] = usePanelChannel(RUN_COMMAND_CHANNEL, null); const layout = useMemo(() => (runCommand ? splitLayout : baseLayout), [runCommand]); const panelComponents: PanelComponents = useMemo( () => ({ 'system-left': Sidebar, 'system-right': Content, 'system-terminal': SystemTerminalPanel, }), [], ); return (
{}} registry={appRegistry} components={panelComponents} />
); }; // --- AI sections --- function ChatDefaultsSection() { const { settings, saveSettings } = useSettings(); const piMonoModels = useVisiblePiMonoModels(); 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 handleSave = async () => { if (isSaving) return; setIsSaving(true); try { const updated: UserSettings = { ...settings, chat: { defaultProvider: 'pi-mono', defaultModel: model, systemPrompt, temperature, defaultPwd }, }; await saveSettings(updated); toast.success('Chat defaults saved'); } catch { toast.error('Failed to save settings'); } finally { setIsSaving(false); } }; return (