brought all the APIs back

This commit is contained in:
Dhravya 2024-05-25 23:38:48 -05:00
parent ed9f15ec6e
commit c12ecfc431
27 changed files with 430 additions and 98 deletions

View file

@ -0,0 +1,73 @@
import { type NextRequest } from "next/server";
import { ChatHistory } from "@repo/shared-types";
import { ensureAuth } from "../ensureAuth";
export const runtime = "edge";
export async function POST(req: NextRequest) {
const session = await ensureAuth(req);
if (!session) {
return new Response("Unauthorized", { status: 401 });
}
if (!process.env.BACKEND_SECURITY_KEY) {
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 sourcesOnly =
new URL(req.url).searchParams.get("sourcesOnly") ?? "false";
const chatHistory = (await req.json()) as {
chatHistory: ChatHistory[];
};
console.log("CHathistory", chatHistory);
if (!query) {
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 ?? [],
}),
},
);
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 {}
}

View file

@ -0,0 +1,47 @@
import { db } from "@/app/helpers/server/db";
import { and, eq, ne, sql } from "drizzle-orm";
import { sessions, storedContent, users } from "@/app/helpers/server/db/schema";
import { type NextRequest, NextResponse } from "next/server";
import { ensureAuth } from "../ensureAuth";
export const runtime = "edge";
export async function GET(req: NextRequest) {
const session = await ensureAuth(req);
if (!session) {
return new Response("Unauthorized", { status: 401 });
}
const tweetsCount = await db
.select({
count: sql<number>`count(*)`.mapWith(Number),
})
.from(storedContent)
.where(
and(
eq(storedContent.user, session.user.id),
eq(storedContent.type, "twitter-bookmark"),
),
);
const pageCount = await db
.select({
count: sql<number>`count(*)`.mapWith(Number),
})
.from(storedContent)
.where(
and(
eq(storedContent.user, session.user.id),
ne(storedContent.type, "twitter-bookmark"),
),
);
return NextResponse.json({
tweetsCount: tweetsCount[0]!.count,
tweetsLimit: 1000,
pageCount: pageCount[0]!.count,
pageLimit: 100,
user: session.user.email,
});
}

View file

@ -0,0 +1,47 @@
import { db } from "@/app/helpers/server/db";
import { eq } from "drizzle-orm";
import { sessions, users } from "@/app/helpers/server/db/schema";
import { type NextRequest, NextResponse } from "next/server";
export const runtime = "edge";
export async function GET(req: NextRequest) {
const token =
req.cookies.get("next-auth.session-token")?.value ??
req.cookies.get("__Secure-authjs.session-token")?.value ??
req.cookies.get("authjs.session-token")?.value ??
req.headers.get("Authorization")?.replace("Bearer ", "");
const session = await db
.select()
.from(sessions)
.where(eq(sessions.sessionToken, token!));
if (!session || session.length === 0) {
return new Response(
JSON.stringify({ message: "Invalid Key, session not found." }),
{ status: 404 },
);
}
const user = await db
.select()
.from(users)
.where(eq(users.id, session[0]!.userId))
.limit(1);
if (!user || user.length === 0) {
return NextResponse.json(
{ message: "Invalid Key, session not found." },
{ status: 404 },
);
}
return new Response(
JSON.stringify({
message: "OK",
data: { session: session[0], user: user[0] },
}),
{ status: 200 },
);
}

View file

@ -0,0 +1,29 @@
import { db } from "@/app/helpers/server/db";
import { sessions, space, users } from "@/app/helpers/server/db/schema";
import { eq } from "drizzle-orm";
import { NextRequest, NextResponse } from "next/server";
import { ensureAuth } from "../ensureAuth";
export const runtime = "edge";
export async function GET(req: NextRequest) {
const session = await ensureAuth(req);
if (!session) {
return new Response("Unauthorized", { status: 401 });
}
const spaces = await db
.select()
.from(space)
.where(eq(space.user, session.user.id))
.all();
return NextResponse.json(
{
message: "OK",
data: spaces,
},
{ status: 200 },
);
}

View file

@ -0,0 +1,121 @@
import { db } from "@/app/helpers/server/db";
import { and, eq, sql, inArray } from "drizzle-orm";
import {
contentToSpace,
sessions,
storedContent,
users,
space,
} from "@/app/helpers/server/db/schema";
import { type NextRequest, NextResponse } from "next/server";
import { getMetaData } from "@/app/helpers/lib/get-metadata";
import { ensureAuth } from "../ensureAuth";
export const runtime = "edge";
export async function POST(req: NextRequest) {
const session = await ensureAuth(req);
if (!session) {
return new Response("Unauthorized", { status: 401 });
}
const data = (await req.json()) as {
pageContent: string;
url: string;
spaces?: string[];
};
const metadata = await getMetaData(data.url);
let storeToSpaces = data.spaces;
if (!storeToSpaces) {
storeToSpaces = [];
}
const count = await db
.select({
count: sql<number>`count(*)`.mapWith(Number),
})
.from(storedContent)
.where(
and(
eq(storedContent.user, session.user.id),
eq(storedContent.type, "page"),
),
);
if (count[0]!.count > 100) {
return NextResponse.json(
{ message: "Error", error: "Limit exceeded" },
{ status: 499 },
);
}
const rep = await db
.insert(storedContent)
.values({
content: data.pageContent,
title: metadata.title,
description: metadata.description,
url: data.url,
baseUrl: metadata.baseUrl,
image: metadata.image,
savedAt: new Date(),
user: session.user.id,
})
.returning({ id: storedContent.id });
const id = rep[0]?.id;
if (!id) {
return NextResponse.json(
{ message: "Error", error: "Error in CF function" },
{ status: 500 },
);
}
if (storeToSpaces.length > 0) {
const spaceData = await db
.select()
.from(space)
.where(
and(
inArray(space.name, storeToSpaces ?? []),
eq(space.user, session.user.id),
),
)
.all();
await Promise.all([
spaceData.forEach(async (space) => {
await db
.insert(contentToSpace)
.values({ contentId: id, spaceId: space.id });
}),
]);
}
const res = (await Promise.race([
fetch("https://cf-ai-backend.dhravya.workers.dev/add", {
method: "POST",
headers: {
"X-Custom-Auth-Key": process.env.BACKEND_SECURITY_KEY,
},
body: JSON.stringify({ ...data, user: session.user.email }),
}),
new Promise((_, reject) =>
setTimeout(() => reject(new Error("Request timed out")), 40000),
),
])) as Response;
if (res.status !== 200) {
console.log(res.status, res.statusText);
return NextResponse.json(
{ message: "Error", error: "Error in CF function" },
{ status: 500 },
);
}
return NextResponse.json({ message: "OK", data: "Success" }, { status: 200 });
}

View file

@ -0,0 +1,40 @@
"use server";
import * as cheerio from "cheerio";
// TODO: THIS SHOULD PROBABLY ALSO FETCH THE OG-IMAGE
export async function getMetaData(url: string) {
const response = await fetch(url);
const html = await response.text();
const $ = cheerio.load(html);
// Extract the base URL
const baseUrl = new URL(url).origin;
// Extract title
const title = $("title").text().trim();
const description = $("meta[name=description]").attr("content") ?? "";
const _favicon =
$("link[rel=icon]").attr("href") ?? "https://supermemory.dhr.wtf/web.svg";
let favicon =
_favicon.trim().length > 0
? _favicon.trim()
: "https://supermemory.dhr.wtf/web.svg";
if (favicon.startsWith("/")) {
favicon = baseUrl + favicon;
} else if (favicon.startsWith("./")) {
favicon = baseUrl + favicon.slice(1);
}
// Prepare the metadata object
const metadata = {
title,
description,
image: favicon,
baseUrl,
};
return metadata;
}

View file

@ -1,5 +1,5 @@
import Image from "next/image";
import type { UploadedFile } from "@repo/shared-types";
// import type { UploadedFile } from '@repo/shared-types';
import {
Card,
@ -7,11 +7,11 @@ import {
CardDescription,
CardHeader,
CardTitle,
} from "@repo/ui/src/card";
import { ScrollArea, ScrollBar } from "@repo/ui/src/scroll-area";
} from "@repo/ui/src/shadcn/card";
import { ScrollArea, ScrollBar } from "@repo/ui/src/shadcn/scroll-area";
interface UploadedFilesCardProps {
uploadedFiles: UploadedFile[];
uploadedFiles: any[];
}
import { ImageIcon } from "@radix-ui/react-icons";

3
apps/web/env.d.ts vendored
View file

@ -1,4 +1,4 @@
// Generated by Wrangler on Sat May 25 2024 17:07:13 GMT-0500 (Central Daylight Time)
// Generated by Wrangler on Sat May 25 2024 23:32:58 GMT-0500 (Central Daylight Time)
// by running `wrangler types --env-interface CloudflareEnv env.d.ts`
interface CloudflareEnv {
@ -9,6 +9,7 @@ interface CloudflareEnv {
R2_ACCESS_ID: string;
R2_SECRET_KEY: string;
R2_BUCKET_NAME: string;
BACKEND_SECURITY_KEY: string;
STORAGE: R2Bucket;
DATABASE: D1Database;
}

View file

@ -1,5 +1,5 @@
{
"name": "web",
"name": "@repo/web",
"version": "1.0.0",
"private": true,
"scripts": {

View file

@ -5,7 +5,10 @@
{
"name": "next"
}
]
],
"paths": {
"@/*": ["./*"]
}
},
"include": [
"cf-env.d.ts",

View file

@ -17,6 +17,7 @@
"@repo/shared-types": "*",
"@repo/tailwind-config": "*",
"@repo/typescript-config": "*",
"@repo/ui": "*",
"@tailwindcss/typography": "^0.5.13",
"autoprefixer": "^10.4.19",
"drizzle-kit": "^0.21.2",
@ -58,6 +59,7 @@
"@radix-ui/react-toast": "^1.1.5",
"@types/readline-sync": "^1.4.8",
"ai": "^3.1.14",
"cheerio": "^1.0.0-rc.12",
"compromise": "^14.13.0",
"drizzle-orm": "^0.30.10",
"framer-motion": "^11.2.6",

View file

@ -1,3 +1,7 @@
import { type ClientUploadedFileData } from "uploadthing/types";
export interface UploadedFile<T = unknown> extends ClientUploadedFileData<T> {}
export type ChatHistory = {
question: string;
answer: {
parts: { text: string }[];
sources: { isNote: boolean; source: string }[];
};
};

View file

@ -1,15 +1,3 @@
{
"name": "@repo/shared-types",
"version": "0.0.0",
"private": true,
"type": "module",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"dependencies": {
"@repo/eslint-config": "*",
"@repo/typescript-config": "*",
"@repo/tailwind-config": "*"
}
"name": "@repo/shared-types"
}

View file

@ -1,73 +0,0 @@
import { UploadedFile } from "@repo/shared-types";
import { useState } from "react";
export function useUploadFile<T>(key: string, options?: T) {
const [isUploading, setIsUploading] = useState(false);
const [uploadedFiles, setUploadedFiles] = useState<UploadedFile[]>([]);
const uploadFiles = async (image: File[]) => {
setIsUploading(true);
await Promise.all(
image.map(async (file) => {
const fileName =
file.name.split(".")[0]! + Date.now() + "." + file.name.split(".")[1];
const formData = new FormData();
formData.append("data", file);
const response = await fetch(`/api/upload_image?filename=${fileName}`, {
method: "PUT",
body: formData,
});
if (response.status !== 200) {
throw new Error(response.statusText);
}
const resp = (await response.json()) as { url: string };
const url = resp.url;
const response2 = await fetch(url, {
method: "PUT",
body: file,
headers: {
"Content-Type": file.type,
},
});
if (response2.status !== 200) {
throw new Error(response2.statusText);
}
// For showing on the client
const uri = URL.createObjectURL(file);
setUploadedFiles((prev) => [
...prev,
{
name: fileName,
url: uri,
size: file.size,
type: file.type,
customId: null,
key: fileName,
serverData: null,
},
]);
return {
url,
filename: fileName,
};
}),
);
setIsUploading(false);
};
return {
uploadFiles,
uploadedFiles,
isUploading,
};
}

View file

@ -1,6 +1,6 @@
import * as React from "react";
import { useCallbackRef } from "@repo/ui/hooks/use-callback-ref";
import { useCallbackRef } from "@repo/ui/src/hooks/use-callback-ref.js";
/**
* @see https://github.com/radix-ui/primitives/blob/main/packages/react/use-controllable-state/src/useControllableState.tsx

View file

@ -0,0 +1,3 @@
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 4.25V16.75M16.25 10.5H3.75" stroke="#C2C7CB" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 228 B

View file

@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.25 16.25L17.5 10M17.5 10L11.25 3.75M17.5 10H2.5" stroke="#369DFD" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 249 B

View file

@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.1875 10C7.1875 10.0829 7.15458 10.1624 7.09597 10.221C7.03737 10.2796 6.95788 10.3125 6.875 10.3125C6.79212 10.3125 6.71263 10.2796 6.65403 10.221C6.59542 10.1624 6.5625 10.0829 6.5625 10C6.5625 9.91712 6.59542 9.83763 6.65403 9.77903C6.71263 9.72042 6.79212 9.6875 6.875 9.6875C6.95788 9.6875 7.03737 9.72042 7.09597 9.77903C7.15458 9.83763 7.1875 9.91712 7.1875 10ZM7.1875 10H6.875M10.3125 10C10.3125 10.0829 10.2796 10.1624 10.221 10.221C10.1624 10.2796 10.0829 10.3125 10 10.3125C9.91712 10.3125 9.83763 10.2796 9.77903 10.221C9.72042 10.1624 9.6875 10.0829 9.6875 10C9.6875 9.91712 9.72042 9.83763 9.77903 9.77903C9.83763 9.72042 9.91712 9.6875 10 9.6875C10.0829 9.6875 10.1624 9.72042 10.221 9.77903C10.2796 9.83763 10.3125 9.91712 10.3125 10ZM10.3125 10H10M13.4375 10C13.4375 10.0829 13.4046 10.1624 13.346 10.221C13.2874 10.2796 13.2079 10.3125 13.125 10.3125C13.0421 10.3125 12.9626 10.2796 12.904 10.221C12.8454 10.1624 12.8125 10.0829 12.8125 10C12.8125 9.91712 12.8454 9.83763 12.904 9.77903C12.9626 9.72042 13.0421 9.6875 13.125 9.6875C13.2079 9.6875 13.2874 9.72042 13.346 9.77903C13.4046 9.83763 13.4375 9.91712 13.4375 10ZM13.4375 10H13.125M17.5 10C17.5 13.7967 14.1417 16.875 10 16.875C9.28099 16.8759 8.56503 16.7814 7.87083 16.5942C6.8923 17.2824 5.6986 17.5951 4.50833 17.475C4.376 17.4622 4.24422 17.4442 4.11333 17.4208C4.52406 16.9368 4.80456 16.356 4.92833 15.7333C5.00333 15.3525 4.8175 14.9825 4.53917 14.7117C3.275 13.4817 2.5 11.8242 2.5 10C2.5 6.20333 5.85833 3.125 10 3.125C14.1417 3.125 17.5 6.20333 17.5 10Z" stroke="#989EA4" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.5 17.5L14 14L17.5 6.5L10 10L6.5 17.5ZM12 13C11.7167 13 11.4793 12.904 11.288 12.712C11.0967 12.52 11.0007 12.2827 11 12C10.9993 11.7173 11.0953 11.48 11.288 11.288C11.4807 11.096 11.718 11 12 11C12.282 11 12.5197 11.096 12.713 11.288C12.9063 11.48 13.002 11.7173 13 12C12.998 12.2827 12.902 12.5203 12.712 12.713C12.522 12.9057 12.2847 13.0013 12 13ZM12 22C10.6167 22 9.31667 21.7373 8.1 21.212C6.88334 20.6867 5.825 19.9743 4.925 19.075C4.025 18.1757 3.31267 17.1173 2.788 15.9C2.26333 14.6827 2.00067 13.3827 2 12C1.99933 10.6173 2.262 9.31733 2.788 8.1C3.314 6.88267 4.02633 5.82433 4.925 4.925C5.82367 4.02567 6.882 3.31333 8.1 2.788C9.318 2.26267 10.618 2 12 2C13.382 2 14.682 2.26267 15.9 2.788C17.118 3.31333 18.1763 4.02567 19.075 4.925C19.9737 5.82433 20.6863 6.88267 21.213 8.1C21.7397 9.31733 22.002 10.6173 22 12C21.998 13.3827 21.7353 14.6827 21.212 15.9C20.6887 17.1173 19.9763 18.1757 19.075 19.075C18.1737 19.9743 17.1153 20.687 15.9 21.213C14.6847 21.739 13.3847 22.0013 12 22Z" fill="#6A737D"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2.25C6.615 2.25 2.25 6.615 2.25 12C2.25 17.385 6.615 21.75 12 21.75C17.385 21.75 21.75 17.385 21.75 12C21.75 6.615 17.385 2.25 12 2.25ZM12.75 6C12.75 5.80109 12.671 5.61032 12.5303 5.46967C12.3897 5.32902 12.1989 5.25 12 5.25C11.8011 5.25 11.6103 5.32902 11.4697 5.46967C11.329 5.61032 11.25 5.80109 11.25 6V12C11.25 12.414 11.586 12.75 12 12.75H16.5C16.6989 12.75 16.8897 12.671 17.0303 12.5303C17.171 12.3897 17.25 12.1989 17.25 12C17.25 11.8011 17.171 11.6103 17.0303 11.4697C16.8897 11.329 16.6989 11.25 16.5 11.25H12.75V6Z" fill="#6A737D"/>
</svg>

After

Width:  |  Height:  |  Size: 701 B

View file

@ -0,0 +1,17 @@
import AddIcon from "./add.svg";
import ChatIcon from "./chat.svg";
import HistoryIcon from "./history.svg";
import ExploreIcon from "./explore.svg";
import MemoriesIcon from "./memories.svg";
import ArrowRightIcon from "./arrowRight.svg";
import SelectIcon from "./select.svg";
export {
AddIcon,
ChatIcon,
HistoryIcon,
ExploreIcon,
MemoriesIcon,
ArrowRightIcon,
SelectIcon,
};

View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.566 4.657C5.95195 4.55235 6.35011 4.49955 6.75 4.5H17.25C17.66 4.5 18.056 4.555 18.433 4.657C18.1837 4.15898 17.8006 3.7402 17.3268 3.44754C16.8529 3.15489 16.3069 2.99993 15.75 3H8.25C7.69288 2.99974 7.1467 3.15462 6.67265 3.44728C6.1986 3.73994 5.8154 4.15883 5.566 4.657ZM2.25 12C2.25 11.2044 2.56607 10.4413 3.12868 9.87868C3.69129 9.31607 4.45435 9 5.25 9H18.75C19.5456 9 20.3087 9.31607 20.8713 9.87868C21.4339 10.4413 21.75 11.2044 21.75 12V18C21.75 18.7956 21.4339 19.5587 20.8713 20.1213C20.3087 20.6839 19.5456 21 18.75 21H5.25C4.45435 21 3.69129 20.6839 3.12868 20.1213C2.56607 19.5587 2.25 18.7956 2.25 18V12ZM5.25 7.5C4.84 7.5 4.444 7.555 4.066 7.657C4.3154 7.15883 4.6986 6.73994 5.17265 6.44728C5.6467 6.15462 6.19288 5.99974 6.75 6H17.25C17.8069 5.99993 18.3529 6.15489 18.8268 6.44754C19.3006 6.7402 19.6837 7.15898 19.933 7.657C19.5474 7.55244 19.1496 7.49964 18.75 7.5H5.25Z" fill="#6A737D"/>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,4 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.25 11.25L9 15L12.75 11.25" stroke="#858B92" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.25 6.75L9 3L12.75 6.75" stroke="#858B92" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 347 B

View file

@ -0,0 +1,7 @@
import { cn } from "@repo/ui/lib/utils";
function Divider({ className }: { className?: string }) {
return <div className={cn("bg-[#2D343A] h-[1px] w-full", className)}></div>;
}
export default Divider;

View file

@ -11,7 +11,7 @@ import {
} from "react-hook-form";
import { cn } from "@repo/ui/lib/utils";
import { Label } from "@repo/ui/shadcn/label";
import { Label } from "@repo/ui/src/shadcn/label";
const Form = FormProvider;

4
packages/ui/src/ui.d.ts vendored Normal file
View file

@ -0,0 +1,4 @@
declare module "*.svg" {
const content: any;
export default content;
}