diff --git a/apps/web/package.json b/apps/web/package.json
index 9e502513..8e8a70ed 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -44,6 +44,7 @@
"eslint-config-next": "14.1.0",
"eslint-plugin-next-on-pages": "^1.11.0",
"postcss": "^8",
+ "tailwind-scrollbar": "^3.1.0",
"tailwindcss": "^3.3.0",
"typescript": "^5",
"vercel": "^33.6.2",
diff --git a/apps/web/src/components/ChatMessage.tsx b/apps/web/src/components/ChatMessage.tsx
index c6ee662f..5d24d23b 100644
--- a/apps/web/src/components/ChatMessage.tsx
+++ b/apps/web/src/components/ChatMessage.tsx
@@ -1,25 +1,106 @@
-import React from "react";
-import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar";
-import { User } from "next-auth";
-import { User2 } from "lucide-react";
-import Image from "next/image";
+import React, { useEffect } from "react";
+import { motion } from "framer-motion";
+import { ArrowUpRight, Globe } from "lucide-react";
+import { convertRemToPixels } from "@/lib/utils";
export function ChatAnswer({
children: message,
sources,
+ loading = false,
}: {
children: string;
sources?: string[];
+ loading?: boolean;
}) {
- return
{message}
;
+ return (
+
+ {loading ? (
+
+ ) : (
+
{message}
+ )}
+ {sources && sources?.length > 0 && (
+ <>
+
+
+ Sources
+
+
+ >
+ )}
+
+ );
}
export function ChatQuestion({ children }: { children: string }) {
return (
200 ? "text-xl" : "text-2xl"} font-light`}
+ className={`text-rgray-12 w-full text-left ${children.length > 200 ? "text-xl" : "text-2xl"}`}
>
{children}
);
}
+
+export function ChatMessage({
+ children,
+ isLast = false,
+ index,
+}: {
+ children: React.ReactNode | React.ReactNode[];
+ isLast?: boolean;
+ index: number;
+}) {
+ const messageRef = React.useRef(null);
+
+ useEffect(() => {
+ if (!isLast) return;
+ console.log(
+ "last",
+ messageRef.current?.offsetTop,
+ messageRef.current?.parentElement,
+ );
+ messageRef.current?.parentElement?.scrollTo({
+ top: messageRef.current?.offsetTop,
+ behavior: "smooth",
+ });
+ }, []);
+
+ return (
+
+ {children}
+
+ );
+}
+
+function MessageSkeleton() {
+ return (
+
+ );
+}
diff --git a/apps/web/src/components/Main.tsx b/apps/web/src/components/Main.tsx
index a3e6e7d1..aaa87a69 100644
--- a/apps/web/src/components/Main.tsx
+++ b/apps/web/src/components/Main.tsx
@@ -8,9 +8,35 @@ import useViewport from "@/hooks/useViewport";
import { AnimatePresence, motion } from "framer-motion";
import { cn, countLines } from "@/lib/utils";
import { ChatHistory } from "../../types/memory";
-import { ChatAnswer, ChatQuestion } from "./ChatMessage";
+import { ChatAnswer, ChatMessage, ChatQuestion } from "./ChatMessage";
import { useSession } from "next-auth/react";
-import { Card, CardContent } from "./ui/card";
+
+const dummyChatHistory: ChatHistory = {
+ question: "What is the capital of France?",
+ answer: {
+ parts: [
+ {
+ text: "Paris",
+ },
+ {
+ text: "is",
+ },
+ {
+ text: "the",
+ },
+ {
+ text: "capital",
+ },
+ {
+ text: "of",
+ },
+ {
+ text: "France",
+ },
+ ],
+ sources: ["Wikipedia"],
+ },
+};
function supportsDVH() {
try {
@@ -39,20 +65,6 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
Record
>({});
- // helper function to append a new msg
- const appendToChatHistory = useCallback(
- (role: "user" | "model", content: string) => {
- setChatHistory((prev) => [
- ...prev,
- {
- role,
- parts: [{ text: content }],
- },
- ]);
- },
- [],
- );
-
// This is the streamed AI response we get from the server.
const [aiResponse, setAIResponse] = useState("");
@@ -112,10 +124,7 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
// Append to chat history in this way:
// If the last message was from the model, append to that message
// Otherwise, Start a new message from the model and append to that
- if (
- chatHistory.length > 0 &&
- chatHistory[chatHistory.length - 1].role === "model"
- ) {
+ if (chatHistory.length > 0) {
setChatHistory((prev: any) => {
const lastMessage = prev[prev.length - 1];
const newParts = [
@@ -124,17 +133,16 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
];
return [
...prev.slice(0, prev.length - 1),
- { ...lastMessage, parts: newParts },
+ {
+ ...lastMessage,
+ answer: {
+ parts: newParts,
+ sources: lastMessage.answer.sources,
+ },
+ },
];
});
} else {
- setChatHistory((prev) => [
- ...prev,
- {
- role: "model",
- parts: [{ text: parsedPart.response }],
- },
- ]);
}
}
} catch (error) {
@@ -166,12 +174,16 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
const getSearchResults = async () => {
setIsAiLoading(true);
- console.log(value);
+ const _value = value.trim();
+ setValue("");
- appendToChatHistory("user", value);
+ // @dhravya, this is using temporary dummy data remove this before testing
+ setChatHistory((prev) => [...prev, dummyChatHistory]);
+ setTimeout(() => setIsAiLoading(false), 5000);
+ return;
const sourcesResponse = await fetch(
- `/api/chat?sourcesOnly=true&q=${value}`,
+ `/api/chat?sourcesOnly=true&q=${_value}`,
{
method: "POST",
body: JSON.stringify({
@@ -189,7 +201,7 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
);
// TODO: PASS THE `SPACE` TO THE API
- const response = await fetch(`/api/chat?q=${value}`, {
+ const response = await fetch(`/api/chat?q=${_value}`, {
method: "POST",
body: JSON.stringify({
chatHistory,
@@ -201,8 +213,19 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
return;
}
+ setChatHistory((prev) => [
+ ...prev,
+ {
+ question: _value,
+ answer: {
+ parts: [],
+ sources: sourcesInJson.ids ?? [],
+ },
+ },
+ ]);
+
if (response.body) {
- let reader = response.body.getReader();
+ let reader = response.body?.getReader();
let decoder = new TextDecoder("utf-8");
let result = "";
@@ -218,7 +241,7 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
handleStreamData(decoder.decode(value));
- return reader.read().then(processText);
+ return reader?.read().then(processText);
});
}
};
@@ -232,7 +255,15 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
<>
{layout === "chat" ? (
-
+
) : (
-
- {/* {chatHistory.map((chat, index) => (
-
part.text).join("")}
- user={chat.role === "model" ? "ai" : session?.user!}
- />
- ))} */}
- {searchResults.length > 0 && (
-
-
Related memories
-
- {searchResults.map((value, index) => (
-
- {value}
-
- ))}
-
-
- )}
-
Ask your Second brain
@@ -316,9 +326,22 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
);
}
-export function Chat({ sidebarOpen }: { sidebarOpen: boolean }) {
+export function Chat({
+ sidebarOpen,
+ chatHistory,
+ isLoading = false,
+ askQuestion,
+ setValue,
+ value,
+}: {
+ sidebarOpen: boolean;
+ isLoading?: boolean;
+ chatHistory: ChatHistory[];
+ askQuestion: () => void;
+ setValue: (value: string) => void;
+ value: string;
+}) {
const textArea = useRef(null);
- const [value, setValue] = useState("");
function onValueChange(e: React.ChangeEvent) {
const value = e.target.value;
@@ -334,21 +357,20 @@ export function Chat({ sidebarOpen }: { sidebarOpen: boolean }) {
"sidebar relative flex w-full flex-col items-end gap-5 px-5 pt-5 transition-[padding-left,padding-top,padding-right] delay-200 duration-200 md:items-center md:gap-10 md:px-72 [&[data-sidebar-open='true']]:pr-10 [&[data-sidebar-open='true']]:delay-0 md:[&[data-sidebar-open='true']]:pl-[calc(2.5rem+30vw)]",
)}
>
-
-
who is dhravya
-
- Dhravya Shah is an 18-year-old full-stack developer based in Arizona,
- USA. He is a passionate developer who focuses on creating products
- that people love. Dhravya has a background in entrepreneurship, having
- been a 2x acquired founder and a participant in various hackathons. He
- is also involved in open-source contributions, content creation to
- inspire others in coding, and has a growing community of developers.
- Dhravya's work spans from creating AI-powered note-taking apps to
- personalized music companions and educational tools. Additionally, he
- is a guitarist, student, and active in sharing his experiences as a
- developer and entrepreneur
-
+
+ {chatHistory.map((msg, i) => (
+
+ {msg.question}
+
+ {msg.answer.parts.map((part) => part.text).join(" ")}
+
+
+ ))}
+
{
+ if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
+ askQuestion();
+ }
+ },
}}
>
diff --git a/apps/web/src/lib/utils.ts b/apps/web/src/lib/utils.ts
index 8f4392b9..5eca08cc 100644
--- a/apps/web/src/lib/utils.ts
+++ b/apps/web/src/lib/utils.ts
@@ -71,3 +71,7 @@ export function countLines(textarea: HTMLTextAreaElement): number {
return 0;
}
+
+export function convertRemToPixels(rem: number) {
+ return rem * parseFloat(getComputedStyle(document.documentElement).fontSize);
+}
diff --git a/apps/web/tailwind.config.ts b/apps/web/tailwind.config.ts
index d4b23ada..42a90d93 100644
--- a/apps/web/tailwind.config.ts
+++ b/apps/web/tailwind.config.ts
@@ -81,6 +81,7 @@ const config: Config = {
plugin(function ({ addVariant }) {
addVariant("on", "&[data-state-on='true']");
}),
+ require("tailwind-scrollbar"),
],
};
export default config;
diff --git a/apps/web/types/memory.tsx b/apps/web/types/memory.tsx
index e71e92c9..63e1469f 100644
--- a/apps/web/types/memory.tsx
+++ b/apps/web/types/memory.tsx
@@ -1,6 +1,6 @@
-import { db } from '@/server/db';
-import { contentToSpace, space, StoredContent } from '@/server/db/schema';
-import { eq, inArray, or } from 'drizzle-orm';
+import { db } from "@/server/db";
+import { contentToSpace, space, StoredContent } from "@/server/db/schema";
+import { eq, inArray, or } from "drizzle-orm";
export const transformContent = async (
content: StoredContent[],
@@ -55,6 +55,9 @@ export type CollectedSpaces = {
};
export type ChatHistory = {
- role: 'user' | 'model';
- parts: [{ text: string }];
+ question: string;
+ answer: {
+ parts: { text: string }[];
+ sources: string[];
+ };
};