From a48887da61fb0b67b4584c2013880ba8bde42e8f Mon Sep 17 00:00:00 2001 From: arkml <6592213+arkml@users.noreply.github.com> Date: Wed, 6 May 2026 23:14:00 +0530 Subject: [PATCH] can set a work directory in assistant chats (#534) --- apps/x/apps/main/src/ipc.ts | 13 +++ .../components/chat-input-with-mentions.tsx | 109 ++++++++++++++++-- apps/x/packages/core/src/agents/runtime.ts | 35 ++++++ apps/x/packages/shared/src/ipc.ts | 10 ++ 4 files changed, 159 insertions(+), 8 deletions(-) diff --git a/apps/x/apps/main/src/ipc.ts b/apps/x/apps/main/src/ipc.ts index 682d46e6..64ff7f2d 100644 --- a/apps/x/apps/main/src/ipc.ts +++ b/apps/x/apps/main/src/ipc.ts @@ -685,6 +685,19 @@ export function setupIpcHandlers() { const mimeType = mimeMap[ext] || 'application/octet-stream'; return { data: buffer.toString('base64'), mimeType, size: stat.size }; }, + 'dialog:openDirectory': async (event, args) => { + const win = BrowserWindow.fromWebContents(event.sender); + const defaultPath = args.defaultPath ? resolveShellPath(args.defaultPath) : os.homedir(); + const result = await dialog.showOpenDialog(win!, { + title: args.title ?? 'Choose work directory', + defaultPath, + properties: ['openDirectory', 'createDirectory'], + }); + if (result.canceled || result.filePaths.length === 0) { + return { path: null }; + } + return { path: result.filePaths[0] ?? null }; + }, // Knowledge version history handlers 'knowledge:history': async (_event, args) => { const commits = await versionHistory.getFileHistory(args.path); diff --git a/apps/x/apps/renderer/src/components/chat-input-with-mentions.tsx b/apps/x/apps/renderer/src/components/chat-input-with-mentions.tsx index e1fb950f..9d552905 100644 --- a/apps/x/apps/renderer/src/components/chat-input-with-mentions.tsx +++ b/apps/x/apps/renderer/src/components/chat-input-with-mentions.tsx @@ -10,8 +10,10 @@ import { FileSpreadsheet, FileText, FileVideo, + FolderCog, Globe, Headphones, + ImagePlus, LoaderIcon, Mic, Plus, @@ -23,8 +25,10 @@ import { Button } from '@/components/ui/button' import { DropdownMenu, DropdownMenuContent, + DropdownMenuItem, DropdownMenuRadioGroup, DropdownMenuRadioItem, + DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' import { @@ -169,6 +173,7 @@ function ChatInputInner({ const [searchEnabled, setSearchEnabled] = useState(false) const [searchAvailable, setSearchAvailable] = useState(false) const [isRowboatConnected, setIsRowboatConnected] = useState(false) + const [workDir, setWorkDir] = useState(null) // When a run exists, freeze the dropdown to the run's resolved model+provider. useEffect(() => { @@ -251,6 +256,55 @@ function ChatInputInner({ return () => window.removeEventListener('models-config-changed', handler) }, [loadModelConfig]) + // Load currently configured work directory + const loadWorkDir = useCallback(async () => { + try { + const result = await window.ipc.invoke('workspace:readFile', { path: 'config/workdir.json' }) + const parsed = JSON.parse(result.data) + const value = typeof parsed?.path === 'string' ? parsed.path.trim() : '' + setWorkDir(value || null) + } catch { + setWorkDir(null) + } + }, []) + + useEffect(() => { + loadWorkDir() + }, [isActive, loadWorkDir]) + + const handleSetWorkDir = useCallback(async () => { + try { + const { path: chosen } = await window.ipc.invoke('dialog:openDirectory', { + title: 'Choose work directory', + defaultPath: workDir ?? undefined, + }) + if (!chosen) return + await window.ipc.invoke('workspace:writeFile', { + path: 'config/workdir.json', + data: JSON.stringify({ path: chosen }, null, 2), + }) + setWorkDir(chosen) + toast.success(`Work directory set: ${chosen}`) + } catch (err) { + console.error('Failed to set work directory', err) + toast.error('Failed to set work directory') + } + }, [workDir]) + + const handleClearWorkDir = useCallback(async () => { + try { + await window.ipc.invoke('workspace:writeFile', { + path: 'config/workdir.json', + data: JSON.stringify({}, null, 2), + }) + setWorkDir(null) + toast.success('Work directory cleared') + } catch (err) { + console.error('Failed to clear work directory', err) + toast.error('Failed to clear work directory') + } + }, []) + // Check search tool availability (exa or signed-in via gateway) useEffect(() => { const checkSearch = async () => { @@ -484,14 +538,53 @@ function ChatInputInner({ />
- + + + + + + fileInputRef.current?.click()}> + + Add files or photos + + { void handleSetWorkDir() }}> + + {workDir ? 'Change work directory' : 'Set work directory'} + + {workDir && ( + <> + + { void handleClearWorkDir() }}> + + Clear work directory + + + )} + + + {workDir && ( + + + + + + Work directory: {workDir} + + + )} {searchAvailable && ( searchEnabled ? (