diff --git a/frontend/src/app/(app)/projects/[projectId]/page.tsx b/frontend/src/app/(app)/projects/[projectId]/page.tsx index fcff46e..24c4ab8 100644 --- a/frontend/src/app/(app)/projects/[projectId]/page.tsx +++ b/frontend/src/app/(app)/projects/[projectId]/page.tsx @@ -74,6 +74,12 @@ export default function Project() { const [messages, setMessages] = useState< Array<{ sender: string; text: string; timestamp: Date }> >([]); + + const [chatDraft, setChatDraft] = useState<{ + draft: string; + draftedMessageIndexes: Array; + }>({ draft: "", draftedMessageIndexes: [] }); + const [chatEnabled, setChatEnabled] = useState(false); const [processType, setProcessType] = useState(null); const { ref, inView } = useInView(); @@ -446,6 +452,8 @@ export default function Project() { project_id={project?.id} messages={messages} setMessages={setMessages} + chatDraft={chatDraft} + setChatDraft={setChatDraft} chatEnabled={chatEnabled} setChatEnabled={setChatEnabled} /> diff --git a/frontend/src/app/style/globals.css b/frontend/src/app/style/globals.css index 67f4fd8..e400c24 100644 --- a/frontend/src/app/style/globals.css +++ b/frontend/src/app/style/globals.css @@ -268,3 +268,8 @@ tr:hover .action-icons { margin: 0; /* Remove default margins */ padding: 0; /* Remove default padding */ } + +.ql-editor { + max-height: 80vh; /* Set fixed max height */ + overflow-y: auto; /* Enable scrolling */ +} diff --git a/frontend/src/components/ChatBox.tsx b/frontend/src/components/ChatBox.tsx index 4b44544..74fb8cc 100644 --- a/frontend/src/components/ChatBox.tsx +++ b/frontend/src/components/ChatBox.tsx @@ -9,6 +9,8 @@ import { useQuery } from "@tanstack/react-query"; import { motion } from "framer-motion"; import ChatBubble from "@/components/ui/ChatBubble"; import { ChatReferences } from "@/interfaces/chat"; +import ChatDraftDrawer from "./ChatDraftDrawer"; +import { FilePenLine } from "lucide-react"; export const NoChatPlaceholder = ({ isLoading }: { isLoading: boolean }) => { return ( @@ -36,10 +38,17 @@ interface ChatMessage { timestamp: Date; } +interface ChatDraft { + draft: string; + draftedMessageIndexes: Array; +} + interface ChatProps { project_id?: string; messages: Array; setMessages: React.Dispatch>; + chatDraft: ChatDraft; + setChatDraft: React.Dispatch>; chatEnabled: boolean; setChatEnabled: React.Dispatch>; } @@ -48,6 +57,8 @@ const ChatBox = ({ project_id, messages, setMessages, + chatDraft, + setChatDraft, chatEnabled, setChatEnabled, }: ChatProps) => { @@ -55,6 +66,8 @@ const ChatBox = ({ const [input, setInput] = useState(""); const [loading, setLoading] = useState(false); const messagesEndRef = useRef(null); + const [openChatDraftDrawer, setOpenChatDraftDrawer] = + useState(false); const { data: statusData, isLoading } = useQuery({ queryKey: ["chatStatus", project_id], @@ -92,6 +105,33 @@ const ChatBox = ({ setConversationId(response.conversation_id); }; + const handleAddToDraft = (index: number) => { + const new_draft = + chatDraft.draft + + `

${messages[index - 1].text}

${messages[index].text}

`; + + setChatDraft({ + draft: new_draft, + draftedMessageIndexes: [...chatDraft.draftedMessageIndexes, index], + }); + setOpenChatDraftDrawer(true); + }; + + const onCloseChatDraft = () => { + setOpenChatDraftDrawer(false); + }; + + const onOpenChatDraft = () => { + setOpenChatDraftDrawer(true); + }; + + const handleDraftEdit = (draft: string) => { + setChatDraft({ + draft: draft, + draftedMessageIndexes: [...chatDraft.draftedMessageIndexes], + }); + }; + useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages]); @@ -126,6 +166,7 @@ const ChatBox = ({ timestamp={message.timestamp} references={message.references} sender={message.sender as "user" | "bot"} + onAddToDraft={() => handleAddToDraft(index)} /> ))} @@ -165,14 +206,24 @@ const ChatBox = ({ containerStyle="w-full" /> -
+
+
)} + + ); }; diff --git a/frontend/src/components/ChatDraftDrawer.tsx b/frontend/src/components/ChatDraftDrawer.tsx new file mode 100644 index 0000000..5ae33be --- /dev/null +++ b/frontend/src/components/ChatDraftDrawer.tsx @@ -0,0 +1,89 @@ +"use client"; +import React, { useEffect, useRef } from "react"; +import Drawer from "./ui/Drawer"; +import { Button } from "./ui/Button"; +import ReactQuill from "react-quill"; +import { BookTextIcon } from "lucide-react"; + +interface IProps { + draft: string; + isOpen?: boolean; + onCancel: () => void; + onSubmit: (data: string) => void; +} + +const quill_modules = { + toolbar: [ + [{ header: [1, 2, false] }], + ["bold", "italic", "underline", "strike", "blockquote"], + [ + { list: "ordered" }, + { list: "bullet" }, + { indent: "-1" }, + { indent: "+1" }, + ], + ["link", "image"], + ["clean"], + ], +}; + +const quill_formats = [ + "header", + "bold", + "italic", + "underline", + "strike", + "blockquote", + "list", + "bullet", + "indent", + "link", + "image", +]; + +const ChatDraftDrawer = ({ + isOpen = true, + draft, + onSubmit, + onCancel, +}: IProps) => { + const quillRef = useRef(null); + + useEffect(() => { + if (quillRef.current) { + const editor = quillRef.current.getEditor(); + const editorContainer = editor.root; + if (editorContainer) { + editorContainer.scrollTop = editorContainer.scrollHeight; + } + } + }, [draft]); + + return ( + +
+ +
+
+ +
+
+
+
+ ); +}; + +export default ChatDraftDrawer; diff --git a/frontend/src/components/ui/ChatBubble.tsx b/frontend/src/components/ui/ChatBubble.tsx index 04ce908..34227ce 100644 --- a/frontend/src/components/ui/ChatBubble.tsx +++ b/frontend/src/components/ui/ChatBubble.tsx @@ -4,15 +4,23 @@ import rehypeSanitize from "rehype-sanitize"; import { markify_text } from "@/lib/utils"; import { ChatReference, ChatReferences } from "@/interfaces/chat"; import ChatReferenceDrawer from "../ChatReferenceDrawer"; -import { FileIcon, BookOpen, ChevronDown, ExternalLink } from "lucide-react"; +import { + FileIcon, + BookOpen, + ChevronDown, + ExternalLink, + FilePenLine, +} from "lucide-react"; import { motion, AnimatePresence } from "framer-motion"; import MessageWithReferences from "./MessageWithReferences"; +import TooltipWrapper from "./Tooltip"; interface ChatBubbleProps { message: string; timestamp: Date; sender: "user" | "bot"; references?: ChatReferences[]; + onAddToDraft?: () => void; } export const ChatBubbleWrapper: React.FC<{ @@ -35,6 +43,7 @@ const ChatBubble: React.FC = ({ timestamp, references, sender, + onAddToDraft, }) => { const [selectedReference, setSelectedReference] = useState< ChatReference | undefined @@ -135,9 +144,25 @@ const ChatBubble: React.FC = ({ {markify_text(message)} )} -
- {timestamp.toLocaleTimeString()} -
+ {/* Add to draft button for chat */} + {sender == "bot" && onAddToDraft ? ( +
+
+ {timestamp.toLocaleTimeString()} +
+ + + +
+ ) : ( +
+ {timestamp.toLocaleTimeString()} +
+ )} {references && references.length > 0 && (