new chat (incomplete)

This commit is contained in:
Yash 2024-04-09 10:29:59 +00:00
parent f4917ef8f6
commit 91b566b616
8 changed files with 288 additions and 191 deletions

View file

@ -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/*",
"<all_urls>"
]
}
@ -27,6 +28,7 @@
"activeTab",
"storage",
"http://localhost:3000/*",
"https://opulent-funicular-94rx4v9w775f96q-3000.app.github.dev/*",
"https://anycontext.dhr.wtf/*"
],
"background": {

View file

@ -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 (
<MemoryProvider spaces={collectedSpaces}>
<Content />
<MessagePoster jwt={token} />
{/* <MessagePoster jwt={token} /> */}
</MemoryProvider>
);
}

View file

@ -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 <div className="mt-5 w-full text-lg">{message}</div>;
}
export function ChatQuestion({ children }: { children: string }) {
return (
<div className="flex flex-col gap-4">
<div
className={`font-bold ${!(user === 'ai') && 'text-xl '} flex flex-col md:flex-row items-center gap-4`}
>
<Avatar>
{user === 'ai' ? (
<Image
src="/logo.png"
width={48}
height={48}
alt="AI"
className="rounded-md w-12 h-12"
/>
) : user?.image ? (
<>
<AvatarImage
className="h-6 w-6 rounded-lg"
src={user?.image}
alt="user pfp"
/>
<AvatarFallback>
{user?.name?.split(' ').map((n) => n[0])}{' '}
</AvatarFallback>
</>
) : (
<User2 strokeWidth={1.3} className="h-6 w-6" />
)}
</Avatar>
<div className="ml-4">{message}</div>
</div>
<div className="w-full h-0.5 bg-gray-700 my-2 md:my-0 md:mx-4 mt-8"></div>
<div
className={`text-rgray-12 w-full text-left ${children.length > 200 ? "text-xl" : "text-2xl"} font-light`}
>
{children}
</div>
);
}
export { ChatMessage };

View file

@ -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<string[]>([]);
@ -57,7 +58,7 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
const [toBeParsed, setToBeParsed] = useState("");
const textArea = useRef<HTMLTextAreaElement>(null);
const textArea = useRef<HTMLDivElement>(null);
const main = useRef<HTMLDivElement>(null);
useEffect(() => {
@ -162,8 +163,7 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
}
}, [toBeParsed]);
const getSearchResults = async (e: React.FormEvent<HTMLFormElement>) => {
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 (
<motion.main
data-sidebar-open={sidebarOpen}
ref={main}
className={cn(
"sidebar flex w-full flex-col items-end justify-center 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)]",
hide ? "" : "main-hidden",
)}
>
<div className="flex w-full flex-col">
{chatHistory.map((chat, index) => (
<ChatMessage
key={index}
message={chat.parts.map((part) => part.text).join("")}
user={chat.role === "model" ? "ai" : session?.user!}
/>
))}
{searchResults.length > 0 && (
<div className="mt-4">
<h1>Related memories</h1>
<div className="grid gap-6">
{searchResults.map((value, index) => (
<Card key={index}>
<CardContent className="space-y-2">{value}</CardContent>
</Card>
<>
<AnimatePresence mode="wait">
{layout === "chat" ? (
<Chat key="chat" sidebarOpen={sidebarOpen} />
) : (
<main
data-sidebar-open={sidebarOpen}
ref={main}
className={cn(
"sidebar flex w-full flex-col items-end justify-center 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)]",
hide ? "" : "main-hidden",
)}
>
<div className="flex w-full flex-col">
{chatHistory.map((chat, index) => (
// <ChatMessage
// key={index}
// message={chat.parts.map((part) => part.text).join("")}
// user={chat.role === "model" ? "ai" : session?.user!}
// />
))}
{searchResults.length > 0 && (
<div className="mt-4">
<h1>Related memories</h1>
<div className="grid gap-6">
{searchResults.map((value, index) => (
<Card key={index}>
<CardContent className="space-y-2">{value}</CardContent>
</Card>
))}
</div>
</div>
)}
</div>
</div>
)}
</div>
<h1 className="text-rgray-11 mt-auto w-full text-center text-3xl md:mt-0">
Ask your Second brain
</h1>
<form
className="overflow-none mt-auto h-max min-h-[3em] w-full resize-y flex-row items-start justify-center py-5 md:mt-0 md:h-[20vh] md:resize-none md:flex-col md:items-center md:justify-center md:p-2 md:pb-2 md:pt-2"
onSubmit={async (e) => await getSearchResults(e)}
>
<Textarea2
ref={textArea}
textAreaProps={{
placeholder: "Ask your SuperMemory...",
className:
"h-auto overflow-auto md:h-full md:resize-none text-lg py-0 px-2 pt-2 md:py-0 md:p-5 resize-y text-rgray-11 w-full min-h-[1em]",
value,
autoFocus: true,
onChange: (e) => setValue(e.target.value),
}}
>
<div className="text-rgray-11/70 flex h-full w-fit items-center justify-center pl-0 md:w-full md:p-2">
<FilterCombobox className="hidden md:flex" />
<button
type="submit"
disabled={value.trim().length < 1}
className="text-rgray-11/70 bg-rgray-3 focus-visible:ring-rgray-8 hover:bg-rgray-4 mt-auto flex items-center justify-center rounded-full p-2 ring-2 ring-transparent focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 md:ml-auto md:mt-0"
<h1 className="text-rgray-11 mt-auto w-full text-center text-3xl md:mt-0">
Ask your Second brain
</h1>
<Textarea2
ref={textArea}
exit={{
opacity: 0,
y: 50,
}}
transition={{
type: "tween",
duration: 0.2,
}}
textAreaProps={{
placeholder: "Ask your SuperMemory...",
className:
"h-auto overflow-auto md:h-full md:resize-none text-lg py-0 px-2 pt-2 md:py-0 md:p-5 resize-y text-rgray-11 w-full min-h-[1em]",
value,
autoFocus: true,
onChange: (e) => setValue(e.target.value),
onKeyDown: (e) => {
console.log(e.key, e.ctrlKey, e.metaKey);
if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
onSend();
}
},
}}
>
<ArrowRight className="h-5 w-5" />
</button>
</div>
</Textarea2>
</form>
{width <= 768 && <MemoryDrawer hide={hide} />}
</motion.main>
<div className="text-rgray-11/70 flex h-full w-fit items-center justify-center pl-0 md:w-full md:p-2">
<FilterCombobox
onClose={() => {
textArea.current?.querySelector("textarea")?.focus();
}}
className="hidden md:flex"
/>
<button
onClick={onSend}
disabled={value.trim().length < 1}
className="text-rgray-11/70 bg-rgray-3 focus-visible:ring-rgray-8 hover:bg-rgray-4 mt-auto flex items-center justify-center rounded-full p-2 ring-2 ring-transparent focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 md:ml-auto md:mt-0"
>
<ArrowRight className="h-5 w-5" />
</button>
</div>
</Textarea2>
{width <= 768 && <MemoryDrawer hide={hide} />}
</main>
)}
</AnimatePresence>
</>
);
}
export function Chat({ sidebarOpen }: { sidebarOpen: boolean }) {
const textArea = useRef<HTMLDivElement>(null);
const [value, setValue] = useState("");
function onValueChange(e: React.ChangeEvent<HTMLTextAreaElement>) {
const value = e.target.value;
setValue(value);
const lines = countLines(e.target);
e.target.rows = Math.min(5, lines);
}
return (
<main
data-sidebar-open={sidebarOpen}
@ -298,52 +334,58 @@ 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)]",
)}
>
<div className="absolute bottom-5 left-5 w-full ">
{/* <Textarea2
// ref={textArea}
className="fixed bottom-5 mt-auto h-min max-h-[30em] min-h-[3em] w-[50%] resize-y flex-row items-start justify-center overflow-auto border-red-500 py-5 md:mt-0 md:h-[20vh] md:resize-none md:items-center md:justify-center md:p-2 md:pb-2 md:pt-2"
textAreaProps={{
placeholder: "Ask your SuperMemory...",
className:
"h-auto overflow-auto md:h-full border md:resize-none text-lg py-0 px-2 w-full md:py-0 md:p-5 resize-y text-rgray-11 w-full min-h-[1em]",
value,
autoFocus: true,
onChange: (e) => setValue(e.target.value),
}}
>
<div className="text-rgray-11/70 ml-auto flex h-full w-min items-center justify-center pl-0 md:p-2">
<button
type="submit"
disabled={value.trim().length < 1}
className="text-rgray-11/70 bg-rgray-3 focus-visible:ring-rgray-8 hover:bg-rgray-4 mt-auto flex items-center justify-center rounded-full p-2 ring-2 ring-transparent focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 md:mt-0"
>
<ArrowUp className="h-5 w-5" />
</button>
</div>
</Textarea2> */}
<Textarea2
// ref={textArea}
className="fixed bottom-5 mt-auto h-[3em] max-h-[3em] w-[50%] resize-y flex-row items-start justify-center overflow-auto border-red-500 py-5 md:mt-0 md:h-[20vh] md:resize-none md:items-center md:justify-center md:p-2 md:pb-2 md:pt-2"
textAreaProps={{
placeholder: "Ask your SuperMemory...",
className:
"overflow-auto md:h-full h-[2em] max-h-[2em] p-0 border md:resize-none text-lg py-0 px-2 w-full md:py-0 md:p-5 resize-y text-rgray-11 w-full min-h-[1em]",
value,
autoFocus: true,
onChange: (e) => setValue(e.target.value),
}}
>
<div className="text-rgray-11/70 ml-auto flex h-full w-min items-center justify-center pl-0 md:p-2">
<button
type="submit"
disabled={value.trim().length < 1}
className="text-rgray-11/70 bg-rgray-3 focus-visible:ring-rgray-8 hover:bg-rgray-4 mt-auto flex items-center justify-center rounded-full p-2 ring-2 ring-transparent focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 md:mt-0"
>
<ArrowUp className="h-5 w-5" />
</button>
</div>
</Textarea2>
<div className="min-h-[100%] w-full px-5 pt-10">
<ChatQuestion>who is dhravya</ChatQuestion>
<ChatAnswer>
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
</ChatAnswer>
</div>
<div
data-sidebar-open={sidebarOpen}
className="absolute flex w-full items-center justify-center"
>
<div className="animate-from-top fixed bottom-10 mt-auto flex w-[50%] flex-col items-start justify-center gap-2">
<FilterCombobox
onClose={() => {
textArea.current?.querySelector("textarea")?.focus();
}}
side="top"
align="start"
className="bg-white/5"
/>
<Textarea2
ref={textArea}
className="h-auto w-full flex-row items-start justify-center overflow-auto px-3 md:items-center md:justify-center"
textAreaProps={{
placeholder: "Ask your SuperMemory...",
className:
"overflow-auto h-auto p-3 md:resize-none text-lg w-auto resize-y text-rgray-11 w-full",
value,
rows: 1,
autoFocus: true,
onChange: onValueChange,
}}
>
<div className="text-rgray-11/70 ml-auto mt-auto flex h-full w-min items-center justify-center pb-3 pr-2">
<button
type="submit"
disabled={value.trim().length < 1}
className="text-rgray-11/70 bg-rgray-3 focus-visible:ring-rgray-8 hover:bg-rgray-4 mt-auto flex items-center justify-center rounded-full p-2 ring-2 ring-transparent transition-[filter] focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
>
<ArrowUp className="h-5 w-5" />
</button>
</div>
</Textarea2>
</div>
</div>
</main>
);

View file

@ -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<HTMLButtonElement> {}
export interface Props extends React.ButtonHTMLAttributes<HTMLButtonElement> {
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 (
<AnimatePresence mode="popLayout">
<LayoutGroup>
@ -62,7 +78,12 @@ export function FilterCombobox({ className, ...props }: Props) {
</div>
</button>
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0">
<PopoverContent
onCloseAutoFocus={(e) => e.preventDefault()}
align={align}
side={side}
className="w-[200px] p-0"
>
<Command
filter={(val, search) =>
spaces

View file

@ -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<HTMLTextAreaElement> {}
@ -21,16 +22,18 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
);
Textarea.displayName = "Textarea";
export interface Textarea2Props extends React.HTMLAttributes<HTMLDivElement> {
export interface Textarea2Props extends HTMLMotionProps<"div"> {
textAreaProps?: TextareaProps;
children: React.ReactNode | React.ReactNode[];
}
const Textarea2 = React.forwardRef<HTMLTextAreaElement, Textarea2Props>(
const Textarea2 = React.forwardRef<HTMLDivElement, Textarea2Props>(
({ className, children, textAreaProps: _textAreaProps, ...props }, ref) => {
const { className: textAreaClassName, ...textAreaProps } =
_textAreaProps || {};
return (
<div
<motion.div
ref={ref}
className={cn(
"border-rgray-6 text-rgray-11 has-[textarea:focus-visible]:ring-rgray-7 flex h-auto min-h-[80px] w-full flex-col items-start justify-center rounded-md border bg-transparent px-3 py-2 text-sm transition has-[textarea:focus-visible]:ring-2",
className,
@ -42,11 +45,10 @@ const Textarea2 = React.forwardRef<HTMLTextAreaElement, Textarea2Props>(
"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}
</div>
</motion.div>
);
},
);

View file

@ -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;
}

View file

@ -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))",