diff --git a/src/workspaces/officerdev/src/apps/ChatHistory/DirPickerModal.tsx b/src/workspaces/officerdev/src/apps/ChatHistory/DirPickerModal.tsx new file mode 100644 index 00000000..c8afd6af --- /dev/null +++ b/src/workspaces/officerdev/src/apps/ChatHistory/DirPickerModal.tsx @@ -0,0 +1,136 @@ +import { useState } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { Folder, ChevronRight, Loader2, FolderPlus, Check } from 'lucide-react'; +import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { useFilesAPI, type DirEntry } from '../../hooks/useFilesAPI'; + +type DirPickerModalProps = { + open: boolean; + onClose: () => void; + onSelect: (absPath: string) => void; +}; + +// A simplified file-browser modal for picking a working directory (returns an absolute path). +// Navigates within the home root; dirs elsewhere are reachable via the selector's free-text field. +export const DirPickerModal = ({ open, onClose, onSelect }: DirPickerModalProps) => { + const api = useFilesAPI('home'); + const [path, setPath] = useState('/'); // root-relative, always starts with '/' + const [creating, setCreating] = useState(false); + const [newName, setNewName] = useState(''); + + const { data, isLoading, refetch } = useQuery({ + queryKey: ['dir-picker', path], + queryFn: () => api.listDir(path), + enabled: open, + }); + + const rootDir = data?.rootDir ?? ''; + const dirs = ((data?.entries ?? []) as DirEntry[]) + .filter((e) => e.type === 'directory') + .sort((a, b) => a.name.localeCompare(b.name)); + const absCurrent = path === '/' ? rootDir : `${rootDir}${path}`; + + const segments = path.split('/').filter(Boolean); + const goto = (idx: number) => setPath(idx < 0 ? '/' : `/${segments.slice(0, idx + 1).join('/')}`); + const enter = (name: string) => setPath(path === '/' ? `/${name}` : `${path}/${name}`); + + const createFolder = async () => { + const n = newName.trim(); + if (!n) return; + await api.createDir(path === '/' ? `/${n}` : `${path}/${n}`); + setNewName(''); + setCreating(false); + refetch(); + }; + + return ( + !o && onClose()}> + + + Choose a working directory + + + {/* Breadcrumb */} +
+ + {segments.map((s, i) => ( + + + + + ))} +
+ + {/* Directory list */} +
+ {isLoading ? ( +
+ +
+ ) : dirs.length === 0 ? ( +
No subfolders here
+ ) : ( + dirs.map((d) => ( + + )) + )} +
+ + {/* Footer */} +
+ {creating ? ( +
+ setNewName(ev.target.value)} + onKeyDown={(ev) => { + if (ev.key === 'Enter') createFolder(); + if (ev.key === 'Escape') setCreating(false); + }} + placeholder="New folder name" + className="min-w-0 flex-1 rounded border border-duck-dark/15 dark:border-foreground/15 bg-transparent px-2 py-1 text-xs outline-none" + /> + +
+ ) : ( + + )} +
+ + {absCurrent} + + + +
+
+
+
+ ); +}; diff --git a/src/workspaces/officerdev/src/apps/ChatHistory/PwdSelector.tsx b/src/workspaces/officerdev/src/apps/ChatHistory/PwdSelector.tsx index 3a8e29fc..6f99af7f 100644 --- a/src/workspaces/officerdev/src/apps/ChatHistory/PwdSelector.tsx +++ b/src/workspaces/officerdev/src/apps/ChatHistory/PwdSelector.tsx @@ -1,6 +1,7 @@ import { useState } from 'react'; -import { FolderOpen, ChevronDown, Check, CornerDownLeft } from 'lucide-react'; +import { FolderOpen, ChevronDown, Check, CornerDownLeft, FolderSearch } from 'lucide-react'; import { useChatPwds } from 'state/useClaudeSessions'; +import { DirPickerModal } from './DirPickerModal'; type PwdSelectorProps = { value: string | null; // null = the default claude_sessions dir @@ -14,6 +15,7 @@ const basename = (cwd: string) => cwd.split('/').filter(Boolean).pop() ?? cwd; export const PwdSelector = ({ value, onChange }: PwdSelectorProps) => { const { pwds, defaultCwd } = useChatPwds(); const [open, setOpen] = useState(false); + const [browse, setBrowse] = useState(false); const [custom, setCustom] = useState(''); const isDefaultActive = value === null || value === defaultCwd; @@ -62,6 +64,15 @@ export const PwdSelector = ({ value, onChange }: PwdSelectorProps) => { ); })} +
{
)} + + setBrowse(false)} onSelect={(p) => pick(p)} /> ); };