diff options
Diffstat (limited to 'apps/web/src')
31 files changed, 843 insertions, 501 deletions
diff --git a/apps/web/src/app/MessagePoster.tsx b/apps/web/src/app/MessagePoster.tsx index 64dc89fd..9b7011a8 100644 --- a/apps/web/src/app/MessagePoster.tsx +++ b/apps/web/src/app/MessagePoster.tsx @@ -1,21 +1,21 @@ -'use client'; +"use client"; -import { useEffect } from 'react'; +import { useEffect } from "react"; function MessagePoster({ jwt }: { jwt: string }) { useEffect(() => { - if (typeof window === 'undefined') return; - window.postMessage({ jwt }, '*'); + if (typeof window === "undefined") return; + window.postMessage({ jwt }, "*"); }, [jwt]); return ( <button onClick={() => { - if (typeof window === 'undefined') return; - window.postMessage({ jwt }, '*'); + if (typeof window === "undefined") return; + window.postMessage({ jwt }, "*"); }} > - Send message + Extension Auth </button> ); } diff --git a/apps/web/src/app/api/ask/route.ts b/apps/web/src/app/api/ask/route.ts index cad7a671..89123ac9 100644 --- a/apps/web/src/app/api/ask/route.ts +++ b/apps/web/src/app/api/ask/route.ts @@ -7,42 +7,62 @@ import { env } from "@/env"; export const runtime = "edge"; export async function POST(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 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 sessionData = await db.select().from(sessions).where(eq(sessions.sessionToken, token!)) + const sessionData = await db + .select() + .from(sessions) + .where(eq(sessions.sessionToken, token!)); - if (!sessionData || sessionData.length === 0) { - return new Response(JSON.stringify({ message: "Invalid Key, session not found." }), { status: 404 }); - } + if (!sessionData || sessionData.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, sessionData[0].userId)).limit(1) + const user = await db + .select() + .from(users) + .where(eq(users.id, sessionData[0].userId)) + .limit(1); - if (!user || user.length === 0) { - return NextResponse.json({ message: "Invalid Key, session not found." }, { status: 404 }); - } + if (!user || user.length === 0) { + return NextResponse.json( + { message: "Invalid Key, session not found." }, + { status: 404 }, + ); + } - const body = await req.json() as { - query: string; - } + const body = (await req.json()) as { + query: string; + }; - const resp = await fetch(`https://cf-ai-backend.dhravya.workers.dev/ask`, { - headers: { - "X-Custom-Auth-Key": env.BACKEND_SECURITY_KEY, - }, - method: "POST", - body: JSON.stringify({ - query: body.query, - }), - }) + const resp = await fetch(`https://cf-ai-backend.dhravya.workers.dev/ask`, { + headers: { + "X-Custom-Auth-Key": env.BACKEND_SECURITY_KEY, + }, + method: "POST", + body: JSON.stringify({ + query: body.query, + }), + }); - if (resp.status !== 200 || !resp.ok) { - const errorData = await resp.json(); - return new Response(JSON.stringify({ message: "Error in CF function", error: errorData }), { status: resp.status }); - } + if (resp.status !== 200 || !resp.ok) { + const errorData = await resp.json(); + 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); + // Stream the response back to the client + const { readable, writable } = new TransformStream(); + resp && resp.body!.pipeTo(writable); - return new Response(readable, { status: 200 }); -}
\ No newline at end of file + return new Response(readable, { status: 200 }); +} diff --git a/apps/web/src/app/api/chat/route.ts b/apps/web/src/app/api/chat/route.ts index aec5b0ea..bc7a4ee4 100644 --- a/apps/web/src/app/api/chat/route.ts +++ b/apps/web/src/app/api/chat/route.ts @@ -8,60 +8,85 @@ import { ChatHistory } from "../../../../types/memory"; export const runtime = "edge"; export async function POST(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 sessionData = await db.select().from(sessions).where(eq(sessions.sessionToken, token!)) - - if (!sessionData || sessionData.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, sessionData[0].userId)).limit(1) - - if (!user || user.length === 0) { - return NextResponse.json({ message: "Invalid Key, session not found." }, { status: 404 }); - } - - const session = { session: sessionData[0], user: user[0] } - - 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 }); - } - - - 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": env.BACKEND_SECURITY_KEY, - }, - method: "POST", - body: JSON.stringify({ - chatHistory: chatHistory.chatHistory ?? [] - }) - }) - - console.log(resp.status) - console.log(resp.statusText) - - 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 }); -}
\ No newline at end of file + 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 sessionData = await db + .select() + .from(sessions) + .where(eq(sessions.sessionToken, token!)); + + if (!sessionData || sessionData.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, sessionData[0].userId)) + .limit(1); + + if (!user || user.length === 0) { + return NextResponse.json( + { message: "Invalid Key, session not found." }, + { status: 404 }, + ); + } + + const session = { session: sessionData[0], user: user[0] }; + + 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, + }); + } + + 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": env.BACKEND_SECURITY_KEY, + }, + method: "POST", + body: JSON.stringify({ + chatHistory: chatHistory.chatHistory ?? [], + }), + }, + ); + + console.log(resp.status); + console.log(resp.statusText); + + 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 }); +} diff --git a/apps/web/src/app/api/me/route.ts b/apps/web/src/app/api/me/route.ts index a2e713e1..6d269872 100644 --- a/apps/web/src/app/api/me/route.ts +++ b/apps/web/src/app/api/me/route.ts @@ -7,19 +7,42 @@ import { env } from "@/env"; 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 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!)) + 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 }); - } + 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) + 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 }); - } + 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 }); -}
\ No newline at end of file + return new Response( + JSON.stringify({ + message: "OK", + data: { session: session[0], user: user[0] }, + }), + { status: 200 }, + ); +} diff --git a/apps/web/src/app/api/query/route.ts b/apps/web/src/app/api/query/route.ts index 28f441bc..02bb79da 100644 --- a/apps/web/src/app/api/query/route.ts +++ b/apps/web/src/app/api/query/route.ts @@ -7,46 +7,72 @@ import { env } from "@/env"; 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 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 sessionData = await db.select().from(sessions).where(eq(sessions.sessionToken, token!)) + const sessionData = await db + .select() + .from(sessions) + .where(eq(sessions.sessionToken, token!)); - if (!sessionData || sessionData.length === 0) { - return new Response(JSON.stringify({ message: "Invalid Key, session not found." }), { status: 404 }); - } + if (!sessionData || sessionData.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, sessionData[0].userId)).limit(1) + const user = await db + .select() + .from(users) + .where(eq(users.id, sessionData[0].userId)) + .limit(1); - if (!user || user.length === 0) { - return NextResponse.json({ message: "Invalid Key, session not found." }, { status: 404 }); - } + if (!user || user.length === 0) { + return NextResponse.json( + { message: "Invalid Key, session not found." }, + { status: 404 }, + ); + } - const session = { session: sessionData[0], user: user[0] } + const session = { session: sessionData[0], user: user[0] }; - const query = new URL(req.url).searchParams.get("q"); - const sourcesOnly = new URL(req.url).searchParams.get("sourcesOnly") ?? "false"; + const query = new URL(req.url).searchParams.get("q"); + const sourcesOnly = + new URL(req.url).searchParams.get("sourcesOnly") ?? "false"; - if (!query) { - return new Response(JSON.stringify({ message: "Invalid query" }), { status: 400 }); - } + if (!query) { + return new Response(JSON.stringify({ message: "Invalid query" }), { + status: 400, + }); + } - const resp = await fetch(`https://cf-ai-backend.dhravya.workers.dev/query?q=${query}&user=${session.user.email ?? session.user.name}&sourcesOnly=${sourcesOnly}`, { - headers: { - "X-Custom-Auth-Key": env.BACKEND_SECURITY_KEY, - } - }) + const resp = await fetch( + `https://cf-ai-backend.dhravya.workers.dev/query?q=${query}&user=${session.user.email ?? session.user.name}&sourcesOnly=${sourcesOnly}`, + { + headers: { + "X-Custom-Auth-Key": env.BACKEND_SECURITY_KEY, + }, + }, + ); - console.log(resp.status) + console.log(resp.status); - 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 }); - } + 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); + // Stream the response back to the client + const { readable, writable } = new TransformStream(); + resp && resp.body!.pipeTo(writable); - return new Response(readable, { status: 200 }); -}
\ No newline at end of file + return new Response(readable, { status: 200 }); +} diff --git a/apps/web/src/app/api/store/route.ts b/apps/web/src/app/api/store/route.ts index 06db08b9..ebe23077 100644 --- a/apps/web/src/app/api/store/route.ts +++ b/apps/web/src/app/api/store/route.ts @@ -1,6 +1,12 @@ import { db } from "@/server/db"; import { and, eq } from "drizzle-orm"; -import { contentToSpace, sessions, storedContent, users, space } from "@/server/db/schema"; +import { + contentToSpace, + sessions, + storedContent, + users, + space, +} from "@/server/db/schema"; import { type NextRequest, NextResponse } from "next/server"; import { env } from "@/env"; import { getMetaData } from "@/server/helpers"; @@ -8,94 +14,123 @@ import { getMetaData } from "@/server/helpers"; export const runtime = "edge"; export async function POST(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 ", ""); - - if (!token) { - return new Response(JSON.stringify({ message: "Invalid Key, session not found." }), { status: 404 }); - } - - const sessionData = await db.select().from(sessions).where(eq(sessions.sessionToken, token!)) - - if (!sessionData || sessionData.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, sessionData[0].userId)).limit(1) - - if (!user || user.length === 0) { - return NextResponse.json({ message: "Invalid Key, session not found." }, { status: 404 }); - } - - const session = { session: sessionData[0], user: user[0] } - - const data = await req.json() as { - pageContent: string, - url: string, - space?: string - }; - - const metadata = await getMetaData(data.url); - - - let id: number | undefined = undefined; - - let storeToSpace = data.space - - if (!storeToSpace) { - storeToSpace = 'all' - } - - const storedContentId = 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 - }) - - id = storedContentId.meta.last_row_id; - - if (!id) { - return NextResponse.json({ message: "Error", error: "Error in CF function" }, { status: 500 }); - } - - let spaceID = 0; - - const spaceData = await db.select().from(space).where(and(eq(space.name, storeToSpace), eq(space.user, session.user.id))).limit(1) - spaceID = spaceData[0]?.id - - if (!spaceData || spaceData.length === 0) { - const spaceId = await db.insert(space).values({ - name: storeToSpace, - user: session.user.id - }) - spaceID = spaceId.meta.last_row_id; - } - - await db.insert(contentToSpace).values({ - contentId: id as number, - spaceId: spaceID - }) - - const res = await Promise.race([ - fetch("https://cf-ai-backend.dhravya.workers.dev/add", { - method: "POST", - headers: { - "X-Custom-Auth-Key": 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) { - return NextResponse.json({ message: "Error", error: "Error in CF function" }, { status: 500 }); - } - - return NextResponse.json({ message: "OK", data: "Success" }, { status: 200 }); -}
\ No newline at end of file + 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 ", ""); + + if (!token) { + return new Response( + JSON.stringify({ message: "Invalid Key, session not found." }), + { status: 404 }, + ); + } + + const sessionData = await db + .select() + .from(sessions) + .where(eq(sessions.sessionToken, token!)); + + if (!sessionData || sessionData.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, sessionData[0].userId)) + .limit(1); + + if (!user || user.length === 0) { + return NextResponse.json( + { message: "Invalid Key, session not found." }, + { status: 404 }, + ); + } + + const session = { session: sessionData[0], user: user[0] }; + + const data = (await req.json()) as { + pageContent: string; + url: string; + space?: string; + }; + + const metadata = await getMetaData(data.url); + + let id: number | undefined = undefined; + + let storeToSpace = data.space; + + if (!storeToSpace) { + storeToSpace = "all"; + } + + const storedContentId = 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, + }); + + id = storedContentId.meta.last_row_id; + + if (!id) { + return NextResponse.json( + { message: "Error", error: "Error in CF function" }, + { status: 500 }, + ); + } + + let spaceID = 0; + + const spaceData = await db + .select() + .from(space) + .where(and(eq(space.name, storeToSpace), eq(space.user, session.user.id))) + .limit(1); + spaceID = spaceData[0]?.id; + + if (!spaceData || spaceData.length === 0) { + const spaceId = await db.insert(space).values({ + name: storeToSpace, + user: session.user.id, + }); + spaceID = spaceId.meta.last_row_id; + } + + await db.insert(contentToSpace).values({ + contentId: id as number, + spaceId: spaceID, + }); + + const res = (await Promise.race([ + fetch("https://cf-ai-backend.dhravya.workers.dev/add", { + method: "POST", + headers: { + "X-Custom-Auth-Key": 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) { + return NextResponse.json( + { message: "Error", error: "Error in CF function" }, + { status: 500 }, + ); + } + + return NextResponse.json({ message: "OK", data: "Success" }, { status: 200 }); +} diff --git a/apps/web/src/app/content.tsx b/apps/web/src/app/content.tsx index 39f2948d..50e0617c 100644 --- a/apps/web/src/app/content.tsx +++ b/apps/web/src/app/content.tsx @@ -1,16 +1,16 @@ -'use client'; -import Main from '@/components/Main'; -import Sidebar from '@/components/Sidebar/index'; -import { SessionProvider } from 'next-auth/react'; -import { useState } from 'react'; +"use client"; +import Main from "@/components/Main"; +import Sidebar from "@/components/Sidebar/index"; +import { SessionProvider } from "next-auth/react"; +import { useState } from "react"; -export default function Content() { +export default function Content({ jwt }: { jwt: string }) { const [selectedItem, setSelectedItem] = useState<string | null>(null); return ( <SessionProvider> <div className="flex w-screen"> - <Sidebar selectChange={setSelectedItem} /> + <Sidebar jwt={jwt} selectChange={setSelectedItem} /> <Main sidebarOpen={selectedItem !== null} /> </div> </SessionProvider> diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index cedb03dc..23caee5b 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -20,7 +20,7 @@ } body { - @apply bg-rgray-2 text-rgray-11 max-h-screen overflow-y-hidden; + @apply text-rgray-11 max-h-screen overflow-y-hidden bg-white; /* color: rgb(var(--foreground-rgb)); background: linear-gradient( to bottom, @@ -66,7 +66,7 @@ body { } .chat-answer h1 { - @apply text-rgray-11 text-xl font-medium my-5; + @apply text-rgray-11 my-5 text-xl font-medium; } .chat-answer img { diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index 1b204dec..42485461 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -1,8 +1,9 @@ import type { Metadata } from "next"; -import { Roboto } from "next/font/google"; +import { Roboto, Inter } from "next/font/google"; import "./globals.css"; const roboto = Roboto({ weight: ["300", "400", "500"], subsets: ["latin"] }); +const inter = Inter({ weight: ["300", "400", "500"], subsets: ["latin"] }); export const metadata: Metadata = { title: "Create Next App", @@ -16,7 +17,7 @@ export default function RootLayout({ }>) { return ( <html lang="en" className="dark"> - <body className={roboto.className}> + <body className={inter.className}> <div vaul-drawer-wrapper="" className="min-w-screen overflow-x-hidden"> {children} </div> diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx index 95e18e83..6f9ca753 100644 --- a/apps/web/src/app/page.tsx +++ b/apps/web/src/app/page.tsx @@ -59,26 +59,26 @@ export default async function Home() { const collectedSpaces = contents.length > 0 ? await transformContent(contents) : []; - collectedSpaces.push({ - id: 2, - title: "Test", - content: [ - { - id: 1, - content: "Test", - title: "Vscode", - description: "Test", - url: "https://vscode-remake.vercel.app/", - savedAt: new Date(), - baseUrl: "https://vscode-remake.vercel.app/", - image: "https://vscode-remake.vercel.app/favicon.svg", - }, - ], - }); + // collectedSpaces.push({ + // id: 2, + // title: "Test", + // content: [ + // { + // id: 1, + // content: "Test", + // title: "Vscode", + // description: "Test", + // url: "https://vscode-remake.vercel.app/", + // savedAt: new Date(), + // baseUrl: "https://vscode-remake.vercel.app/", + // image: "https://vscode-remake.vercel.app/favicon.svg", + // }, + // ], + // }); return ( <MemoryProvider spaces={collectedSpaces}> - <Content /> + <Content jwt={token} /> {/* <MessagePoster jwt={token} /> */} </MemoryProvider> ); diff --git a/apps/web/src/app/privacy/page.tsx b/apps/web/src/app/privacy/page.tsx index 5d0ae2b8..8d126dff 100644 --- a/apps/web/src/app/privacy/page.tsx +++ b/apps/web/src/app/privacy/page.tsx @@ -1,6 +1,6 @@ -import React from 'react'; -import Markdown from 'react-markdown'; -import { policy } from './privacy'; +import React from "react"; +import Markdown from "react-markdown"; +import { policy } from "./privacy"; function Page() { return ( diff --git a/apps/web/src/app/privacy/privacy.ts b/apps/web/src/app/privacy/privacy.ts index 3e3df4fb..2034f191 100644 --- a/apps/web/src/app/privacy/privacy.ts +++ b/apps/web/src/app/privacy/privacy.ts @@ -46,4 +46,4 @@ If you have any questions about this Privacy Policy, the practices of this site, - Email: [email protected] -This document was last updated on March 2, 2024.`
\ No newline at end of file +This document was last updated on March 2, 2024.`; diff --git a/apps/web/src/components/Main.tsx b/apps/web/src/components/Main.tsx index c621c68f..b34755f9 100644 --- a/apps/web/src/components/Main.tsx +++ b/apps/web/src/components/Main.tsx @@ -11,6 +11,7 @@ import { ChatHistory } from "../../types/memory"; import { ChatAnswer, ChatMessage, ChatQuestion } from "./ChatMessage"; import { useRouter, useSearchParams } from "next/navigation"; import { useMemory } from "@/contexts/MemoryContext"; +import WordMark from "./WordMark"; function supportsDVH() { try { @@ -293,8 +294,8 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) { hide ? "" : "main-hidden", )} > - <h1 className="text-rgray-11 mt-auto w-full text-center text-3xl md:mt-0"> - Ask your Second brain + <h1 className="text-rgray-11 mt-auto w-full text-center text-3xl font-bold tracking-tight md:mt-0"> + Ask your second brain </h1> <Textarea2 @@ -308,14 +309,13 @@ export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) { duration: 0.2, }} textAreaProps={{ - placeholder: "Ask your SuperMemory...", + placeholder: "Ask your second brain...", className: "h-auto overflow-auto md:h-full md:resize-none text-lg py-0 px-2 pt-2 md:py-0 md:p-5 resize-y text-rgray-11 w-full min-h-[1em]", value, autoFocus: true, onChange: (e) => setValue(e.target.value), onKeyDown: (e) => { - console.log(e.key, e.ctrlKey, e.metaKey); if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) { onSend(); } diff --git a/apps/web/src/components/QueryAI.tsx b/apps/web/src/components/QueryAI.tsx new file mode 100644 index 00000000..894b5d2d --- /dev/null +++ b/apps/web/src/components/QueryAI.tsx @@ -0,0 +1,139 @@ +"use client"; + +import { Label } from "./ui/label"; +import React, { useEffect, useState } from "react"; +import { Input } from "./ui/input"; +import { Button } from "./ui/button"; +import SearchResults from "./SearchResults"; + +function QueryAI() { + const [searchResults, setSearchResults] = useState<string[]>([]); + const [isAiLoading, setIsAiLoading] = useState(false); + + const [aiResponse, setAIResponse] = useState(""); + const [input, setInput] = useState(""); + const [toBeParsed, setToBeParsed] = useState(""); + + const handleStreamData = (newChunk: string) => { + // Append the new chunk to the existing data to be parsed + setToBeParsed((prev) => prev + newChunk); + }; + + useEffect(() => { + // Define a function to try parsing the accumulated data + const tryParseAccumulatedData = () => { + // Attempt to parse the "toBeParsed" state as JSON + try { + // Split the accumulated data by the known delimiter "\n\n" + const parts = toBeParsed.split("\n\n"); + let remainingData = ""; + + // Process each part to extract JSON objects + parts.forEach((part, index) => { + try { + const parsedPart = JSON.parse(part.replace("data: ", "")); // Try to parse the part as JSON + + // If the part is the last one and couldn't be parsed, keep it to accumulate more data + if (index === parts.length - 1 && !parsedPart) { + remainingData = part; + } else if (parsedPart && parsedPart.response) { + // If the part is parsable and has the "response" field, update the AI response state + setAIResponse((prev) => prev + parsedPart.response); + } + } catch (error) { + // If parsing fails and it's not the last part, it's a malformed JSON + if (index !== parts.length - 1) { + console.error("Malformed JSON part: ", part); + } else { + // If it's the last part, it may be incomplete, so keep it + remainingData = part; + } + } + }); + + // Update the toBeParsed state to only contain the unparsed remainder + if (remainingData !== toBeParsed) { + setToBeParsed(remainingData); + } + } catch (error) { + console.error("Error parsing accumulated data: ", error); + } + }; + + // Call the parsing function if there's data to be parsed + if (toBeParsed) { + tryParseAccumulatedData(); + } + }, [toBeParsed]); + + const getSearchResults = async (e: React.FormEvent<HTMLFormElement>) => { + e.preventDefault(); + setIsAiLoading(true); + + const sourcesResponse = await fetch( + `/api/query?sourcesOnly=true&q=${input}`, + ); + + const sourcesInJson = (await sourcesResponse.json()) as { + ids: string[]; + }; + + setSearchResults(sourcesInJson.ids); + + const response = await fetch(`/api/query?q=${input}`); + + if (response.status !== 200) { + setIsAiLoading(false); + return; + } + + if (response.body) { + let reader = response.body.getReader(); + let decoder = new TextDecoder("utf-8"); + let result = ""; + + // @ts-ignore + reader.read().then(function processText({ done, value }) { + if (done) { + // setSearchResults(JSON.parse(result.replace('data: ', ''))); + // setIsAiLoading(false); + return; + } + + handleStreamData(decoder.decode(value)); + + return reader.read().then(processText); + }); + } + }; + + return ( + <div className="mx-auto w-full max-w-2xl"> + <form onSubmit={async (e) => await getSearchResults(e)} className="mt-8"> + <Label htmlFor="searchInput">Ask your SuperMemory</Label> + <div className="flex flex-col space-y-2 md:w-full md:flex-row md:items-center md:space-x-2 md:space-y-0"> + <Input + value={input} + onChange={(e) => setInput(e.target.value)} + placeholder="Search using AI... ✨" + id="searchInput" + /> + <Button + disabled={isAiLoading} + className="max-w-min md:w-full" + type="submit" + variant="default" + > + Ask AI + </Button> + </div> + </form> + + {searchResults && ( + <SearchResults aiResponse={aiResponse} sources={searchResults} /> + )} + </div> + ); +} + +export default QueryAI; diff --git a/apps/web/src/components/SearchResults.tsx b/apps/web/src/components/SearchResults.tsx new file mode 100644 index 00000000..d348814e --- /dev/null +++ b/apps/web/src/components/SearchResults.tsx @@ -0,0 +1,40 @@ +"use client"; + +import React from "react"; +import { Card, CardContent } from "./ui/card"; +import Markdown from "react-markdown"; +import remarkGfm from "remark-gfm"; + +function SearchResults({ + aiResponse, + sources, +}: { + aiResponse: string; + sources: string[]; +}) { + return ( + <div + style={{ + backgroundImage: `linear-gradient(to right, #E5D9F2, #CDC1FF)`, + }} + className="mx-auto mt-4 w-full max-w-2xl space-y-6 rounded-xl border px-4 py-6" + > + <div className="text-start"> + <div className="text-xl text-black"> + <Markdown remarkPlugins={[remarkGfm]}> + {aiResponse.replace("</s>", "")} + </Markdown> + </div> + </div> + <div className="grid gap-6"> + {sources.map((value, index) => ( + <Card key={index}> + <CardContent className="space-y-2">{value}</CardContent> + </Card> + ))} + </div> + </div> + ); +} + +export default SearchResults; diff --git a/apps/web/src/components/Sidebar/CategoryItem.tsx b/apps/web/src/components/Sidebar/CategoryItem.tsx index 0cf8a70c..7fb571b5 100644 --- a/apps/web/src/components/Sidebar/CategoryItem.tsx +++ b/apps/web/src/components/Sidebar/CategoryItem.tsx @@ -1,13 +1,13 @@ -'use client'; -import { cleanUrl } from '@/lib/utils'; -import { StoredContent } from '@/server/db/schema'; +"use client"; +import { cleanUrl } from "@/lib/utils"; +import { StoredContent } from "@/server/db/schema"; import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, -} from '../ui/dropdown-menu'; -import { Label } from '../ui/label'; +} from "../ui/dropdown-menu"; +import { Label } from "../ui/label"; import { ArrowUpRight, MoreHorizontal, @@ -19,8 +19,8 @@ import { ChevronRight, Plus, Minus, -} from 'lucide-react'; -import { useState } from 'react'; +} from "lucide-react"; +import { useState } from "react"; import { Drawer, DrawerContent, @@ -29,106 +29,106 @@ import { DrawerDescription, DrawerFooter, DrawerClose, -} from '../ui/drawer'; -import { Input } from '../ui/input'; -import { Textarea } from '../ui/textarea'; -import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'; +} from "../ui/drawer"; +import { Input } from "../ui/input"; +import { Textarea } from "../ui/textarea"; +import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"; import { AnimatePresence, motion, Reorder, useMotionValue, -} from 'framer-motion'; +} from "framer-motion"; const pages: StoredContent[] = [ { id: 1, - content: '', - title: 'Visual Studio Code', - url: 'https://code.visualstudio.com', - description: '', - image: 'https://code.visualstudio.com/favicon.ico', - baseUrl: 'https://code.visualstudio.com', + content: "", + title: "Visual Studio Code", + url: "https://code.visualstudio.com", + description: "", + image: "https://code.visualstudio.com/favicon.ico", + baseUrl: "https://code.visualstudio.com", savedAt: new Date(), }, { id: 2, - content: '', + content: "", title: "yxshv/vscode: An unofficial remake of vscode's landing page", - url: 'https://github.com/yxshv/vscode', - description: '', - image: 'https://github.com/favicon.ico', - baseUrl: 'https://github.com', + url: "https://github.com/yxshv/vscode", + description: "", + image: "https://github.com/favicon.ico", + baseUrl: "https://github.com", savedAt: new Date(), }, { id: 3, - content: '', + content: "", title: "yxshv/vscode: An unofficial remake of vscode's landing page", - url: 'https://github.com/yxshv/vscode', - description: '', - image: 'https://github.com/favicon.ico', - baseUrl: 'https://github.com', + url: "https://github.com/yxshv/vscode", + description: "", + image: "https://github.com/favicon.ico", + baseUrl: "https://github.com", savedAt: new Date(), }, { id: 4, - content: '', + content: "", title: "yxshv/vscode: An unofficial remake of vscode's landing page", - url: 'https://github.com/yxshv/vscode', - description: '', - image: 'https://github.com/favicon.ico', - baseUrl: 'https://github.com', + url: "https://github.com/yxshv/vscode", + description: "", + image: "https://github.com/favicon.ico", + baseUrl: "https://github.com", savedAt: new Date(), }, { id: 5, - content: '', + content: "", title: "yxshv/vscode: An unofficial remake of vscode's landing page", - url: 'https://github.com/yxshv/vscode', - description: '', - image: 'https://github.com/favicon.ico', - baseUrl: 'https://github.com', + url: "https://github.com/yxshv/vscode", + description: "", + image: "https://github.com/favicon.ico", + baseUrl: "https://github.com", savedAt: new Date(), }, { id: 6, - content: '', + content: "", title: "yxshv/vscode: An unofficial remake of vscode's landing page", - url: 'https://github.com/yxshv/vscode', - description: '', - image: 'https://github.com/favicon.ico', - baseUrl: 'https://github.com', + url: "https://github.com/yxshv/vscode", + description: "", + image: "https://github.com/favicon.ico", + baseUrl: "https://github.com", savedAt: new Date(), }, { id: 7, - content: '', + content: "", title: "yxshv/vscode: An unofficial remake of vscode's landing page", - url: 'https://github.com/yxshv/vscode', - description: '', - image: 'https://github.com/favicon.ico', - baseUrl: 'https://github.com', + url: "https://github.com/yxshv/vscode", + description: "", + image: "https://github.com/favicon.ico", + baseUrl: "https://github.com", savedAt: new Date(), }, { id: 8, - content: '', + content: "", title: "yxshv/vscode: An unofficial remake of vscode's landing page", - url: 'https://github.com/yxshv/vscode', - description: '', - image: 'https://github.com/favicon.ico', - baseUrl: 'https://github.com', + url: "https://github.com/yxshv/vscode", + description: "", + image: "https://github.com/favicon.ico", + baseUrl: "https://github.com", savedAt: new Date(), }, { id: 9, - content: '', + content: "", title: "yxshv/vscode: An unofficial remake of vscode's landing page", - url: 'https://github.com/yxshv/vscode', - description: '', - image: 'https://github.com/favicon.ico', - baseUrl: 'https://github.com', + url: "https://github.com/yxshv/vscode", + description: "", + image: "https://github.com/favicon.ico", + baseUrl: "https://github.com", savedAt: new Date(), }, ]; @@ -153,13 +153,13 @@ export const CategoryItem: React.FC<{ item: StoredContent }> = ({ item }) => { /> <ChevronDown data-down-icon - className={`absolute left-1/2 top-1/2 z-[2] h-4 w-4 min-w-4 -translate-x-1/2 -translate-y-1/2 scale-75 opacity-0 transition-[transform,opacity] duration-150 ${isExpanded ? 'rotate-180' : 'rotate-0'}`} + className={`absolute left-1/2 top-1/2 z-[2] h-4 w-4 min-w-4 -translate-x-1/2 -translate-y-1/2 scale-75 opacity-0 transition-[transform,opacity] duration-150 ${isExpanded ? "rotate-180" : "rotate-0"}`} strokeWidth={1.5} /> </div> <span className="w-full truncate text-nowrap text-left"> - {item.title ?? 'Untitled website'} + {item.title ?? "Untitled website"} </span> </button> <Drawer @@ -178,7 +178,7 @@ export const CategoryItem: React.FC<{ item: StoredContent }> = ({ item }) => { href={item.url} className="text-rgray-11/90 bg-rgray-3 text-md absolute right-0 top-0 flex w-min translate-y-1/2 items-center justify-center gap-1 rounded-full px-5 py-1" > - <img src={item.image ?? '/brain.png'} className="h-4 w-4" /> + <img src={item.image ?? "/brain.png"} className="h-4 w-4" /> {cleanUrl(item.url)} </a> </DrawerHeader> @@ -188,16 +188,16 @@ export const CategoryItem: React.FC<{ item: StoredContent }> = ({ item }) => { <Input className="" required - value={item.title ?? ''} - placeholder={item.title ?? 'Enter the title for the page'} + value={item.title ?? ""} + placeholder={item.title ?? "Enter the title for the page"} /> </div> <div className="mt-5"> <Label>Additional Context</Label> <Textarea className="" - value={item.content ?? ''} - placeholder={'Enter additional context for this page'} + value={item.content ?? ""} + placeholder={"Enter additional context for this page"} /> </div> <DrawerFooter className="flex flex-row-reverse items-center justify-end px-0 pt-5"> @@ -224,7 +224,7 @@ export const CategoryItem: React.FC<{ item: StoredContent }> = ({ item }) => { onReorder={setItems} as="div" initial={{ height: 0 }} - animate={{ height: 'auto' }} + animate={{ height: "auto" }} exit={{ height: 0, transition: {}, @@ -272,8 +272,8 @@ export const CategoryPage: React.FC<{ > <div className="relative h-4 min-w-4"> <img - src={item.image ?? '/brain.png'} - alt={item.title ?? 'Untitiled website'} + src={item.image ?? "/brain.png"} + alt={item.title ?? "Untitiled website"} className="z-1 h-4 w-4 transition-[transform,opacity] delay-150 duration-150" /> <ArrowUpRight @@ -284,7 +284,7 @@ export const CategoryPage: React.FC<{ </div> <span className="w-full truncate text-nowrap"> - {item.title ?? 'Untitled website'} + {item.title ?? "Untitled website"} </span> </a> <button diff --git a/apps/web/src/components/Sidebar/FilterCombobox.tsx b/apps/web/src/components/Sidebar/FilterCombobox.tsx index 76b66db9..a8e3a1e5 100644 --- a/apps/web/src/components/Sidebar/FilterCombobox.tsx +++ b/apps/web/src/components/Sidebar/FilterCombobox.tsx @@ -1,10 +1,10 @@ -'use client'; +"use client"; -import * as React from 'react'; -import { Check, ChevronsUpDown } from 'lucide-react'; +import * as React from "react"; +import { Check, ChevronsUpDown } from "lucide-react"; -import { cn } from '@/lib/utils'; -import { Button } from '@/components/ui/button'; +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; import { Command, CommandEmpty, @@ -12,28 +12,30 @@ import { CommandInput, CommandItem, CommandList, -} from '@/components/ui/command'; +} from "@/components/ui/command"; import { Popover, PopoverContent, PopoverTrigger, -} from '@/components/ui/popover'; -import { SpaceIcon } from '@/assets/Memories'; -import { AnimatePresence, LayoutGroup, motion } from 'framer-motion'; -import { useMemory } from '@/contexts/MemoryContext'; +} from "@/components/ui/popover"; +import { SpaceIcon } from "@/assets/Memories"; +import { AnimatePresence, LayoutGroup, motion } from "framer-motion"; +import { useMemory } from "@/contexts/MemoryContext"; export interface Props extends React.ButtonHTMLAttributes<HTMLButtonElement> { - side?: 'top' | 'bottom'; - align?: 'end' | 'start' | 'center'; + side?: "top" | "bottom"; + align?: "end" | "start" | "center"; onClose?: () => void; selectedSpaces: number[]; - setSelectedSpaces: (spaces: number[] | ((prev: number[]) => number[])) => void; + setSelectedSpaces: ( + spaces: number[] | ((prev: number[]) => number[]), + ) => void; } export function FilterCombobox({ className, - side = 'bottom', - align = 'center', + side = "bottom", + align = "center", onClose, selectedSpaces, setSelectedSpaces, @@ -65,7 +67,7 @@ export function FilterCombobox({ <button data-state-on={open} className={cn( - 'text-rgray-11/70 on:bg-rgray-3 focus-visible:ring-rgray-8 hover:bg-rgray-3 relative flex items-center justify-center gap-1 rounded-md px-3 py-1.5 ring-2 ring-transparent focus-visible:outline-none', + "text-rgray-11/70 on:bg-rgray-3 focus-visible:ring-rgray-8 hover:bg-rgray-3 relative flex items-center justify-center gap-1 rounded-md px-3 py-1.5 ring-2 ring-transparent focus-visible:outline-none", className, )} {...props} @@ -129,7 +131,7 @@ export function FilterCombobox({ <Check data-state-on={selectedSpaces.includes(space.id)} className={cn( - 'on:opacity-100 ml-auto h-4 w-4 opacity-0', + "on:opacity-100 ml-auto h-4 w-4 opacity-0", )} /> </motion.div> diff --git a/apps/web/src/components/Sidebar/index.tsx b/apps/web/src/components/Sidebar/index.tsx index 830b0f05..568aa3dd 100644 --- a/apps/web/src/components/Sidebar/index.tsx +++ b/apps/web/src/components/Sidebar/index.tsx @@ -1,29 +1,35 @@ -'use client'; -import { MemoryIcon } from '../../assets/Memories'; -import { Trash2, User2 } from 'lucide-react'; -import React, { useEffect, useState } from 'react'; -import { MemoriesBar } from './MemoriesBar'; -import { AnimatePresence, motion } from 'framer-motion'; -import { Bin } from '@/assets/Bin'; -import { Avatar, AvatarFallback, AvatarImage } from '@radix-ui/react-avatar'; -import { useSession } from 'next-auth/react'; +"use client"; +import { MemoryIcon } from "../../assets/Memories"; +import { Trash2, User2 } from "lucide-react"; +import React, { useEffect, useState } from "react"; +import { MemoriesBar } from "./MemoriesBar"; +import { AnimatePresence, motion } from "framer-motion"; +import { Bin } from "@/assets/Bin"; +import { Avatar, AvatarFallback, AvatarImage } from "@radix-ui/react-avatar"; +import { useSession } from "next-auth/react"; +import MessagePoster from "@/app/MessagePoster"; +import Image from "next/image"; +import WordMark from "../WordMark"; export type MenuItem = { icon: React.ReactNode | React.ReactNode[]; label: string; content?: React.ReactNode; + labelDisplay?: React.ReactNode; }; export default function Sidebar({ selectChange, + jwt, }: { selectChange?: (selectedItem: string | null) => void; + jwt: string; }) { const { data: session } = useSession(); const menuItemsTop: Array<MenuItem> = [ { icon: <MemoryIcon className="h-10 w-10" />, - label: 'Memories', + label: "Memories", content: <MemoriesBar />, }, ]; @@ -31,7 +37,7 @@ export default function Sidebar({ const menuItemsBottom: Array<MenuItem> = [ { icon: <Trash2 strokeWidth={1.3} className="h-6 w-6" />, - label: 'Trash', + label: "Trash", }, { icon: ( @@ -47,12 +53,12 @@ export default function Sidebar({ <User2 strokeWidth={1.3} className="h-6 w-6" /> )} <AvatarFallback> - {session?.user?.name?.split(' ').map((n) => n[0])}{' '} + {session?.user?.name?.split(" ").map((n) => n[0])}{" "} </AvatarFallback> </Avatar> </div> ), - label: 'Profile', + label: "Profile", }, ]; @@ -70,22 +76,30 @@ export default function Sidebar({ return ( <> <div className="relative hidden h-screen max-h-screen w-max flex-col items-center text-sm font-light md:flex"> - <div className="bg-rgray-2 border-r-rgray-6 relative z-[50] flex h-full w-full flex-col items-center justify-center border-r px-2 py-5 "> + <div className="bg-rgray-3 border-r-rgray-6 relative z-[50] flex h-full w-full flex-col items-center justify-center border-r px-2 py-5 "> + <Image + className="mb-4 rounded-md" + src="/icons/logo_bw_without_bg.png" + alt="Smort logo" + width={50} + height={50} + /> + + <div className="bg-rgray-6 mb-8 h-[1px] w-full" /> + <MenuItem item={{ - label: 'Memories', + label: "Memories", icon: <MemoryIcon className="h-10 w-10" />, content: <MemoriesBar />, }} selectedItem={selectedItem} setSelectedItem={setSelectedItem} /> - <div className="mt-auto" /> - <MenuItem item={{ - label: 'Trash', + label: "Trash", icon: <Bin id="trash" className="z-[300] h-7 w-7" />, }} selectedItem={selectedItem} @@ -94,7 +108,7 @@ export default function Sidebar({ /> <MenuItem item={{ - label: 'Profile', + label: "Profile", icon: ( <div className="mb-2"> <Avatar> @@ -108,7 +122,7 @@ export default function Sidebar({ <User2 strokeWidth={1.3} className="h-6 w-6" /> )} <AvatarFallback> - {session?.user?.name?.split(' ').map((n) => n[0])}{' '} + {session?.user?.name?.split(" ").map((n) => n[0])}{" "} </AvatarFallback> </Avatar> </div> @@ -117,6 +131,7 @@ export default function Sidebar({ selectedItem={selectedItem} setSelectedItem={setSelectedItem} /> + <MessagePoster jwt={jwt} /> </div> <AnimatePresence> {selectedItem && <SubSidebar>{Subbar}</SubSidebar>} @@ -127,7 +142,7 @@ export default function Sidebar({ } const MenuItem = ({ - item: { icon, label }, + item: { icon, label, labelDisplay }, selectedItem, setSelectedItem, ...props @@ -143,18 +158,18 @@ const MenuItem = ({ {...props} > {icon} - <span className="">{label}</span> + <span className="">{labelDisplay ?? label}</span> </button> ); export function SubSidebar({ children }: { children?: React.ReactNode }) { return ( <motion.div - initial={{ opacity: 0, x: '-100%' }} + initial={{ opacity: 0, x: "-100%" }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, - x: '-100%', + x: "-100%", transition: { delay: 0.2 }, }} transition={{ diff --git a/apps/web/src/components/WordMark.tsx b/apps/web/src/components/WordMark.tsx new file mode 100644 index 00000000..eb55647c --- /dev/null +++ b/apps/web/src/components/WordMark.tsx @@ -0,0 +1,12 @@ +import { cn } from "@/lib/utils"; +import React from "react"; + +function WordMark({ className }: { className?: string }) { + return ( + <span className={cn(`text-xl font-bold tracking-tight ${className}`)}> + smort. + </span> + ); +} + +export default WordMark; diff --git a/apps/web/src/components/ui/avatar.tsx b/apps/web/src/components/ui/avatar.tsx index fb190df3..47795451 100644 --- a/apps/web/src/components/ui/avatar.tsx +++ b/apps/web/src/components/ui/avatar.tsx @@ -1,9 +1,9 @@ -"use client" +"use client"; -import * as React from "react" -import * as AvatarPrimitive from "@radix-ui/react-avatar" +import * as React from "react"; +import * as AvatarPrimitive from "@radix-ui/react-avatar"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const Avatar = React.forwardRef< React.ElementRef<typeof AvatarPrimitive.Root>, @@ -13,12 +13,12 @@ const Avatar = React.forwardRef< ref={ref} className={cn( "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", - className + className, )} {...props} /> -)) -Avatar.displayName = AvatarPrimitive.Root.displayName +)); +Avatar.displayName = AvatarPrimitive.Root.displayName; const AvatarImage = React.forwardRef< React.ElementRef<typeof AvatarPrimitive.Image>, @@ -29,8 +29,8 @@ const AvatarImage = React.forwardRef< className={cn("aspect-square h-full w-full", className)} {...props} /> -)) -AvatarImage.displayName = AvatarPrimitive.Image.displayName +)); +AvatarImage.displayName = AvatarPrimitive.Image.displayName; const AvatarFallback = React.forwardRef< React.ElementRef<typeof AvatarPrimitive.Fallback>, @@ -40,11 +40,11 @@ const AvatarFallback = React.forwardRef< ref={ref} className={cn( "flex h-full w-full items-center justify-center rounded-full bg-gray-100 dark:bg-gray-800", - className + className, )} {...props} /> -)) -AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName +)); +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; -export { Avatar, AvatarImage, AvatarFallback } +export { Avatar, AvatarImage, AvatarFallback }; diff --git a/apps/web/src/components/ui/badge.tsx b/apps/web/src/components/ui/badge.tsx index 1e21383f..40b15b91 100644 --- a/apps/web/src/components/ui/badge.tsx +++ b/apps/web/src/components/ui/badge.tsx @@ -1,7 +1,7 @@ -import * as React from "react" -import { cva, type VariantProps } from "class-variance-authority" +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const badgeVariants = cva( "inline-flex items-center rounded-full border border-gray-200 px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-gray-950 focus:ring-offset-2 dark:border-gray-800 dark:focus:ring-gray-300", @@ -20,8 +20,8 @@ const badgeVariants = cva( defaultVariants: { variant: "default", }, - } -) + }, +); export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, @@ -30,7 +30,7 @@ export interface BadgeProps function Badge({ className, variant, ...props }: BadgeProps) { return ( <div className={cn(badgeVariants({ variant }), className)} {...props} /> - ) + ); } -export { Badge, badgeVariants } +export { Badge, badgeVariants }; diff --git a/apps/web/src/components/ui/button.tsx b/apps/web/src/components/ui/button.tsx index b67d2657..24fa903e 100644 --- a/apps/web/src/components/ui/button.tsx +++ b/apps/web/src/components/ui/button.tsx @@ -1,8 +1,8 @@ -import * as React from "react" -import { Slot } from "@radix-ui/react-slot" -import { cva, type VariantProps } from "class-variance-authority" +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const buttonVariants = cva( "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-gray-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 dark:ring-offset-gray-950 dark:focus-visible:ring-gray-300", @@ -30,27 +30,27 @@ const buttonVariants = cva( variant: "default", size: "default", }, - } -) + }, +); export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> { - asChild?: boolean + asChild?: boolean; } const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( ({ className, variant, size, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : "button" + const Comp = asChild ? Slot : "button"; return ( <button className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} /> - ) - } -) -Button.displayName = "Button" + ); + }, +); +Button.displayName = "Button"; -export { Button, buttonVariants } +export { Button, buttonVariants }; diff --git a/apps/web/src/components/ui/card.tsx b/apps/web/src/components/ui/card.tsx index 65119a16..e98d500c 100644 --- a/apps/web/src/components/ui/card.tsx +++ b/apps/web/src/components/ui/card.tsx @@ -1,6 +1,6 @@ -import * as React from "react" +import * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const Card = React.forwardRef< HTMLDivElement, @@ -10,12 +10,12 @@ const Card = React.forwardRef< ref={ref} className={cn( "rounded-lg border border-gray-200 bg-white text-gray-950 shadow-sm dark:border-gray-800 dark:bg-gray-950 dark:text-gray-50", - className + className, )} {...props} /> -)) -Card.displayName = "Card" +)); +Card.displayName = "Card"; const CardHeader = React.forwardRef< HTMLDivElement, @@ -26,8 +26,8 @@ const CardHeader = React.forwardRef< className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} /> -)) -CardHeader.displayName = "CardHeader" +)); +CardHeader.displayName = "CardHeader"; const CardTitle = React.forwardRef< HTMLParagraphElement, @@ -37,12 +37,12 @@ const CardTitle = React.forwardRef< ref={ref} className={cn( "text-2xl font-semibold leading-none tracking-tight", - className + className, )} {...props} /> -)) -CardTitle.displayName = "CardTitle" +)); +CardTitle.displayName = "CardTitle"; const CardDescription = React.forwardRef< HTMLParagraphElement, @@ -53,16 +53,16 @@ const CardDescription = React.forwardRef< className={cn("text-sm text-gray-500 dark:text-gray-400", className)} {...props} /> -)) -CardDescription.displayName = "CardDescription" +)); +CardDescription.displayName = "CardDescription"; const CardContent = React.forwardRef< HTMLDivElement, React.HTMLAttributes<HTMLDivElement> >(({ className, ...props }, ref) => ( <div ref={ref} className={cn("p-6 pt-0", className)} {...props} /> -)) -CardContent.displayName = "CardContent" +)); +CardContent.displayName = "CardContent"; const CardFooter = React.forwardRef< HTMLDivElement, @@ -73,7 +73,14 @@ const CardFooter = React.forwardRef< className={cn("flex items-center p-6 pt-0", className)} {...props} /> -)) -CardFooter.displayName = "CardFooter" +)); +CardFooter.displayName = "CardFooter"; -export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardDescription, + CardContent, +}; diff --git a/apps/web/src/components/ui/label.tsx b/apps/web/src/components/ui/label.tsx index 53418217..84f8b0c7 100644 --- a/apps/web/src/components/ui/label.tsx +++ b/apps/web/src/components/ui/label.tsx @@ -1,14 +1,14 @@ -"use client" +"use client"; -import * as React from "react" -import * as LabelPrimitive from "@radix-ui/react-label" -import { cva, type VariantProps } from "class-variance-authority" +import * as React from "react"; +import * as LabelPrimitive from "@radix-ui/react-label"; +import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const labelVariants = cva( - "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" -) + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", +); const Label = React.forwardRef< React.ElementRef<typeof LabelPrimitive.Root>, @@ -20,7 +20,7 @@ const Label = React.forwardRef< className={cn(labelVariants(), className)} {...props} /> -)) -Label.displayName = LabelPrimitive.Root.displayName +)); +Label.displayName = LabelPrimitive.Root.displayName; -export { Label } +export { Label }; diff --git a/apps/web/src/contexts/MemoryContext.tsx b/apps/web/src/contexts/MemoryContext.tsx index 3727c464..eab1e4fe 100644 --- a/apps/web/src/contexts/MemoryContext.tsx +++ b/apps/web/src/contexts/MemoryContext.tsx @@ -1,6 +1,6 @@ -'use client'; -import React, { useCallback } from 'react'; -import { CollectedSpaces } from '../../types/memory'; +"use client"; +import React, { useCallback } from "react"; +import { CollectedSpaces } from "../../types/memory"; // temperory (will change) export const MemoryContext = React.createContext<{ @@ -41,7 +41,7 @@ export const MemoryProvider: React.FC< export const useMemory = () => { const context = React.useContext(MemoryContext); if (context === undefined) { - throw new Error('useMemory must be used within a MemoryProvider'); + throw new Error("useMemory must be used within a MemoryProvider"); } return context; }; diff --git a/apps/web/src/env.js b/apps/web/src/env.js index 3d3085fa..2495d75b 100644 --- a/apps/web/src/env.js +++ b/apps/web/src/env.js @@ -1,7 +1,6 @@ import { createEnv } from "@t3-oss/env-nextjs"; import { z } from "zod"; - export const env = createEnv({ /** * Specify your server-side environment variables schema here. This way you can ensure the app @@ -12,15 +11,13 @@ export const env = createEnv({ .enum(["development", "test", "production"]) .default("development"), NEXTAUTH_SECRET: - process.env.NODE_ENV === "production" - ? z.string() - : z.string(), + process.env.NODE_ENV === "production" ? z.string() : z.string(), NEXTAUTH_URL: z.preprocess( // This makes Vercel deployments not fail if you don't set NEXTAUTH_URL // Since NextAuth.js automatically uses the VERCEL_URL if present. (str) => process.env.VERCEL_URL ?? str, // VERCEL_URL doesn't include `https` so it cant be validated as a URL - process.env.VERCEL ? z.string() : z.string().url() + process.env.VERCEL ? z.string() : z.string().url(), ), GOOGLE_CLIENT_ID: z.string(), GOOGLE_CLIENT_SECRET: z.string(), diff --git a/apps/web/src/lib/searchParams.ts b/apps/web/src/lib/searchParams.ts index b435295d..aae3f4c7 100644 --- a/apps/web/src/lib/searchParams.ts +++ b/apps/web/src/lib/searchParams.ts @@ -1,12 +1,12 @@ import { - createSearchParamsCache, - parseAsInteger, - parseAsString - } from 'nuqs/server' - // Note: import from 'nuqs/server' to avoid the "use client" directive - - export const searchParamsCache = createSearchParamsCache({ - // List your search param keys and associated parsers here: - q: parseAsString.withDefault(''), - maxResults: parseAsInteger.withDefault(10) - })
\ No newline at end of file + createSearchParamsCache, + parseAsInteger, + parseAsString, +} from "nuqs/server"; +// Note: import from 'nuqs/server' to avoid the "use client" directive + +export const searchParamsCache = createSearchParamsCache({ + // List your search param keys and associated parsers here: + q: parseAsString.withDefault(""), + maxResults: parseAsInteger.withDefault(10), +}); diff --git a/apps/web/src/server/auth.ts b/apps/web/src/server/auth.ts index c32efe55..95edcf35 100644 --- a/apps/web/src/server/auth.ts +++ b/apps/web/src/server/auth.ts @@ -1,7 +1,7 @@ import { env } from "@/env"; import NextAuth from "next-auth"; import Google from "next-auth/providers/google"; -import { DrizzleAdapter } from "@auth/drizzle-adapter" +import { DrizzleAdapter } from "@auth/drizzle-adapter"; import { db } from "./db"; export const { @@ -15,9 +15,9 @@ export const { ...session, user: { ...session.user, - id: user.id + id: user.id, }, - }) + }), }, adapter: DrizzleAdapter(db), providers: [ diff --git a/apps/web/src/server/db/index.ts b/apps/web/src/server/db/index.ts index 5aa87fc1..4d671bea 100644 --- a/apps/web/src/server/db/index.ts +++ b/apps/web/src/server/db/index.ts @@ -1,8 +1,5 @@ -import { drizzle } from 'drizzle-orm/d1'; +import { drizzle } from "drizzle-orm/d1"; import * as schema from "./schema"; -export const db = drizzle( - process.env.DATABASE, - { schema, logger: true } -); +export const db = drizzle(process.env.DATABASE, { schema, logger: true }); diff --git a/apps/web/src/server/db/schema.ts b/apps/web/src/server/db/schema.ts index a80eb7cf..e0ddbdbc 100644 --- a/apps/web/src/server/db/schema.ts +++ b/apps/web/src/server/db/schema.ts @@ -6,7 +6,7 @@ import { sqliteTableCreator, text, integer, - unique + unique, } from "drizzle-orm/sqlite-core"; export const createTable = sqliteTableCreator((name) => `${name}`); @@ -78,7 +78,6 @@ export const verificationTokens = createTable( }), ); - export const storedContent = createTable( "storedContent", { @@ -103,8 +102,12 @@ export const storedContent = createTable( export const contentToSpace = createTable( "contentToSpace", { - contentId: integer("contentId").notNull().references(() => storedContent.id), - spaceId: integer("spaceId").notNull().references(() => space.id), + contentId: integer("contentId") + .notNull() + .references(() => storedContent.id), + spaceId: integer("spaceId") + .notNull() + .references(() => space.id), }, (cts) => ({ compoundKey: primaryKey({ columns: [cts.contentId, cts.spaceId] }), @@ -115,7 +118,7 @@ export const space = createTable( "space", { id: integer("id").notNull().primaryKey({ autoIncrement: true }), - name: text('name').notNull().default('all'), + name: text("name").notNull().default("all"), user: text("user", { length: 255 }).references(() => users.id), }, (space) => ({ @@ -124,4 +127,4 @@ export const space = createTable( }), ); -export type StoredContent = Omit<typeof storedContent.$inferSelect, 'user'>
\ No newline at end of file +export type StoredContent = Omit<typeof storedContent.$inferSelect, "user">; diff --git a/apps/web/src/server/helpers.ts b/apps/web/src/server/helpers.ts index 1f6cf977..519e4b17 100644 --- a/apps/web/src/server/helpers.ts +++ b/apps/web/src/server/helpers.ts @@ -1,34 +1,34 @@ export async function getMetaData(url: string) { - const response = await fetch(url); - const html = await response.text(); - - // Extract the base URL - const baseUrl = new URL(url).origin; - - // Extract title - const titleMatch = html.match(/<title>(.*?)<\/title>/); - const title = titleMatch ? titleMatch[1] : 'Title not found'; - - // Extract meta description - const descriptionMatch = html.match( - /<meta name="description" content="(.*?)"\s*\/?>/, - ); - const description = descriptionMatch - ? descriptionMatch[1] - : 'Description not found'; - - // Extract Open Graph image - const imageMatch = html.match( - /<meta property="og:image" content="(.*?)"\s*\/?>/, - ); - const image = imageMatch ? imageMatch[1] : 'Image not found'; - - // Prepare the metadata object - const metadata = { - title, - description, - image, - baseUrl, - }; - return metadata; - }
\ No newline at end of file + const response = await fetch(url); + const html = await response.text(); + + // Extract the base URL + const baseUrl = new URL(url).origin; + + // Extract title + const titleMatch = html.match(/<title>(.*?)<\/title>/); + const title = titleMatch ? titleMatch[1] : "Title not found"; + + // Extract meta description + const descriptionMatch = html.match( + /<meta name="description" content="(.*?)"\s*\/?>/, + ); + const description = descriptionMatch + ? descriptionMatch[1] + : "Description not found"; + + // Extract Open Graph image + const imageMatch = html.match( + /<meta property="og:image" content="(.*?)"\s*\/?>/, + ); + const image = imageMatch ? imageMatch[1] : "Image not found"; + + // Prepare the metadata object + const metadata = { + title, + description, + image, + baseUrl, + }; + return metadata; +} |