Skip to content

Commit e13db25

Browse files
committed
fix(ui): wire drag-and-drop file upload in chat view
Lift file state from ChatInput to ChatPage so DropZone.onDrop can append dropped files to the shared state. Previously drag-and-drop showed the overlay but never passed files to the input.
1 parent 9100f98 commit e13db25

2 files changed

Lines changed: 19 additions & 11 deletions

File tree

ui/web/src/components/chat/chat-input.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,25 @@ interface ChatInputProps {
1414
onAbort: () => void;
1515
isRunning: boolean;
1616
disabled?: boolean;
17+
files: AttachedFile[];
18+
onFilesChange: (files: AttachedFile[]) => void;
1719
}
1820

19-
export function ChatInput({ onSend, onAbort, isRunning, disabled }: ChatInputProps) {
21+
export function ChatInput({ onSend, onAbort, isRunning, disabled, files, onFilesChange }: ChatInputProps) {
2022
const { t } = useTranslation("common");
2123
const [value, setValue] = useState("");
22-
const [files, setFiles] = useState<AttachedFile[]>([]);
2324
const textareaRef = useRef<HTMLTextAreaElement>(null);
2425
const fileInputRef = useRef<HTMLInputElement>(null);
2526

2627
const handleSend = useCallback(() => {
2728
if ((!value.trim() && files.length === 0) || disabled) return;
2829
onSend(value, files.length > 0 ? files : undefined);
2930
setValue("");
30-
setFiles([]);
31+
onFilesChange([]);
3132
if (textareaRef.current) {
3233
textareaRef.current.style.height = "auto";
3334
}
34-
}, [value, files, onSend, disabled]);
35+
}, [value, files, onSend, onFilesChange, disabled]);
3536

3637
const handleKeyDown = useCallback(
3738
(e: KeyboardEvent<HTMLTextAreaElement>) => {
@@ -58,14 +59,14 @@ export function ChatInput({ onSend, onAbort, isRunning, disabled }: ChatInputPro
5859
const selected = e.target.files;
5960
if (!selected) return;
6061
const newFiles: AttachedFile[] = Array.from(selected).map((f) => ({ file: f }));
61-
setFiles((prev) => [...prev, ...newFiles]);
62+
onFilesChange([...files, ...newFiles]);
6263
// Reset input so the same file can be re-selected
6364
e.target.value = "";
64-
}, []);
65+
}, [files, onFilesChange]);
6566

6667
const removeFile = useCallback((index: number) => {
67-
setFiles((prev) => prev.filter((_, i) => i !== index));
68-
}, []);
68+
onFilesChange(files.filter((_, i) => i !== index));
69+
}, [files, onFilesChange]);
6970

7071
return (
7172
<div

ui/web/src/pages/chat/chat-page.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export function ChatPage() {
2424
const userId = useAuthStore((s) => s.userId);
2525

2626
const [scrollTrigger, setScrollTrigger] = useState(0);
27+
const [files, setFiles] = useState<AttachedFile[]>([]);
2728

2829
// sessionKey derived from URL — single source of truth, no separate state
2930
const sessionKey = urlSessionKey ?? "";
@@ -118,18 +119,22 @@ export function ChatPage() {
118119
);
119120

120121
const handleSend = useCallback(
121-
(message: string, files?: AttachedFile[]) => {
122+
(message: string, sendFiles?: AttachedFile[]) => {
122123
let key = sessionKey;
123124
if (!key) {
124125
key = buildNewSessionKey();
125126
navigate(`/chat/${encodeURIComponent(key)}`, { replace: true });
126127
}
127-
send(message, key, files);
128+
send(message, key, sendFiles);
128129
setScrollTrigger((n) => n + 1);
129130
},
130131
[sessionKey, send, buildNewSessionKey, navigate],
131132
);
132133

134+
const handleDropFiles = useCallback((dropped: File[]) => {
135+
setFiles((prev) => [...prev, ...dropped.map((f) => ({ file: f }))]);
136+
}, []);
137+
133138
const handleAbort = useCallback(() => {
134139
abort(sessionKey);
135140
}, [abort, sessionKey]);
@@ -215,7 +220,7 @@ export function ChatPage() {
215220
</div>
216221
)}
217222

218-
<DropZone onDrop={() => { /* TODO: wire to chat input */ }}>
223+
<DropZone onDrop={handleDropFiles}>
219224
<ChatThread
220225
messages={messages}
221226
streamText={streamText}
@@ -235,6 +240,8 @@ export function ChatPage() {
235240
onAbort={handleAbort}
236241
isRunning={isRunning}
237242
disabled={!connected}
243+
files={files}
244+
onFilesChange={setFiles}
238245
/>
239246
) : (
240247
<div className="mx-3 mb-3 flex items-center gap-2 rounded-xl border bg-muted/50 px-4 py-3 text-sm text-muted-foreground shadow-sm">

0 commit comments

Comments
 (0)