Skip to content

Commit f8cec2c

Browse files
committed
merge: PR daggerhashimoto#233 slice/workspace-file-add-to-chat
2 parents f33ec89 + 0215573 commit f8cec2c

4 files changed

Lines changed: 70 additions & 3 deletions

File tree

src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -916,6 +916,7 @@ export default function App({ onLogout }: AppProps) {
916916
<FileTreePanel
917917
workspaceAgentId={workspaceAgentId}
918918
onOpenFile={openFile}
919+
onAddToChat={(path, kind) => chatPanelRef.current?.addWorkspacePath(path, kind)}
919920
lastChangedEvent={lastChangedEvent}
920921
revealRequest={revealRequest}
921922
onRemapOpenPaths={remapOpenPaths}
@@ -942,6 +943,7 @@ export default function App({ onLogout }: AppProps) {
942943
<FileTreePanel
943944
workspaceAgentId={workspaceAgentId}
944945
onOpenFile={openFile}
946+
onAddToChat={(path, kind) => chatPanelRef.current?.addWorkspacePath(path, kind)}
945947
lastChangedEvent={lastChangedEvent}
946948
revealRequest={revealRequest}
947949
onRemapOpenPaths={remapOpenPaths}

src/features/chat/ChatPanel.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ interface ChatPanelProps {
4949

5050
export interface ChatPanelHandle {
5151
focusInput: () => void;
52+
addWorkspacePath: (path: string, kind: 'file' | 'directory') => Promise<void>;
5253
}
5354

5455
/** Main chat panel with message list, infinite scroll, search, and input bar. */
@@ -119,7 +120,10 @@ export const ChatPanel = forwardRef<ChatPanelHandle, ChatPanelProps>(function Ch
119120

120121
// Expose focusInput to parent
121122
useImperativeHandle(ref, () => ({
122-
focusInput: () => inputBarRef.current?.focus()
123+
focusInput: () => inputBarRef.current?.focus(),
124+
addWorkspacePath: async (path: string, kind: 'file' | 'directory') => {
125+
await inputBarRef.current?.addWorkspacePath(path, kind);
126+
},
123127
}), []);
124128

125129
// Clean up stale messageRefs when messages change

src/features/file-browser/FileTreePanel.test.tsx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ function createDeferred<T>() {
6060
}
6161

6262
const mockOnOpenFile = vi.fn();
63+
const mockOnAddToChat = vi.fn();
6364
const mockOnRemapOpenPaths = vi.fn();
6465
const mockOnCloseOpenPaths = vi.fn();
6566

@@ -200,6 +201,50 @@ describe('FileTreePanel', () => {
200201
});
201202
});
202203

204+
describe('context menu add to chat', () => {
205+
it('shows "Add to chat" for files and calls the callback', async () => {
206+
render(
207+
<FileTreePanel
208+
onOpenFile={mockOnOpenFile}
209+
onAddToChat={mockOnAddToChat}
210+
onRemapOpenPaths={mockOnRemapOpenPaths}
211+
onCloseOpenPaths={mockOnCloseOpenPaths}
212+
collapsed={false}
213+
/>
214+
);
215+
216+
fireEvent.contextMenu(screen.getByText('package.json'), new MouseEvent('contextmenu', { bubbles: true }));
217+
218+
await waitFor(() => {
219+
expect(screen.getByText('Add to chat')).toBeInTheDocument();
220+
});
221+
222+
fireEvent.click(screen.getByText('Add to chat'));
223+
224+
await waitFor(() => {
225+
expect(mockOnAddToChat).toHaveBeenCalledWith('package.json', 'file');
226+
});
227+
});
228+
229+
it('does not show "Add to chat" for directories', async () => {
230+
render(
231+
<FileTreePanel
232+
onOpenFile={mockOnOpenFile}
233+
onAddToChat={mockOnAddToChat}
234+
onRemapOpenPaths={mockOnRemapOpenPaths}
235+
onCloseOpenPaths={mockOnCloseOpenPaths}
236+
collapsed={false}
237+
/>
238+
);
239+
240+
fireEvent.contextMenu(screen.getByText('src'), new MouseEvent('contextmenu', { bubbles: true }));
241+
242+
await waitFor(() => {
243+
expect(screen.queryByText('Add to chat')).not.toBeInTheDocument();
244+
});
245+
});
246+
});
247+
203248
describe('context menu for deletion', () => {
204249
it('shows "Move to Trash" for default workspace', async () => {
205250
render(

src/features/file-browser/FileTreePanel.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
*/
77

88
import { useRef, useState, useCallback, useEffect } from 'react';
9-
import { PanelLeftClose, RefreshCw, Pencil, Trash2, RotateCcw, X } from 'lucide-react';
9+
import { PanelLeftClose, RefreshCw, Pencil, Trash2, RotateCcw, X, Paperclip } from 'lucide-react';
1010
import { FileTreeNode } from './FileTreeNode';
1111
import { useFileTree } from './hooks/useFileTree';
1212
import { ConfirmDialog } from '../../components/ConfirmDialog';
@@ -59,6 +59,7 @@ export interface FileTreeChangeEvent {
5959
interface FileTreePanelProps {
6060
workspaceAgentId: string;
6161
onOpenFile: (path: string) => void;
62+
onAddToChat?: (path: string, kind: 'file' | 'directory') => Promise<void> | void;
6263
onRemapOpenPaths?: (fromPath: string, toPath: string, targetAgentId?: string) => void;
6364
onCloseOpenPaths?: (pathPrefix: string, targetAgentId?: string) => void;
6465
/** Called externally when a file changes (SSE) — refreshes affected directory. */
@@ -100,6 +101,7 @@ function isSameScopedSession<T extends ScopedSessionState>(current: T | null, ta
100101
export function FileTreePanel({
101102
workspaceAgentId = 'main',
102103
onOpenFile,
104+
onAddToChat,
103105
onRemapOpenPaths,
104106
onCloseOpenPaths,
105107
lastChangedEvent,
@@ -669,6 +671,7 @@ export function FileTreePanel({
669671
const menuPath = menuEntry?.path || '';
670672
const menuInTrash = isTrashItemPath(menuPath);
671673
const showRestore = menuInTrash;
674+
const showAddToChat = Boolean(onAddToChat && menuEntry?.type === 'file' && !menuPath.startsWith('.trash') && menuPath !== '.trash');
672675
const showRename = Boolean(menuEntry && menuPath !== '.trash');
673676
const showTrashAction = Boolean(menuEntry && !menuPath.startsWith('.trash') && menuPath !== '.trash');
674677

@@ -793,6 +796,19 @@ export function FileTreePanel({
793796
</button>
794797
)}
795798

799+
{showAddToChat && (
800+
<button
801+
className="w-full px-3 py-1.5 text-left text-xs text-foreground hover:bg-muted/60 flex items-center gap-2"
802+
onClick={() => {
803+
setContextMenu(null);
804+
void onAddToChat?.(menuEntry.path, 'file');
805+
}}
806+
>
807+
<Paperclip size={12} />
808+
Add to chat
809+
</button>
810+
)}
811+
796812
{showRename && (
797813
<button
798814
className="w-full px-3 py-1.5 text-left text-xs text-foreground hover:bg-muted/60 flex items-center gap-2"
@@ -813,7 +829,7 @@ export function FileTreePanel({
813829
</button>
814830
)}
815831

816-
{!showRestore && !showRename && !showTrashAction && (
832+
{!showRestore && !showAddToChat && !showRename && !showTrashAction && (
817833
<div className="px-3 py-1.5 text-xs text-muted-foreground">
818834
No actions
819835
</div>

0 commit comments

Comments
 (0)