diff options
| author | codetorso <[email protected]> | 2024-07-25 10:56:32 +0530 |
|---|---|---|
| committer | codetorso <[email protected]> | 2024-07-25 10:56:32 +0530 |
| commit | c7b98a39b8024c96f1230a513a6d64d763b74277 (patch) | |
| tree | 0bc3260cfce7cbf5666948f21864806b6d2a9760 /apps/web/app | |
| parent | parse suggestions for handling edge case (diff) | |
| download | supermemory-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.tsx | 22 | ||||
| -rw-r--r-- | apps/web/app/(canvas)/canvas/search&create.tsx | 45 | ||||
| -rw-r--r-- | apps/web/app/(canvas)/canvas/thinkPad.tsx | 276 | ||||
| -rw-r--r-- | apps/web/app/(canvas)/canvas/thinkPads.tsx | 32 | ||||
| -rw-r--r-- | apps/web/app/(canvas)/layout.tsx | 30 | ||||
| -rw-r--r-- | apps/web/app/(dash)/menu.tsx | 10 | ||||
| -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.tsx | 22 | ||||
| -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.tsx | 47 | ||||
| -rw-r--r-- | apps/web/app/(thinkpad)/thinkpad/page.tsx | 115 | ||||
| -rw-r--r-- | apps/web/app/actions/doers.ts | 6 | ||||
| -rw-r--r-- | apps/web/app/actions/fetchers.ts | 6 | ||||
| -rw-r--r-- | apps/web/app/api/canvasai/route.ts | 43 | ||||
| -rw-r--r-- | apps/web/app/api/unfirlsite/route.ts | 156 |
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, - }), - ); - } -} |