Skip to content

Commit

Permalink
feat[ChatDraft]: adding chat draft UI (#48)
Browse files Browse the repository at this point in the history
* feat[ChatDraft]: adding chat draft UI

* feat(ChatDraft): change to make chat ui better

* feat(ChatDraft): refactor unnecessary code

* feat(ChatDraft): rename quill modules and formats

* fix: prettier issue

* feat(ChatDraft): position add to draft button
  • Loading branch information
ArslanSaleem authored Oct 29, 2024
1 parent d876353 commit d29df65
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 5 deletions.
8 changes: 8 additions & 0 deletions frontend/src/app/(app)/projects/[projectId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<number>;
}>({ draft: "", draftedMessageIndexes: [] });

const [chatEnabled, setChatEnabled] = useState<boolean>(false);
const [processType, setProcessType] = useState<string | null>(null);
const { ref, inView } = useInView();
Expand Down Expand Up @@ -446,6 +452,8 @@ export default function Project() {
project_id={project?.id}
messages={messages}
setMessages={setMessages}
chatDraft={chatDraft}
setChatDraft={setChatDraft}
chatEnabled={chatEnabled}
setChatEnabled={setChatEnabled}
/>
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/app/style/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
}
53 changes: 52 additions & 1 deletion frontend/src/components/ChatBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -36,10 +38,17 @@ interface ChatMessage {
timestamp: Date;
}

interface ChatDraft {
draft: string;
draftedMessageIndexes: Array<number>;
}

interface ChatProps {
project_id?: string;
messages: Array<ChatMessage>;
setMessages: React.Dispatch<React.SetStateAction<ChatMessage[]>>;
chatDraft: ChatDraft;
setChatDraft: React.Dispatch<React.SetStateAction<ChatDraft>>;
chatEnabled: boolean;
setChatEnabled: React.Dispatch<React.SetStateAction<boolean>>;
}
Expand All @@ -48,13 +57,17 @@ const ChatBox = ({
project_id,
messages,
setMessages,
chatDraft,
setChatDraft,
chatEnabled,
setChatEnabled,
}: ChatProps) => {
const [conversationId, setConversationId] = useState<string | null>(null);
const [input, setInput] = useState("");
const [loading, setLoading] = useState<boolean>(false);
const messagesEndRef = useRef<HTMLInputElement | null>(null);
const [openChatDraftDrawer, setOpenChatDraftDrawer] =
useState<boolean>(false);

const { data: statusData, isLoading } = useQuery({
queryKey: ["chatStatus", project_id],
Expand Down Expand Up @@ -92,6 +105,33 @@ const ChatBox = ({
setConversationId(response.conversation_id);
};

const handleAddToDraft = (index: number) => {
const new_draft =
chatDraft.draft +
`<br/><p><strong>${messages[index - 1].text}</strong></p><p>${messages[index].text}</p>`;

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]);
Expand Down Expand Up @@ -126,6 +166,7 @@ const ChatBox = ({
timestamp={message.timestamp}
references={message.references}
sender={message.sender as "user" | "bot"}
onAddToDraft={() => handleAddToDraft(index)}
/>
</motion.div>
))}
Expand Down Expand Up @@ -165,14 +206,24 @@ const ChatBox = ({
containerStyle="w-full"
/>
</div>
<div>
<div className="flex justify-center gap-2">
<Button onClick={handleSend} variant="secondary">
Send
</Button>
<Button onClick={onOpenChatDraft} variant="secondary">
<FilePenLine width={18} />
</Button>
</div>
</div>
</>
)}

<ChatDraftDrawer
isOpen={openChatDraftDrawer}
draft={chatDraft?.draft}
onCancel={onCloseChatDraft}
onSubmit={handleDraftEdit}
/>
</div>
);
};
Expand Down
89 changes: 89 additions & 0 deletions frontend/src/components/ChatDraftDrawer.tsx
Original file line number Diff line number Diff line change
@@ -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<ReactQuill | null>(null);

useEffect(() => {
if (quillRef.current) {
const editor = quillRef.current.getEditor();
const editorContainer = editor.root;
if (editorContainer) {
editorContainer.scrollTop = editorContainer.scrollHeight;
}
}
}, [draft]);

return (
<Drawer isOpen={isOpen} onClose={onCancel} title="Draft Chat">
<div className="flex flex-col h-full">
<ReactQuill
ref={quillRef}
theme="snow"
value={draft}
onChange={onSubmit}
modules={quill_modules}
formats={quill_formats}
/>
<div className="sticky bottom-0 bg-white pb-4">
<div className="flex gap-2">
<Button
// onClick={onSubmit}
className="mt-4 px-4 py-2 bg-primary text-white rounded hover:bg-primary-dark"
>
<BookTextIcon className="inline-block mr-2" size={16} />
Rewrite with AI
</Button>
</div>
</div>
</div>
</Drawer>
);
};

export default ChatDraftDrawer;
33 changes: 29 additions & 4 deletions frontend/src/components/ui/ChatBubble.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<{
Expand All @@ -35,6 +43,7 @@ const ChatBubble: React.FC<ChatBubbleProps> = ({
timestamp,
references,
sender,
onAddToDraft,
}) => {
const [selectedReference, setSelectedReference] = useState<
ChatReference | undefined
Expand Down Expand Up @@ -135,9 +144,25 @@ const ChatBubble: React.FC<ChatBubbleProps> = ({
{markify_text(message)}
</ReactMarkdown>
)}
<div className="text-xs text-gray-500 mt-2 text-right">
{timestamp.toLocaleTimeString()}
</div>
{/* Add to draft button for chat */}
{sender == "bot" && onAddToDraft ? (
<div className="w-full flex justify-between">
<div className="text-xs text-gray-500 mt-2 text-right">
{timestamp.toLocaleTimeString()}
</div>
<TooltipWrapper content={"Add to draft"}>
<FilePenLine
width={18}
className="hover:cursor-pointer"
onClick={onAddToDraft}
/>
</TooltipWrapper>
</div>
) : (
<div className="text-xs text-gray-500 mt-2 text-left">
{timestamp.toLocaleTimeString()}
</div>
)}
</ChatBubbleWrapper>

{references && references.length > 0 && (
Expand Down

0 comments on commit d29df65

Please sign in to comment.