mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-08-28 05:25:33 +00:00
fixed builds, added friend integration
This commit is contained in:
parent
a3f551e557
commit
3e9793436a
10 changed files with 216 additions and 181 deletions
|
|
@ -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<any>();
|
||||
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
44
apps/web/app/api/store/friend/route.ts
Normal file
44
apps/web/app/api/store/friend/route.ts
Normal file
|
|
@ -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" }));
|
||||
}
|
||||
159
apps/web/app/api/store/helper.ts
Normal file
159
apps/web/app/api/store/helper.ts
Normal file
|
|
@ -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}`,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
@ -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<number>`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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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<any>(null);
|
||||
const [isLeftPanelCollapsed, setIsLeftPanelCollapsed] = useState(false);
|
||||
|
||||
const handleResize = () => {
|
||||
if (isLeftPanelCollapsed && panelGroupRef.current) {
|
||||
panelGroupRef.current.setLayout([20, 80]);
|
||||
panelGroupRef.current?.setLayout([20, 80]);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export default function Card({
|
|||
const [isDragging, setIsDragging] = useState(false);
|
||||
|
||||
const handleDragStart = (
|
||||
event: React.DragEvent<HTMLDivElement>,
|
||||
event: React.DragEvent<HTMLAnchorElement | HTMLDivElement>,
|
||||
dragSource: "icon" | "link" | "parent",
|
||||
) => {
|
||||
setIsDragging(true);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
// @ts-nocheck
|
||||
|
||||
import cheerio from "cheerio";
|
||||
|
||||
export async function unfurl(url: string) {
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue