diff --git a/src/components/chat/AttachmentsList.tsx b/src/components/chat/AttachmentsList.tsx
new file mode 100644
index 0000000..2cb269b
--- /dev/null
+++ b/src/components/chat/AttachmentsList.tsx
@@ -0,0 +1,62 @@
+import { FileText, X } from "lucide-react";
+import { useEffect } from "react";
+
+interface AttachmentsListProps {
+ attachments: File[];
+ onRemove: (index: number) => void;
+}
+
+export function AttachmentsList({
+ attachments,
+ onRemove,
+}: AttachmentsListProps) {
+ if (attachments.length === 0) return null;
+
+ return (
+
+ {attachments.map((file, index) => (
+
+ {file.type.startsWith("image/") ? (
+
+
})
+ URL.revokeObjectURL((e.target as HTMLImageElement).src)
+ }
+ onError={(e) =>
+ URL.revokeObjectURL((e.target as HTMLImageElement).src)
+ }
+ />
+
+
})
+ URL.revokeObjectURL((e.target as HTMLImageElement).src)
+ }
+ />
+
+
+ ) : (
+
+ )}
+
{file.name}
+
+
+ ))}
+
+ );
+}
diff --git a/src/components/chat/ChatInput.tsx b/src/components/chat/ChatInput.tsx
index 8b349d4..a45b83c 100644
--- a/src/components/chat/ChatInput.tsx
+++ b/src/components/chat/ChatInput.tsx
@@ -16,6 +16,7 @@ import {
ChevronsUpDown,
ChevronsDownUp,
BarChart2,
+ Paperclip,
} from "lucide-react";
import type React from "react";
import { useCallback, useEffect, useRef, useState } from "react";
@@ -54,6 +55,9 @@ import {
} from "../ui/tooltip";
import { useNavigate } from "@tanstack/react-router";
import { useVersions } from "@/hooks/useVersions";
+import { useAttachments } from "@/hooks/useAttachments";
+import { AttachmentsList } from "./AttachmentsList";
+import { DragDropOverlay } from "./DragDropOverlay";
const showTokenBarAtom = atom(false);
@@ -73,6 +77,20 @@ export function ChatInput({ chatId }: { chatId?: number }) {
const setIsPreviewOpen = useSetAtom(isPreviewOpenAtom);
const [showTokenBar, setShowTokenBar] = useAtom(showTokenBarAtom);
+ // Use the attachments hook
+ const {
+ attachments,
+ fileInputRef,
+ isDraggingOver,
+ handleAttachmentClick,
+ handleFileChange,
+ removeAttachment,
+ handleDragOver,
+ handleDragLeave,
+ handleDrop,
+ clearAttachments,
+ } = useAttachments();
+
// Use the hook to fetch the proposal
const {
proposalResult,
@@ -118,13 +136,25 @@ export function ChatInput({ chatId }: { chatId?: number }) {
};
const handleSubmit = async () => {
- if (!inputValue.trim() || isStreaming || !chatId) {
+ if (
+ (!inputValue.trim() && attachments.length === 0) ||
+ isStreaming ||
+ !chatId
+ ) {
return;
}
const currentInput = inputValue;
setInputValue("");
- await streamMessage({ prompt: currentInput, chatId });
+
+ // Send message with attachments and clear them after sending
+ await streamMessage({
+ prompt: currentInput,
+ chatId,
+ attachments,
+ redo: false,
+ });
+ clearAttachments();
posthog.capture("chat:submit");
};
@@ -236,7 +266,14 @@ export function ChatInput({ chatId }: { chatId?: number }) {
)}
-
+
{/* Only render ChatInputActions if proposal is loaded */}
{proposal && proposalResult?.chatId === chatId && (
)}
+
+ {/* Use the AttachmentsList component */}
+
+
+ {/* Use the DragDropOverlay component */}
+
+
+
+ {/* File attachment button */}
+
+
+
{isStreaming ? (