diff --git a/apps/drizzle-studio/package.json b/apps/drizzle-studio/package.json index 28fe38e..f56a64e 100644 --- a/apps/drizzle-studio/package.json +++ b/apps/drizzle-studio/package.json @@ -1,14 +1,14 @@ { "name": "@acme/drizzle-studio", - "version": "0.0.0", - "private": true, - "type": "module", - "scripts": { - "dev": "NODE_ENV=development pnpm with-env pnpm --filter=db studio", - "with-env": "dotenv -e ../../.env --" - }, - "devDependencies": { - "@acme/db": "workspace:*", - "dotenv-cli": "^8.0.0" - } + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "NODE_ENV=development pnpm with-env pnpm --filter=db studio", + "with-env": "dotenv -e ../../.env --" + }, + "devDependencies": { + "@acme/db": "workspace:*", + "dotenv-cli": "^8.0.0" + } } diff --git a/apps/web/next.config.js b/apps/web/next.config.js index 31e6ded..ecf77e8 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -10,7 +10,13 @@ const config = { /** We already do linting and typechecking as separate tasks in CI */ eslint: { ignoreDuringBuilds: true }, /** These packages won't be bundled in the server build */ - serverExternalPackages: ["@mastra/*"], + /** @see https://nextjs.org/docs/app/api-reference/config/next-config-js/serverExternalPackages */ + serverExternalPackages: [ + "@mastra/*", + "@blocknote/server-util", + "@blocknote/react", + "@blocknote/core", + ], /** Enables hot reloading for local packages without a build step */ transpilePackages: ["@acme/api", "@acme/auth", "@acme/db"], typescript: { ignoreBuildErrors: true }, diff --git a/apps/web/package.json b/apps/web/package.json index 08faaac..59ea022 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -25,6 +25,7 @@ "@blocknote/core": "^0.35.0", "@blocknote/mantine": "^0.35.0", "@blocknote/react": "^0.35.0", + "@blocknote/server-util": "^0.35.0", "@daveyplate/better-auth-ui": "^2.1.0", "@hookform/resolvers": "^5.0.1", "@mantine/core": "^8.1.3", @@ -32,6 +33,7 @@ "@mastra/libsql": "^0.12.0", "@mastra/loggers": "^0.10.5", "@mastra/memory": "^0.12.0", + "@mastra/rag": "^1.1.0", "@ngrok/ngrok": "^1.5.1", "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-dialog": "^1.1.14", @@ -61,6 +63,7 @@ "react-hook-form": "^7.57.0", "react-virtuoso": "^4.13.0", "remark-gfm": "^4.0.1", + "remove-markdown": "^0.6.2", "sonner": "^2.0.5", "superjson": "2.2.2", "tailwind-merge": "^3.3.0", diff --git a/apps/web/src/ai/mastra/agents/journl-agent.ts b/apps/web/src/ai/mastra/agents/journl-agent.ts index 8713da0..e4e2822 100644 --- a/apps/web/src/ai/mastra/agents/journl-agent.ts +++ b/apps/web/src/ai/mastra/agents/journl-agent.ts @@ -1,5 +1,5 @@ import { Agent } from "@mastra/core/agent"; -import { model } from "~/ai/providers/openai/llm"; +import { model } from "~/ai/providers/openai/text"; import { semanticJournalSearch } from "../tools/semantic-journal-search"; import { semanticPageSearch } from "../tools/semantic-page-search"; import { temporalJournalSearch } from "../tools/temporal-journal-search"; diff --git a/apps/web/src/ai/mastra/tools/semantic-journal-search.ts b/apps/web/src/ai/mastra/tools/semantic-journal-search.ts index 7200948..9149205 100644 --- a/apps/web/src/ai/mastra/tools/semantic-journal-search.ts +++ b/apps/web/src/ai/mastra/tools/semantic-journal-search.ts @@ -15,8 +15,11 @@ export const semanticJournalSearch = createTool({ }); return results.map((result) => ({ - ...result, - link: `/journal/${result.date}`, + content: result.embedding.chunk_markdown_text, + date: result.journal_entry.date, + id: result.journal_entry.id, + link: `/journal/${result.journal_entry.date}`, + similarity: result.similarity, })); }, id: "semantic-journal-search", diff --git a/apps/web/src/ai/mastra/tools/semantic-page-search.ts b/apps/web/src/ai/mastra/tools/semantic-page-search.ts index ea376c8..68d548e 100644 --- a/apps/web/src/ai/mastra/tools/semantic-page-search.ts +++ b/apps/web/src/ai/mastra/tools/semantic-page-search.ts @@ -15,8 +15,11 @@ export const semanticPageSearch = createTool({ }); return result.map((result) => ({ - ...result, - link: `/pages/${result.page_id}`, + content: result.embedding.chunk_markdown_text, + link: `/pages/${result.page.id}`, + page_id: result.page.id, + page_title: result.page.title, + similarity: result.similarity, })); }, id: "semantic-page-search", @@ -31,6 +34,7 @@ export const semanticPageSearch = createTool({ outputSchema: z.array( z.object({ content: z.string(), + link: z.string(), page_id: z.string(), page_title: z.string(), similarity: z.number(), diff --git a/apps/web/src/ai/providers/openai/embedding.ts b/apps/web/src/ai/providers/openai/embedding.ts index d3823cc..2ed2b1e 100644 --- a/apps/web/src/ai/providers/openai/embedding.ts +++ b/apps/web/src/ai/providers/openai/embedding.ts @@ -1,4 +1,4 @@ import { openai } from "@ai-sdk/openai"; -// TODO: Move this to a shared package called `@acme/ai`. +// ! TODO: Move this to a shared package called `@acme/ai`. export const model = openai.embedding("text-embedding-3-small"); diff --git a/apps/web/src/ai/providers/openai/llm.ts b/apps/web/src/ai/providers/openai/text.ts similarity index 58% rename from apps/web/src/ai/providers/openai/llm.ts rename to apps/web/src/ai/providers/openai/text.ts index e7df99e..4a067b2 100644 --- a/apps/web/src/ai/providers/openai/llm.ts +++ b/apps/web/src/ai/providers/openai/text.ts @@ -1,4 +1,4 @@ import { openai } from "@ai-sdk/openai"; -// TODO: Move this to a shared package called `@acme/ai`. +// ! TODO: Move this to a shared package called `@acme/ai`. export const model = openai("gpt-4o-mini"); diff --git a/apps/web/src/app/(app)/@header/_components/header-search-modal.tsx b/apps/web/src/app/(app)/@header/_components/header-search-modal.tsx index 8fa4aeb..96e7035 100644 --- a/apps/web/src/app/(app)/@header/_components/header-search-modal.tsx +++ b/apps/web/src/app/(app)/@header/_components/header-search-modal.tsx @@ -4,6 +4,7 @@ import { X } from "lucide-react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; +import { useDebounce } from "use-debounce"; import { Badge } from "~/components/ui/badge"; import { Button } from "~/components/ui/button"; import { @@ -30,22 +31,27 @@ import { useTRPC } from "~/trpc/react"; const MIN_QUERY_LENGTH = 2; const DEFAULT_THRESHOLD = 0.25; const DEFAULT_LIMIT = 10; +const DEFAULT_DEBOUNCE_TIME = 500; + type HeaderSearchButtonProps = React.ComponentProps & { children: React.ReactNode; limit?: number; threshold?: number; + debounceTime?: number; }; export function HeaderSearchButton({ children, limit = DEFAULT_LIMIT, threshold = DEFAULT_THRESHOLD, + debounceTime = DEFAULT_DEBOUNCE_TIME, ...rest }: HeaderSearchButtonProps) { const isMobile = useIsMobile(); const [isOpen, setIsOpen] = useState(false); const [query, setQuery] = useState(""); const router = useRouter(); + const [debouncedQuery] = useDebounce(query, debounceTime); // Keyboard shortcut handlers useEffect(() => { @@ -65,13 +71,17 @@ export function HeaderSearchButton({ }, [isOpen]); const trpc = useTRPC(); - const { data: notes, isLoading } = useQuery({ + const { + data: notes, + isLoading, + isError, + } = useQuery({ ...trpc.notes.getSimilarNotes.queryOptions({ limit, - query, + query: debouncedQuery, threshold, }), - enabled: query.length > MIN_QUERY_LENGTH, + enabled: debouncedQuery.length > MIN_QUERY_LENGTH, }); return ( @@ -97,7 +107,6 @@ export function HeaderSearchButton({ autoCorrect="off" spellCheck={false} autoFocus - value={query} onValueChange={(value) => setQuery(value)} /> @@ -113,7 +122,9 @@ export function HeaderSearchButton({ ) : !notes ? ( - Write something to search + {isError + ? "Something went wrong. Please try again later." + : "Write something to search."} ) : ( diff --git a/apps/web/src/app/(app)/journal/_components/journal-entry.tsx b/apps/web/src/app/(app)/journal/_components/journal-entry.tsx index b1903c7..9317683 100644 --- a/apps/web/src/app/(app)/journal/_components/journal-entry.tsx +++ b/apps/web/src/app/(app)/journal/_components/journal-entry.tsx @@ -1,9 +1,9 @@ "use client"; +import type { PlaceholderJournalEntry } from "@acme/api"; import type { JournalEntry } from "@acme/db/schema"; import { useMutation } from "@tanstack/react-query"; import Link from "next/link"; -import type { PlaceholderJournalEntry } from "node_modules/@acme/api/src/router/journal"; import type React from "react"; import { type ComponentProps, diff --git a/apps/web/src/app/_components/app-providers.tsx b/apps/web/src/app/_components/app-providers.tsx index afc433d..8343c35 100644 --- a/apps/web/src/app/_components/app-providers.tsx +++ b/apps/web/src/app/_components/app-providers.tsx @@ -6,7 +6,7 @@ import { ThreadRuntime } from "~/components/ai/thread-runtime"; export function AppProviders({ children }: { children: React.ReactNode }) { return ( - + {children} diff --git a/apps/web/src/app/api/ai/blocknote/.gitkeep b/apps/web/src/app/api/ai/blocknote/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/apps/web/src/app/api/chat/route.ts b/apps/web/src/app/api/ai/journl-agent/route.ts similarity index 100% rename from apps/web/src/app/api/chat/route.ts rename to apps/web/src/app/api/ai/journl-agent/route.ts diff --git a/apps/web/src/app/api/webhooks/_lib/validate-signature.ts b/apps/web/src/app/api/supabase/_lib/validate-signature.ts similarity index 100% rename from apps/web/src/app/api/webhooks/_lib/validate-signature.ts rename to apps/web/src/app/api/supabase/_lib/validate-signature.ts diff --git a/apps/web/src/app/api/webhooks/_lib/webhook-handler.ts b/apps/web/src/app/api/supabase/_lib/webhook-handler.ts similarity index 100% rename from apps/web/src/app/api/webhooks/_lib/webhook-handler.ts rename to apps/web/src/app/api/supabase/_lib/webhook-handler.ts diff --git a/apps/web/src/app/api/supabase/embed-document/route.ts b/apps/web/src/app/api/supabase/embed-document/route.ts new file mode 100644 index 0000000..2d7758c --- /dev/null +++ b/apps/web/src/app/api/supabase/embed-document/route.ts @@ -0,0 +1,157 @@ +import { + zDocumentEmbeddingTask, + type zInsertDocumentEmbedding, +} from "@acme/db/schema"; +import { ServerBlockNoteEditor } from "@blocknote/server-util"; +import { type ChunkParams, MDocument } from "@mastra/rag"; +import { embedMany } from "ai"; +import { NextResponse } from "next/server"; +import removeMarkdown from "remove-markdown"; +import type { z } from "zod/v4"; +import { model } from "~/ai/providers/openai/embedding"; +import { schema } from "~/components/editor/block-schema"; +import { embedder } from "~/trpc/server"; +import { handler } from "../_lib/webhook-handler"; + +const CHUNK_PARAMS: ChunkParams = { + extract: { + keywords: true, + summary: true, + title: true, + }, + strategy: "semantic-markdown", +}; +const REMOVE_MARKDOWN_PARAMS: Parameters[1] = { + /* GitHub-Flavored Markdown */ + gfm: true, + /* Char to insert instead of stripped list leaders */ + listUnicodeChar: "", + /* Strip list leaders */ + stripListLeaders: true, + /* Replace images with alt-text, if present */ + useImgAltText: true, +}; + +/** + * This webhook will embed a document when the task is marked as ready by the Supabase Cronjob. + * + * @privateRemarks + * + * There are three jobs that are responsible for the lifecycle of the document embedding task: + * + * 1. `document-embedding-scheduler`: This job will mark debounced tasks as ready after 2 minutes without updates. + * ```sql + * -- Debounced → Ready (after 2 minutes) + * UPDATE document_embedding_task + * SET + * status = 'ready', + * updated_at = NOW() + * WHERE + * status = 'debounced' + * AND updated_at < NOW() - INTERVAL '2 minutes'; + * ``` + * + * 2. `document-embedding-retrier`: This job will retry failed tasks that have failed less than 3 times every 5 minutes. + * ```sql + * -- Failed → Debounced (with retry increment, max 3 retries) + * UPDATE document_embedding_task + * SET + * status = 'debounced', + * retries = retries + 1, + * updated_at = NOW() + * WHERE + * status = 'failed' + * AND retries < 3 + * AND updated_at < NOW() - INTERVAL '5 minutes'; + * ``` + * + * 3. `document-embedding-sentinel`: This job picks up tasks that been ready for more than 15 minutes without updates. + * ```sql + * -- Stuck Ready → Failed (after 15 minutes) + * UPDATE document_embedding_task + * SET + * status = 'failed', + * updated_at = NOW() + * WHERE + * status = 'ready' + * AND updated_at < NOW() - INTERVAL '15 minutes'; + * ``` + */ +// ! TODO: Track embeddings token usage. +export const POST = handler(zDocumentEmbeddingTask, async (payload) => { + if (payload.type === "DELETE" || payload.record.status !== "ready") { + return NextResponse.json({ success: true }); + } + + try { + const document = await embedder.document.getById({ + id: payload.record.document_id, + user_id: payload.record.user_id, + }); + + if (!document?.tree) { + throw new Error("Document not found"); + } + + const editor = ServerBlockNoteEditor.create({ + schema, + }); + + const markdown = await editor.blocksToMarkdownLossy(document.tree); + + const mDocument = + await MDocument.fromMarkdown(markdown).chunk(CHUNK_PARAMS); + + // ! TODO: Here's where we get the usage tokens, we must process these in a different transaction + // ! To guarantee that the usage is tracked regardless of the success of the embedding. + const { embeddings, usage: _usage } = await embedMany({ + maxRetries: 5, + model, + values: mDocument.map((chunk) => chunk.text), + }); + + const insertions: z.infer[] = []; + + for (const [index, embedding] of embeddings.entries()) { + const chunk = mDocument.at(index); + + if (!chunk) { + continue; + } + + console.debug("DocumentEmbedding 👀", { + chunk, + }); + + insertions.push({ + chunk_id: index, + chunk_markdown_text: chunk.text, + chunk_raw_text: removeMarkdown(chunk.text, REMOVE_MARKDOWN_PARAMS), + document_id: document.id, + metadata: { + documentTitle: chunk.metadata.documentTitle, + excerptKeywords: chunk.metadata.excerptKeywords, + sectionSummary: chunk.metadata.sectionSummary, + }, + user_id: document.user_id, + vector: embedding, + }); + } + + await embedder.documentEmbedding.embedDocument({ + document_id: payload.record.document_id, + embeddings: insertions, + task_id: payload.record.id, + user_id: payload.record.user_id, + }); + } catch (error) { + console.error("Error embedding document 👀", error); + + await embedder.documentEmbeddingTask.updateStatus({ + id: payload.record.id, + status: "failed", + }); + } + + return NextResponse.json({ success: true }); +}); diff --git a/apps/web/src/app/api/trpc/[trpc]/route.ts b/apps/web/src/app/api/trpc/[trpc]/route.ts index bb36197..74d2d0b 100644 --- a/apps/web/src/app/api/trpc/[trpc]/route.ts +++ b/apps/web/src/app/api/trpc/[trpc]/route.ts @@ -1,4 +1,4 @@ -import { appRouter, createTRPCContext } from "@acme/api"; +import { apiRouter, createTRPCContext } from "@acme/api"; import { fetchRequestHandler } from "@trpc/server/adapters/fetch"; import type { NextRequest } from "next/server"; @@ -35,7 +35,7 @@ const handler = async (req: NextRequest) => { console.error(`>>> tRPC Error on '${path}'`, error); }, req, - router: appRouter, + router: apiRouter, }); setCorsHeaders(response); diff --git a/apps/web/src/app/api/webhooks/journal_entry/route.ts b/apps/web/src/app/api/webhooks/journal_entry/route.ts deleted file mode 100644 index 6bb78bf..0000000 --- a/apps/web/src/app/api/webhooks/journal_entry/route.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { db } from "@acme/db/client"; -import { JournalEmbedding, zJournalEntry } from "@acme/db/schema"; -import { openai } from "@ai-sdk/openai"; -import { embed } from "ai"; -import { NextResponse } from "next/server"; -import { handler } from "../_lib/webhook-handler"; - -/** - * Handles Supabase webhook payloads for journal entries - */ -export const POST = handler(zJournalEntry, async (payload) => { - if (payload.type === "INSERT" || payload.type === "UPDATE") { - if (!payload.record.content) { - return NextResponse.json( - { error: "No content", success: false }, - { status: 400 }, - ); - } - - const { embedding } = await embed({ - maxRetries: 5, - model: openai.embedding("text-embedding-3-small"), - value: payload.record.content, - }); - - // Store the embedding in the database - await db - .insert(JournalEmbedding) - .values({ - chunk_text: payload.record.content, - date: payload.record.date, - embedding, - journal_entry_id: payload.record.id, - user_id: payload.record.user_id, - }) - .onConflictDoUpdate({ - set: { - chunk_text: payload.record.content, - embedding, - }, - target: [JournalEmbedding.journal_entry_id], - }); - - console.debug( - "Embedding stored for journal entry", - payload.record.id, - "of date", - payload.record.date, - ); - } - - return NextResponse.json({ success: true }); -}); diff --git a/apps/web/src/app/api/webhooks/page/route.ts b/apps/web/src/app/api/webhooks/page/route.ts deleted file mode 100644 index b897050..0000000 --- a/apps/web/src/app/api/webhooks/page/route.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { zPage } from "@acme/db/schema"; -import { NextResponse } from "next/server"; -import { handler } from "../_lib/webhook-handler"; - -export const POST = handler(zPage, async () => { - // TODO: Refactor the PageEmbedding logic. - return NextResponse.json({ success: true }); -}); diff --git a/apps/web/src/components/editor/block-schema.ts b/apps/web/src/components/editor/block-schema.ts index 93f9c3b..45867fa 100644 --- a/apps/web/src/components/editor/block-schema.ts +++ b/apps/web/src/components/editor/block-schema.ts @@ -1,5 +1,3 @@ -"use client"; - import { type Block, type BlockNoteEditor, diff --git a/apps/web/src/trpc/react.tsx b/apps/web/src/trpc/react.tsx index 5e10e5d..f8c373c 100644 --- a/apps/web/src/trpc/react.tsx +++ b/apps/web/src/trpc/react.tsx @@ -1,6 +1,6 @@ "use client"; -import type { AppRouter } from "@acme/api"; +import type { ApiRouter } from "@acme/api"; import type { QueryClient } from "@tanstack/react-query"; import { QueryClientProvider } from "@tanstack/react-query"; import { @@ -28,13 +28,13 @@ const getQueryClient = () => { return clientQueryClientSingleton; }; -export const { useTRPC, TRPCProvider } = createTRPCContext(); +export const { useTRPC, TRPCProvider } = createTRPCContext(); export function TRPCReactProvider(props: { children: React.ReactNode }) { const queryClient = getQueryClient(); const [trpcClient] = useState(() => - createTRPCClient({ + createTRPCClient({ links: [ loggerLink({ enabled: (op) => diff --git a/apps/web/src/trpc/server.tsx b/apps/web/src/trpc/server.tsx index 5522703..fc2ce1a 100644 --- a/apps/web/src/trpc/server.tsx +++ b/apps/web/src/trpc/server.tsx @@ -1,5 +1,5 @@ -import type { AppRouter } from "@acme/api"; -import { appRouter, createTRPCContext } from "@acme/api"; +import type { ApiRouter } from "@acme/api"; +import { apiRouter, createTRPCContext, embedderRouter } from "@acme/api"; import { dehydrate, HydrationBoundary } from "@tanstack/react-query"; import type { ResolverDef, TRPCQueryOptions } from "@trpc/tanstack-react-query"; import { createTRPCOptionsProxy } from "@trpc/tanstack-react-query"; @@ -25,12 +25,14 @@ const createContext = cache(async () => { const getQueryClient = cache(createQueryClient); -export const api = appRouter.createCaller(createContext); +export const api = apiRouter.createCaller(createContext); -export const trpc = createTRPCOptionsProxy({ +export const embedder = embedderRouter.createCaller(createContext); + +export const trpc = createTRPCOptionsProxy({ ctx: createContext, queryClient: getQueryClient, - router: appRouter, + router: apiRouter, }); export function HydrateClient(props: { children: React.ReactNode }) { diff --git a/cspell.config.yaml b/cspell.config.yaml index 758b2d3..04b2480 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -46,5 +46,6 @@ words: - sonner - supabase - topo + - upserted - weathercode - ZROK diff --git a/packages/api/src/router/auth.ts b/packages/api/src/api-router/auth.ts similarity index 100% rename from packages/api/src/router/auth.ts rename to packages/api/src/api-router/auth.ts diff --git a/packages/api/src/router/blocks.ts b/packages/api/src/api-router/blocks.ts similarity index 93% rename from packages/api/src/router/blocks.ts rename to packages/api/src/api-router/blocks.ts index c596ceb..7fe42a2 100644 --- a/packages/api/src/router/blocks.ts +++ b/packages/api/src/api-router/blocks.ts @@ -97,7 +97,8 @@ export const blocksRouter = { } } - // Anytime we save a transaction we need to update the document embedding task status to debounced. + // ! TODO: Check if a block with text was amongst the upserted, if so, trigger the document embedding task. + // ! Anytime we save a transaction we need to update the document embedding task status to debounced. await tx .insert(DocumentEmbeddingTask) .values({ diff --git a/packages/api/src/router/document.ts b/packages/api/src/api-router/document.ts similarity index 99% rename from packages/api/src/router/document.ts rename to packages/api/src/api-router/document.ts index f7504b2..5de5217 100644 --- a/packages/api/src/router/document.ts +++ b/packages/api/src/api-router/document.ts @@ -1,7 +1,6 @@ import { and, eq } from "@acme/db"; import { Document, zDocument } from "@acme/db/schema"; import type { TRPCRouterRecord } from "@trpc/server"; - import { protectedProcedure } from "../trpc.js"; export const documentRouter = { diff --git a/packages/api/src/api-router/index.ts b/packages/api/src/api-router/index.ts new file mode 100644 index 0000000..dfa7544 --- /dev/null +++ b/packages/api/src/api-router/index.ts @@ -0,0 +1,18 @@ +import { createTRPCRouter } from "../trpc.js"; +import { authRouter } from "./auth.js"; +import { blocksRouter } from "./blocks.js"; +import { documentRouter } from "./document.js"; +import { journalRouter } from "./journal.js"; +import { notesRouter } from "./notes.js"; +import { pagesRouter } from "./pages.js"; + +export const apiRouter = createTRPCRouter({ + auth: authRouter, + blocks: blocksRouter, + document: documentRouter, + journal: journalRouter, + notes: notesRouter, + pages: pagesRouter, +}); + +export type ApiRouter = typeof apiRouter; diff --git a/packages/api/src/router/journal.ts b/packages/api/src/api-router/journal.ts similarity index 89% rename from packages/api/src/router/journal.ts rename to packages/api/src/api-router/journal.ts index 3ceeed7..8ba6806 100644 --- a/packages/api/src/router/journal.ts +++ b/packages/api/src/api-router/journal.ts @@ -1,6 +1,6 @@ import { and, between, cosineDistance, desc, eq, gt, sql } from "@acme/db"; import { - JournalEmbedding, + DocumentEmbedding, JournalEntry, zJournalEntryDate, } from "@acme/db/schema"; @@ -88,32 +88,35 @@ export const journalRouter = { .query(async ({ ctx, input }) => { try { const { embedding } = await embed({ + // ! TODO: Move this to a shared package called `@acme/ai`. model: openai.embedding("text-embedding-3-small"), value: input.query, }); - const similarity = sql`1 - (${cosineDistance(JournalEmbedding.embedding, embedding)})`; + const embeddingSimilarity = sql`1 - (${cosineDistance(DocumentEmbedding.vector, embedding)})`; const results = await ctx.db - .select({ - content: JournalEntry.content, - date: JournalEntry.date, - id: JournalEntry.id, - similarity, + .selectDistinctOn([DocumentEmbedding.document_id], { + embedding: DocumentEmbedding, + journal_entry: JournalEntry, + similarity: embeddingSimilarity, }) - .from(JournalEmbedding) + .from(DocumentEmbedding) .where( and( - eq(JournalEntry.user_id, ctx.session.user.id), - gt(similarity, input.threshold), + eq(DocumentEmbedding.user_id, ctx.session.user.id), + gt(embeddingSimilarity, input.threshold), ), ) .innerJoin( JournalEntry, - eq(JournalEmbedding.journal_entry_id, JournalEntry.id), + eq(DocumentEmbedding.document_id, JournalEntry.document_id), ) - .orderBy(desc(similarity)) - .limit(input.limit); + .orderBy(DocumentEmbedding.document_id, desc(embeddingSimilarity)); + + results.sort((a, b) => { + return b.similarity - a.similarity; + }); return results; } catch (error) { diff --git a/packages/api/src/api-router/notes.ts b/packages/api/src/api-router/notes.ts new file mode 100644 index 0000000..af7d862 --- /dev/null +++ b/packages/api/src/api-router/notes.ts @@ -0,0 +1,113 @@ +import { and, cosineDistance, desc, eq, gt, sql } from "@acme/db"; +import { DocumentEmbedding, Page } from "@acme/db/schema"; +import { openai } from "@ai-sdk/openai"; +import { TRPCError, type TRPCRouterRecord } from "@trpc/server"; +import { embed } from "ai"; +import { z } from "zod/v4"; +import { protectedProcedure } from "../trpc.js"; + +type Note = + | { + type: "page"; + id: string; + content: string; + created_at: string; + header: string; + similarity: number; + updated_at: string; + } + | { + type: "journal"; + id: string; + date: string; + content: string; + created_at: string; + header: string; + similarity: number; + updated_at: string; + }; + +export const notesRouter = { + getSimilarNotes: protectedProcedure + .input( + z.object({ + limit: z.number().min(1).max(20).default(5), + offset: z.number().min(0).default(0), + query: z.string().max(10000), + threshold: z.number().min(0).max(1).default(0.3), + }), + ) + .query(async ({ ctx, input }) => { + try { + const { embedding } = await embed({ + // ! TODO: Move this to a shared package called `@acme/ai`. + model: openai.embedding("text-embedding-3-small"), + value: input.query, + }); + + const embeddingSimilarity = sql`1 - (${cosineDistance(DocumentEmbedding.vector, embedding)})`; + + const results = await ctx.db + .selectDistinctOn([DocumentEmbedding.document_id], { + embedding: DocumentEmbedding, + // journal_entry: JournalEntry, + page: Page, + similarity: embeddingSimilarity, + }) + .from(DocumentEmbedding) + .where( + and( + eq(DocumentEmbedding.user_id, ctx.session.user.id), + gt(embeddingSimilarity, input.threshold), + ), + ) + .leftJoin(Page, eq(DocumentEmbedding.document_id, Page.document_id)) + // .leftJoin( + // JournalEntry, + // eq(DocumentEmbedding.document_id, JournalEntry.document_id), + // ) + .orderBy(DocumentEmbedding.document_id, desc(embeddingSimilarity)); + + results.sort((a, b) => { + return b.similarity - a.similarity; + }); + + const notes: Note[] = []; + + for (const result of results) { + if (result.page) { + notes.push({ + content: result.embedding.chunk_raw_text, + created_at: result.page.created_at, + header: result.page.title, + id: result.page.id, + similarity: result.similarity, + type: "page", + updated_at: result.embedding.updated_at, + }); + } + + // if (result.journal_entry) { + // notes.push({ + // content: result.embedding.chunk_raw_text, + // created_at: result.journal_entry.created_at, + // date: result.journal_entry.date, + // header: result.journal_entry.content, + // id: result.journal_entry.id, + // similarity: result.similarity, + // type: "journal", + // updated_at: result.embedding.updated_at, + // }); + // } + } + + return notes; + } catch (error) { + console.error("Database error in journal.getRelevantEntries:", error); + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Failed to fetch similar journal entries", + }); + } + }), +} satisfies TRPCRouterRecord; diff --git a/packages/api/src/router/pages.ts b/packages/api/src/api-router/pages.ts similarity index 68% rename from packages/api/src/router/pages.ts rename to packages/api/src/api-router/pages.ts index 00d39b2..e484a07 100644 --- a/packages/api/src/router/pages.ts +++ b/packages/api/src/api-router/pages.ts @@ -3,8 +3,8 @@ import { BlockEdge, BlockNode, Document, + DocumentEmbedding, Page, - PageEmbedding, zInsertPage, } from "@acme/db/schema"; import { openai } from "@ai-sdk/openai"; @@ -89,23 +89,23 @@ export const pagesRouter = { edges: BlockEdge[]; } >(sql` - WITH page AS ( - SELECT * FROM ${Page} - WHERE ${Page.id} = ${input.id} AND ${Page.user_id} = ${ctx.session.user.id} - LIMIT 1 - ) - SELECT - page.*, - COALESCE( - (SELECT json_agg(${BlockNode}.*) FROM ${BlockNode} WHERE ${BlockNode.document_id} = page.document_id), - '[]'::json - ) as blocks, - COALESCE( - (SELECT json_agg(${BlockEdge}.*) FROM ${BlockEdge} WHERE ${BlockEdge.document_id} = page.document_id), - '[]'::json - ) as edges - FROM page - `); + WITH page AS ( + SELECT * FROM ${Page} + WHERE ${Page.id} = ${input.id} AND ${Page.user_id} = ${ctx.session.user.id} + LIMIT 1 + ) + SELECT + page.*, + COALESCE( + (SELECT json_agg(${BlockNode}.*) FROM ${BlockNode} WHERE ${BlockNode.document_id} = page.document_id), + '[]'::json + ) as blocks, + COALESCE( + (SELECT json_agg(${BlockEdge}.*) FROM ${BlockEdge} WHERE ${BlockEdge.document_id} = page.document_id), + '[]'::json + ) as edges + FROM page + `); if (!page) { return null; @@ -135,34 +135,43 @@ export const pagesRouter = { }), ) .query(async ({ ctx, input }) => { - // embed the query - const { embedding } = await embed({ - model: openai.embedding("text-embedding-3-small"), - value: input.query, - }); + try { + const { embedding } = await embed({ + // ! TODO: Move this to a shared package called `@acme/ai`. + model: openai.embedding("text-embedding-3-small"), + value: input.query, + }); - // https://orm.drizzle.team/docs/guides/vector-similarity-search - const similarity = sql`1 - (${cosineDistance(PageEmbedding.embedding, embedding)})`; + const embeddingSimilarity = sql`1 - (${cosineDistance(DocumentEmbedding.vector, embedding)})`; - const similarPages = await ctx.db - .select({ - content: PageEmbedding.chunk_text, - page_id: Page.id, - page_title: Page.title, - similarity, - }) - .from(PageEmbedding) - .where( - and( - eq(Page.user_id, ctx.session.user.id), - gt(similarity, input.threshold), - ), - ) - .innerJoin(Page, eq(PageEmbedding.page_id, Page.id)) - .orderBy(desc(similarity)) - .limit(input.limit); + const results = await ctx.db + .selectDistinctOn([DocumentEmbedding.document_id], { + embedding: DocumentEmbedding, + page: Page, + similarity: embeddingSimilarity, + }) + .from(DocumentEmbedding) + .where( + and( + eq(DocumentEmbedding.user_id, ctx.session.user.id), + gt(embeddingSimilarity, input.threshold), + ), + ) + .innerJoin(Page, eq(DocumentEmbedding.document_id, Page.document_id)) + .orderBy(DocumentEmbedding.document_id, desc(embeddingSimilarity)); - return similarPages; + results.sort((a, b) => { + return b.similarity - a.similarity; + }); + + return results; + } catch (error) { + console.error("Database error in journal.getRelevantEntries:", error); + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Failed to fetch similar journal entries", + }); + } }), updateTitle: protectedProcedure .input( diff --git a/packages/api/src/embedder-router/document-embedding-task.ts b/packages/api/src/embedder-router/document-embedding-task.ts new file mode 100644 index 0000000..5de110f --- /dev/null +++ b/packages/api/src/embedder-router/document-embedding-task.ts @@ -0,0 +1,17 @@ +import { eq } from "@acme/db"; +import { DocumentEmbeddingTask, zDocumentEmbeddingTask } from "@acme/db/schema"; +import type { TRPCRouterRecord } from "@trpc/server"; +import { publicProcedure } from "../trpc.js"; + +export const documentEmbeddingTaskRouter = { + updateStatus: publicProcedure + .input(zDocumentEmbeddingTask.pick({ id: true, status: true })) + .mutation(async ({ ctx, input }) => { + await ctx.db + .update(DocumentEmbeddingTask) + .set({ + status: input.status, + }) + .where(eq(DocumentEmbeddingTask.id, input.id)); + }), +} satisfies TRPCRouterRecord; diff --git a/packages/api/src/embedder-router/document-embedding.ts b/packages/api/src/embedder-router/document-embedding.ts new file mode 100644 index 0000000..f3b5ac5 --- /dev/null +++ b/packages/api/src/embedder-router/document-embedding.ts @@ -0,0 +1,48 @@ +import { eq } from "@acme/db"; +import { + DocumentEmbedding, + DocumentEmbeddingTask, + zInsertDocumentEmbedding, +} from "@acme/db/schema"; +import type { TRPCRouterRecord } from "@trpc/server"; +import z from "zod/v4"; +import { publicProcedure } from "../trpc.js"; + +export const documentEmbeddingRouter = { + embedDocument: publicProcedure + .input( + z.object({ + document_id: z.string(), + embeddings: z.array(zInsertDocumentEmbedding), + task_id: z.string(), + user_id: z.string(), + }), + ) + .mutation(async ({ ctx, input }) => { + try { + await ctx.db.transaction(async (tx) => { + await tx + .delete(DocumentEmbedding) + .where(eq(DocumentEmbedding.document_id, input.document_id)); + + await tx.insert(DocumentEmbedding).values(input.embeddings); + + await tx + .update(DocumentEmbeddingTask) + .set({ + status: "completed", + }) + .where(eq(DocumentEmbeddingTask.id, input.task_id)); + }); + } catch (error) { + console.error("Error embedding document 👀", error); + + await ctx.db + .update(DocumentEmbeddingTask) + .set({ + status: "failed", + }) + .where(eq(DocumentEmbeddingTask.id, input.task_id)); + } + }), +} satisfies TRPCRouterRecord; diff --git a/packages/api/src/embedder-router/document.ts b/packages/api/src/embedder-router/document.ts new file mode 100644 index 0000000..6ea9465 --- /dev/null +++ b/packages/api/src/embedder-router/document.ts @@ -0,0 +1,58 @@ +import { sql } from "@acme/db"; +import { BlockEdge, BlockNode, Document } from "@acme/db/schema"; +import { TRPCError, type TRPCRouterRecord } from "@trpc/server"; +import z from "zod/v4"; +import { blockNoteTree } from "../shared/block-note-tree.js"; +import { publicProcedure } from "../trpc.js"; + +export const documentRouter = { + getById: publicProcedure + .input(z.object({ id: z.uuid(), user_id: z.string() })) + .query(async ({ ctx, input }) => { + try { + const { + rows: [document], + } = await ctx.db.execute< + Document & { + blocks: BlockNode[]; + edges: BlockEdge[]; + } + >(sql` + WITH document AS ( + SELECT * FROM ${Document} + WHERE ${Document.id} = ${input.id} AND ${Document.user_id} = ${input.user_id} + LIMIT 1 + ) + SELECT + document.*, + COALESCE( + (SELECT json_agg(${BlockNode}.*) FROM ${BlockNode} WHERE ${BlockNode.document_id} = document.id), + '[]'::json + ) as blocks, + COALESCE( + (SELECT json_agg(${BlockEdge}.*) FROM ${BlockEdge} WHERE ${BlockEdge.document_id} = document.id), + '[]'::json + ) as edges + FROM document + `); + + if (!document) { + return null; + } + + return { + ...document, + tree: blockNoteTree(document.blocks, document.edges), + }; + } catch (error) { + if (error instanceof TRPCError) { + throw error; + } + console.error("Database error in pages.byId:", error); + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Failed to fetch page", + }); + } + }), +} satisfies TRPCRouterRecord; diff --git a/packages/api/src/embedder-router/index.ts b/packages/api/src/embedder-router/index.ts new file mode 100644 index 0000000..9c873ed --- /dev/null +++ b/packages/api/src/embedder-router/index.ts @@ -0,0 +1,12 @@ +import { createTRPCRouter } from "../trpc.js"; +import { documentRouter } from "./document.js"; +import { documentEmbeddingRouter } from "./document-embedding.js"; +import { documentEmbeddingTaskRouter } from "./document-embedding-task.js"; + +export const embedderRouter = createTRPCRouter({ + document: documentRouter, + documentEmbedding: documentEmbeddingRouter, + documentEmbeddingTask: documentEmbeddingTaskRouter, +}); + +export type EmbedderRouter = typeof embedderRouter; diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index 2dadd8a..7759fe3 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -1,7 +1,10 @@ import type { inferRouterInputs, inferRouterOutputs } from "@trpc/server"; -import type { AppRouter } from "./root.js"; -import { appRouter } from "./root.js"; +import { type ApiRouter, apiRouter } from "./api-router/index.js"; +import { + type EmbedderRouter, + embedderRouter, +} from "./embedder-router/index.js"; import { createTRPCContext } from "./trpc.js"; /** @@ -10,7 +13,7 @@ import { createTRPCContext } from "./trpc.js"; * type PostByIdInput = RouterInputs['post']['byId'] * ^? { id: number } **/ -type RouterInputs = inferRouterInputs; +type RouterInputs = inferRouterInputs; /** * Inference helpers for output types @@ -18,10 +21,11 @@ type RouterInputs = inferRouterInputs; * type AllPostsOutput = RouterOutputs['post']['all'] * ^? Post[] **/ -type RouterOutputs = inferRouterOutputs; +type RouterOutputs = inferRouterOutputs; -export { createTRPCContext, appRouter }; -export type { AppRouter, RouterInputs, RouterOutputs }; +export { createTRPCContext, apiRouter, embedderRouter }; +export type { ApiRouter, RouterInputs, RouterOutputs, EmbedderRouter }; // Export types from sub-routers -export type { BlockTransaction } from "./router/blocks.js"; +export type { BlockTransaction } from "./api-router/blocks.js"; +export type { PlaceholderJournalEntry } from "./api-router/journal.js"; diff --git a/packages/api/src/root.ts b/packages/api/src/root.ts deleted file mode 100644 index 5d48fa4..0000000 --- a/packages/api/src/root.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { authRouter } from "./router/auth.js"; -import { blocksRouter } from "./router/blocks.js"; -import { documentRouter } from "./router/document.js"; -import { journalRouter } from "./router/journal.js"; -import { notesRouter } from "./router/notes.js"; -import { pagesRouter } from "./router/pages.js"; -import { createTRPCRouter } from "./trpc.js"; - -export const appRouter = createTRPCRouter({ - auth: authRouter, - blocks: blocksRouter, - document: documentRouter, - journal: journalRouter, - notes: notesRouter, - pages: pagesRouter, -}); - -export type AppRouter = typeof appRouter; diff --git a/packages/api/src/router/notes.ts b/packages/api/src/router/notes.ts deleted file mode 100644 index a9d00a4..0000000 --- a/packages/api/src/router/notes.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { and, cosineDistance, desc, eq, gt, sql } from "@acme/db"; -import { JournalEmbedding, Page, PageEmbedding } from "@acme/db/schema"; -import { openai } from "@ai-sdk/openai"; -import type { TRPCRouterRecord } from "@trpc/server"; -import { TRPCError } from "@trpc/server"; -import { embed } from "ai"; -import { z } from "zod/v4"; -import { protectedProcedure } from "../trpc.js"; - -export const notesRouter = { - getSimilarNotes: protectedProcedure - .input( - z.object({ - limit: z.number().min(1).max(20).default(5), - offset: z.number().min(0).default(0), - query: z.string().max(10000), - threshold: z.number().min(0).max(1).default(0.3), - }), - ) - .query(async ({ ctx, input }) => { - try { - const { embedding } = await embed({ - model: openai.embedding("text-embedding-3-small"), - value: input.query, - }); - - const journalEmbeddingSimilarity = sql`1 - (${cosineDistance(JournalEmbedding.embedding, embedding)})`; - const pageEmbeddingSimilarity = sql`1 - (${cosineDistance(PageEmbedding.embedding, embedding)})`; - - const results = await ctx.db - .select({ - content: JournalEmbedding.chunk_text, - created_at: JournalEmbedding.created_at, - date: JournalEmbedding.date, - header: sql`''`, - id: JournalEmbedding.journal_entry_id, - similarity: journalEmbeddingSimilarity, - type: sql<"journal" | "page">`'journal'`, - updated_at: JournalEmbedding.updated_at, - }) - .from(JournalEmbedding) - .where( - and( - eq(JournalEmbedding.user_id, ctx.session.user.id), - gt(journalEmbeddingSimilarity, input.threshold), - ), - ) - .orderBy(desc(journalEmbeddingSimilarity)) - .limit(input.limit) - .union( - ctx.db - .select({ - content: PageEmbedding.chunk_text, - created_at: PageEmbedding.created_at, - date: sql`'1970-01-01'`, - header: Page.title, - id: PageEmbedding.page_id, - similarity: pageEmbeddingSimilarity, - type: sql<"journal" | "page">`'page'`, - updated_at: PageEmbedding.updated_at, - }) - .from(PageEmbedding) - .where( - and( - eq(PageEmbedding.user_id, ctx.session.user.id), - gt(pageEmbeddingSimilarity, input.threshold), - ), - ) - .innerJoin(Page, eq(PageEmbedding.page_id, Page.id)) - .orderBy(desc(pageEmbeddingSimilarity)) - .limit(input.limit), - ); - - // Sorting the results of both tables by similarity and header. - results.sort((a, b) => { - if (a.similarity === b.similarity) { - return a.type === "journal" ? -1 : 1; - } - return b.similarity - a.similarity; - }); - - return results; - } catch (error) { - console.error("Database error in journal.getRelevantEntries:", error); - throw new TRPCError({ - code: "INTERNAL_SERVER_ERROR", - message: "Failed to fetch similar journal entries", - }); - } - }), -} satisfies TRPCRouterRecord; diff --git a/packages/db/src/core/document-embedding-task.schema.ts b/packages/db/src/core/document-embedding-task.schema.ts index c4d0114..08258e1 100644 --- a/packages/db/src/core/document-embedding-task.schema.ts +++ b/packages/db/src/core/document-embedding-task.schema.ts @@ -7,7 +7,7 @@ import { Document } from "./document.schema.js"; export const DocumentEmbeddingTaskStatus = pgEnum( "document_embedding_task_status", - ["debounced", "ready", "running", "completed", "failed"], + ["debounced", "ready", "completed", "failed"], ); export const DocumentEmbeddingTask = pgTable( @@ -22,6 +22,7 @@ export const DocumentEmbeddingTask = pgTable( .notNull() .references(() => Document.id, { onDelete: "cascade" }), status: DocumentEmbeddingTaskStatus().notNull().default("debounced"), + retries: t.integer().notNull().default(0), created_at: t .timestamp({ mode: "string", withTimezone: true }) .defaultNow() @@ -35,7 +36,7 @@ export const DocumentEmbeddingTask = pgTable( (t) => [ // Unique constraint: only one non-completed task per page // This allows multiple completed tasks but prevents multiple active tasks - uniqueIndex("unique_non_completed_task_per_page") + uniqueIndex("document_embedding_task_unique_non_completed_task_per_page") .on(t.document_id) .where(sql`${t.status} != 'completed'`), ], diff --git a/packages/db/src/core/document-embedding.schema.ts b/packages/db/src/core/document-embedding.schema.ts new file mode 100644 index 0000000..2836a8b --- /dev/null +++ b/packages/db/src/core/document-embedding.schema.ts @@ -0,0 +1,60 @@ +import { sql } from "drizzle-orm"; +import { index, pgTable, text, vector } from "drizzle-orm/pg-core"; +import { createInsertSchema, createSelectSchema } from "drizzle-zod"; +import { user } from "../auth/user.schema.js"; +import { Document } from "./document.schema.js"; + +export const DocumentEmbedding = pgTable( + "document_embedding", + (t) => ({ + id: t.uuid().notNull().primaryKey().defaultRandom(), + user_id: text() + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + document_id: t + .uuid() + .notNull() + .references(() => Document.id, { onDelete: "cascade" }), + vector: vector({ dimensions: 1536 }).notNull(), + chunk_id: t.integer().notNull(), + chunk_markdown_text: t.text().notNull(), + chunk_raw_text: t.text().notNull(), + metadata: t.jsonb().notNull().$type<{ + sectionSummary: string; + excerptKeywords: string; + documentTitle: string; + }>(), + created_at: t + .timestamp({ mode: "string", withTimezone: true }) + .defaultNow() + .notNull(), + updated_at: t + .timestamp({ mode: "string", withTimezone: true }) + .defaultNow() + .notNull() + .$onUpdateFn(() => sql`now()`), + }), + (t) => [ + // HNSW index for efficient similarity search + index("document_embedding_hnsw_index").using( + "hnsw", + t.vector.op("vector_cosine_ops"), + ), + ], +); + +export type DocumentEmbedding = typeof DocumentEmbedding.$inferSelect; + +export const zInsertDocumentEmbedding = createInsertSchema( + DocumentEmbedding, +).pick({ + user_id: true, + document_id: true, + vector: true, + chunk_id: true, + chunk_markdown_text: true, + chunk_raw_text: true, + metadata: true, +}); + +export const zDocumentEmbedding = createSelectSchema(DocumentEmbedding); diff --git a/packages/db/src/core/journal-embedding.schema.ts b/packages/db/src/core/journal-embedding.schema.ts deleted file mode 100644 index 9ba4235..0000000 --- a/packages/db/src/core/journal-embedding.schema.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { sql } from "drizzle-orm"; -import { index, pgTable, text, unique, vector } from "drizzle-orm/pg-core"; -import { createSelectSchema } from "drizzle-zod"; -import { user } from "../auth/user.schema.js"; -import { JournalEntry } from "./journal-entry.schema.js"; - -export const JournalEmbedding = pgTable( - "journal_embedding", - (t) => ({ - id: t.uuid().notNull().primaryKey().defaultRandom(), - journal_entry_id: t - .uuid() - .notNull() - .references(() => JournalEntry.id, { onDelete: "cascade" }), - user_id: text() - .notNull() - .references(() => user.id, { onDelete: "cascade" }), - date: t.date({ mode: "string" }).notNull(), - // Chunk information for managing large page content - chunk_text: t.text().notNull(), // The actual text content of this chunk - // The actual embedding vector for this chunk - embedding: vector({ dimensions: 1536 }).notNull(), - created_at: t - .timestamp({ mode: "string", withTimezone: true }) - .defaultNow() - .notNull(), - updated_at: t - .timestamp({ mode: "string", withTimezone: true }) - .defaultNow() - .notNull() - .$onUpdateFn(() => sql`now()`), - }), - (table) => [ - // Enforce uniqueness: one embedding per journal entry - unique("unique_journal_embedding_entry").on(table.journal_entry_id), - // Also maintain uniqueness per user per date for additional constraint - unique("unique_journal_embedding_user_date").on(table.user_id, table.date), - // HNSW index for efficient similarity search - index("hnsw_journal_embedding_index").using( - "hnsw", - table.embedding.op("vector_cosine_ops"), - ), - ], -); - -export type JournalEmbedding = typeof JournalEmbedding.$inferSelect; - -export const zJournalEmbedding = createSelectSchema(JournalEmbedding); diff --git a/packages/db/src/core/journal-entry.schema.ts b/packages/db/src/core/journal-entry.schema.ts index 23c5047..2994de0 100644 --- a/packages/db/src/core/journal-entry.schema.ts +++ b/packages/db/src/core/journal-entry.schema.ts @@ -3,6 +3,7 @@ import { pgTable, text, unique } from "drizzle-orm/pg-core"; import { createSelectSchema } from "drizzle-zod"; import z from "zod/v4"; import { user } from "../auth/user.schema.js"; +import { Document } from "./document.schema.js"; export const JournalEntry = pgTable( "journal_entry", @@ -11,9 +12,12 @@ export const JournalEntry = pgTable( user_id: text() .notNull() .references(() => user.id, { onDelete: "cascade" }), - // Content stores the journal entry text content - content: text().notNull(), + document_id: t + .uuid() + // .notNull() + .references(() => Document.id, { onDelete: "cascade" }), date: t.date({ mode: "string" }).notNull(), + content: text().notNull(), created_at: t .timestamp({ mode: "string", withTimezone: true }) .defaultNow() @@ -24,9 +28,9 @@ export const JournalEntry = pgTable( .notNull() .$onUpdateFn(() => sql`now()`), }), - (table) => [ + (t) => [ // Enforce uniqueness: one journal entry per user per day - unique("unique_journal_entry_user_date").on(table.user_id, table.date), + unique("journal_entry_unique_user_date").on(t.user_id, t.date), ], ); diff --git a/packages/db/src/core/page-embedding.schema.ts b/packages/db/src/core/page-embedding.schema.ts deleted file mode 100644 index 3d92e9a..0000000 --- a/packages/db/src/core/page-embedding.schema.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { sql } from "drizzle-orm"; -import { index, pgTable, text, unique, vector } from "drizzle-orm/pg-core"; -import { createSelectSchema } from "drizzle-zod"; -import { user } from "../auth/user.schema.js"; -import { Page } from "./page.schema.js"; - -const EMBEDDING_DIMENSIONS = 1536; - -export const PageEmbedding = pgTable( - "page_embedding", - (t) => ({ - id: t.uuid().notNull().primaryKey().defaultRandom(), - page_id: t - .uuid() - .notNull() - .references(() => Page.id, { onDelete: "cascade" }), - user_id: text() - .notNull() - .references(() => user.id, { onDelete: "cascade" }), - // Chunk information for managing large page content - chunk_index: t.integer().notNull(), // 0-based index of the chunk within the page - chunk_text: t.text().notNull(), // The actual text content of this chunk - // Metadata about the chunk stored as JSONB - metadata: t.jsonb().notNull(), // Contains block_ids, chunk_size, etc. - // Hash of the entire page's text content for change detection - page_text_hash: t.text().notNull(), - // The actual embedding vector for this chunk - embedding: vector({ dimensions: EMBEDDING_DIMENSIONS }).notNull(), - created_at: t - .timestamp({ mode: "string", withTimezone: true }) - .defaultNow() - .notNull(), - updated_at: t - .timestamp({ mode: "string", withTimezone: true }) - .defaultNow() - .notNull() - .$onUpdateFn(() => sql`now()`), - }), - (table) => [ - // Ensure unique chunk indices per page - unique("unique_page_embedding_chunk").on(table.page_id, table.chunk_index), - - // Prevent duplicate embeddings for identical page content - unique("unique_page_embedding_text_hash").on( - table.user_id, - table.page_text_hash, - table.chunk_index, - ), - - // Indexes for efficient page-based searches - index("page_embedding_page_idx").on(table.page_id), - index("page_embedding_user_idx").on(table.user_id), - index("page_embedding_user_page_idx").on(table.user_id, table.page_id), - index("page_embedding_text_hash_idx").on(table.page_text_hash), - - // GIN index for JSONB metadata queries - index("page_embedding_metadata_gin_idx").using("gin", table.metadata), - - // HNSW index for similarity search - index("hnsw_page_embedding_index").using( - "hnsw", - table.embedding.op("vector_cosine_ops"), - ), - - // Filtered HNSW index for user-specific similarity search - index("hnsw_page_embedding_user_index") - .using("hnsw", table.embedding.op("vector_cosine_ops")) - .where(sql`user_id IS NOT NULL`), - ], -); - -export type PageEmbedding = typeof PageEmbedding.$inferSelect; - -export const zPageEmbedding = createSelectSchema(PageEmbedding); diff --git a/packages/db/src/schema.ts b/packages/db/src/schema.ts index ec2a1a7..d793777 100644 --- a/packages/db/src/schema.ts +++ b/packages/db/src/schema.ts @@ -5,8 +5,7 @@ export * from "./auth/user.schema.js"; export * from "./auth/verification.schema.js"; export * from "./core/block-node.schema.js"; export * from "./core/document.schema.js"; +export * from "./core/document-embedding.schema.js"; export * from "./core/document-embedding-task.schema.js"; -export * from "./core/journal-embedding.schema.js"; export * from "./core/journal-entry.schema.js"; export * from "./core/page.schema.js"; -export * from "./core/page-embedding.schema.js"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bd5849e..56514a6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -98,6 +98,9 @@ importers: '@blocknote/react': specifier: ^0.35.0 version: 0.35.0(@types/hast@3.0.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@blocknote/server-util': + specifier: ^0.35.0 + version: 0.35.0(@types/hast@3.0.4)(bufferutil@4.0.9)(prosemirror-model@1.25.2)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@daveyplate/better-auth-ui': specifier: ^2.1.0 version: 2.1.0(7503a6438a2a526a6ce1f7001bf6c5f8) @@ -119,6 +122,9 @@ importers: '@mastra/memory': specifier: ^0.12.0 version: 0.12.0(@mastra/core@0.12.1(arktype@2.1.20)(openapi-types@12.1.3)(react@19.1.0)(valibot@1.0.0-beta.15(typescript@5.8.3))(zod@3.25.71))(react@19.1.0) + '@mastra/rag': + specifier: ^1.1.0 + version: 1.1.0(@mastra/core@0.12.1(arktype@2.1.20)(openapi-types@12.1.3)(react@19.1.0)(valibot@1.0.0-beta.15(typescript@5.8.3))(zod@3.25.71))(ai@4.3.17(react@19.1.0)(zod@3.25.71)) '@ngrok/ngrok': specifier: ^1.5.1 version: 1.5.1 @@ -206,6 +212,9 @@ importers: remark-gfm: specifier: ^4.0.1 version: 4.0.1 + remove-markdown: + specifier: ^0.6.2 + version: 0.6.2 sonner: specifier: ^2.0.5 version: 2.0.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -419,6 +428,9 @@ packages: '@ark/util@0.46.0': resolution: {integrity: sha512-JPy/NGWn/lvf1WmGCPw2VGpBg5utZraE84I7wli18EDF3p3zc/e9WolT35tINeZO3l7C77SjqRJeAUoT0CvMRg==} + '@asamuzakjp/css-color@3.2.0': + resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} + '@assistant-ui/react-ai-sdk@0.10.16': resolution: {integrity: sha512-GNbt7WXrOV4WwO8AesA9L53nmiCAr53TkZa7pxXKWP96cPgSoM602HMWFINVDBy9X1I8vSmSX+rdCAn8FR+TxQ==} peerDependencies: @@ -609,6 +621,12 @@ packages: react: ^18.0 || ^19.0 || >= 19.0.0-rc react-dom: ^18.0 || ^19.0 || >= 19.0.0-rc + '@blocknote/server-util@0.35.0': + resolution: {integrity: sha512-r0vt5k8LxvX+W3BaAcUBjiInnPHRB008P9gNbt6ZL85KTTAqJ/zIq0Ylm6H3Ho79eQfaDA4fxIiD2E6w0dX1eA==} + peerDependencies: + react: ^18.0 || ^19.0 || >= 19.0.0-rc + react-dom: ^18.0 || ^19.0 || >= 19.0.0-rc + '@clack/core@0.5.0': resolution: {integrity: sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==} @@ -623,6 +641,34 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} + '@csstools/color-helpers@5.1.0': + resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} + engines: {node: '>=18'} + + '@csstools/css-calc@2.1.4': + resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-color-parser@3.1.0': + resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-parser-algorithms@3.0.5': + resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-tokenizer@3.0.4': + resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} + engines: {node: '>=18'} + '@date-fns/tz@1.2.0': resolution: {integrity: sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==} @@ -1457,6 +1503,12 @@ packages: peerDependencies: '@mastra/core': '>=0.12.0-0 <0.13.0-0' + '@mastra/rag@1.1.0': + resolution: {integrity: sha512-Gi0MlEwRIAxj2bn4KoK7/dl4Yvr2CnhOQFaG9pguenS953i7/O/FBpqOPIxUhIgIKsFNXT5SSKbQRRJ76AVt4Q==} + peerDependencies: + '@mastra/core': '>=0.14.0-0 <0.15.0-0' + ai: ^4.0.0 + '@mastra/schema-compat@0.10.5': resolution: {integrity: sha512-Qhz8W4Hz7b9tNoVW306NMzotVy11bya1OjiTN+pthj00HZoaH7nmK8SWBiX4drS+PyhIqA16X5AenELe2QgZag==} peerDependencies: @@ -2098,6 +2150,9 @@ packages: resolution: {integrity: sha512-9+qMSaDpahC0+vX2ChM46/ls6a5Ankqs6RTLrHSaFpm7o1mFanP82e+jm9/0o5D660ueK8dWJGPCXQrBxBNNWA==} engines: {node: '>= 12'} + '@paralleldrive/cuid2@2.2.2': + resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==} + '@peculiar/asn1-android@2.3.16': resolution: {integrity: sha512-a1viIv3bIahXNssrOIkXZIlI2ePpZaNmR30d4aBL99mu2rO+mT9D6zBsp7H6eROWGtmwv0Ionp5olJurIo09dw==} @@ -3600,6 +3655,9 @@ packages: '@types/node-fetch@2.6.12': resolution: {integrity: sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==} + '@types/node@18.19.123': + resolution: {integrity: sha512-K7DIaHnh0mzVxreCR9qwgNxp3MH9dltPNIEddW9MYUlcKAzm+3grKNSTe2vCJHI1FaLpvpL5JGJrz1UZDKYvDg==} + '@types/node@22.15.29': resolution: {integrity: sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==} @@ -3682,6 +3740,10 @@ packages: '@types/react': optional: true + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} @@ -3704,6 +3766,10 @@ packages: resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} engines: {node: '>= 14'} + agentkeepalive@4.6.0: + resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} + engines: {node: '>= 8.0.0'} + aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} @@ -3820,6 +3886,9 @@ packages: better-call@1.0.12: resolution: {integrity: sha512-ssq5OfB9Ungv2M1WVrRnMBomB0qz1VKuhkY2WxjHaLtlsHoSe9EPolj1xf7xf8LY9o3vfk3Rx6rCWI4oVHeBRg==} + big.js@7.0.1: + resolution: {integrity: sha512-iFgV784tD8kq4ccF1xtNMZnXeZzVuXWWM+ERFzKQjv+A5G9HC8CY3DuV45vgzFFcW+u2tIvmF95+AzWgs6BjCg==} + bignumber.js@9.3.1: resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} @@ -4090,6 +4159,10 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + cssstyle@4.6.0: + resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} + engines: {node: '>=18'} + csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} @@ -4105,6 +4178,10 @@ packages: resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} engines: {node: '>= 14'} + data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + date-fns-jalali@4.1.0-0: resolution: {integrity: sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==} @@ -4144,6 +4221,9 @@ packages: supports-color: optional: true + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + decode-named-character-reference@1.2.0: resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} @@ -4481,6 +4561,10 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + eventsource-parser@3.0.3: resolution: {integrity: sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA==} engines: {node: '>=20.0.0'} @@ -4588,10 +4672,17 @@ packages: debug: optional: true + form-data-encoder@1.7.2: + resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==} + form-data@4.0.4: resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} engines: {node: '>= 6'} + formdata-node@4.4.1: + resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} + engines: {node: '>= 12.20'} + formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} @@ -4838,6 +4929,13 @@ packages: resolution: {integrity: sha512-DRMYbR3aFk6YET1FCSAFbgF2cWYTz5j0YAFYPECx9fmrbKBDAYnWU+YCgRTpOaatxMYN6e68U/2IG39zRP4W/A==} engines: {node: '>=16.9.0'} + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + + html-entities@2.6.0: + resolution: {integrity: sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==} + html-to-text@9.0.5: resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==} engines: {node: '>=14'} @@ -4874,6 +4972,9 @@ packages: resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} engines: {node: '>=18.18.0'} + humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -5006,6 +5107,9 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} @@ -5095,6 +5199,15 @@ packages: jsbn@1.1.0: resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} + jsdom@25.0.1: + resolution: {integrity: sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^2.11.2 + peerDependenciesMeta: + canvas: + optional: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -5259,6 +5372,9 @@ packages: lower-case@1.1.4: resolution: {integrity: sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==} + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -5598,6 +5714,9 @@ packages: resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} hasBin: true + node-html-better-parser@1.5.3: + resolution: {integrity: sha512-rvnbT4FUS+pIQPAs3bBpzeuWdgdjne0LsgrEINdsMfAvjAKHTEGVhknMEqBriGuVRWM8iRL1LKhRhZ9RB6gPVA==} + node-plop@0.26.3: resolution: {integrity: sha512-Cov028YhBZ5aB7MdMWJEmwyBig43aGL5WT4vdoB28Oitau1zZAcHUn8Sgfk9HM33TqhtLJ9PlM/O0Mv+QpV/4Q==} engines: {node: '>=8.9.4'} @@ -5613,6 +5732,9 @@ packages: resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} engines: {node: '>=18'} + nwsapi@2.2.21: + resolution: {integrity: sha512-o6nIY3qwiSXl7/LuOU0Dmuctd34Yay0yeuZRLFmDPrrdHpXKFndPj3hM+YEPVHYC5fx2otBx4Ilc/gyYSAUaIA==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -6223,6 +6345,9 @@ packages: remark-stringify@11.0.0: resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + remove-markdown@0.6.2: + resolution: {integrity: sha512-EijDXJZbzpGbQBd852ViUzcqgpMujthM+SAEHiWCMcZonRbZ+xViWKLJA/vrwbDwYdxrs1aFDjpBhcGrZoJRGA==} + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -6288,6 +6413,12 @@ packages: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} + rrweb-cssom@0.7.1: + resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==} + + rrweb-cssom@0.8.0: + resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + run-applescript@7.0.0: resolution: {integrity: sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==} engines: {node: '>=18'} @@ -6316,6 +6447,10 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + scheduler@0.26.0: resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} @@ -6562,6 +6697,9 @@ packages: peerDependencies: react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + tabbable@6.2.0: resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} @@ -6604,6 +6742,13 @@ packages: title-case@2.1.1: resolution: {integrity: sha512-EkJoZ2O3zdCz3zJsYCsxyq2OC5hrxR9mfdd5I+w8h/tmFfeOxJ+vvkxsKxdmN0WtS9zLdHEgfgVOiMVgv+Po4Q==} + tldts-core@6.1.86: + resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} + + tldts@6.1.86: + resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} + hasBin: true + tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} @@ -6616,9 +6761,17 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + tough-cookie@5.1.2: + resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} + engines: {node: '>=16'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + tr46@5.1.1: + resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} + engines: {node: '>=18'} + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -6737,6 +6890,9 @@ packages: uncrypto@0.1.3: resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -6905,6 +7061,10 @@ packages: w3c-keyname@2.2.8: resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + warning@4.0.3: resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==} @@ -6918,12 +7078,32 @@ packages: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} + web-streams-polyfill@4.0.0-beta.3: + resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} + engines: {node: '>= 14'} + web-worker@1.5.0: resolution: {integrity: sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==} webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@14.2.0: + resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} + engines: {node: '>=18'} + whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} @@ -6966,6 +7146,13 @@ packages: resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} engines: {node: '>=18'} + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + xstate@5.20.1: resolution: {integrity: sha512-i9ZpNnm/XhCOMUxae1suT8PjYNTStZWbhmuKt4xeTPaYG5TS0Fz0i+Ka5yxoNPpaHW3VW6JIowrwFgSTZONxig==} @@ -7040,6 +7227,9 @@ packages: resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==} engines: {node: '>=18'} + zeroentropy@0.1.0-alpha.6: + resolution: {integrity: sha512-8roJUFph+VgQ13ABYm5XmtlHvabMD5r1TA9tOKQA7MVc2h0y2JymtmoWJl4qaaHQtpFkqzJu4pNY7rLksMOWBg==} + zod-from-json-schema@0.0.5: resolution: {integrity: sha512-zYEoo86M1qpA1Pq6329oSyHLS785z/mTwfr9V1Xf/ZLhuuBGaMlDGu/pDVGVUe4H4oa1EFgWZT53DP0U3oT9CQ==} @@ -7137,6 +7327,14 @@ snapshots: '@ark/util@0.46.0': optional: true + '@asamuzakjp/css-color@3.2.0': + dependencies: + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + lru-cache: 10.4.3 + '@assistant-ui/react-ai-sdk@0.10.16(@assistant-ui/react@0.10.26(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)))(@types/react@19.1.8)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0))': dependencies: '@ai-sdk/provider': 1.1.3 @@ -7441,6 +7639,33 @@ snapshots: - sugar-high - supports-color + '@blocknote/server-util@0.35.0(@types/hast@3.0.4)(bufferutil@4.0.9)(prosemirror-model@1.25.2)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@blocknote/core': 0.35.0(@types/hast@3.0.4) + '@blocknote/react': 0.35.0(@types/hast@3.0.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) + '@tiptap/pm': 2.26.1 + jsdom: 25.0.1(bufferutil@4.0.9) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + y-prosemirror: 1.3.7(prosemirror-model@1.25.2)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27) + y-protocols: 1.0.6(yjs@13.6.27) + yjs: 13.6.27 + transitivePeerDependencies: + - '@hocuspocus/provider' + - '@types/hast' + - bufferutil + - canvas + - highlight.js + - lowlight + - prosemirror-model + - prosemirror-state + - prosemirror-view + - refractor + - sugar-high + - supports-color + - utf-8-validate + '@clack/core@0.5.0': dependencies: picocolors: 1.1.1 @@ -7458,6 +7683,26 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 + '@csstools/color-helpers@5.1.0': {} + + '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/color-helpers': 5.1.0 + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-tokenizer@3.0.4': {} + '@date-fns/tz@1.2.0': {} '@daveyplate/better-auth-tanstack@1.3.6(@tanstack/query-core@5.80.7)(@tanstack/react-query@5.80.7(react@19.1.0))(better-auth@1.3.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': @@ -8166,6 +8411,20 @@ snapshots: - pg-native - react + '@mastra/rag@1.1.0(@mastra/core@0.12.1(arktype@2.1.20)(openapi-types@12.1.3)(react@19.1.0)(valibot@1.0.0-beta.15(typescript@5.8.3))(zod@3.25.71))(ai@4.3.17(react@19.1.0)(zod@3.25.71))': + dependencies: + '@mastra/core': 0.12.1(arktype@2.1.20)(openapi-types@12.1.3)(react@19.1.0)(valibot@1.0.0-beta.15(typescript@5.8.3))(zod@3.25.71) + '@paralleldrive/cuid2': 2.2.2 + ai: 4.3.17(react@19.1.0)(zod@3.25.71) + big.js: 7.0.1 + js-tiktoken: 1.0.20 + node-html-better-parser: 1.5.3 + pathe: 2.0.3 + zeroentropy: 0.1.0-alpha.6 + zod: 3.25.71 + transitivePeerDependencies: + - encoding + '@mastra/schema-compat@0.10.5(ai@4.3.17(react@19.1.0)(zod@3.25.71))(zod@3.25.71)': dependencies: ai: 4.3.17(react@19.1.0)(zod@3.25.71) @@ -8986,6 +9245,10 @@ snapshots: estree-walker: 2.0.2 magic-string: 0.30.17 + '@paralleldrive/cuid2@2.2.2': + dependencies: + '@noble/hashes': 1.8.0 + '@peculiar/asn1-android@2.3.16': dependencies: '@peculiar/asn1-schema': 2.3.15 @@ -10515,6 +10778,10 @@ snapshots: '@types/node': 22.15.29 form-data: 4.0.4 + '@types/node@18.19.123': + dependencies: + undici-types: 5.26.5 + '@types/node@22.15.29': dependencies: undici-types: 6.21.0 @@ -10608,6 +10875,10 @@ snapshots: optionalDependencies: '@types/react': 19.1.8 + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + accepts@2.0.0: dependencies: mime-types: 3.0.1 @@ -10625,6 +10896,10 @@ snapshots: agent-base@7.1.3: {} + agentkeepalive@4.6.0: + dependencies: + humanize-ms: 1.2.1 + aggregate-error@3.1.0: dependencies: clean-stack: 2.2.0 @@ -10775,6 +11050,8 @@ snapshots: set-cookie-parser: 2.7.1 uncrypto: 0.1.3 + big.js@7.0.1: {} + bignumber.js@9.3.1: {} bl@4.1.0: @@ -11062,6 +11339,11 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + cssstyle@4.6.0: + dependencies: + '@asamuzakjp/css-color': 3.2.0 + rrweb-cssom: 0.8.0 + csstype@3.1.3: {} cycle@1.0.3: {} @@ -11070,6 +11352,11 @@ snapshots: data-uri-to-buffer@6.0.2: {} + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + date-fns-jalali@4.1.0-0: {} date-fns@3.6.0: {} @@ -11090,6 +11377,8 @@ snapshots: dependencies: ms: 2.1.3 + decimal.js@10.6.0: {} + decode-named-character-reference@1.2.0: dependencies: character-entities: 2.0.2 @@ -11385,6 +11674,8 @@ snapshots: etag@1.8.1: {} + event-target-shim@5.0.1: {} + eventsource-parser@3.0.3: {} eventsource@3.0.7: @@ -11532,6 +11823,8 @@ snapshots: optionalDependencies: debug: 4.3.2 + form-data-encoder@1.7.2: {} + form-data@4.0.4: dependencies: asynckit: 0.4.0 @@ -11540,6 +11833,11 @@ snapshots: hasown: 2.0.2 mime-types: 2.1.35 + formdata-node@4.4.1: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 4.0.0-beta.3 + formdata-polyfill@4.0.10: dependencies: fetch-blob: 3.2.0 @@ -11867,6 +12165,12 @@ snapshots: hono@4.8.10: {} + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + + html-entities@2.6.0: {} + html-to-text@9.0.5: dependencies: '@selderee/plugin-htmlparser2': 0.11.0 @@ -11914,6 +12218,10 @@ snapshots: human-signals@8.0.1: {} + humanize-ms@1.2.1: + dependencies: + ms: 2.1.3 + iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 @@ -12044,6 +12352,8 @@ snapshots: is-plain-obj@4.1.0: {} + is-potential-custom-element-name@1.0.1: {} + is-promise@4.0.0: {} is-reference@1.2.1: @@ -12110,6 +12420,34 @@ snapshots: jsbn@1.1.0: {} + jsdom@25.0.1(bufferutil@4.0.9): + dependencies: + cssstyle: 4.6.0 + data-urls: 5.0.0 + decimal.js: 10.6.0 + form-data: 4.0.4 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.21 + parse5: 7.3.0 + rrweb-cssom: 0.7.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 5.1.2 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + ws: 8.18.3(bufferutil@4.0.9) + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + jsesc@3.1.0: {} json-bigint@1.0.0: @@ -12255,6 +12593,8 @@ snapshots: lower-case@1.1.4: {} + lru-cache@10.4.3: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -12810,6 +13150,10 @@ snapshots: node-gyp-build@4.8.4: {} + node-html-better-parser@1.5.3: + dependencies: + html-entities: 2.6.0 + node-plop@0.26.3: dependencies: '@babel/runtime-corejs3': 7.27.4 @@ -12835,6 +13179,8 @@ snapshots: path-key: 4.0.0 unicorn-magic: 0.3.0 + nwsapi@2.2.21: {} + object-assign@4.1.1: {} object-inspect@1.13.4: {} @@ -13611,6 +13957,8 @@ snapshots: mdast-util-to-markdown: 2.1.2 unified: 11.0.5 + remove-markdown@0.6.2: {} + require-directory@2.1.1: {} require-in-the-middle@7.5.2: @@ -13699,6 +14047,10 @@ snapshots: transitivePeerDependencies: - supports-color + rrweb-cssom@0.7.1: {} + + rrweb-cssom@0.8.0: {} + run-applescript@7.0.0: {} run-async@2.4.1: {} @@ -13721,6 +14073,10 @@ snapshots: safer-buffer@2.1.2: {} + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + scheduler@0.26.0: {} secure-json-parse@2.7.0: {} @@ -13990,6 +14346,8 @@ snapshots: react: 19.1.0 use-sync-external-store: 1.5.0(react@19.1.0) + symbol-tree@3.2.4: {} + tabbable@6.2.0: {} tailwind-merge@3.3.1: {} @@ -14038,6 +14396,12 @@ snapshots: no-case: 2.3.2 upper-case: 1.1.3 + tldts-core@6.1.86: {} + + tldts@6.1.86: + dependencies: + tldts-core: 6.1.86 + tmp@0.0.33: dependencies: os-tmpdir: 1.0.2 @@ -14048,8 +14412,16 @@ snapshots: toidentifier@1.0.1: {} + tough-cookie@5.1.2: + dependencies: + tldts: 6.1.86 + tr46@0.0.3: {} + tr46@5.1.1: + dependencies: + punycode: 2.3.1 + tree-kill@1.2.2: {} trim-lines@3.0.1: {} @@ -14153,6 +14525,8 @@ snapshots: uncrypto@0.1.3: {} + undici-types@5.26.5: {} + undici-types@6.21.0: {} unicorn-magic@0.3.0: {} @@ -14312,6 +14686,10 @@ snapshots: w3c-keyname@2.2.8: {} + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + warning@4.0.3: dependencies: loose-envify: 1.4.0 @@ -14324,10 +14702,25 @@ snapshots: web-streams-polyfill@3.3.3: {} + web-streams-polyfill@4.0.0-beta.3: {} + web-worker@1.5.0: {} webidl-conversions@3.0.1: {} + webidl-conversions@7.0.0: {} + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + + whatwg-url@14.2.0: + dependencies: + tr46: 5.1.1 + webidl-conversions: 7.0.0 + whatwg-url@5.0.0: dependencies: tr46: 0.0.3 @@ -14370,6 +14763,10 @@ snapshots: dependencies: is-wsl: 3.1.0 + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + xstate@5.20.1: {} xtend@4.0.2: {} @@ -14434,6 +14831,18 @@ snapshots: yoctocolors@2.1.1: {} + zeroentropy@0.1.0-alpha.6: + dependencies: + '@types/node': 18.19.123 + '@types/node-fetch': 2.6.12 + abort-controller: 3.0.0 + agentkeepalive: 4.6.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + zod-from-json-schema@0.0.5: dependencies: zod: 3.25.71 diff --git a/turbo.json b/turbo.json index d2d7561..b1fbfec 100644 --- a/turbo.json +++ b/turbo.json @@ -50,5 +50,5 @@ "dependsOn": ["^topo", "^build"] } }, - "ui": "stream" + "ui": "tui" }