From 91b566b6166d8854e43a6c0486986188c775642e Mon Sep 17 00:00:00 2001 From: Yash Date: Tue, 9 Apr 2024 10:29:59 +0000 Subject: [PATCH] new chat (incomplete) --- apps/extension/manifest.json | 6 +- apps/web/src/app/page.tsx | 54 ++-- apps/web/src/components/ChatMessage.tsx | 59 ++-- apps/web/src/components/Main.tsx | 264 ++++++++++-------- .../src/components/Sidebar/FilterCombobox.tsx | 27 +- apps/web/src/components/ui/textarea.tsx | 12 +- apps/web/src/lib/utils.ts | 46 +++ apps/web/tailwind.config.ts | 11 + 8 files changed, 288 insertions(+), 191 deletions(-) diff --git a/apps/extension/manifest.json b/apps/extension/manifest.json index b8b66cce..7b7bf460 100644 --- a/apps/extension/manifest.json +++ b/apps/extension/manifest.json @@ -17,8 +17,9 @@ "src/content.tsx" ], "matches": [ - "http://localhost:3000/", - "https://anycontext.dhr.wtf/", + "http://localhost:3000/*", + "https://opulent-funicular-94rx4v9w775f96q-3000.app.github.dev/*", + "https://anycontext.dhr.wtf/*", "" ] } @@ -27,6 +28,7 @@ "activeTab", "storage", "http://localhost:3000/*", + "https://opulent-funicular-94rx4v9w775f96q-3000.app.github.dev/*", "https://anycontext.dhr.wtf/*" ], "background": { diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx index bec0223f..95e18e83 100644 --- a/apps/web/src/app/page.tsx +++ b/apps/web/src/app/page.tsx @@ -1,4 +1,4 @@ -import { db } from '@/server/db'; +import { db } from "@/server/db"; import { contentToSpace, sessions, @@ -6,28 +6,28 @@ import { StoredContent, storedContent, users, -} from '@/server/db/schema'; -import { eq, inArray } from 'drizzle-orm'; -import { cookies, headers } from 'next/headers'; -import { redirect } from 'next/navigation'; -import Sidebar from '@/components/Sidebar/index'; -import Main from '@/components/Main'; -import MessagePoster from './MessagePoster'; -import { transformContent } from '../../types/memory'; -import { MemoryProvider } from '@/contexts/MemoryContext'; -import Content from './content'; +} from "@/server/db/schema"; +import { eq, inArray } from "drizzle-orm"; +import { cookies, headers } from "next/headers"; +import { redirect } from "next/navigation"; +import Sidebar from "@/components/Sidebar/index"; +import Main from "@/components/Main"; +import MessagePoster from "./MessagePoster"; +import { transformContent } from "../../types/memory"; +import { MemoryProvider } from "@/contexts/MemoryContext"; +import Content from "./content"; -export const runtime = 'edge'; +export const runtime = "edge"; export default async function Home() { const token = - cookies().get('next-auth.session-token')?.value ?? - cookies().get('__Secure-authjs.session-token')?.value ?? - cookies().get('authjs.session-token')?.value ?? - headers().get('Authorization')?.replace('Bearer ', ''); + cookies().get("next-auth.session-token")?.value ?? + cookies().get("__Secure-authjs.session-token")?.value ?? + cookies().get("authjs.session-token")?.value ?? + headers().get("Authorization")?.replace("Bearer ", ""); if (!token) { - return redirect('/api/auth/signin'); + return redirect("/api/auth/signin"); } const session = await db @@ -36,7 +36,7 @@ export default async function Home() { .where(eq(sessions.sessionToken, token!)); if (!session || session.length === 0) { - return redirect('/api/auth/signin'); + return redirect("/api/auth/signin"); } const [userData] = await db @@ -46,7 +46,7 @@ export default async function Home() { .limit(1); if (!userData) { - return redirect('/api/auth/signin'); + return redirect("/api/auth/signin"); } // Fetch all content for the user @@ -61,17 +61,17 @@ export default async function Home() { collectedSpaces.push({ id: 2, - title: 'Test', + title: "Test", content: [ { id: 1, - content: 'Test', - title: 'Vscode', - description: 'Test', - url: 'https://vscode-remake.vercel.app/', + content: "Test", + title: "Vscode", + description: "Test", + url: "https://vscode-remake.vercel.app/", savedAt: new Date(), - baseUrl: 'https://vscode-remake.vercel.app/', - image: 'https://vscode-remake.vercel.app/favicon.svg', + baseUrl: "https://vscode-remake.vercel.app/", + image: "https://vscode-remake.vercel.app/favicon.svg", }, ], }); @@ -79,7 +79,7 @@ export default async function Home() { return ( - + {/* */} ); } diff --git a/apps/web/src/components/ChatMessage.tsx b/apps/web/src/components/ChatMessage.tsx index 114d0a48..c6ee662f 100644 --- a/apps/web/src/components/ChatMessage.tsx +++ b/apps/web/src/components/ChatMessage.tsx @@ -1,52 +1,25 @@ -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 from "react"; +import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar"; +import { User } from "next-auth"; +import { User2 } from "lucide-react"; +import Image from "next/image"; -function ChatMessage({ - message, - user, +export function ChatAnswer({ + children: message, sources, }: { - message: string; - user: User | 'ai'; + children: string; sources?: string[]; }) { + return
{message}
; +} + +export function ChatQuestion({ children }: { children: string }) { return ( -
-
- - {user === 'ai' ? ( - AI - ) : user?.image ? ( - <> - - - {user?.name?.split(' ').map((n) => n[0])}{' '} - - - ) : ( - - )} - -
{message}
-
-
+
200 ? "text-xl" : "text-2xl"} font-light`} + > + {children}
); } - -export { ChatMessage }; diff --git a/apps/web/src/components/Main.tsx b/apps/web/src/components/Main.tsx index 3f021906..04539de4 100644 --- a/apps/web/src/components/Main.tsx +++ b/apps/web/src/components/Main.tsx @@ -5,10 +5,10 @@ import { Textarea2 } from "./ui/textarea"; import { ArrowRight, ArrowUp } from "lucide-react"; import { MemoryDrawer } from "./MemoryDrawer"; import useViewport from "@/hooks/useViewport"; -import { motion } from "framer-motion"; -import { cn } from "@/lib/utils"; +import { AnimatePresence, motion } from "framer-motion"; +import { cn, countLines } from "@/lib/utils"; import { ChatHistory } from "../../types/memory"; -import { ChatMessage } from "./ChatMessage"; +import { ChatAnswer, ChatQuestion } from "./ChatMessage"; import { useSession } from "next-auth/react"; import { Card, CardContent } from "./ui/card"; @@ -22,6 +22,7 @@ function supportsDVH() { export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) { const [hide, setHide] = useState(false); + const [layout, setLayout] = useState<"chat" | "initial">("initial"); const [value, setValue] = useState(""); const { width } = useViewport(); const [searchResults, setSearchResults] = useState([]); @@ -57,7 +58,7 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) { const [toBeParsed, setToBeParsed] = useState(""); - const textArea = useRef(null); + const textArea = useRef(null); const main = useRef(null); useEffect(() => { @@ -162,8 +163,7 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) { } }, [toBeParsed]); - const getSearchResults = async (e: React.FormEvent) => { - e.preventDefault(); + const getSearchResults = async () => { setIsAiLoading(true); console.log(value); @@ -185,7 +185,7 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) { }; setSearchResults((prev) => - Array.from(new Set([...prev, ...sourcesInJson.ids])), + Array.from(new Set([...prev, ...(sourcesInJson.ids ?? [])])), ); // TODO: PASS THE `SPACE` TO THE API @@ -223,74 +223,110 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) { } }; + const onSend = async () => { + setLayout("chat"); + await getSearchResults(); + }; + return ( - -
- {chatHistory.map((chat, index) => ( - part.text).join("")} - user={chat.role === "model" ? "ai" : session?.user!} - /> - ))} - {searchResults.length > 0 && ( -
-

Related memories

-
- {searchResults.map((value, index) => ( - - {value} - + <> + + {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 -

-
await getSearchResults(e)} - > - setValue(e.target.value), - }} - > -
- - -
-
-
- {width <= 768 && } - +
+ { + textArea.current?.querySelector("textarea")?.focus(); + }} + className="hidden md:flex" + /> + +
+ + {width <= 768 && } + + )} + + ); } export function Chat({ sidebarOpen }: { sidebarOpen: boolean }) { + const textArea = useRef(null); const [value, setValue] = useState(""); + function onValueChange(e: React.ChangeEvent) { + const value = e.target.value; + setValue(value); + const lines = countLines(e.target); + e.target.rows = Math.min(5, lines); + } + return (
-
- {/* setValue(e.target.value), - }} - > -
- -
-
*/} - - setValue(e.target.value), - }} - > -
- -
-
+
+ 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 + +
+
+
+ { + textArea.current?.querySelector("textarea")?.focus(); + }} + side="top" + align="start" + className="bg-white/5" + /> + +
+ +
+
+
); diff --git a/apps/web/src/components/Sidebar/FilterCombobox.tsx b/apps/web/src/components/Sidebar/FilterCombobox.tsx index 89bea100..6f91c686 100644 --- a/apps/web/src/components/Sidebar/FilterCombobox.tsx +++ b/apps/web/src/components/Sidebar/FilterCombobox.tsx @@ -22,9 +22,19 @@ import { SpaceIcon } from "@/assets/Memories"; import { AnimatePresence, LayoutGroup, motion } from "framer-motion"; import { useMemory } from "@/contexts/MemoryContext"; -export interface Props extends React.ButtonHTMLAttributes {} +export interface Props extends React.ButtonHTMLAttributes { + side?: "top" | "bottom"; + align?: "end" | "start" | "center"; + onClose?: () => void; +} -export function FilterCombobox({ className, ...props }: Props) { +export function FilterCombobox({ + className, + side = "bottom", + align = "center", + onClose, + ...props +}: Props) { const { spaces, addSpace } = useMemory(); const [open, setOpen] = React.useState(false); @@ -38,6 +48,12 @@ export function FilterCombobox({ className, ...props }: Props) { : 0, ); + React.useEffect(() => { + if (!open) { + onClose?.(); + } + }, [open]); + return ( @@ -62,7 +78,12 @@ export function FilterCombobox({ className, ...props }: Props) {
- + e.preventDefault()} + align={align} + side={side} + className="w-[200px] p-0" + > spaces diff --git a/apps/web/src/components/ui/textarea.tsx b/apps/web/src/components/ui/textarea.tsx index 04ad611b..3b2c9ddd 100644 --- a/apps/web/src/components/ui/textarea.tsx +++ b/apps/web/src/components/ui/textarea.tsx @@ -1,6 +1,7 @@ import * as React from "react"; import { cn } from "@/lib/utils"; +import { HTMLMotionProps, motion } from "framer-motion"; export interface TextareaProps extends React.TextareaHTMLAttributes {} @@ -21,16 +22,18 @@ const Textarea = React.forwardRef( ); Textarea.displayName = "Textarea"; -export interface Textarea2Props extends React.HTMLAttributes { +export interface Textarea2Props extends HTMLMotionProps<"div"> { textAreaProps?: TextareaProps; + children: React.ReactNode | React.ReactNode[]; } -const Textarea2 = React.forwardRef( +const Textarea2 = React.forwardRef( ({ className, children, textAreaProps: _textAreaProps, ...props }, ref) => { const { className: textAreaClassName, ...textAreaProps } = _textAreaProps || {}; return ( -
( "text-rgray-11 h-full w-full resize-none bg-transparent placeholder:text-white/50 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50", textAreaClassName, )} - ref={ref} {...textAreaProps} /> {children} -
+ ); }, ); diff --git a/apps/web/src/lib/utils.ts b/apps/web/src/lib/utils.ts index 665f0973..8f4392b9 100644 --- a/apps/web/src/lib/utils.ts +++ b/apps/web/src/lib/utils.ts @@ -25,3 +25,49 @@ export function generateId() { export function svgId(prefix: string, id: string) { return `${prefix}-${id}`; } + +export function countLines(textarea: HTMLTextAreaElement): number { + let _buffer: HTMLTextAreaElement | null = null; + + if (_buffer == null) { + _buffer = document.createElement("textarea"); + _buffer.style.border = "none"; + _buffer.style.height = "0"; + _buffer.style.overflow = "hidden"; + _buffer.style.padding = "0"; + _buffer.style.position = "absolute"; + _buffer.style.left = "0"; + _buffer.style.top = "0"; + _buffer.style.zIndex = "-1"; + document.body.appendChild(_buffer); + } + + const cs = window.getComputedStyle(textarea); + const pl = parseInt(cs.paddingLeft as string); + const pr = parseInt(cs.paddingRight as string); + let lh = parseInt(cs.lineHeight as string); + + // [cs.lineHeight] may return 'normal', which means line height = font size. + if (isNaN(lh)) lh = parseInt(cs.fontSize as string); + + // Copy content width. + if (_buffer) { + _buffer.style.width = textarea.clientWidth - pl - pr + "px"; + + // Copy text properties. + _buffer.style.font = cs.font as string; + _buffer.style.letterSpacing = cs.letterSpacing as string; + _buffer.style.whiteSpace = cs.whiteSpace as string; + _buffer.style.wordBreak = cs.wordBreak as string; + _buffer.style.wordSpacing = cs.wordSpacing as string; + _buffer.style.wordWrap = cs.wordWrap as string; + + // Copy value. + _buffer.value = textarea.value; + + const result = Math.floor(_buffer.scrollHeight / lh); + return result > 0 ? result : 1; + } + + return 0; +} diff --git a/apps/web/tailwind.config.ts b/apps/web/tailwind.config.ts index 46f9b5ff..d4b23ada 100644 --- a/apps/web/tailwind.config.ts +++ b/apps/web/tailwind.config.ts @@ -34,11 +34,22 @@ const config: Config = { opacity: "1", }, }, + "from-top": { + "0%": { + transform: "translateY(-50px)", + opacity: "0", + }, + "100%": { + transform: "translateY(0)", + opacity: "1", + }, + }, }, animation: { "scale-in": "scale-in 0.2s cubic-bezier(0.16, 1, 0.3, 1)", "scale-out": "scale-out 0.2s cubic-bezier(0.16, 1, 0.3, 1)", "fade-in": "fade-in 0.2s 0.5s forwards cubic-bezier(0.16, 1, 0.3, 1)", + "from-top": "from-top 0.2s ease-in-out", }, backgroundImage: { "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",