mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-13 12:14:40 +00:00
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.
This commit is contained in:
1 parent
9100f981d7
commit
e13db25919
2 files changed
+19
-11
No files matched your search
@@ -14,12 +14,13 @@ interface ChatInputProps {
|
||||
onAbort: () => void;
|
||||
isRunning: boolean;
|
||||
disabled?: boolean;
|
||||
files: AttachedFile[];
|
||||
onFilesChange: (files: AttachedFile[]) => void;
|
||||
}
|
||||
|
||||
export function ChatInput({ onSend, onAbort, isRunning, disabled }: ChatInputProps) {
|
||||
export function ChatInput({ onSend, onAbort, isRunning, disabled, files, onFilesChange }: ChatInputProps) {
|
||||
const { t } = useTranslation("common");
|
||||
const [value, setValue] = useState("");
|
||||
const [files, setFiles] = useState<AttachedFile[]>([]);
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
@@ -27,11 +28,11 @@ export function ChatInput({ onSend, onAbort, isRunning, disabled }: ChatInputPro
|
||||
if ((!value.trim() && files.length === 0) || disabled) return;
|
||||
onSend(value, files.length > 0 ? files : undefined);
|
||||
setValue("");
|
||||
setFiles([]);
|
||||
onFilesChange([]);
|
||||
if (textareaRef.current) {
|
||||
textareaRef.current.style.height = "auto";
|
||||
}
|
||||
}, [value, files, onSend, disabled]);
|
||||
}, [value, files, onSend, onFilesChange, disabled]);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
@@ -58,14 +59,14 @@ export function ChatInput({ onSend, onAbort, isRunning, disabled }: ChatInputPro
|
||||
const selected = e.target.files;
|
||||
if (!selected) return;
|
||||
const newFiles: AttachedFile[] = Array.from(selected).map((f) => ({ file: f }));
|
||||
setFiles((prev) => [...prev, ...newFiles]);
|
||||
onFilesChange([...files, ...newFiles]);
|
||||
// Reset input so the same file can be re-selected
|
||||
e.target.value = "";
|
||||
}, []);
|
||||
}, [files, onFilesChange]);
|
||||
|
||||
const removeFile = useCallback((index: number) => {
|
||||
setFiles((prev) => prev.filter((_, i) => i !== index));
|
||||
}, []);
|
||||
onFilesChange(files.filter((_, i) => i !== index));
|
||||
}, [files, onFilesChange]);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -24,6 +24,7 @@ export function ChatPage() {
|
||||
const userId = useAuthStore((s) => s.userId);
|
||||
|
||||
const [scrollTrigger, setScrollTrigger] = useState(0);
|
||||
const [files, setFiles] = useState<AttachedFile[]>([]);
|
||||
|
||||
// sessionKey derived from URL — single source of truth, no separate state
|
||||
const sessionKey = urlSessionKey ?? "";
|
||||
@@ -118,18 +119,22 @@ export function ChatPage() {
|
||||
);
|
||||
|
||||
const handleSend = useCallback(
|
||||
(message: string, files?: AttachedFile[]) => {
|
||||
(message: string, sendFiles?: AttachedFile[]) => {
|
||||
let key = sessionKey;
|
||||
if (!key) {
|
||||
key = buildNewSessionKey();
|
||||
navigate(`/chat/${encodeURIComponent(key)}`, { replace: true });
|
||||
}
|
||||
send(message, key, files);
|
||||
send(message, key, sendFiles);
|
||||
setScrollTrigger((n) => n + 1);
|
||||
},
|
||||
[sessionKey, send, buildNewSessionKey, navigate],
|
||||
);
|
||||
|
||||
const handleDropFiles = useCallback((dropped: File[]) => {
|
||||
setFiles((prev) => [...prev, ...dropped.map((f) => ({ file: f }))]);
|
||||
}, []);
|
||||
|
||||
const handleAbort = useCallback(() => {
|
||||
abort(sessionKey);
|
||||
}, [abort, sessionKey]);
|
||||
@@ -215,7 +220,7 @@ export function ChatPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DropZone onDrop={() => { /* TODO: wire to chat input */ }}>
|
||||
<DropZone onDrop={handleDropFiles}>
|
||||
<ChatThread
|
||||
messages={messages}
|
||||
streamText={streamText}
|
||||
@@ -235,6 +240,8 @@ export function ChatPage() {
|
||||
onAbort={handleAbort}
|
||||
isRunning={isRunning}
|
||||
disabled={!connected}
|
||||
files={files}
|
||||
onFilesChange={setFiles}
|
||||
/>
|
||||
) : (
|
||||
<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">
|
||||
|
||||
Reference in new issue
Block a user