Skip to content

Commit

Permalink
chatbox created
Browse files Browse the repository at this point in the history
  • Loading branch information
abhishekHegde2000 committed Nov 24, 2023
1 parent f423876 commit c43a4ab
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/app/notes/NavBar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";

import logo from "@/assets/logo.png";
import AIChatButton from "@/components/AIChatButton";
import AddEditNoteDialog from "@/components/AddEditNoteDialog";
import ThemeToggleButton from "@/components/ThemeToggleButton";
import { Button } from "@/components/ui/button";
Expand Down Expand Up @@ -38,6 +39,7 @@ export default function NavBar() {
<Plus size={20} className="mr-2" />
Add Note
</Button>
<AIChatButton />
</div>
</div>
</div>
Expand Down
142 changes: 142 additions & 0 deletions src/components/AIChatBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { cn } from "@/lib/utils";
import { useUser } from "@clerk/nextjs";
import { Message } from "ai";
import { useChat } from "ai/react";
import { Bot, Trash, XCircle } from "lucide-react";
import Image from "next/image";
import { useEffect, useRef } from "react";
import { Button } from "./ui/button";
import { Input } from "./ui/input";

interface AIChatBoxProps {
open: boolean;
onClose: () => void;
}

export default function AIChatBox({ open, onClose }: AIChatBoxProps) {
const {
messages,
input,
handleInputChange,
handleSubmit,
setMessages,
isLoading,
error,
} = useChat();

const inputRef = useRef<HTMLInputElement>(null);
const scrollRef = useRef<HTMLDivElement>(null);

useEffect(() => {
if (scrollRef.current) {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
}
}, [messages]);

useEffect(() => {
if (open) {
inputRef.current?.focus();
}
}, [open]);

const lastMessageIsUser = messages[messages.length - 1]?.role === "user";

return (
<div
className={cn(
"bottom-0 right-0 z-10 w-full max-w-[500px] p-1 xl:right-36",
open ? "fixed" : "hidden",
)}
>
<button onClick={onClose} className="mb-1 ms-auto block">
<XCircle size={30} />
</button>
<div className="flex h-[600px] flex-col rounded border bg-background shadow-xl">
<div className="mt-3 h-full overflow-y-auto px-3" ref={scrollRef}>
{messages.map((message) => (
<ChatMessage message={message} key={message.id} />
))}
{isLoading && lastMessageIsUser && (
<ChatMessage
message={{
role: "assistant",
content: "Thinking...",
}}
/>
)}
{error && (
<ChatMessage
message={{
role: "assistant",
content: "Something went wrong. Please try again.",
}}
/>
)}
{!error && messages.length === 0 && (
<div className="flex h-full items-center justify-center gap-3">
<Bot />
Ask the AI a question about your notes
</div>
)}
</div>
<form onSubmit={handleSubmit} className="m-3 flex gap-1">
<Button
title="Clear chat"
variant="outline"
size="icon"
className="shrink-0"
type="button"
onClick={() => setMessages([])}
>
<Trash />
</Button>
<Input
value={input}
onChange={handleInputChange}
placeholder="Say something..."
ref={inputRef}
/>
<Button type="submit">Send</Button>
</form>
</div>
</div>
);
}

function ChatMessage({
message: { role, content },
}: {
message: Pick<Message, "role" | "content">;
}) {
const { user } = useUser();

const isAiMessage = role === "assistant";

return (
<div
className={cn(
"mb-3 flex items-center",
isAiMessage ? "me-5 justify-start" : "ms-5 justify-end",
)}
>
{isAiMessage && <Bot className="mr-2 shrink-0" />}
<p
className={cn(
"whitespace-pre-line rounded-md border px-3 py-2",
isAiMessage ? "bg-background" : "bg-primary text-primary-foreground",
)}
>
{content}
</p>
{!isAiMessage && user?.imageUrl && (
<Image
src={user.imageUrl}
alt="User image"
width={100}
height={100}
className="ml-2 h-10 w-10 rounded-full object-cover"
/>
)}
</div>
);
}
18 changes: 18 additions & 0 deletions src/components/AIChatButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Bot } from "lucide-react";
import { useState } from "react";
import AIChatBox from "./AIChatBox";
import { Button } from "./ui/button";

export default function AIChatButton() {
const [chatBoxOpen, setChatBoxOpen] = useState(false);

return (
<>
<Button onClick={() => setChatBoxOpen(true)}>
<Bot size={20} className="mr-2" />
AI Chat
</Button>
<AIChatBox open={chatBoxOpen} onClose={() => setChatBoxOpen(false)} />
</>
);
}

0 comments on commit c43a4ab

Please sign in to comment.