feat: add directory picker for new session dialog

- Add Browse button to open native directory picker
- Add selectDirectory IPC handler using dialog.showOpenDialog
- User can still type path manually or use system picker

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jiayuan
2026-01-14 03:35:43 +08:00
parent fcf81bd712
commit ff83cc99ab
5 changed files with 47 additions and 13 deletions

View File

@@ -2,7 +2,7 @@
* IPC handlers for main process
* Registers all IPC handlers for communication with renderer process
*/
import { ipcMain } from 'electron'
import { ipcMain, dialog } from 'electron'
import { IPC_CHANNELS } from '../../shared/ipc-channels'
import { DEFAULT_AGENTS } from '../config/defaults'
import type { Conductor } from '../conductor/Conductor'
@@ -107,5 +107,20 @@ export function registerIPCHandlers(conductor: Conductor): void {
return config
})
// --- Dialog handlers ---
ipcMain.handle(IPC_CHANNELS.DIALOG_SELECT_DIRECTORY, async () => {
const result = await dialog.showOpenDialog({
properties: ['openDirectory', 'createDirectory'],
title: 'Select Working Directory',
})
if (result.canceled || result.filePaths.length === 0) {
return null
}
return result.filePaths[0]
})
console.log('[IPC] All handlers registered')
}

View File

@@ -41,6 +41,9 @@ const electronAPI: ElectronAPI = {
updateConfig: (config) => ipcRenderer.invoke(IPC_CHANNELS.CONFIG_UPDATE, config),
// Dialog
selectDirectory: () => ipcRenderer.invoke(IPC_CHANNELS.DIALOG_SELECT_DIRECTORY),
// Event listeners
onAgentMessage: (callback) => {
const listener = (_event: Electron.IpcRendererEvent, message: unknown) =>

View File

@@ -130,18 +130,28 @@ function App(): React.JSX.Element {
<label className="mb-2 block text-sm text-[var(--color-text-muted)]">
Working Directory
</label>
<input
type="text"
value={newSessionCwd}
onChange={(e) => setNewSessionCwd(e.target.value)}
placeholder="/path/to/project"
className="mb-4 w-full rounded-lg border border-[var(--color-border)] bg-[var(--color-background)] px-4 py-2 text-[var(--color-text)] outline-none placeholder:text-[var(--color-text-muted)] focus:border-[var(--color-primary)]"
autoFocus
onKeyDown={(e) => {
if (e.key === 'Enter') handleCreateSession()
if (e.key === 'Escape') setShowNewSession(false)
}}
/>
<div className="mb-4 flex gap-2">
<input
type="text"
value={newSessionCwd}
onChange={(e) => setNewSessionCwd(e.target.value)}
placeholder="Select a directory..."
className="flex-1 rounded-lg border border-[var(--color-border)] bg-[var(--color-background)] px-4 py-2 text-[var(--color-text)] outline-none placeholder:text-[var(--color-text-muted)] focus:border-[var(--color-primary)]"
onKeyDown={(e) => {
if (e.key === 'Enter') handleCreateSession()
if (e.key === 'Escape') setShowNewSession(false)
}}
/>
<button
onClick={async () => {
const dir = await window.electronAPI.selectDirectory()
if (dir) setNewSessionCwd(dir)
}}
className="flex-shrink-0 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-hover)] px-4 py-2 text-[var(--color-text)] transition-colors hover:bg-[var(--color-border)]"
>
Browse...
</button>
</div>
<div className="flex justify-end gap-2">
<button

View File

@@ -38,6 +38,9 @@ export interface ElectronAPI {
getConfig(): Promise<AppConfig>
updateConfig(config: Partial<AppConfig>): Promise<AppConfig>
// Dialog
selectDirectory(): Promise<string | null>
// Event listeners (return unsubscribe function)
onAgentMessage(callback: (message: AgentMessage) => void): () => void
onAgentStatus(callback: (status: AgentStatus) => void): () => void

View File

@@ -24,6 +24,9 @@ export const IPC_CHANNELS = {
CONFIG_GET: 'config:get',
CONFIG_UPDATE: 'config:update',
// Dialog
DIALOG_SELECT_DIRECTORY: 'dialog:select-directory',
// File system (V2)
FILE_APPROVAL_REQUEST: 'file:approval-request',
FILE_APPROVAL_RESPONSE: 'file:approval-response',