From 3e9793436afd216859051d8eebc4c445349e4984 Mon Sep 17 00:00:00 2001 From: Dhravya Shah Date: Sun, 28 Jul 2024 19:33:50 -0700 Subject: [PATCH] fixed builds, added friend integration --- apps/web/app/(thinkpad)/thinkpad/image.tsx | 4 +- apps/web/app/(thinkpad)/thinkpad/page.tsx | 2 +- apps/web/app/api/store/friend/route.ts | 44 +++++ apps/web/app/api/store/helper.ts | 159 ++++++++++++++++ apps/web/app/api/store/route.ts | 176 +----------------- .../web/components/canvas/resizablelayout.tsx | 4 +- apps/web/components/canvas/sidepanel.tsx | 2 + apps/web/components/canvas/sidepanelcard.tsx | 2 +- apps/web/lib/unfirlsite.ts | 2 + packages/shared-types/index.ts | 2 +- 10 files changed, 216 insertions(+), 181 deletions(-) create mode 100644 apps/web/app/api/store/friend/route.ts create mode 100644 apps/web/app/api/store/helper.ts diff --git a/apps/web/app/(thinkpad)/thinkpad/image.tsx b/apps/web/app/(thinkpad)/thinkpad/image.tsx index d9b61eb3..b55b9a81 100644 --- a/apps/web/app/(thinkpad)/thinkpad/image.tsx +++ b/apps/web/app/(thinkpad)/thinkpad/image.tsx @@ -7,7 +7,7 @@ import { memo, useEffect, useState } from "react"; import { Box, TldrawImage } from "tldraw"; const ImageComponent = memo(({ id }: { id: string }) => { - const [snapshot, setSnapshot] = useState({}); + const [snapshot, setSnapshot] = useState(); useEffect(() => { (async () => { @@ -15,7 +15,7 @@ const ImageComponent = memo(({ id }: { id: string }) => { })(); }, []); - if (snapshot.bounds) { + if (snapshot && snapshot.bounds) { const pageBounds = new Box( snapshot.bounds.x, snapshot.bounds.y, diff --git a/apps/web/app/(thinkpad)/thinkpad/page.tsx b/apps/web/app/(thinkpad)/thinkpad/page.tsx index 8cdc3602..defa8e43 100644 --- a/apps/web/app/(thinkpad)/thinkpad/page.tsx +++ b/apps/web/app/(thinkpad)/thinkpad/page.tsx @@ -1,5 +1,5 @@ import { createCanvas } from "@/app/actions/doers"; -import { getCanvas, getCanvasData } from "@/app/actions/fetchers"; +import { getCanvas } from "@/app/actions/fetchers"; import Link from "next/link"; import React from "react"; import ImageComponent from "./image"; diff --git a/apps/web/app/api/store/friend/route.ts b/apps/web/app/api/store/friend/route.ts new file mode 100644 index 00000000..554b1cee --- /dev/null +++ b/apps/web/app/api/store/friend/route.ts @@ -0,0 +1,44 @@ +import { type NextRequest } from "next/server"; +import { createMemoryFromAPI } from "../helper"; + +type FriendData = { + id: string; + created_at: string; + transcript: string; + structured: { + title: string; + overview: string; + action_items: [ + { + description: string; + }, + ]; + }; +}; + +export async function POST(req: NextRequest) { + const body: FriendData = await req.json(); + + const userId = new URL(req.url).searchParams.get("uid"); + + if (!userId) { + return new Response( + JSON.stringify({ status: 400, body: "Missing user ID" }), + ); + } + + await createMemoryFromAPI({ + data: { + title: "Friend: " + body.structured.title, + description: body.structured.overview, + pageContent: + body.transcript + "\n\n" + JSON.stringify(body.structured.action_items), + spaces: [], + type: "note", + url: "https://basedhardware.com", + }, + userId: userId, + }); + + return new Response(JSON.stringify({ status: 200, body: "success" })); +} diff --git a/apps/web/app/api/store/helper.ts b/apps/web/app/api/store/helper.ts new file mode 100644 index 00000000..f8833970 --- /dev/null +++ b/apps/web/app/api/store/helper.ts @@ -0,0 +1,159 @@ +import { z } from "zod"; +import { db } from "@/server/db"; +import { contentToSpace, space, storedContent } from "@/server/db/schema"; +import { and, eq, inArray } from "drizzle-orm"; +import { LIMITS } from "@/lib/constants"; +import { limit } from "@/app/actions/doers"; +import { type AddFromAPIType } from "@repo/shared-types"; + +export const createMemoryFromAPI = async (input: { + data: AddFromAPIType; + userId: string; +}) => { + if (!(await limit(input.userId, input.data.type))) { + return { + success: false, + data: 0, + error: `You have exceeded the limit of ${LIMITS[input.data.type as keyof typeof LIMITS]} ${input.data.type}s.`, + }; + } + + const vectorSaveResponse = await fetch( + `${process.env.BACKEND_BASE_URL}/api/add`, + { + method: "POST", + body: JSON.stringify({ + pageContent: input.data.pageContent, + title: input.data.title, + description: input.data.description, + url: input.data.url, + spaces: input.data.spaces, + user: input.userId, + type: input.data.type, + }), + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + process.env.BACKEND_SECURITY_KEY, + }, + }, + ); + + if (!vectorSaveResponse.ok) { + const errorData = await vectorSaveResponse.text(); + console.error(errorData); + return { + success: false, + data: 0, + error: `Failed to save to vector store. Backend returned error: ${errorData}`, + }; + } + + let contentId: number; + + const saveToDbUrl = + (input.data.url.split("#supermemory-user-")[0] ?? input.data.url) + + "#supermemory-user-" + + input.userId; + + const noteId = new Date().getTime(); + + // Insert into database + try { + const insertResponse = await db + .insert(storedContent) + .values({ + content: input.data.pageContent, + title: input.data.title, + description: input.data.description, + url: saveToDbUrl, + baseUrl: saveToDbUrl, + image: input.data.image, + savedAt: new Date(), + userId: input.userId, + type: input.data.type, + noteId, + }) + .returning({ id: storedContent.id }); + + if (!insertResponse[0]?.id) { + return { + success: false, + data: 0, + error: "Failed to save to database", + }; + } + + contentId = insertResponse[0].id; + } catch (e) { + const error = e as Error; + console.log("Error: ", error.message); + + if (error.message.includes("D1_ERROR: UNIQUE constraint failed:")) { + return { + success: false, + data: 0, + error: "Content already exists", + }; + } + + return { + success: false, + data: 0, + error: "Failed to save to database with error: " + error.message, + }; + } + + if (input.data.spaces.length > 0) { + // Adding the many-to-many relationship between content and spaces + const spaceData = await db + .select() + .from(space) + .where( + and( + inArray( + space.id, + input.data.spaces.map((s) => parseInt(s)), + ), + eq(space.user, input.userId), + ), + ) + .all(); + + await Promise.all( + spaceData.map(async (s) => { + await db + .insert(contentToSpace) + .values({ contentId: contentId, spaceId: s.id }); + + await db.update(space).set({ numItems: s.numItems + 1 }); + }), + ); + } + + try { + const response = await vectorSaveResponse.json(); + + const expectedResponse = z.object({ status: z.literal("ok") }); + + const parsedResponse = expectedResponse.safeParse(response); + + if (!parsedResponse.success) { + return { + success: false, + data: 0, + error: `Failed to save to vector store. Backend returned error: ${parsedResponse.error.message}`, + }; + } + + return { + success: true, + data: 1, + }; + } catch (e) { + return { + success: false, + data: 0, + error: `Failed to save to vector store. Backend returned error: ${e as string}`, + }; + } +}; diff --git a/apps/web/app/api/store/route.ts b/apps/web/app/api/store/route.ts index e0ff82cd..12af7894 100644 --- a/apps/web/app/api/store/route.ts +++ b/apps/web/app/api/store/route.ts @@ -1,182 +1,10 @@ import { type NextRequest } from "next/server"; -import { addFromAPIType, AddFromAPIType } from "@repo/shared-types"; +import { addFromAPIType } from "@repo/shared-types"; import { ensureAuth } from "../ensureAuth"; -import { z } from "zod"; -import { db } from "@/server/db"; -import { contentToSpace, space, storedContent } from "@/server/db/schema"; -import { and, eq, gt, inArray, sql } from "drizzle-orm"; -import { LIMITS } from "@/lib/constants"; -import { limit } from "@/app/actions/doers"; +import { createMemoryFromAPI } from "./helper"; export const runtime = "edge"; -const createMemoryFromAPI = async (input: { - data: AddFromAPIType; - userId: string; -}) => { - if (!(await limit(input.userId, input.data.type))) { - return { - success: false, - data: 0, - error: `You have exceeded the limit of ${LIMITS[input.data.type as keyof typeof LIMITS]} ${input.data.type}s.`, - }; - } - - // Get number of items saved in the last 2 hours - const last2Hours = new Date(Date.now() - 2 * 60 * 60 * 1000); - - const numberOfItemsSavedInLast2Hours = await db - .select({ - count: sql`count(*)`.mapWith(Number), - }) - .from(storedContent) - .where( - and( - gt(storedContent.savedAt, last2Hours), - eq(storedContent.userId, input.userId), - ), - ); - - const vectorSaveResponse = await fetch( - `${process.env.BACKEND_BASE_URL}/api/add`, - { - method: "POST", - body: JSON.stringify({ - pageContent: input.data.pageContent, - title: input.data.title, - description: input.data.description, - url: input.data.url, - spaces: input.data.spaces, - user: input.userId, - type: input.data.type, - }), - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + process.env.BACKEND_SECURITY_KEY, - }, - }, - ); - - if (!vectorSaveResponse.ok) { - const errorData = await vectorSaveResponse.text(); - console.error(errorData); - return { - success: false, - data: 0, - error: `Failed to save to vector store. Backend returned error: ${errorData}`, - }; - } - - let contentId: number; - - const saveToDbUrl = - (input.data.url.split("#supermemory-user-")[0] ?? input.data.url) + - "#supermemory-user-" + - input.userId; - - const noteId = new Date().getTime(); - - // Insert into database - try { - const insertResponse = await db - .insert(storedContent) - .values({ - content: input.data.pageContent, - title: input.data.title, - description: input.data.description, - url: saveToDbUrl, - baseUrl: saveToDbUrl, - image: input.data.image, - savedAt: new Date(), - userId: input.userId, - type: input.data.type, - noteId, - }) - .returning({ id: storedContent.id }); - - if (!insertResponse[0]?.id) { - return { - success: false, - data: 0, - error: "Failed to save to database", - }; - } - - contentId = insertResponse[0].id; - } catch (e) { - const error = e as Error; - console.log("Error: ", error.message); - - if (error.message.includes("D1_ERROR: UNIQUE constraint failed:")) { - return { - success: false, - data: 0, - error: "Content already exists", - }; - } - - return { - success: false, - data: 0, - error: "Failed to save to database with error: " + error.message, - }; - } - - if (input.data.spaces.length > 0) { - // Adding the many-to-many relationship between content and spaces - const spaceData = await db - .select() - .from(space) - .where( - and( - inArray( - space.id, - input.data.spaces.map((s) => parseInt(s)), - ), - eq(space.user, input.userId), - ), - ) - .all(); - - await Promise.all( - spaceData.map(async (s) => { - await db - .insert(contentToSpace) - .values({ contentId: contentId, spaceId: s.id }); - - await db.update(space).set({ numItems: s.numItems + 1 }); - }), - ); - } - - try { - const response = await vectorSaveResponse.json(); - - const expectedResponse = z.object({ status: z.literal("ok") }); - - const parsedResponse = expectedResponse.safeParse(response); - - if (!parsedResponse.success) { - return { - success: false, - data: 0, - error: `Failed to save to vector store. Backend returned error: ${parsedResponse.error.message}`, - }; - } - - return { - success: true, - data: 1, - }; - } catch (e) { - return { - success: false, - data: 0, - error: `Failed to save to vector store. Backend returned error: ${e}`, - }; - } -}; - export async function POST(req: NextRequest) { const session = await ensureAuth(req); diff --git a/apps/web/components/canvas/resizablelayout.tsx b/apps/web/components/canvas/resizablelayout.tsx index 21cb6e8a..a65afb20 100644 --- a/apps/web/components/canvas/resizablelayout.tsx +++ b/apps/web/components/canvas/resizablelayout.tsx @@ -9,12 +9,12 @@ import { useRef, useState } from "react"; import { ChevronRight } from "lucide-react"; export default function ResizableLayout({ id }: { id: string }) { - const panelGroupRef = useRef(null); + const panelGroupRef = useRef(null); const [isLeftPanelCollapsed, setIsLeftPanelCollapsed] = useState(false); const handleResize = () => { if (isLeftPanelCollapsed && panelGroupRef.current) { - panelGroupRef.current.setLayout([20, 80]); + panelGroupRef.current?.setLayout([20, 80]); } }; diff --git a/apps/web/components/canvas/sidepanel.tsx b/apps/web/components/canvas/sidepanel.tsx index 81186486..75f544c9 100644 --- a/apps/web/components/canvas/sidepanel.tsx +++ b/apps/web/components/canvas/sidepanel.tsx @@ -55,6 +55,8 @@ function Search({ setContent }: { setContent: (e: any) => void }) { const sources = await sourcesFetch.json(); + console.log(sources); + const sourcesParsed = sourcesZod.safeParse(sources); if (!sourcesParsed.success) { diff --git a/apps/web/components/canvas/sidepanelcard.tsx b/apps/web/components/canvas/sidepanelcard.tsx index bfbe7195..36014e4c 100644 --- a/apps/web/components/canvas/sidepanelcard.tsx +++ b/apps/web/components/canvas/sidepanelcard.tsx @@ -21,7 +21,7 @@ export default function Card({ const [isDragging, setIsDragging] = useState(false); const handleDragStart = ( - event: React.DragEvent, + event: React.DragEvent, dragSource: "icon" | "link" | "parent", ) => { setIsDragging(true); diff --git a/apps/web/lib/unfirlsite.ts b/apps/web/lib/unfirlsite.ts index a7a77c0c..9020ab18 100644 --- a/apps/web/lib/unfirlsite.ts +++ b/apps/web/lib/unfirlsite.ts @@ -1,3 +1,5 @@ +// @ts-nocheck + import cheerio from "cheerio"; export async function unfurl(url: string) { diff --git a/packages/shared-types/index.ts b/packages/shared-types/index.ts index 051e24a4..a9933b84 100644 --- a/packages/shared-types/index.ts +++ b/packages/shared-types/index.ts @@ -77,7 +77,7 @@ export function convertChatHistoryList( } export const sourcesZod = z.object({ - ids: z.array(z.string()), + ids: z.array(z.string().nullable()), metadata: z.array(z.any()), normalizedData: z.array(z.any()).optional(), proModeListedQueries: z.array(z.string()).optional(),