aboutsummaryrefslogtreecommitdiff
path: root/apps/web
diff options
context:
space:
mode:
authorKartik <[email protected]>2024-06-15 20:36:41 +0530
committerKartik <[email protected]>2024-06-15 20:36:41 +0530
commit8ecfc654c77262a40f8458b2b4e44c163ad094bd (patch)
tree1c2b3ad23bb55808acf98f0229b13d2f7e8649ef /apps/web
parentchore: Remove unused variables and dependencies (diff)
parentgetspaces and other features integrated with the backend (diff)
downloadsupermemory-8ecfc654c77262a40f8458b2b4e44c163ad094bd.tar.xz
supermemory-8ecfc654c77262a40f8458b2b4e44c163ad094bd.zip
Merge branch 'codetorso' of https://github.com/Dhravya/supermemory into codetorso
Diffstat (limited to 'apps/web')
-rw-r--r--apps/web/app/(dash)/actions.ts48
-rw-r--r--apps/web/app/(dash)/chat/CodeBlock.tsx90
-rw-r--r--apps/web/app/(dash)/chat/actions.ts1
-rw-r--r--apps/web/app/(dash)/chat/chatWindow.tsx184
-rw-r--r--apps/web/app/(dash)/chat/markdownRenderHelpers.tsx25
-rw-r--r--apps/web/app/(dash)/chat/page.tsx4
-rw-r--r--apps/web/app/(dash)/dynamicisland.tsx109
-rw-r--r--apps/web/app/(dash)/header.tsx2
-rw-r--r--apps/web/app/(dash)/home/page.tsx11
-rw-r--r--apps/web/app/(dash)/home/queryinput.tsx21
-rw-r--r--apps/web/app/(dash)/layout.tsx7
-rw-r--r--apps/web/app/(dash)/menu.tsx2
-rw-r--r--apps/web/app/(landing)/package.json5
-rw-r--r--apps/web/app/(landing)/page.tsx2
-rw-r--r--apps/web/app/actions/doers.ts43
-rw-r--r--apps/web/app/actions/fetchers.ts25
-rw-r--r--apps/web/app/actions/types.ts10
-rw-r--r--apps/web/app/api/chat/route.ts103
-rw-r--r--apps/web/app/helpers/constants.ts37
-rw-r--r--apps/web/app/helpers/server/auth.ts25
-rw-r--r--apps/web/app/helpers/server/db/schema.ts99
-rw-r--r--apps/web/migrations/000_setup.sql55
-rw-r--r--apps/web/migrations/meta/0000_snapshot.json158
-rw-r--r--apps/web/migrations/meta/_journal.json4
-rw-r--r--apps/web/next.config.mjs1
-rw-r--r--apps/web/package.json3
26 files changed, 794 insertions, 280 deletions
diff --git a/apps/web/app/(dash)/actions.ts b/apps/web/app/(dash)/actions.ts
deleted file mode 100644
index 70c2a567..00000000
--- a/apps/web/app/(dash)/actions.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-"use server";
-
-import { cookies, headers } from "next/headers";
-import { db } from "../helpers/server/db";
-import { sessions, users, space } from "../helpers/server/db/schema";
-import { eq } from "drizzle-orm";
-import { redirect } from "next/navigation";
-
-export async function ensureAuth() {
- 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 ", "");
-
- if (!token) {
- return undefined;
- }
-
- const sessionData = await db
- .select()
- .from(sessions)
- .innerJoin(users, eq(users.id, sessions.userId))
- .where(eq(sessions.sessionToken, token));
-
- if (!sessionData || sessionData.length < 0) {
- return undefined;
- }
-
- return {
- user: sessionData[0]!.user,
- session: sessionData[0]!,
- };
-}
-
-export async function getSpaces() {
- const data = await ensureAuth();
- if (!data) {
- redirect("/signin");
- }
-
- const sp = await db
- .select()
- .from(space)
- .where(eq(space.user, data.user.email));
-
- return sp;
-}
diff --git a/apps/web/app/(dash)/chat/CodeBlock.tsx b/apps/web/app/(dash)/chat/CodeBlock.tsx
new file mode 100644
index 00000000..0bb6a19d
--- /dev/null
+++ b/apps/web/app/(dash)/chat/CodeBlock.tsx
@@ -0,0 +1,90 @@
+import React, { useRef, useState } from "react";
+
+const CodeBlock = ({
+ lang,
+ codeChildren,
+}: {
+ lang: string;
+ codeChildren: React.ReactNode & React.ReactNode[];
+}) => {
+ const codeRef = useRef<HTMLElement>(null);
+
+ return (
+ <div className="bg-black rounded-md">
+ <CodeBar lang={lang} codeRef={codeRef} />
+ <div className="p-4 overflow-y-auto">
+ <code ref={codeRef} className={`!whitespace-pre hljs language-${lang}`}>
+ {codeChildren}
+ </code>
+ </div>
+ </div>
+ );
+};
+
+const CodeBar = React.memo(
+ ({
+ lang,
+ codeRef,
+ }: {
+ lang: string;
+ codeRef: React.RefObject<HTMLElement>;
+ }) => {
+ const [isCopied, setIsCopied] = useState<boolean>(false);
+ return (
+ <div className="flex items-center relative text-gray-200 bg-gray-800 px-4 py-2 text-xs font-sans">
+ <span className="">{lang}</span>
+ <button
+ className="flex ml-auto gap-2"
+ aria-label="copy codeblock"
+ onClick={async () => {
+ const codeString = codeRef.current?.textContent;
+ if (codeString)
+ navigator.clipboard.writeText(codeString).then(() => {
+ setIsCopied(true);
+ setTimeout(() => setIsCopied(false), 3000);
+ });
+ }}
+ >
+ {isCopied ? (
+ <>
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ viewBox="0 0 24 24"
+ strokeWidth={1.5}
+ stroke="currentColor"
+ className="size-4"
+ >
+ <path
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ d="M11.35 3.836c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75 2.25 2.25 0 0 0-.1-.664m-5.8 0A2.251 2.251 0 0 1 13.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m8.9-4.414c.376.023.75.05 1.124.08 1.131.094 1.976 1.057 1.976 2.192V16.5A2.25 2.25 0 0 1 18 18.75h-2.25m-7.5-10.5H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V18.75m-7.5-10.5h6.375c.621 0 1.125.504 1.125 1.125v9.375m-8.25-3 1.5 1.5 3-3.75"
+ />
+ </svg>
+ Copied!
+ </>
+ ) : (
+ <>
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ viewBox="0 0 24 24"
+ strokeWidth={1.5}
+ stroke="currentColor"
+ className="size-4"
+ >
+ <path
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 0 1-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 0 1 1.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 0 0-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 0 1-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H9.75"
+ />
+ </svg>
+ Copy code
+ </>
+ )}
+ </button>
+ </div>
+ );
+ },
+);
+export default CodeBlock;
diff --git a/apps/web/app/(dash)/chat/actions.ts b/apps/web/app/(dash)/chat/actions.ts
index 908fe79e..e69de29b 100644
--- a/apps/web/app/(dash)/chat/actions.ts
+++ b/apps/web/app/(dash)/chat/actions.ts
@@ -1 +0,0 @@
-"use server";
diff --git a/apps/web/app/(dash)/chat/chatWindow.tsx b/apps/web/app/(dash)/chat/chatWindow.tsx
index 43c337ee..b631c835 100644
--- a/apps/web/app/(dash)/chat/chatWindow.tsx
+++ b/apps/web/app/(dash)/chat/chatWindow.tsx
@@ -6,29 +6,92 @@ import QueryInput from "../home/queryinput";
import { cn } from "@repo/ui/lib/utils";
import { motion } from "framer-motion";
import { useRouter } from "next/navigation";
+import { ChatHistory } from "@repo/shared-types";
+import {
+ Accordion,
+ AccordionContent,
+ AccordionItem,
+ AccordionTrigger,
+} from "@repo/ui/shadcn/accordion";
+import Markdown from "react-markdown";
+import remarkGfm from "remark-gfm";
+import remarkMath from "remark-math";
+import rehypeKatex from "rehype-katex";
+import rehypeHighlight from "rehype-highlight";
+import { code, p } from "./markdownRenderHelpers";
+import { codeLanguageSubset } from "@/app/helpers/constants";
-function ChatWindow({ q }: { q: string }) {
+function ChatWindow({
+ q,
+ spaces,
+}: {
+ q: string;
+ spaces: { id: string; name: string }[];
+}) {
const [layout, setLayout] = useState<"chat" | "initial">("initial");
+ const [chatHistory, setChatHistory] = useState<ChatHistory[]>([
+ {
+ question: q,
+ answer: {
+ parts: [
+ // {
+ // text: `It seems like there might be a typo in your question. Could you please clarify or provide more context? If you meant "interesting," please let me know what specific information or topic you find interesting, and I can help you with that.`,
+ // },
+ ],
+ sources: [],
+ },
+ },
+ ]);
const router = useRouter();
+ const getAnswer = async (query: string, spaces: string[]) => {
+ const resp = await fetch(`/api/chat?q=${query}&spaces=${spaces}`, {
+ method: "POST",
+ body: JSON.stringify({ chatHistory }),
+ });
+
+ const reader = resp.body?.getReader();
+ let done = false;
+ let result = "";
+ while (!done && reader) {
+ const { value, done: d } = await reader.read();
+ done = d;
+
+ setChatHistory((prevChatHistory) => {
+ const newChatHistory = [...prevChatHistory];
+ const lastAnswer = newChatHistory[newChatHistory.length - 1];
+ if (!lastAnswer) return prevChatHistory;
+ lastAnswer.answer.parts.push({ text: new TextDecoder().decode(value) });
+ return newChatHistory;
+ });
+ }
+
+ console.log(result);
+ };
+
useEffect(() => {
- if (q !== "") {
+ if (q.trim().length > 0) {
+ getAnswer(
+ q,
+ spaces.map((s) => s.id),
+ );
setTimeout(() => {
setLayout("chat");
}, 300);
} else {
router.push("/home");
}
- }, [q]);
+ }, []);
+
return (
- <div>
+ <div className="h-full">
<AnimatePresence mode="popLayout">
{layout === "initial" ? (
<motion.div
exit={{ opacity: 0 }}
key="initial"
- className="max-w-3xl flex mx-auto w-full flex-col"
+ className="max-w-3xl h-full justify-center items-center flex mx-auto w-full flex-col"
>
<div className="w-full h-96">
<QueryInput initialQuery={q} initialSpaces={[]} disabled />
@@ -36,16 +99,111 @@ function ChatWindow({ q }: { q: string }) {
</motion.div>
) : (
<div
- className="max-w-3xl flex mx-auto w-full flex-col mt-8"
+ className="max-w-3xl flex mx-auto w-full flex-col mt-24"
key="chat"
>
- <h2
- className={cn(
- "transition-all transform translate-y-0 opacity-100 duration-500 ease-in-out font-semibold text-2xl",
- )}
- >
- {q}
- </h2>
+ {chatHistory.map((chat, idx) => (
+ <div
+ key={idx}
+ className={`mt-8 ${idx != chatHistory.length - 1 ? "pb-2 border-b" : ""}`}
+ >
+ <h2
+ className={cn(
+ "text-white transition-all transform translate-y-0 opacity-100 duration-500 ease-in-out font-semibold text-2xl",
+ )}
+ >
+ {chat.question}
+ </h2>
+
+ <div className="flex flex-col gap-2 mt-2">
+ <div
+ className={`${chat.answer.sources.length > 0 || chat.answer.parts.length === 0 ? "flex" : "hidden"}`}
+ >
+ <Accordion
+ defaultValue={
+ idx === chatHistory.length - 1 ? "memories" : ""
+ }
+ type="single"
+ collapsible
+ >
+ <AccordionItem value="memories">
+ <AccordionTrigger className="text-foreground-menu">
+ Related Memories
+ </AccordionTrigger>
+ {/* TODO: fade out content on the right side, the fade goes away when the user scrolls */}
+ <AccordionContent
+ className="relative flex gap-2 max-w-3xl overflow-auto no-scrollbar"
+ defaultChecked
+ >
+ {/* Loading state */}
+ {chat.answer.sources.length > 0 ||
+ (chat.answer.parts.length === 0 && (
+ <>
+ {[1, 2, 3, 4].map((_, idx) => (
+ <div
+ key={`loadingState-${idx}`}
+ className="rounded-xl bg-secondary p-4 flex flex-col gap-2 min-w-72 animate-pulse"
+ >
+ <div className="bg-slate-700 h-2 rounded-full w-1/2"></div>
+ <div className="bg-slate-700 h-2 rounded-full w-full"></div>
+ </div>
+ ))}
+ </>
+ ))}
+ {chat.answer.sources.map((source, idx) => (
+ <div
+ key={idx}
+ className="rounded-xl bg-secondary p-4 flex flex-col gap-2 min-w-72"
+ >
+ <div className="text-foreground-menu">
+ {source.type}
+ </div>
+ <div>{source.title}</div>
+ </div>
+ ))}
+ </AccordionContent>
+ </AccordionItem>
+ </Accordion>
+ </div>
+
+ {/* Summary */}
+ <div>
+ <div className="text-foreground-menu py-2">Summary</div>
+ <div className="text-base">
+ {chat.answer.parts.length === 0 && (
+ <div className="animate-pulse flex space-x-4">
+ <div className="flex-1 space-y-3 py-1">
+ <div className="h-2 bg-slate-700 rounded"></div>
+ <div className="h-2 bg-slate-700 rounded"></div>
+ </div>
+ </div>
+ )}
+ <Markdown
+ remarkPlugins={[remarkGfm, [remarkMath]]}
+ rehypePlugins={[
+ rehypeKatex,
+ [
+ rehypeHighlight,
+ {
+ detect: true,
+ ignoreMissing: true,
+ subset: codeLanguageSubset,
+ },
+ ],
+ ]}
+ components={{
+ code: code as any,
+ p: p as any,
+ }}
+ className="flex flex-col gap-2"
+ >
+ {chat.answer.parts.map((part) => part.text).join("")}
+ </Markdown>
+ </div>
+ </div>
+ </div>
+ </div>
+ ))}
</div>
)}
</AnimatePresence>
diff --git a/apps/web/app/(dash)/chat/markdownRenderHelpers.tsx b/apps/web/app/(dash)/chat/markdownRenderHelpers.tsx
new file mode 100644
index 00000000..747d4fca
--- /dev/null
+++ b/apps/web/app/(dash)/chat/markdownRenderHelpers.tsx
@@ -0,0 +1,25 @@
+import { DetailedHTMLProps, HTMLAttributes, memo } from "react";
+import { ExtraProps } from "react-markdown";
+import CodeBlock from "./CodeBlock";
+
+export const code = memo((props: JSX.IntrinsicElements["code"]) => {
+ const { className, children } = props;
+ const match = /language-(\w+)/.exec(className || "");
+ const lang = match && match[1];
+
+ return <CodeBlock lang={lang || "text"} codeChildren={children as any} />;
+});
+
+export const p = memo(
+ (
+ props?: Omit<
+ DetailedHTMLProps<
+ HTMLAttributes<HTMLParagraphElement>,
+ HTMLParagraphElement
+ >,
+ "ref"
+ >,
+ ) => {
+ return <p className="whitespace-pre-wrap">{props?.children}</p>;
+ },
+);
diff --git a/apps/web/app/(dash)/chat/page.tsx b/apps/web/app/(dash)/chat/page.tsx
index 9e28fda7..fd4de826 100644
--- a/apps/web/app/(dash)/chat/page.tsx
+++ b/apps/web/app/(dash)/chat/page.tsx
@@ -1,5 +1,7 @@
import ChatWindow from "./chatWindow";
import { chatSearchParamsCache } from "../../helpers/lib/searchParams";
+// @ts-expect-error
+await import("katex/dist/katex.min.css");
function Page({
searchParams,
@@ -10,7 +12,7 @@ function Page({
console.log(spaces);
- return <ChatWindow q={q} />;
+ return <ChatWindow q={q} spaces={[]} />;
}
export default Page;
diff --git a/apps/web/app/(dash)/dynamicisland.tsx b/apps/web/app/(dash)/dynamicisland.tsx
index b703d55a..31f76fda 100644
--- a/apps/web/app/(dash)/dynamicisland.tsx
+++ b/apps/web/app/(dash)/dynamicisland.tsx
@@ -9,6 +9,17 @@ import { motion } from "framer-motion";
import { Label } from "@repo/ui/shadcn/label";
import { Input } from "@repo/ui/shadcn/input";
import { Textarea } from "@repo/ui/shadcn/textarea";
+import { createSpace } from "../actions/doers";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@repo/ui/shadcn/select";
+import { Space } from "../actions/types";
+import { getSpaces } from "../actions/fetchers";
+import { toast } from "sonner";
export function DynamicIsland() {
const { scrollYProgress } = useScroll();
@@ -80,7 +91,7 @@ function DynamicIslandContent() {
{show ? (
<div
onClick={() => setshow(!show)}
- className="bg-[#1F2428] px-3 w-[2.23rem] overflow-hidden hover:w-[9.2rem] whitespace-nowrap py-2 rounded-3xl transition-[width] cursor-pointer"
+ className="bg-secondary px-3 w-[2.23rem] overflow-hidden hover:w-[9.2rem] whitespace-nowrap py-2 rounded-3xl transition-[width] cursor-pointer"
>
<div className="flex gap-4 items-center">
<Image src={AddIcon} alt="Add icon" />
@@ -99,7 +110,25 @@ function DynamicIslandContent() {
const fakeitems = ["spaces", "page", "note"];
function ToolBar({ cancelfn }: { cancelfn: () => void }) {
+ const [spaces, setSpaces] = useState<Space[]>([]);
+
const [index, setIndex] = useState(0);
+
+ useEffect(() => {
+ (async () => {
+ let spaces = await getSpaces();
+
+ if (!spaces.success || !spaces.data) {
+ toast.warning("Unable to get spaces", {
+ richColors: true,
+ });
+ setSpaces([]);
+ return;
+ }
+ setSpaces(spaces.data);
+ })();
+ }, []);
+
return (
<AnimatePresence mode="wait">
<motion.div
@@ -120,7 +149,7 @@ function ToolBar({ cancelfn }: { cancelfn: () => void }) {
}}
className="flex flex-col items-center"
>
- <div className="bg-[#1F2428] py-[.35rem] px-[.6rem] rounded-2xl">
+ <div className="bg-secondary py-[.35rem] px-[.6rem] rounded-2xl">
<HoverEffect
items={fakeitems}
index={index}
@@ -130,9 +159,9 @@ function ToolBar({ cancelfn }: { cancelfn: () => void }) {
{index === 0 ? (
<SpaceForm cancelfn={cancelfn} />
) : index === 1 ? (
- <PageForm cancelfn={cancelfn} />
+ <PageForm cancelfn={cancelfn} spaces={spaces} />
) : (
- <NoteForm cancelfn={cancelfn} />
+ <NoteForm cancelfn={cancelfn} spaces={spaces} />
)}
</motion.div>
</AnimatePresence>
@@ -182,7 +211,10 @@ export const HoverEffect = ({
function SpaceForm({ cancelfn }: { cancelfn: () => void }) {
return (
- <div className="bg-[#1F2428] px-4 py-3 rounded-2xl mt-2 flex flex-col gap-3">
+ <form
+ action={createSpace}
+ className="bg-secondary border border-muted-foreground px-4 py-3 rounded-2xl mt-2 flex flex-col gap-3"
+ >
<div>
<Label className="text-[#858B92]" htmlFor="name">
Name
@@ -190,34 +222,55 @@ function SpaceForm({ cancelfn }: { cancelfn: () => void }) {
<Input
className="bg-[#2B3237] focus-visible:ring-0 border-none focus-visible:ring-offset-0"
id="name"
+ name="name"
/>
</div>
<div className="flex justify-between">
<a className="text-blue-500" href="">
pull from store
</a>
- <div
+ {/* <div
onClick={cancelfn}
className="bg-[#2B3237] px-2 py-1 rounded-xl cursor-pointer"
>
cancel
- </div>
+ </div> */}
+ <button
+ type="submit"
+ className="bg-[#2B3237] px-2 py-1 rounded-xl cursor-pointer"
+ >
+ Submit
+ </button>
</div>
- </div>
+ </form>
);
}
-function PageForm({ cancelfn }: { cancelfn: () => void }) {
+function PageForm({
+ cancelfn,
+ spaces,
+}: {
+ cancelfn: () => void;
+ spaces: Space[];
+}) {
return (
- <div className="bg-[#1F2428] px-4 py-3 rounded-2xl mt-2 flex flex-col gap-3">
+ <div className="bg-secondary border border-muted-foreground px-4 py-3 rounded-2xl mt-2 flex flex-col gap-3">
<div>
- <Label className="text-[#858B92]" htmlFor="name">
+ <Label className="text-[#858B92]" htmlFor="space">
Space
</Label>
- <Input
- className="bg-[#2B3237] focus-visible:ring-0 border-none focus-visible:ring-offset-0"
- id="name"
- />
+ <Select>
+ <SelectTrigger>
+ <SelectValue placeholder="Space" />
+ </SelectTrigger>
+ <SelectContent className="bg-secondary text-white">
+ {spaces.map((space) => (
+ <SelectItem key={space.id} value={space.id.toString()}>
+ {space.name}
+ </SelectItem>
+ ))}
+ </SelectContent>
+ </Select>
</div>
<div>
<Label className="text-[#858B92]" htmlFor="name">
@@ -240,17 +293,31 @@ function PageForm({ cancelfn }: { cancelfn: () => void }) {
);
}
-function NoteForm({ cancelfn }: { cancelfn: () => void }) {
+function NoteForm({
+ cancelfn,
+ spaces,
+}: {
+ cancelfn: () => void;
+ spaces: Space[];
+}) {
return (
- <div className="bg-[#1F2428] px-4 py-3 rounded-2xl mt-2 flex flex-col gap-3">
+ <div className="bg-secondary border border-muted-foreground px-4 py-3 rounded-2xl mt-2 flex flex-col gap-3">
<div>
<Label className="text-[#858B92]" htmlFor="name">
Space
</Label>
- <Input
- className="bg-[#2B3237] focus-visible:ring-0 border-none focus-visible:ring-offset-0"
- id="name"
- />
+ <Select>
+ <SelectTrigger>
+ <SelectValue placeholder="Space" />
+ </SelectTrigger>
+ <SelectContent className="bg-secondary text-white">
+ {spaces.map((space) => (
+ <SelectItem key={space.id} value={space.id.toString()}>
+ {space.name}
+ </SelectItem>
+ ))}
+ </SelectContent>
+ </Select>
</div>
<div>
<Label className="text-[#858B92]" htmlFor="name">
diff --git a/apps/web/app/(dash)/header.tsx b/apps/web/app/(dash)/header.tsx
index c5aeca3b..026cb080 100644
--- a/apps/web/app/(dash)/header.tsx
+++ b/apps/web/app/(dash)/header.tsx
@@ -9,7 +9,7 @@ import DynamicIsland from "./dynamicisland";
function Header() {
return (
<div>
- <div className="absolute left-0 w-full flex items-center justify-between z-10">
+ <div className="fixed left-0 w-full flex items-center justify-between z-10">
<Link className="px-5" href="/home">
<Image
src={Logo}
diff --git a/apps/web/app/(dash)/home/page.tsx b/apps/web/app/(dash)/home/page.tsx
index 0c75e457..b4bafb38 100644
--- a/apps/web/app/(dash)/home/page.tsx
+++ b/apps/web/app/(dash)/home/page.tsx
@@ -3,7 +3,7 @@ import Menu from "../menu";
import Header from "../header";
import QueryInput from "./queryinput";
import { homeSearchParamsCache } from "@/app/helpers/lib/searchParams";
-import { getSpaces } from "../actions";
+import { getSpaces } from "@/app/actions/fetchers";
async function Page({
searchParams,
@@ -13,7 +13,12 @@ async function Page({
// TODO: use this to show a welcome page/modal
const { firstTime } = homeSearchParamsCache.parse(searchParams);
- const spaces = await getSpaces();
+ let spaces = await getSpaces();
+
+ if (!spaces.success) {
+ // TODO: handle this error properly.
+ spaces.data = [];
+ }
return (
<div className="max-w-3xl h-full justify-center flex mx-auto w-full flex-col">
@@ -21,7 +26,7 @@ async function Page({
{/* <div className="">hi {firstTime ? 'first time' : ''}</div> */}
<div className="w-full h-96">
- <QueryInput initialSpaces={spaces} />
+ <QueryInput initialSpaces={spaces.data} />
</div>
</div>
);
diff --git a/apps/web/app/(dash)/home/queryinput.tsx b/apps/web/app/(dash)/home/queryinput.tsx
index 4cb1fdb2..d0c27b8d 100644
--- a/apps/web/app/(dash)/home/queryinput.tsx
+++ b/apps/web/app/(dash)/home/queryinput.tsx
@@ -2,10 +2,11 @@
import { ArrowRightIcon } from "@repo/ui/icons";
import Image from "next/image";
-import React, { useState } from "react";
+import React, { useEffect, useMemo, useState } from "react";
import Divider from "@repo/ui/shadcn/divider";
import { MultipleSelector, Option } from "@repo/ui/shadcn/combobox";
import { useRouter } from "next/navigation";
+import { getSpaces } from "@/app/actions/fetchers";
function QueryInput({
initialQuery = "",
@@ -13,7 +14,10 @@ function QueryInput({
disabled = false,
}: {
initialQuery?: string;
- initialSpaces?: { user: string | null; id: number; name: string }[];
+ initialSpaces?: {
+ id: number;
+ name: string;
+ }[];
disabled?: boolean;
}) {
const [q, setQ] = useState(initialQuery);
@@ -41,10 +45,14 @@ function QueryInput({
return newQ;
};
- const options = initialSpaces.map((x) => ({
- label: x.name,
- value: x.id.toString(),
- }));
+ const options = useMemo(
+ () =>
+ initialSpaces.map((x) => ({
+ label: x.name,
+ value: x.id.toString(),
+ })),
+ [initialSpaces],
+ );
return (
<div>
@@ -82,6 +90,7 @@ function QueryInput({
{/* selected sources */}
<div className="flex items-center gap-6 p-2 h-auto bg-secondary rounded-b-[24px]">
<MultipleSelector
+ key={options.length}
disabled={disabled}
defaultOptions={options}
onChange={(e) => setSelectedSpaces(e.map((x) => parseInt(x.value)))}
diff --git a/apps/web/app/(dash)/layout.tsx b/apps/web/app/(dash)/layout.tsx
index 85f8476e..b879a2f5 100644
--- a/apps/web/app/(dash)/layout.tsx
+++ b/apps/web/app/(dash)/layout.tsx
@@ -1,10 +1,11 @@
import Header from "./header";
import Menu from "./menu";
-import { ensureAuth } from "./actions";
import { redirect } from "next/navigation";
+import { auth } from "../helpers/server/auth";
+import { Toaster } from "@repo/ui/shadcn/sonner";
async function Layout({ children }: { children: React.ReactNode }) {
- const info = await ensureAuth();
+ const info = await auth();
if (!info) {
return redirect("/signin");
@@ -17,6 +18,8 @@ async function Layout({ children }: { children: React.ReactNode }) {
<Menu />
{children}
+
+ <Toaster />
</main>
);
}
diff --git a/apps/web/app/(dash)/menu.tsx b/apps/web/app/(dash)/menu.tsx
index dfd60b96..5f26f545 100644
--- a/apps/web/app/(dash)/menu.tsx
+++ b/apps/web/app/(dash)/menu.tsx
@@ -23,7 +23,7 @@ function Menu() {
];
return (
- <div className="absolute h-screen pb-[25vh] w-full p-4 flex items-end justify-end lg:justify-start lg:items-center top-0 left-0 pointer-events-none">
+ <div className="fixed h-screen pb-[25vh] w-full p-4 flex items-end justify-end lg:justify-start lg:items-center top-0 left-0 pointer-events-none">
<div className="">
<div className="pointer-events-auto group flex w-14 text-foreground-menu text-[15px] font-medium flex-col items-start gap-6 overflow-hidden rounded-[28px] bg-secondary px-3 py-4 duration-200 hover:w-40">
{menuItems.map((item) => (
diff --git a/apps/web/app/(landing)/package.json b/apps/web/app/(landing)/package.json
deleted file mode 100644
index a7fabf2f..00000000
--- a/apps/web/app/(landing)/package.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "scripts": {
- "vercel-build": "next build"
- }
-}
diff --git a/apps/web/app/(landing)/page.tsx b/apps/web/app/(landing)/page.tsx
index 562e9af9..09f94d92 100644
--- a/apps/web/app/(landing)/page.tsx
+++ b/apps/web/app/(landing)/page.tsx
@@ -16,7 +16,7 @@ export default async function Home() {
console.log(user);
if (user) {
- // await redirect("/home")
+ await redirect("/home");
}
return (
diff --git a/apps/web/app/actions/doers.ts b/apps/web/app/actions/doers.ts
new file mode 100644
index 00000000..c8a1f3b4
--- /dev/null
+++ b/apps/web/app/actions/doers.ts
@@ -0,0 +1,43 @@
+"use server";
+
+import { revalidatePath } from "next/cache";
+import { db } from "../helpers/server/db";
+import { space } from "../helpers/server/db/schema";
+import { ServerActionReturnType } from "./types";
+import { auth } from "../helpers/server/auth";
+
+export const createSpace = async (
+ input: string | FormData,
+): ServerActionReturnType<number> => {
+ const data = await auth();
+
+ if (!data || !data.user) {
+ return { error: "Not authenticated", success: false };
+ }
+
+ if (typeof input === "object") {
+ input = (input as FormData).get("name") as string;
+ }
+
+ try {
+ const resp = await db
+ .insert(space)
+ .values({ name: input, user: data.user.id });
+
+ revalidatePath("/home");
+ return { success: true, data: 1 };
+ } catch (e: unknown) {
+ const error = e as Error;
+ if (
+ error.message.includes("D1_ERROR: UNIQUE constraint failed: space.name")
+ ) {
+ return { success: false, data: 0, error: "Space already exists" };
+ } else {
+ return {
+ success: false,
+ data: 0,
+ error: "Failed to create space with error: " + error.message,
+ };
+ }
+ }
+};
diff --git a/apps/web/app/actions/fetchers.ts b/apps/web/app/actions/fetchers.ts
new file mode 100644
index 00000000..9c2527f0
--- /dev/null
+++ b/apps/web/app/actions/fetchers.ts
@@ -0,0 +1,25 @@
+"use server";
+
+import { eq } from "drizzle-orm";
+import { db } from "../helpers/server/db";
+import { users } from "../helpers/server/db/schema";
+import { ServerActionReturnType, Space } from "./types";
+import { auth } from "../helpers/server/auth";
+
+export const getSpaces = async (): ServerActionReturnType<Space[]> => {
+ const data = await auth();
+
+ if (!data || !data.user) {
+ return { error: "Not authenticated", success: false };
+ }
+
+ const spaces = await db.query.space.findMany({
+ where: eq(users, data.user.id),
+ });
+
+ const spacesWithoutUser = spaces.map((space) => {
+ return { ...space, user: undefined };
+ });
+
+ return { success: true, data: spacesWithoutUser };
+};
diff --git a/apps/web/app/actions/types.ts b/apps/web/app/actions/types.ts
new file mode 100644
index 00000000..fbf669e2
--- /dev/null
+++ b/apps/web/app/actions/types.ts
@@ -0,0 +1,10 @@
+export type Space = {
+ id: number;
+ name: string;
+};
+
+export type ServerActionReturnType<T> = Promise<{
+ error?: string;
+ success: boolean;
+ data?: T;
+}>;
diff --git a/apps/web/app/api/chat/route.ts b/apps/web/app/api/chat/route.ts
index 34099848..aba8784c 100644
--- a/apps/web/app/api/chat/route.ts
+++ b/apps/web/app/api/chat/route.ts
@@ -1,6 +1,11 @@
import { type NextRequest } from "next/server";
-import { ChatHistory } from "@repo/shared-types";
+import {
+ ChatHistory,
+ ChatHistoryZod,
+ convertChatHistoryList,
+} from "@repo/shared-types";
import { ensureAuth } from "../ensureAuth";
+import { z } from "zod";
export const runtime = "edge";
@@ -15,59 +20,69 @@ export async function POST(req: NextRequest) {
return new Response("Missing BACKEND_SECURITY_KEY", { status: 500 });
}
- const query = new URL(req.url).searchParams.get("q");
- const spaces = new URL(req.url).searchParams.get("spaces");
+ const url = new URL(req.url);
- const sourcesOnly =
- new URL(req.url).searchParams.get("sourcesOnly") ?? "false";
+ const query = url.searchParams.get("q");
+ const spaces = url.searchParams.get("spaces");
- const chatHistory = (await req.json()) as {
- chatHistory: ChatHistory[];
- };
+ const sourcesOnly = url.searchParams.get("sourcesOnly") ?? "false";
- console.log("CHathistory", chatHistory);
+ const chatHistory = await req.json();
- if (!query) {
+ if (!query || query.trim.length < 0) {
return new Response(JSON.stringify({ message: "Invalid query" }), {
status: 400,
});
}
- try {
- const resp = await fetch(
- `https://cf-ai-backend.dhravya.workers.dev/chat?q=${query}&user=${session.user.email ?? session.user.name}&sourcesOnly=${sourcesOnly}&spaces=${spaces}`,
- {
- headers: {
- "X-Custom-Auth-Key": process.env.BACKEND_SECURITY_KEY!,
- },
- method: "POST",
- body: JSON.stringify({
- chatHistory: chatHistory.chatHistory ?? [],
- }),
+ const validated = z
+ .object({ chatHistory: z.array(ChatHistoryZod) })
+ .safeParse(chatHistory ?? []);
+
+ if (!validated.success) {
+ return new Response(
+ JSON.stringify({
+ message: "Invalid chat history",
+ error: validated.error,
+ }),
+ { status: 400 },
+ );
+ }
+
+ const modelCompatible = await convertChatHistoryList(
+ validated.data.chatHistory,
+ );
+
+ const resp = await fetch(
+ `https://new-cf-ai-backend.dhravya.workers.dev/api/chat?query=${query}&user=${session.user.email}&sourcesOnly=${sourcesOnly}&spaces=${spaces}`,
+ {
+ headers: {
+ Authorization: `Bearer ${process.env.BACKEND_SECURITY_KEY}`,
+ "Content-Type": "application/json",
},
+ method: "POST",
+ body: JSON.stringify({
+ chatHistory: modelCompatible,
+ }),
+ },
+ );
+
+ console.log("sourcesOnly", sourcesOnly);
+
+ if (sourcesOnly == "true") {
+ const data = await resp.json();
+ console.log("data", data);
+ return new Response(JSON.stringify(data), { status: 200 });
+ }
+
+ if (resp.status !== 200 || !resp.ok) {
+ const errorData = await resp.text();
+ console.log(errorData);
+ return new Response(
+ JSON.stringify({ message: "Error in CF function", error: errorData }),
+ { status: resp.status },
);
+ }
- console.log("sourcesOnly", sourcesOnly);
-
- if (sourcesOnly == "true") {
- const data = await resp.json();
- console.log("data", data);
- return new Response(JSON.stringify(data), { status: 200 });
- }
-
- if (resp.status !== 200 || !resp.ok) {
- const errorData = await resp.json();
- console.log(errorData);
- return new Response(
- JSON.stringify({ message: "Error in CF function", error: errorData }),
- { status: resp.status },
- );
- }
-
- // Stream the response back to the client
- const { readable, writable } = new TransformStream();
- resp && resp.body!.pipeTo(writable);
-
- return new Response(readable, { status: 200 });
- } catch {}
+ return new Response(resp.body, { status: 200 });
}
diff --git a/apps/web/app/helpers/constants.ts b/apps/web/app/helpers/constants.ts
new file mode 100644
index 00000000..c3fc640a
--- /dev/null
+++ b/apps/web/app/helpers/constants.ts
@@ -0,0 +1,37 @@
+export const codeLanguageSubset = [
+ "python",
+ "javascript",
+ "java",
+ "go",
+ "bash",
+ "c",
+ "cpp",
+ "csharp",
+ "css",
+ "diff",
+ "graphql",
+ "json",
+ "kotlin",
+ "less",
+ "lua",
+ "makefile",
+ "markdown",
+ "objectivec",
+ "perl",
+ "php",
+ "php-template",
+ "plaintext",
+ "python-repl",
+ "r",
+ "ruby",
+ "rust",
+ "scss",
+ "shell",
+ "sql",
+ "swift",
+ "typescript",
+ "vbnet",
+ "wasm",
+ "xml",
+ "yaml",
+];
diff --git a/apps/web/app/helpers/server/auth.ts b/apps/web/app/helpers/server/auth.ts
index 73119d87..c4e426d4 100644
--- a/apps/web/app/helpers/server/auth.ts
+++ b/apps/web/app/helpers/server/auth.ts
@@ -2,6 +2,7 @@ import NextAuth, { NextAuthResult } from "next-auth";
import Google from "next-auth/providers/google";
import { DrizzleAdapter } from "@auth/drizzle-adapter";
import { db } from "./db";
+import { accounts, sessions, users, verificationTokens } from "./db/schema";
export const {
handlers: { GET, POST },
@@ -10,16 +11,20 @@ export const {
auth,
} = NextAuth({
secret: process.env.BACKEND_SECURITY_KEY,
- callbacks: {
- session: ({ session, token, user }) => ({
- ...session,
- user: {
- ...session.user,
- id: user.id,
- },
- }),
- },
- adapter: DrizzleAdapter(db),
+ // callbacks: {
+ // session: ({ session, token, user }) => ({
+ // ...session,
+ // user: {
+ // ...session.user,
+ // },
+ // }),
+ // },
+ adapter: DrizzleAdapter(db, {
+ usersTable: users,
+ accountsTable: accounts,
+ sessionsTable: sessions,
+ verificationTokensTable: verificationTokens,
+ }),
providers: [
Google({
clientId: process.env.GOOGLE_CLIENT_ID,
diff --git a/apps/web/app/helpers/server/db/schema.ts b/apps/web/app/helpers/server/db/schema.ts
index c4616eb2..e3e789c6 100644
--- a/apps/web/app/helpers/server/db/schema.ts
+++ b/apps/web/app/helpers/server/db/schema.ts
@@ -7,75 +7,88 @@ import {
text,
integer,
} from "drizzle-orm/sqlite-core";
+import type { AdapterAccountType } from "next-auth/adapters";
export const createTable = sqliteTableCreator((name) => `${name}`);
export const users = createTable("user", {
- id: text("id", { length: 255 }).notNull().primaryKey(),
- name: text("name", { length: 255 }),
- email: text("email", { length: 255 }).notNull(),
- emailVerified: int("emailVerified", { mode: "timestamp" }).default(
- sql`CURRENT_TIMESTAMP`,
- ),
- image: text("image", { length: 255 }),
+ id: text("id")
+ .primaryKey()
+ .$defaultFn(() => crypto.randomUUID()),
+ name: text("name"),
+ email: text("email").notNull(),
+ emailVerified: integer("emailVerified", { mode: "timestamp_ms" }),
+ image: text("image"),
});
export type User = typeof users.$inferSelect;
-export const usersRelations = relations(users, ({ many }) => ({
- accounts: many(accounts),
- sessions: many(sessions),
-}));
-
export const accounts = createTable(
"account",
{
- id: integer("id").notNull().primaryKey({ autoIncrement: true }),
- userId: text("userId", { length: 255 })
+ userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
- type: text("type", { length: 255 }).notNull(),
- provider: text("provider", { length: 255 }).notNull(),
- providerAccountId: text("providerAccountId", { length: 255 }).notNull(),
+ type: text("type").$type<AdapterAccountType>().notNull(),
+ provider: text("provider").notNull(),
+ providerAccountId: text("providerAccountId").notNull(),
refresh_token: text("refresh_token"),
access_token: text("access_token"),
- expires_at: int("expires_at"),
- token_type: text("token_type", { length: 255 }),
- scope: text("scope", { length: 255 }),
+ expires_at: integer("expires_at"),
+ token_type: text("token_type"),
+ scope: text("scope"),
id_token: text("id_token"),
- session_state: text("session_state", { length: 255 }),
- oauth_token_secret: text("oauth_token_secret"),
- oauth_token: text("oauth_token"),
+ session_state: text("session_state"),
},
(account) => ({
- userIdIdx: index("account_userId_idx").on(account.userId),
+ compoundKey: primaryKey({
+ columns: [account.provider, account.providerAccountId],
+ }),
}),
);
-export const sessions = createTable(
- "session",
+export const sessions = createTable("session", {
+ sessionToken: text("sessionToken").primaryKey(),
+ userId: text("userId")
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
+});
+
+export const verificationTokens = createTable(
+ "verificationToken",
{
- id: integer("id").notNull().primaryKey({ autoIncrement: true }),
- sessionToken: text("sessionToken", { length: 255 }).notNull(),
- userId: text("userId", { length: 255 })
- .notNull()
- .references(() => users.id, { onDelete: "cascade" }),
- expires: int("expires", { mode: "timestamp" }).notNull(),
+ identifier: text("identifier").notNull(),
+ token: text("token").notNull(),
+ expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
},
- (session) => ({
- userIdIdx: index("session_userId_idx").on(session.userId),
+ (verificationToken) => ({
+ compositePk: primaryKey({
+ columns: [verificationToken.identifier, verificationToken.token],
+ }),
}),
);
-export const verificationTokens = createTable(
- "verificationToken",
+export const authenticators = createTable(
+ "authenticator",
{
- identifier: text("identifier", { length: 255 }).notNull(),
- token: text("token", { length: 255 }).notNull(),
- expires: int("expires", { mode: "timestamp" }).notNull(),
+ credentialID: text("credentialID").notNull().unique(),
+ userId: text("userId")
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ providerAccountId: text("providerAccountId").notNull(),
+ credentialPublicKey: text("credentialPublicKey").notNull(),
+ counter: integer("counter").notNull(),
+ credentialDeviceType: text("credentialDeviceType").notNull(),
+ credentialBackedUp: integer("credentialBackedUp", {
+ mode: "boolean",
+ }).notNull(),
+ transports: text("transports"),
},
- (vt) => ({
- compoundKey: primaryKey({ columns: [vt.identifier, vt.token] }),
+ (authenticator) => ({
+ compositePK: primaryKey({
+ columns: [authenticator.userId, authenticator.credentialID],
+ }),
}),
);
@@ -94,7 +107,7 @@ export const storedContent = createTable(
"page",
),
image: text("image", { length: 255 }),
- user: text("user", { length: 255 }).references(() => users.id, {
+ userId: int("user").references(() => users.id, {
onDelete: "cascade",
}),
},
@@ -102,7 +115,7 @@ export const storedContent = createTable(
urlIdx: index("storedContent_url_idx").on(sc.url),
savedAtIdx: index("storedContent_savedAt_idx").on(sc.savedAt),
titleInx: index("storedContent_title_idx").on(sc.title),
- userIdx: index("storedContent_user_idx").on(sc.user),
+ userIdx: index("storedContent_user_idx").on(sc.userId),
}),
);
diff --git a/apps/web/migrations/000_setup.sql b/apps/web/migrations/000_setup.sql
index db7f9444..0c151b98 100644
--- a/apps/web/migrations/000_setup.sql
+++ b/apps/web/migrations/000_setup.sql
@@ -1,18 +1,29 @@
CREATE TABLE `account` (
- `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
- `userId` text(255) NOT NULL,
- `type` text(255) NOT NULL,
- `provider` text(255) NOT NULL,
- `providerAccountId` text(255) NOT NULL,
+ `userId` text NOT NULL,
+ `type` text NOT NULL,
+ `provider` text NOT NULL,
+ `providerAccountId` text NOT NULL,
`refresh_token` text,
`access_token` text,
`expires_at` integer,
- `token_type` text(255),
- `scope` text(255),
+ `token_type` text,
+ `scope` text,
`id_token` text,
- `session_state` text(255),
- `oauth_token_secret` text,
- `oauth_token` text,
+ `session_state` text,
+ PRIMARY KEY(`provider`, `providerAccountId`),
+ FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `authenticator` (
+ `credentialID` text NOT NULL,
+ `userId` text NOT NULL,
+ `providerAccountId` text NOT NULL,
+ `credentialPublicKey` text NOT NULL,
+ `counter` integer NOT NULL,
+ `credentialDeviceType` text NOT NULL,
+ `credentialBackedUp` integer NOT NULL,
+ `transports` text,
+ PRIMARY KEY(`credentialID`, `userId`),
FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
@@ -25,9 +36,8 @@ CREATE TABLE `contentToSpace` (
);
--> statement-breakpoint
CREATE TABLE `session` (
- `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
- `sessionToken` text(255) NOT NULL,
- `userId` text(255) NOT NULL,
+ `sessionToken` text PRIMARY KEY NOT NULL,
+ `userId` text NOT NULL,
`expires` integer NOT NULL,
FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
);
@@ -50,27 +60,26 @@ CREATE TABLE `storedContent` (
`ogImage` text(255),
`type` text DEFAULT 'page',
`image` text(255),
- `user` text(255),
+ `user` integer,
FOREIGN KEY (`user`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `user` (
- `id` text(255) PRIMARY KEY NOT NULL,
- `name` text(255),
- `email` text(255) NOT NULL,
- `emailVerified` integer DEFAULT CURRENT_TIMESTAMP,
- `image` text(255)
+ `id` text PRIMARY KEY NOT NULL,
+ `name` text,
+ `email` text NOT NULL,
+ `emailVerified` integer,
+ `image` text
);
--> statement-breakpoint
CREATE TABLE `verificationToken` (
- `identifier` text(255) NOT NULL,
- `token` text(255) NOT NULL,
+ `identifier` text NOT NULL,
+ `token` text NOT NULL,
`expires` integer NOT NULL,
PRIMARY KEY(`identifier`, `token`)
);
--> statement-breakpoint
-CREATE INDEX `account_userId_idx` ON `account` (`userId`);--> statement-breakpoint
-CREATE INDEX `session_userId_idx` ON `session` (`userId`);--> statement-breakpoint
+CREATE UNIQUE INDEX `authenticator_credentialID_unique` ON `authenticator` (`credentialID`);--> statement-breakpoint
CREATE UNIQUE INDEX `space_name_unique` ON `space` (`name`);--> statement-breakpoint
CREATE INDEX `spaces_name_idx` ON `space` (`name`);--> statement-breakpoint
CREATE INDEX `spaces_user_idx` ON `space` (`user`);--> statement-breakpoint
diff --git a/apps/web/migrations/meta/0000_snapshot.json b/apps/web/migrations/meta/0000_snapshot.json
index 29cc4323..20327dda 100644
--- a/apps/web/migrations/meta/0000_snapshot.json
+++ b/apps/web/migrations/meta/0000_snapshot.json
@@ -1,43 +1,36 @@
{
"version": "6",
"dialect": "sqlite",
- "id": "409cec60-0c4b-4cda-8751-3e70768bbb6c",
+ "id": "4a568d9b-a0e6-44ed-946b-694e34b063f3",
"prevId": "00000000-0000-0000-0000-000000000000",
"tables": {
"account": {
"name": "account",
"columns": {
- "id": {
- "name": "id",
- "type": "integer",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": true
- },
"userId": {
"name": "userId",
- "type": "text(255)",
+ "type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"type": {
"name": "type",
- "type": "text(255)",
+ "type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"provider": {
"name": "provider",
- "type": "text(255)",
+ "type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"providerAccountId": {
"name": "providerAccountId",
- "type": "text(255)",
+ "type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
@@ -65,14 +58,14 @@
},
"token_type": {
"name": "token_type",
- "type": "text(255)",
+ "type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"scope": {
"name": "scope",
- "type": "text(255)",
+ "type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
@@ -86,20 +79,86 @@
},
"session_state": {
"name": "session_state",
- "type": "text(255)",
+ "type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "account_userId_user_id_fk": {
+ "name": "account_userId_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": ["userId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "account_provider_providerAccountId_pk": {
+ "columns": ["provider", "providerAccountId"],
+ "name": "account_provider_providerAccountId_pk"
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "authenticator": {
+ "name": "authenticator",
+ "columns": {
+ "credentialID": {
+ "name": "credentialID",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
},
- "oauth_token_secret": {
- "name": "oauth_token_secret",
+ "userId": {
+ "name": "userId",
"type": "text",
"primaryKey": false,
- "notNull": false,
+ "notNull": true,
"autoincrement": false
},
- "oauth_token": {
- "name": "oauth_token",
+ "providerAccountId": {
+ "name": "providerAccountId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "credentialPublicKey": {
+ "name": "credentialPublicKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "counter": {
+ "name": "counter",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "credentialDeviceType": {
+ "name": "credentialDeviceType",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "credentialBackedUp": {
+ "name": "credentialBackedUp",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "transports": {
+ "name": "transports",
"type": "text",
"primaryKey": false,
"notNull": false,
@@ -107,16 +166,16 @@
}
},
"indexes": {
- "account_userId_idx": {
- "name": "account_userId_idx",
- "columns": ["userId"],
- "isUnique": false
+ "authenticator_credentialID_unique": {
+ "name": "authenticator_credentialID_unique",
+ "columns": ["credentialID"],
+ "isUnique": true
}
},
"foreignKeys": {
- "account_userId_user_id_fk": {
- "name": "account_userId_user_id_fk",
- "tableFrom": "account",
+ "authenticator_userId_user_id_fk": {
+ "name": "authenticator_userId_user_id_fk",
+ "tableFrom": "authenticator",
"tableTo": "user",
"columnsFrom": ["userId"],
"columnsTo": ["id"],
@@ -124,7 +183,12 @@
"onUpdate": "no action"
}
},
- "compositePrimaryKeys": {},
+ "compositePrimaryKeys": {
+ "authenticator_userId_credentialID_pk": {
+ "columns": ["credentialID", "userId"],
+ "name": "authenticator_userId_credentialID_pk"
+ }
+ },
"uniqueConstraints": {}
},
"contentToSpace": {
@@ -177,23 +241,16 @@
"session": {
"name": "session",
"columns": {
- "id": {
- "name": "id",
- "type": "integer",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": true
- },
"sessionToken": {
"name": "sessionToken",
- "type": "text(255)",
- "primaryKey": false,
+ "type": "text",
+ "primaryKey": true,
"notNull": true,
"autoincrement": false
},
"userId": {
"name": "userId",
- "type": "text(255)",
+ "type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
@@ -206,13 +263,7 @@
"autoincrement": false
}
},
- "indexes": {
- "session_userId_idx": {
- "name": "session_userId_idx",
- "columns": ["userId"],
- "isUnique": false
- }
- },
+ "indexes": {},
"foreignKeys": {
"session_userId_user_id_fk": {
"name": "session_userId_user_id_fk",
@@ -360,7 +411,7 @@
},
"user": {
"name": "user",
- "type": "text(255)",
+ "type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
@@ -407,21 +458,21 @@
"columns": {
"id": {
"name": "id",
- "type": "text(255)",
+ "type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"name": {
"name": "name",
- "type": "text(255)",
+ "type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"email": {
"name": "email",
- "type": "text(255)",
+ "type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
@@ -431,12 +482,11 @@
"type": "integer",
"primaryKey": false,
"notNull": false,
- "autoincrement": false,
- "default": "CURRENT_TIMESTAMP"
+ "autoincrement": false
},
"image": {
"name": "image",
- "type": "text(255)",
+ "type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
@@ -452,14 +502,14 @@
"columns": {
"identifier": {
"name": "identifier",
- "type": "text(255)",
+ "type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"token": {
"name": "token",
- "type": "text(255)",
+ "type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
diff --git a/apps/web/migrations/meta/_journal.json b/apps/web/migrations/meta/_journal.json
index a77d9616..90bb9df7 100644
--- a/apps/web/migrations/meta/_journal.json
+++ b/apps/web/migrations/meta/_journal.json
@@ -5,8 +5,8 @@
{
"idx": 0,
"version": "6",
- "when": 1716677954608,
- "tag": "0000_calm_monster_badoon",
+ "when": 1718412145023,
+ "tag": "0000_absurd_pandemic",
"breakpoints": true
}
]
diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs
index 51c492f4..cc820fef 100644
--- a/apps/web/next.config.mjs
+++ b/apps/web/next.config.mjs
@@ -4,6 +4,7 @@ import { setupDevPlatform } from "@cloudflare/next-on-pages/next-dev";
/** @type {import('next').NextConfig} */
const nextConfig = {
transpilePackages: ["@repo/ui"],
+ reactStrictMode: false,
};
export default MillionLint.next({
rsc: true,
diff --git a/apps/web/package.json b/apps/web/package.json
index 324f5655..19f77187 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -11,7 +11,8 @@
"pages:build": "bunx @cloudflare/next-on-pages",
"preview": "bun pages:build && wrangler pages dev",
"deploy": "bun pages:build && wrangler pages deploy",
- "schema-update": "bunx drizzle-orm"
+ "schema-update": "bunx drizzle-kit generate sqlite",
+ "update-local-db": "bunx wrangler d1 execute dev-d1-anycontext --local"
},
"dependencies": {
"@million/lint": "^1.0.0-rc.11",