aboutsummaryrefslogtreecommitdiff
path: root/apps/web/app
diff options
context:
space:
mode:
authorcodetorso <[email protected]>2024-07-25 10:56:32 +0530
committercodetorso <[email protected]>2024-07-25 10:56:32 +0530
commitc7b98a39b8024c96f1230a513a6d64d763b74277 (patch)
tree0bc3260cfce7cbf5666948f21864806b6d2a9760 /apps/web/app
parentparse suggestions for handling edge case (diff)
downloadsupermemory-c7b98a39b8024c96f1230a513a6d64d763b74277.tar.xz
supermemory-c7b98a39b8024c96f1230a513a6d64d763b74277.zip
let's go boys!! canvas
Diffstat (limited to 'apps/web/app')
-rw-r--r--apps/web/app/(canvas)/canvas/page.tsx22
-rw-r--r--apps/web/app/(canvas)/canvas/search&create.tsx45
-rw-r--r--apps/web/app/(canvas)/canvas/thinkPad.tsx276
-rw-r--r--apps/web/app/(canvas)/canvas/thinkPads.tsx32
-rw-r--r--apps/web/app/(canvas)/layout.tsx30
-rw-r--r--apps/web/app/(dash)/menu.tsx10
-rw-r--r--apps/web/app/(thinkpad)/canvasStyles.css (renamed from apps/web/app/(canvas)/canvasStyles.css)2
-rw-r--r--apps/web/app/(thinkpad)/layout.tsx22
-rw-r--r--apps/web/app/(thinkpad)/thinkpad/[id]/page.tsx (renamed from apps/web/app/(canvas)/canvas/[id]/page.tsx)13
-rw-r--r--apps/web/app/(thinkpad)/thinkpad/image.tsx47
-rw-r--r--apps/web/app/(thinkpad)/thinkpad/page.tsx115
-rw-r--r--apps/web/app/actions/doers.ts6
-rw-r--r--apps/web/app/actions/fetchers.ts6
-rw-r--r--apps/web/app/api/canvasai/route.ts43
-rw-r--r--apps/web/app/api/unfirlsite/route.ts156
15 files changed, 226 insertions, 599 deletions
diff --git a/apps/web/app/(canvas)/canvas/page.tsx b/apps/web/app/(canvas)/canvas/page.tsx
deleted file mode 100644
index 0bf30d89..00000000
--- a/apps/web/app/(canvas)/canvas/page.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import React from "react";
-import { getCanvas } from "@/app/actions/fetchers";
-import SearchandCreate from "./search&create";
-import ThinkPads from "./thinkPads";
-
-async function page() {
- const canvas = await getCanvas();
- return (
- <div className="h-screen w-full py-32 text-[#FFFFFF] ">
- <div className="flex w-full flex-col items-center gap-8">
- <h1 className="text-4xl font-medium">Your thinkpads</h1>
- <SearchandCreate />
- {
- // @ts-ignore
- canvas.success && <ThinkPads data={canvas.data} />
- }
- </div>
- </div>
- );
-}
-
-export default page;
diff --git a/apps/web/app/(canvas)/canvas/search&create.tsx b/apps/web/app/(canvas)/canvas/search&create.tsx
deleted file mode 100644
index ad64729e..00000000
--- a/apps/web/app/(canvas)/canvas/search&create.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-"use client";
-
-import { useFormStatus } from "react-dom";
-import Image from "next/image";
-import { SearchIcon } from "@repo/ui/icons";
-import { createCanvas } from "@/app/actions/doers";
-import { toast } from "sonner";
-
-export default function SearchandCreate() {
- return (
- <div className="flex w-[90%] max-w-2xl gap-2">
- <div className="flex flex-grow items-center overflow-hidden rounded-xl bg-[#1F2428]">
- <input
- placeholder="search here..."
- className="flex-grow bg-[#1F2428] px-5 py-3 text-xl focus:border-none focus:outline-none"
- />
- <button className="h-full border-l-2 border-[#384149] px-2 pl-2">
- <Image src={SearchIcon} alt="search" />
- </button>
- </div>
-
- <form
- action={async () => {
- const res = await createCanvas();
- if (!res.success) {
- toast.warning(res.message, {
- style: { backgroundColor: "rgb(22 31 42 / 0.3)" },
- });
- }
- }}
- >
- <Button />
- </form>
- </div>
- );
-}
-
-function Button() {
- const { pending } = useFormStatus();
- return (
- <button className="rounded-xl bg-[#1F2428] px-5 py-3 text-xl text-[#B8C4C6]">
- {pending ? "Creating.." : "Create New"}
- </button>
- );
-}
diff --git a/apps/web/app/(canvas)/canvas/thinkPad.tsx b/apps/web/app/(canvas)/canvas/thinkPad.tsx
deleted file mode 100644
index 4d31107d..00000000
--- a/apps/web/app/(canvas)/canvas/thinkPad.tsx
+++ /dev/null
@@ -1,276 +0,0 @@
-import { getCanvasData } from "@/app/actions/fetchers";
-import { AnimatePresence, motion } from "framer-motion";
-import Link from "next/link";
-import {
- EllipsisHorizontalCircleIcon,
- TrashIcon,
- PencilSquareIcon,
-} from "@heroicons/react/24/outline";
-import { toast } from "sonner";
-import { Label } from "@repo/ui/shadcn/label";
-
-const childVariants = {
- hidden: { opacity: 0, y: 10, filter: "blur(2px)" },
- visible: { opacity: 1, y: 0, filter: "blur(0px)" },
-};
-
-export default function ThinkPad({
- title,
- description,
- image,
- id,
-}: {
- title: string;
- description: string;
- image: string;
- id: string;
-}) {
- const [deleted, setDeleted] = useState(false);
- const [info, setInfo] = useState({ title, description });
- return (
- <AnimatePresence mode="sync">
- {!deleted && (
- <motion.div
- layout
- exit={{ opacity: 0, scaleY: 0 }}
- variants={childVariants}
- className="flex h-48 origin-top relative gap-4 rounded-2xl bg-[#1F2428] p-2"
- >
- <Link
- className="h-full select-none min-w-[40%] bg-[#363f46] rounded-xl overflow-hidden"
- href={`/canvas/${id}`}
- >
- <Suspense
- fallback={
- <div className=" h-full w-full flex justify-center items-center">
- Loading...
- </div>
- }
- >
- <ImageComponent id={id} />
- </Suspense>
- </Link>
- <div className="flex flex-col gap-2">
- <motion.h2
- initial={{ opacity: 0, filter: "blur(3px)" }}
- animate={{ opacity: 1, filter: "blur(0px)" }}
- key={info.title}
- >
- {info.title}
- </motion.h2>
- <motion.h3
- key={info.description}
- initial={{ opacity: 0, filter: "blur(3px)" }}
- animate={{ opacity: 1, filter: "blur(0px)" }}
- className="overflow-hidden text-ellipsis text-[#B8C4C6]"
- >
- {info.description}
- </motion.h3>
- </div>
- <Menu
- info={info}
- id={id}
- setDeleted={() => setDeleted(true)}
- setInfo={(e) => setInfo(e)}
- />
- </motion.div>
- )}
- </AnimatePresence>
- );
-}
-
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@repo/ui/shadcn/popover";
-
-function Menu({
- info,
- id,
- setDeleted,
- setInfo,
-}: {
- info: { title: string; description: string };
- id: string;
- setDeleted: () => void;
- setInfo: ({
- title,
- description,
- }: {
- title: string;
- description: string;
- }) => void;
-}) {
- return (
- <Popover>
- <PopoverTrigger className="absolute z-20 top-0 right-0" asChild>
- <Button variant="secondary">
- <EllipsisHorizontalCircleIcon className="size-5 stroke-2 stroke-[#B8C4C6]" />
- </Button>
- </PopoverTrigger>
- <PopoverContent
- align="start"
- className="w-32 px-2 py-2 bg-[#161f2a]/30 text-[#B8C4C6] border-border flex flex-col gap-3"
- >
- <EditToolbar info={info} id={id} setInfo={setInfo} />
- <Button
- onClick={async () => {
- const res = await deleteCanvas(id);
- if (res.success) {
- toast.success("Thinkpad removed.", {
- style: { backgroundColor: "rgb(22 31 42 / 0.3)" },
- });
- setDeleted();
- } else {
- toast.warning("Something went wrong.", {
- style: { backgroundColor: "rgb(22 31 42 / 0.3)" },
- });
- }
- }}
- className="flex gap-2 border-border"
- variant="outline"
- >
- <TrashIcon className="size-8 stroke-1" /> Delete
- </Button>
- </PopoverContent>
- </Popover>
- );
-}
-
-function EditToolbar({
- id,
- setInfo,
- info,
-}: {
- id: string;
- setInfo: ({
- title,
- description,
- }: {
- title: string;
- description: string;
- }) => void;
- info: {
- title: string;
- description: string;
- };
-}) {
- const [open, setOpen] = useState(false);
- return (
- <Dialog open={open} onOpenChange={setOpen}>
- <DialogTrigger asChild>
- <Button className="flex gap-2 border-border" variant="outline">
- <PencilSquareIcon className="size-8 stroke-1" /> Edit
- </Button>
- </DialogTrigger>
- <DialogContent className="sm:max-w-[425px] bg-[#161f2a]/30 border-0">
- <form
- action={async (FormData) => {
- const data = {
- title: FormData.get("title") as string,
- description: FormData.get("description") as string,
- };
- const res = await AddCanvasInfo({ id, ...data });
- if (res.success) {
- setOpen(false);
- setInfo(data);
- } else {
- setOpen(false);
- toast.error("Something went wrong.", {
- style: { backgroundColor: "rgb(22 31 42 / 0.3)" },
- });
- }
- }}
- >
- <DialogHeader>
- <DialogTitle>Edit Canvas</DialogTitle>
- <DialogDescription>
- Add Description to your canvas. Pro tip: Let AI do the job, as you
- add your content into canvas, we will autogenerate your
- description.
- </DialogDescription>
- </DialogHeader>
- <div className="grid gap-4 py-4">
- <div className="grid grid-cols-4 items-center gap-4">
- <Label htmlFor="title" className="text-right">
- Title
- </Label>
- <Input
- defaultValue={info.title}
- name="title"
- id="title"
- placeholder="life planning..."
- className="col-span-3 border-0"
- />
- </div>
- <div className="grid grid-cols-4 items-center gap-4">
- <Label htmlFor="description" className="text-right">
- Description
- </Label>
- <Textarea
- defaultValue={info.description}
- rows={6}
- id="description"
- name="description"
- placeholder="contains information about..."
- className="col-span-3 border-0 resize-none"
- />
- </div>
- </div>
- <DialogFooter>
- <Button type="submit">Save changes</Button>
- </DialogFooter>
- </form>
- </DialogContent>
- </Dialog>
- );
-}
-
-import { Suspense, memo, use, useState } from "react";
-import { Box, TldrawImage } from "tldraw";
-import { Button } from "@repo/ui/shadcn/button";
-import { AddCanvasInfo, deleteCanvas } from "@/app/actions/doers";
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@repo/ui/shadcn/dialog";
-import { Input } from "@repo/ui/shadcn/input";
-import { Textarea } from "@repo/ui/shadcn/textarea";
-import { textCardUtil } from "@/components/canvas/textCard";
-import { twitterCardUtil } from "@/components/canvas/twitterCard";
-
-const ImageComponent = memo(({ id }: { id: string }) => {
- const snapshot = use(getCanvasData(id));
- if (snapshot.bounds) {
- const pageBounds = new Box(
- snapshot.bounds.x,
- snapshot.bounds.y,
- snapshot.bounds.w,
- snapshot.bounds.h,
- );
-
- return (
- <TldrawImage
- shapeUtils={[twitterCardUtil, textCardUtil]}
- snapshot={snapshot.snapshot}
- background={false}
- darkMode={true}
- bounds={pageBounds}
- padding={0}
- scale={1}
- format="png"
- />
- );
- }
- return (
- <div className=" h-full w-full flex justify-center items-center">
- Drew things to seee here
- </div>
- );
-});
diff --git a/apps/web/app/(canvas)/canvas/thinkPads.tsx b/apps/web/app/(canvas)/canvas/thinkPads.tsx
deleted file mode 100644
index c5e92370..00000000
--- a/apps/web/app/(canvas)/canvas/thinkPads.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-"use client";
-import { motion } from "framer-motion";
-import ThinkPad from "./thinkPad";
-
-const containerVariants = {
- hidden: { opacity: 0 },
- visible: {
- opacity: 1,
- transition: {
- staggerChildren: 0.1,
- },
- },
-};
-
-export default function ThinkPads({
- data,
-}: {
- data: { image: string; title: string; description: string; id: string }[];
-}) {
- return (
- <motion.div
- variants={containerVariants}
- initial="hidden"
- animate="visible"
- className="w-[90%] max-w-2xl space-y-6"
- >
- {data.map((item) => {
- return <ThinkPad {...item} />;
- })}
- </motion.div>
- );
-}
diff --git a/apps/web/app/(canvas)/layout.tsx b/apps/web/app/(canvas)/layout.tsx
deleted file mode 100644
index e9d38968..00000000
--- a/apps/web/app/(canvas)/layout.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { auth } from "@/server/auth";
-import "./canvasStyles.css";
-import { redirect } from "next/navigation";
-import BackgroundPlus from "../(landing)/GridPatterns/PlusGrid";
-import { Toaster } from "@repo/ui/shadcn/sonner";
-
-export default async function RootLayout({
- children,
-}: {
- children: React.ReactNode;
-}) {
- const info = await auth();
-
- if (!info) {
- return redirect("/signin");
- }
- return (
- <>
- <div className="relative flex justify-center z-40 pointer-events-none">
- <div
- className="absolute -z-10 left-0 top-[10%] h-32 w-[90%] overflow-x-hidden bg-[rgb(54,157,253)] bg-opacity-100 md:bg-opacity-70 blur-[337.4px]"
- style={{ transform: "rotate(-30deg)" }}
- />
- </div>
- <BackgroundPlus className="absolute top-0 left-0 w-full h-full -z-50 opacity-70" />
- <div>{children}</div>
- <Toaster />
- </>
- );
-}
diff --git a/apps/web/app/(dash)/menu.tsx b/apps/web/app/(dash)/menu.tsx
index c56a3247..cedcd5d2 100644
--- a/apps/web/app/(dash)/menu.tsx
+++ b/apps/web/app/(dash)/menu.tsx
@@ -62,6 +62,12 @@ function Menu() {
url: "/memories",
disabled: false,
},
+ {
+ icon: CanvasIcon,
+ text: "Thinkpad",
+ url: "/thinkpad",
+ disabled: false,
+ },
];
const [content, setContent] = useState("");
@@ -76,9 +82,7 @@ function Menu() {
content.match(/https?:\/\/(x\.com|twitter\.com)\/[\w]+\/[\w]+\/[\d]+/)
) {
return "tweet";
- } else if (content.match(/https?:\/\/[\w\.]+/)) {
- return "page";
- } else if (content.match(/https?:\/\/www\.[\w\.]+/)) {
+ } else if (content.match(/^(https?:\/\/)?(www\.)?[a-z0-9]+([-.]{1}[a-z0-9]+)*\.[a-z]{2,5}(\/.*)?$/i)) {
return "page";
} else {
return "note";
diff --git a/apps/web/app/(canvas)/canvasStyles.css b/apps/web/app/(thinkpad)/canvasStyles.css
index 1664e819..3f0593ad 100644
--- a/apps/web/app/(canvas)/canvasStyles.css
+++ b/apps/web/app/(thinkpad)/canvasStyles.css
@@ -1,3 +1,5 @@
+@import url('tldraw/tldraw.css');
+
.tl-background {
background: #1f2428 !important;
}
diff --git a/apps/web/app/(thinkpad)/layout.tsx b/apps/web/app/(thinkpad)/layout.tsx
new file mode 100644
index 00000000..7e5b6e15
--- /dev/null
+++ b/apps/web/app/(thinkpad)/layout.tsx
@@ -0,0 +1,22 @@
+import { auth } from "@/server/auth";
+import "./canvasStyles.css";
+import { redirect } from "next/navigation";
+import { Toaster } from "@repo/ui/shadcn/sonner";
+
+export default async function RootLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ const info = await auth();
+
+ if (!info) {
+ return redirect("/signin");
+ }
+ return (
+ <div className="h-screen">
+ <div>{children}</div>
+ <Toaster />
+ </div>
+ );
+}
diff --git a/apps/web/app/(canvas)/canvas/[id]/page.tsx b/apps/web/app/(thinkpad)/thinkpad/[id]/page.tsx
index bddd3675..40c1641e 100644
--- a/apps/web/app/(canvas)/canvas/[id]/page.tsx
+++ b/apps/web/app/(thinkpad)/thinkpad/[id]/page.tsx
@@ -1,17 +1,10 @@
import { userHasCanvas } from "@/app/actions/fetchers";
-import {
- RectProvider,
- ResizaleLayout,
-} from "@/components/canvas/resizableLayout";
+import ResizableLayout from "@/components/canvas/resizablelayout";
import { redirect } from "next/navigation";
export default async function page({ params }: any) {
const canvasExists = await userHasCanvas(params.id);
if (!canvasExists.success) {
- redirect("/canvas");
+ redirect("/thinkpad");
}
- return (
- <RectProvider id={params.id}>
- <ResizaleLayout />
- </RectProvider>
- );
+ return <ResizableLayout id={params.id} />;
}
diff --git a/apps/web/app/(thinkpad)/thinkpad/image.tsx b/apps/web/app/(thinkpad)/thinkpad/image.tsx
new file mode 100644
index 00000000..d9b61eb3
--- /dev/null
+++ b/apps/web/app/(thinkpad)/thinkpad/image.tsx
@@ -0,0 +1,47 @@
+"use client";
+
+import { getCanvasData } from "@/app/actions/fetchers";
+import { twitterCardUtil } from "@/components/canvas/custom_nodes/twittercard";
+import { textCardUtil } from "@/components/canvas/custom_nodes/textcard";
+import { memo, useEffect, useState } from "react";
+import { Box, TldrawImage } from "tldraw";
+
+const ImageComponent = memo(({ id }: { id: string }) => {
+ const [snapshot, setSnapshot] = useState({});
+
+ useEffect(() => {
+ (async () => {
+ setSnapshot(await getCanvasData(id));
+ })();
+ }, []);
+
+ if (snapshot.bounds) {
+ const pageBounds = new Box(
+ snapshot.bounds.x,
+ snapshot.bounds.y,
+ snapshot.bounds.w,
+ snapshot.bounds.h,
+ );
+
+ return (
+ <TldrawImage
+ shapeUtils={[twitterCardUtil, textCardUtil]}
+ snapshot={snapshot.snapshot}
+ // background={false}
+ darkMode={true}
+ bounds={pageBounds}
+ padding={0}
+ scale={1}
+ format="svg"
+ />
+ );
+ }
+
+ return (
+ <div className="w-full aspect-video bg-[#2C3439] flex justify-center items-center">
+ Drew things to seee here
+ </div>
+ );
+});
+
+export default ImageComponent;
diff --git a/apps/web/app/(thinkpad)/thinkpad/page.tsx b/apps/web/app/(thinkpad)/thinkpad/page.tsx
new file mode 100644
index 00000000..8cdc3602
--- /dev/null
+++ b/apps/web/app/(thinkpad)/thinkpad/page.tsx
@@ -0,0 +1,115 @@
+import { createCanvas } from "@/app/actions/doers";
+import { getCanvas, getCanvasData } from "@/app/actions/fetchers";
+import Link from "next/link";
+import React from "react";
+import ImageComponent from "./image";
+import Menu from "@/app/(dash)/menu";
+import Header from "@/app/(dash)/header/header";
+import BackgroundPlus from "@/app/(landing)/GridPatterns/PlusGrid";
+
+async function page() {
+ const canvas = await getCanvas();
+
+ return (
+ <div className="max-w-2xl m-auto pt-[20vh]">
+ <div className="text-center mx-auto bg-[linear-gradient(180deg,_#FFF_0%,_rgba(255,_255,_255,_0.00)_202.08%)] bg-clip-text text-4xl tracking-tighter text-transparent md:text-5xl">
+ <span>Your</span>{" "}
+ <span className="inline-flex items-center gap-2 bg-gradient-to-r to-blue-300 from-zinc-300 text-transparent bg-clip-text">
+ ThinkPads
+ </span>
+ </div>
+
+ <BlurHeaderMenu />
+
+ <div className="w-full flex py-20">
+ {!canvas.success || canvas.error ? (
+ <div>Hmmm... Something went wrong. :/</div>
+ ) : (
+ canvas.data &&
+ (canvas.data.length ? (
+ canvas.data.map((v) => (
+ <Canvas description={v.description} title={v.title} id={v.id} />
+ ))
+ ) : (
+ <CreateCanvas />
+ ))
+ )}
+ </div>
+
+ <h3 className="fixed left-1/2 -translate-x-1/2 bottom-4 text-gray-400 pt-20 text-center">
+ *this is under beta and only one canvas is allowed per user
+ </h3>
+ </div>
+ );
+}
+
+function BlurHeaderMenu() {
+ return (
+ <>
+ <div className="relative flex justify-center z-40 pointer-events-none">
+ <div
+ className="absolute -z-10 left-0 top-[10%] h-32 w-[90%] overflow-x-hidden bg-[rgb(54,157,253)] bg-opacity-100 md:bg-opacity-70 blur-[337.4px]"
+ style={{ transform: "rotate(-30deg)" }}
+ />
+ </div>
+ <BackgroundPlus className="absolute top-0 left-0 w-full h-full -z-50 opacity-70" />
+
+ <div className="fixed top-0 left-0 w-full z-40">
+ <Header />
+ </div>
+
+ <Menu />
+ </>
+ );
+}
+
+type TcanvasInfo = {
+ title: string;
+ description: string;
+ id: string;
+};
+
+function CreateCanvas() {
+ return (
+ <form action={createCanvas}>
+ <button
+ type="submit"
+ className="bg-secondary w-72 border-2 border-border rounded-md shadow-md shadow-[#1d1d1dc7] hover:scale-[1.03] active:scale-95"
+ >
+ <div className="w-full aspect-video bg-[#2C3439]"></div>
+ <div className="p-2 text-left">
+ <h2 className="text-lg text-gray-100">Unleash your creativity!</h2>
+ <h3 className="text-base text-gray-300">
+ This description will fill itself as you draw on the canvas
+ </h3>
+ </div>
+ </button>
+ </form>
+ );
+}
+
+function Canvas(props: TcanvasInfo) {
+ const { title, description, id } = props;
+ return (
+ <Link
+ href={`/thinkpad/${id}`}
+ className="bg-secondary w-72 border-2 border-border rounded-md shadow-md shadow-[#1d1d1dc7]"
+ >
+ <div className="w-full aspect-video bg-[#2C3439]">
+ <ImageComponent id={id} />
+ </div>
+ <div className="p-2 text-left">
+ <h2 className="text-lg text-gray-100">
+ {title === "Untitled" ? "Unleash your creativity!" : title}
+ </h2>
+ <h3 className="text-base text-gray-300">
+ {description === "Untitled"
+ ? "This description will fill itself as you draw on the canvas"
+ : description}
+ </h3>
+ </div>
+ </Link>
+ );
+}
+
+export default page;
diff --git a/apps/web/app/actions/doers.ts b/apps/web/app/actions/doers.ts
index fbccd195..16d5542f 100644
--- a/apps/web/app/actions/doers.ts
+++ b/apps/web/app/actions/doers.ts
@@ -93,9 +93,7 @@ const typeDecider = (content: string) => {
// do strict checking with regex
if (content.match(/https?:\/\/(x\.com|twitter\.com)\/[\w]+\/[\w]+\/[\d]+/)) {
return "tweet";
- } else if (content.match(/https?:\/\/[\w\.]+/)) {
- return "page";
- } else if (content.match(/https?:\/\/www\.[\w\.]+/)) {
+ } else if (content.match(/^(https?:\/\/)?(www\.)?[a-z0-9]+([-.]{1}[a-z0-9]+)*\.[a-z]{2,5}(\/.*)?$/i)) {
return "page";
} else {
return "note";
@@ -648,7 +646,7 @@ export const createCanvas = async () => {
.insert(canvas)
.values({ userId: data.user.id })
.returning({ id: canvas.id });
- redirect(`/canvas/${resp[0]!.id}`);
+ redirect(`/thinkpad/${resp[0]!.id}`);
// TODO INVESTIGATE: NO REDIRECT INSIDE TRY CATCH BLOCK
// try {
// const resp = await db
diff --git a/apps/web/app/actions/fetchers.ts b/apps/web/app/actions/fetchers.ts
index 8d6802a7..f35a34ce 100644
--- a/apps/web/app/actions/fetchers.ts
+++ b/apps/web/app/actions/fetchers.ts
@@ -22,6 +22,7 @@ import { ChatHistory, SourceZod } from "@repo/shared-types";
import { z } from "zod";
import { redirect } from "next/navigation";
import { cookies, headers } from "next/headers";
+import { unfurl } from "@/lib/unfirlsite";
export const getUser = async (): ServerActionReturnType<User> => {
const data = await auth();
@@ -377,3 +378,8 @@ export const getCanvasData = async (canvasId: string) => {
return { snapshot: {} };
}
};
+
+export async function unfirlSite(website: string){
+ const data = await unfurl(website)
+ return data;
+} \ No newline at end of file
diff --git a/apps/web/app/api/canvasai/route.ts b/apps/web/app/api/canvasai/route.ts
index ac1baf03..163b8075 100644
--- a/apps/web/app/api/canvasai/route.ts
+++ b/apps/web/app/api/canvasai/route.ts
@@ -1,31 +1,32 @@
import type { NextRequest } from "next/server";
import { ensureAuth } from "../ensureAuth";
+import { SourcesFromApi } from "@repo/shared-types";
export const runtime = "edge";
-export async function POST(request: NextRequest) {
- const session = await ensureAuth(request);
+
+export async function POST(req: NextRequest) {
+ const session = await ensureAuth(req);
+
if (!session) {
return new Response("Unauthorized", { status: 401 });
}
- const res: { query: string } = await request.json();
- try {
- const resp = await fetch(
- `${process.env.BACKEND_BASE_URL}/api/search?query=${res.query}&user=${session.user.id}`,
- );
- 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 },
- );
+ const res: { query: string } = await req.json();
+
+ const response = await fetch(
+ `${process.env.BACKEND_BASE_URL}/api/chat?query=${res.query}&user=${session.user.id}&sourcesOnly=true`,
+ {
+ headers: {
+ Authorization: `Bearer ${process.env.BACKEND_SECURITY_KEY}`,
+ "Content-Type": "application/json",
+ },
+ method: "POST",
+ body: JSON.stringify({})
}
- return new Response(
- JSON.stringify({ response: await resp.json(), status: 200 }),
- );
- } catch (error) {
- return new Response(`Error, ${error}`);
- }
-}
+ )
+
+ const data = (await response.json()) as SourcesFromApi;
+ console.log(data);
+ return new Response(JSON.stringify(data), { status: 200 });
+} \ No newline at end of file
diff --git a/apps/web/app/api/unfirlsite/route.ts b/apps/web/app/api/unfirlsite/route.ts
deleted file mode 100644
index 78b61ca9..00000000
--- a/apps/web/app/api/unfirlsite/route.ts
+++ /dev/null
@@ -1,156 +0,0 @@
-import { load } from "cheerio";
-import { AwsClient } from "aws4fetch";
-
-import type { NextRequest } from "next/server";
-import { ensureAuth } from "../ensureAuth";
-
-export const runtime = "edge";
-
-export async function POST(request: NextRequest) {
- const r2 = new AwsClient({
- accessKeyId: process.env.R2_ACCESS_KEY_ID,
- secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
- });
-
- async function unfurl(url: string) {
- const response = await fetch(url);
- if (response.status >= 400) {
- throw new Error(`Error fetching url: ${response.status}`);
- }
- const contentType = response.headers.get("content-type");
- if (!contentType?.includes("text/html")) {
- throw new Error(`Content-type not right: ${contentType}`);
- }
-
- const content = await response.text();
- const $ = load(content);
-
- const og: { [key: string]: string | undefined } = {};
- const twitter: { [key: string]: string | undefined } = {};
-
- $("meta[property^=og:]").each(
- // @ts-ignore, it just works so why care of type safety if someone has better way go ahead
- (_, el) => (og[$(el).attr("property")!] = $(el).attr("content")),
- );
- $("meta[name^=twitter:]").each(
- // @ts-ignore
- (_, el) => (twitter[$(el).attr("name")!] = $(el).attr("content")),
- );
-
- const title =
- og["og:title"] ??
- twitter["twitter:title"] ??
- $("title").text() ??
- undefined;
- const description =
- og["og:description"] ??
- twitter["twitter:description"] ??
- $('meta[name="description"]').attr("content") ??
- undefined;
- const image =
- og["og:image:secure_url"] ??
- og["og:image"] ??
- twitter["twitter:image"] ??
- undefined;
-
- return {
- title,
- description,
- image,
- };
- }
-
- const d = await ensureAuth(request);
- if (!d) {
- return new Response("Unauthorized", { status: 401 });
- }
-
- if (
- !process.env.R2_ACCESS_KEY_ID ||
- !process.env.R2_ACCOUNT_ID ||
- !process.env.R2_SECRET_ACCESS_KEY ||
- !process.env.R2_BUCKET_NAME
- ) {
- return new Response(
- "Missing one or more R2 env variables: R2_ENDPOINT, R2_ACCESS_ID, R2_SECRET_KEY, R2_BUCKET_NAME. To get them, go to the R2 console, create and paste keys in a `.dev.vars` file in the root of this project.",
- { status: 500 },
- );
- }
-
- const website = new URL(request.url).searchParams.get("website");
-
- if (!website) {
- return new Response("Missing website", { status: 400 });
- }
-
- const salt = () => Math.floor(Math.random() * 11);
- const encodeWebsite = `${encodeURIComponent(website)}${salt()}`;
-
- try {
- // this returns the og image, description and title of website
- const response = await unfurl(website);
-
- if (!response.image) {
- return new Response(JSON.stringify(response));
- }
-
- if (!process.env.DEV_IMAGES) {
- return new Response("Missing DEV_IMAGES namespace.", { status: 500 });
- }
-
- const imageUrl = await process.env.DEV_IMAGES!.get(encodeWebsite);
- if (imageUrl) {
- return new Response(
- JSON.stringify({
- image: imageUrl,
- title: response.title,
- description: response.description,
- }),
- );
- }
-
- const res = await fetch(`${response.image}`);
- const image = await res.blob();
-
- const url = new URL(
- `https://${process.env.R2_BUCKET_NAME}.${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
- );
-
- url.pathname = encodeWebsite;
- url.searchParams.set("X-Amz-Expires", "3600");
-
- const signedPuturl = await r2.sign(
- new Request(url, {
- method: "PUT",
- }),
- {
- aws: { signQuery: true },
- },
- );
- await fetch(signedPuturl.url, {
- method: "PUT",
- body: image,
- });
-
- await process.env.DEV_IMAGES.put(
- encodeWebsite,
- `${process.env.R2_PUBLIC_BUCKET_ADDRESS}/${encodeWebsite}`,
- );
-
- return new Response(
- JSON.stringify({
- image: `${process.env.R2_PUBLIC_BUCKET_ADDRESS}/${encodeWebsite}`,
- title: response.title,
- description: response.description,
- }),
- );
- } catch (error) {
- console.log(error);
- return new Response(
- JSON.stringify({
- status: 500,
- error: error,
- }),
- );
- }
-}