'use client' import { ChevronDown, FolderOpen, GitBranch, Pencil } from 'lucide-react' import { type FC, useEffect, useMemo, useRef, useState } from 'react' import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu' import { Input } from '@/components/ui/input' import { cn } from '@/lib/utils' export type SessionInspectorProps = { open: boolean cwd: string branch: string busy: boolean modelLabel: string modelTitle?: string providerName?: string personality: string personalities: string[] onChangeCwd?: (cwd: string) => void onBrowseCwd?: () => void onOpenModelPicker?: () => void onSelectPersonality?: (name: string) => void } export const SESSION_INSPECTOR_WIDTH = '14rem' // Quiet button-like row: invisible until hovered/focused. const quietControl = 'rounded-md border border-transparent bg-transparent transition-[background-color,border-color,color,box-shadow] hover:border-input hover:bg-background hover:text-foreground focus-visible:border-ring focus-visible:bg-background focus-visible:outline-none focus-visible:ring-[0.1875rem] focus-visible:ring-ring/30' // Bleed interactive rows leftwards by 6px so the hover ring doesn't look // indented relative to the section labels above them. const bleed = '-ml-1.5 w-[calc(100%_+_0.375rem)]' const disabledRow = 'disabled:cursor-default disabled:hover:border-transparent disabled:hover:bg-transparent' export const SessionInspector: FC = ({ open, cwd, branch, busy, modelLabel, modelTitle, providerName, personality, personalities, onChangeCwd, onBrowseCwd, onOpenModelPicker, onSelectPersonality }) => ( ) function SectionLabel({ children }: { children: React.ReactNode }) { return
{children}
} function WorkspaceSection({ cwd, branch, busy, onChangeCwd, onBrowseCwd }: { cwd: string branch: string busy: boolean onChangeCwd?: (cwd: string) => void onBrowseCwd?: () => void }) { const [editing, setEditing] = useState(false) const [draft, setDraft] = useState(cwd) const inputRef = useRef(null) const canChange = Boolean(onChangeCwd) && !busy const beginEdit = () => canChange && setEditing(true) useEffect(() => { if (!editing) { setDraft(cwd) } }, [cwd, editing]) useEffect(() => { if (editing) { inputRef.current?.focus() } }, [editing]) const apply = () => { const next = draft.trim() if (next && next !== cwd) { onChangeCwd?.(next) } setEditing(false) } const branchLabel = branch.trim() return (
cwd {editing ? ( setDraft(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault() apply() } else if (e.key === 'Escape') { e.preventDefault() setEditing(false) } }} placeholder="/path/to/project" ref={inputRef} value={draft} /> ) : (
{canChange && ( )}
)} {branchLabel && (
{branchLabel}
)}
) } function personalityOptionKey(value?: string): string { const key = value?.trim().toLowerCase() || 'none' return key === 'default' ? 'none' : key } function AgentSection({ label: modelLabel, onOpen, providerName, current, options, onSelect }: { label: string title?: string providerName?: string onOpen?: () => void current: string options: string[] onSelect?: (name: string) => void }) { const [open, setOpen] = useState(false) const merged = useMemo( () => [...new Set(['none', ...options, current].map(personalityOptionKey).filter(Boolean))], [current, options] ) const activeKey = personalityOptionKey(current) const personalityLabel = activeKey === 'none' ? 'None' : titleize(activeKey) return (
Agent Personality {merged.map(name => ( { e.preventDefault() onSelect?.(name) setOpen(false) }} > {titleize(name)} ))}
) } function compactPath(path: string): string { if (!path) { return '' } const normalized = path.replace(/\\/g, '/').replace(/\/+$/, '') const parts = normalized.split('/').filter(Boolean) return parts.length <= 4 ? normalized || path : `.../${parts.slice(-3).join('/')}` } function titleize(value: string): string { return value .replace(/[-_]+/g, ' ') .replace(/\s+/g, ' ') .trim() .replace(/(^|\s)\S/g, m => m.toUpperCase()) }