Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat[ChatDraft]: adding chat draft UI #48

Merged
merged 6 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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}
gventuri marked this conversation as resolved.
Show resolved Hide resolved
chatEnabled={chatEnabled}
setChatEnabled={setChatEnabled}
/>
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/app/style/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -268,3 +268,9 @@ 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 */
}
59 changes: 58 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>>;
gventuri marked this conversation as resolved.
Show resolved Hide resolved
chatEnabled: boolean;
setChatEnabled: React.Dispatch<React.SetStateAction<boolean>>;
}
Expand All @@ -48,13 +57,20 @@ 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 [scrollToEditorBottom, setScrollToEditorBottom] =
useState<boolean>(false);

const { data: statusData, isLoading } = useQuery({
queryKey: ["chatStatus", project_id],
Expand Down Expand Up @@ -92,6 +108,35 @@ 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);
setScrollToEditorBottom(true);
};
gventuri marked this conversation as resolved.
Show resolved Hide resolved

const onCloseChatDraft = () => {
setOpenChatDraftDrawer(false);
};

const onOpenChatDraft = () => {
setOpenChatDraftDrawer(true);
setScrollToEditorBottom(false);
};

const handleDraftEdit = (draft: string) => {
setChatDraft({
draft: draft,
draftedMessageIndexes: [...chatDraft.draftedMessageIndexes],
});
};
gventuri marked this conversation as resolved.
Show resolved Hide resolved

useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages]);
Expand Down Expand Up @@ -126,6 +171,7 @@ const ChatBox = ({
timestamp={message.timestamp}
references={message.references}
sender={message.sender as "user" | "bot"}
onAddToDraft={() => handleAddToDraft(index)}
gventuri marked this conversation as resolved.
Show resolved Hide resolved
/>
</motion.div>
))}
Expand Down Expand Up @@ -165,14 +211,25 @@ 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>
gventuri marked this conversation as resolved.
Show resolved Hide resolved
</div>
</div>
</>
)}

<ChatDraftDrawer
isOpen={openChatDraftDrawer}
draft={chatDraft?.draft}
onCancel={onCloseChatDraft}
onSubmit={handleDraftEdit}
scrollToEnd={scrollToEditorBottom}
/>
gventuri marked this conversation as resolved.
Show resolved Hide resolved
</div>
);
};
Expand Down
96 changes: 96 additions & 0 deletions frontend/src/components/ChatDraftDrawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"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;
scrollToEnd?: boolean;
}

const modules = {
toolbar: [
[{ header: [1, 2, false] }],
["bold", "italic", "underline", "strike", "blockquote"],
[
{ list: "ordered" },
{ list: "bullet" },
{ indent: "-1" },
{ indent: "+1" },
],
["link", "image"],
["clean"],
],
};

const formats = [
"header",
"bold",
"italic",
"underline",
"strike",
"blockquote",
"list",
"bullet",
"indent",
"link",
"image",
];
gventuri marked this conversation as resolved.
Show resolved Hide resolved

const ChatDraftDrawer = ({
isOpen = true,
draft,
onSubmit,
onCancel,
scrollToEnd = false,
}: IProps) => {
const quillRef = useRef<ReactQuill | null>(null);

const setEditedSummary = (data: string) => {
onSubmit(data);
};
gventuri marked this conversation as resolved.
Show resolved Hide resolved

useEffect(() => {
if (quillRef.current) {
console.log("Exists!");
const editor = quillRef.current.getEditor();
const editorContainer = editor.root;
if (editorContainer) {
editorContainer.scrollTop = editorContainer.scrollHeight;
}
}
}, [draft]);
gventuri marked this conversation as resolved.
Show resolved Hide resolved

return (
<Drawer isOpen={isOpen} onClose={onCancel} title="Draft Chat">
<div className="flex flex-col h-full">
<ReactQuill
ref={quillRef}
theme="snow"
value={draft}
onChange={setEditedSummary}
modules={modules}
formats={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;
24 changes: 23 additions & 1 deletion 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 @@ -123,6 +132,19 @@ const ChatBubble: React.FC<ChatBubbleProps> = ({
return (
<div className="flex flex-col max-w-2xl">
<ChatBubbleWrapper sender={sender}>
{/* Add to draft button for chat */}
{sender == "bot" && onAddToDraft && (
<div className="w-full flex justify-end">
<TooltipWrapper content={"Add to draft"}>
<FilePenLine
width={18}
className="hover:cursor-pointer"
onClick={onAddToDraft}
/>
</TooltipWrapper>
</div>
)}
gventuri marked this conversation as resolved.
Show resolved Hide resolved

{references && references.length > 0 ? (
<MessageWithReferences
message={message}
Expand Down
Loading