import { useEffect, useState } from 'react'; import { Archive, Maximize2, Minimize2 } from 'lucide-react'; import { toast } from 'sonner'; import { api } from '@/api/client'; import type { Project, Session } from '@/api/types'; import { Button } from '@/components/ui/button'; import { Textarea } from '@/components/ui/textarea'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { ModelPicker } from '@/components/ModelPicker'; import { ThemePicker } from '@/components/ThemePicker'; import { cn } from '@/lib/utils'; type Section = 'session' | 'project' | 'theme'; interface Props { session: Session; project: Project; maximized: boolean; onToggleMaximize: () => void; isMobile: boolean; } // v1.9: hand-rolled Switch primitive. No shadcn switch in the existing // ui/ set and the dispatch said don't pnpm dlx for v1.9 either. Single // purpose — clicking flips aria-checked + calls onCheckedChange. function Switch({ checked, onCheckedChange, disabled, id, }: { checked: boolean; onCheckedChange: (v: boolean) => void; disabled?: boolean; id?: string; }) { return ( ); } export function SettingsPane({ session, project, maximized, onToggleMaximize, isMobile }: Props) { const [activeSection, setActiveSection] = useState
('session'); return (
{(['session', 'project', 'theme'] as const).map((s) => ( ))}
{!isMobile && ( )}
{activeSection === 'session' && } {activeSection === 'project' && } {activeSection === 'theme' && }
); } function SessionSection({ session, project }: { session: Session; project: Project }) { const [name, setName] = useState(session.name); const [systemPrompt, setSystemPrompt] = useState(session.system_prompt); // v1.9: tri-state on the wire (null = inherit). UI surfaces a 3-way toggle // via "Inherit project default" checkbox plus the override switch. const [webSearch, setWebSearch] = useState(session.web_search_enabled); const [saving, setSaving] = useState(false); // v1.9: bulk-archive chats. Two-step: openChatsCount → confirm dialog → // archiveAllChats. Server publishes one chat_archived frame per id so // useSidebar / chat lists update incrementally. const [archiveOpen, setArchiveOpen] = useState(false); const [archiveCount, setArchiveCount] = useState(0); const [archiving, setArchiving] = useState(false); useEffect(() => { setName(session.name); setSystemPrompt(session.system_prompt); setWebSearch(session.web_search_enabled); }, [session.id, session.name, session.system_prompt, session.web_search_enabled]); const dirty = name !== session.name || systemPrompt !== session.system_prompt || webSearch !== session.web_search_enabled; const effectiveWebSearch = webSearch ?? project.default_web_search_enabled; const projectPreview = project.default_system_prompt.trim().slice(0, 200); async function save() { if (saving) return; setSaving(true); try { await api.sessions.update(session.id, { name: name.trim() || session.name, system_prompt: systemPrompt, web_search_enabled: webSearch, }); toast.success('Session saved'); } catch (err) { toast.error(err instanceof Error ? err.message : 'save failed'); } finally { setSaving(false); } } async function resetSystemPrompt() { if (saving) return; setSaving(true); try { await api.sessions.update(session.id, { system_prompt: '' }); toast.success('Reset to project default'); } catch (err) { toast.error(err instanceof Error ? err.message : 'reset failed'); } finally { setSaving(false); } } async function openArchiveDialog() { if (archiving) return; try { const { count } = await api.sessions.openChatsCount(session.id); if (count === 0) { toast('No open chats to archive.'); return; } setArchiveCount(count); setArchiveOpen(true); } catch (err) { toast.error(err instanceof Error ? err.message : 'failed to count chats'); } } async function confirmArchive() { if (archiving) return; setArchiving(true); try { const { archived } = await api.sessions.archiveAllChats(session.id); toast.success(`Archived ${archived} chat${archived === 1 ? '' : 's'}`); setArchiveOpen(false); } catch (err) { toast.error(err instanceof Error ? err.message : 'archive failed'); } finally { setArchiving(false); } } return (
setName(e.target.value)} className="w-full bg-background border border-border rounded px-2 py-1.5 text-sm outline-none focus:border-ring" />
{ try { await api.sessions.update(session.id, { model }); toast.success('Model updated'); } catch (err) { toast.error(err instanceof Error ? err.message : 'failed to set model'); } }} />
setWebSearch(v)} />
setWebSearch(e.target.checked ? null : project.default_web_search_enabled)} />

Plumbed for Batch 8 (web_search tool). No effect yet.