From fb986f3646aaa971a754e3ec8bfefccdcfb36167 Mon Sep 17 00:00:00 2001 From: Daniel David Date: Sun, 26 Apr 2026 11:52:18 +1000 Subject: [PATCH 01/21] chore(repo): scaffold agentic RAG monorepo Bootstrap a pnpm-workspace monorepo for a local-first agentic RAG app: - apps/web: Next.js 15 (App Router) with /api/chat (streaming agent) and /api/ingest endpoints, basic chat UI using ai/react useChat. - packages/shared: zod schemas for documents/chunks/retrieval + env loader. - packages/db: Drizzle schema with pgvector(HNSW, cosine) and pg_trgm GIN, postgres-js client singleton, migrate runner. - packages/rag: recursive chunker, Ollama embedder via ollama-ai-provider, transactional ingest, cosine ANN retrieval. - packages/agent: Vercel AI SDK streamText loop with search_kb / fetch_doc / list_sources tools and a tool-first system prompt. - Infra: docker-compose for pgvector/pg16 + Ollama, init SQL enabling vector + pg_trgm extensions, web Dockerfile (standalone output), ollama-pull.sh helper. - Tooling: TS project references, strict tsconfig, flat ESLint config, Prettier, EditorConfig, .env.example. - Docs: README quickstart, CONTRIBUTING with gitflow, spec/ design docs (overview, architecture, data model, agent design, API, roadmap), CHANGELOG. - CI: GitHub Actions workflow (lint + typecheck + build) on main/develop, PR + issue templates. Co-Authored-By: Claude Opus 4.7 (1M context) --- .dockerignore | 13 +++ .editorconfig | 12 ++ .env.example | 25 ++++ .github/ISSUE_TEMPLATE/bug_report.md | 23 ++++ .github/ISSUE_TEMPLATE/feature_request.md | 14 +++ .github/PULL_REQUEST_TEMPLATE.md | 24 ++++ .github/workflows/ci.yml | 35 ++++++ .gitignore | 43 +++++++ .prettierignore | 7 ++ .prettierrc.json | 7 ++ CHANGELOG.md | 14 +++ CONTRIBUTING.md | 90 +++++++++++++++ README.md | 135 ++++++++++++++++++++++ apps/web/.eslintrc.json | 3 + apps/web/Dockerfile | 37 ++++++ apps/web/next-env.d.ts | 2 + apps/web/next.config.mjs | 11 ++ apps/web/package.json | 31 +++++ apps/web/src/app/api/chat/route.ts | 30 +++++ apps/web/src/app/api/ingest/route.ts | 22 ++++ apps/web/src/app/globals.css | 16 +++ apps/web/src/app/layout.tsx | 15 +++ apps/web/src/app/page.tsx | 25 ++++ apps/web/src/components/chat.tsx | 81 +++++++++++++ apps/web/src/lib/embedder.ts | 7 ++ apps/web/src/lib/env.ts | 3 + apps/web/tsconfig.json | 25 ++++ docker-compose.yml | 59 ++++++++++ docker/postgres/init.sql | 3 + eslint.config.mjs | 21 ++++ package.json | 34 ++++++ packages/agent/package.json | 28 +++++ packages/agent/src/agent.ts | 27 +++++ packages/agent/src/index.ts | 3 + packages/agent/src/prompts.ts | 10 ++ packages/agent/src/tools.ts | 86 ++++++++++++++ packages/agent/tsconfig.json | 14 +++ packages/db/drizzle.config.ts | 13 +++ packages/db/package.json | 30 +++++ packages/db/src/client.ts | 27 +++++ packages/db/src/index.ts | 2 + packages/db/src/migrate.ts | 15 +++ packages/db/src/schema.ts | 59 ++++++++++ packages/db/tsconfig.json | 10 ++ packages/rag/package.json | 27 +++++ packages/rag/src/chunker.ts | 61 ++++++++++ packages/rag/src/embeddings.ts | 26 +++++ packages/rag/src/index.ts | 4 + packages/rag/src/ingest.ts | 57 +++++++++ packages/rag/src/retrieve.ts | 56 +++++++++ packages/rag/tsconfig.json | 10 ++ packages/shared/package.json | 22 ++++ packages/shared/src/env.ts | 26 +++++ packages/shared/src/index.ts | 2 + packages/shared/src/schemas.ts | 44 +++++++ packages/shared/tsconfig.json | 9 ++ pnpm-workspace.yaml | 13 +++ scripts/ollama-pull.sh | 22 ++++ spec/spec-1.0.0/00-overview.md | 43 +++++++ spec/spec-1.0.0/01-architecture.md | 76 ++++++++++++ spec/spec-1.0.0/02-data-model.md | 45 ++++++++ spec/spec-1.0.0/03-agent-design.md | 54 +++++++++ spec/spec-1.0.0/04-api.md | 40 +++++++ spec/spec-1.0.0/05-roadmap.md | 33 ++++++ tsconfig.base.json | 22 ++++ tsconfig.json | 11 ++ 66 files changed, 1894 insertions(+) create mode 100644 .dockerignore create mode 100644 .editorconfig create mode 100644 .env.example create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 .prettierignore create mode 100644 .prettierrc.json create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 README.md create mode 100644 apps/web/.eslintrc.json create mode 100644 apps/web/Dockerfile create mode 100644 apps/web/next-env.d.ts create mode 100644 apps/web/next.config.mjs create mode 100644 apps/web/package.json create mode 100644 apps/web/src/app/api/chat/route.ts create mode 100644 apps/web/src/app/api/ingest/route.ts create mode 100644 apps/web/src/app/globals.css create mode 100644 apps/web/src/app/layout.tsx create mode 100644 apps/web/src/app/page.tsx create mode 100644 apps/web/src/components/chat.tsx create mode 100644 apps/web/src/lib/embedder.ts create mode 100644 apps/web/src/lib/env.ts create mode 100644 apps/web/tsconfig.json create mode 100644 docker-compose.yml create mode 100644 docker/postgres/init.sql create mode 100644 eslint.config.mjs create mode 100644 package.json create mode 100644 packages/agent/package.json create mode 100644 packages/agent/src/agent.ts create mode 100644 packages/agent/src/index.ts create mode 100644 packages/agent/src/prompts.ts create mode 100644 packages/agent/src/tools.ts create mode 100644 packages/agent/tsconfig.json create mode 100644 packages/db/drizzle.config.ts create mode 100644 packages/db/package.json create mode 100644 packages/db/src/client.ts create mode 100644 packages/db/src/index.ts create mode 100644 packages/db/src/migrate.ts create mode 100644 packages/db/src/schema.ts create mode 100644 packages/db/tsconfig.json create mode 100644 packages/rag/package.json create mode 100644 packages/rag/src/chunker.ts create mode 100644 packages/rag/src/embeddings.ts create mode 100644 packages/rag/src/index.ts create mode 100644 packages/rag/src/ingest.ts create mode 100644 packages/rag/src/retrieve.ts create mode 100644 packages/rag/tsconfig.json create mode 100644 packages/shared/package.json create mode 100644 packages/shared/src/env.ts create mode 100644 packages/shared/src/index.ts create mode 100644 packages/shared/src/schemas.ts create mode 100644 packages/shared/tsconfig.json create mode 100644 pnpm-workspace.yaml create mode 100755 scripts/ollama-pull.sh create mode 100644 spec/spec-1.0.0/00-overview.md create mode 100644 spec/spec-1.0.0/01-architecture.md create mode 100644 spec/spec-1.0.0/02-data-model.md create mode 100644 spec/spec-1.0.0/03-agent-design.md create mode 100644 spec/spec-1.0.0/04-api.md create mode 100644 spec/spec-1.0.0/05-roadmap.md create mode 100644 tsconfig.base.json create mode 100644 tsconfig.json diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..c2780e8 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,13 @@ +node_modules +**/node_modules +.next +**/.next +dist +**/dist +.git +.env +.env.local +coverage +.vscode +.idea +*.log diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..1014ba7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..a6c71c1 --- /dev/null +++ b/.env.example @@ -0,0 +1,25 @@ +# ---- Postgres ---- +POSTGRES_USER=rag +POSTGRES_PASSWORD=rag +POSTGRES_DB=rag +POSTGRES_PORT=5432 +DATABASE_URL=postgres://rag:rag@localhost:5432/rag + +# ---- Ollama ---- +# Base URL for the Ollama HTTP API (host machine: http://localhost:11434). +OLLAMA_BASE_URL=http://localhost:11434 +# Chat / generation model. Pull with: ollama pull llama3.1:8b +OLLAMA_CHAT_MODEL=llama3.1:8b +# Embedding model. Pull with: ollama pull nomic-embed-text +OLLAMA_EMBED_MODEL=nomic-embed-text +# Embedding dimension must match the model (nomic-embed-text = 768). +EMBEDDING_DIM=768 + +# ---- App ---- +NODE_ENV=development +NEXT_PUBLIC_APP_URL=http://localhost:3000 + +# ---- RAG tuning ---- +RAG_CHUNK_SIZE=800 +RAG_CHUNK_OVERLAP=120 +RAG_TOP_K=6 diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..3921dca --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,23 @@ +--- +name: Bug report +about: Report a defect or unexpected behavior +title: "bug: " +labels: bug +--- + +## What happened + +## What you expected + +## Steps to reproduce +1. +2. +3. + +## Environment +- OS: +- Node: +- pnpm: +- Docker: + +## Logs / screenshots diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..2bfda6d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,14 @@ +--- +name: Feature request +about: Suggest a new capability or improvement +title: "feat: " +labels: enhancement +--- + +## Problem / motivation + +## Proposed solution + +## Alternatives considered + +## Additional context diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..7c97da4 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,24 @@ +## Summary + + +## Related + + +## Type +- [ ] feat +- [ ] fix +- [ ] refactor +- [ ] docs +- [ ] chore / ci / build + +## Checklist +- [ ] Branched from `develop` (or `main` for hotfix) +- [ ] `pnpm typecheck` passes +- [ ] `pnpm lint` passes +- [ ] `pnpm test` passes (or N/A) +- [ ] Updated `spec/` docs if behavior changed +- [ ] Added migration if schema changed +- [ ] Manual smoke test: + +## Screenshots / logs + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..3edc9b2 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,35 @@ +name: CI + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + with: + version: 10 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + + - name: Install + run: pnpm install --frozen-lockfile=false + + - name: Lint + run: pnpm lint + + - name: Typecheck + run: pnpm typecheck + + - name: Build + run: pnpm build diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..45421d4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +# Dependencies +node_modules/ +.pnpm-store/ + +# Build output +dist/ +build/ +.next/ +out/ +*.tsbuildinfo + +# Environment +.env +.env.local +.env.*.local +!.env.example + +# Logs +*.log +npm-debug.log* +pnpm-debug.log* + +# OS / editor +.DS_Store +.idea/ +.vscode/* +!.vscode/extensions.json +!.vscode/settings.json + +# Test / coverage +coverage/ +.nyc_output/ + +# Drizzle +drizzle/meta/_journal.json.bak + +# Docker volumes (when bind-mounted locally) +.data/ +volumes/ + +# Misc +*.pid +.turbo/ diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..08fbad7 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,7 @@ +node_modules +dist +build +.next +coverage +pnpm-lock.yaml +*.tsbuildinfo diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..4cbc711 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "semi": true, + "singleQuote": true, + "trailingComma": "all", + "printWidth": 100, + "tabWidth": 2 +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..dc0d90c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,14 @@ +# Changelog + +All notable changes to this project will be documented here. Format follows +[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), versioning follows +[SemVer](https://semver.org/). + +## [Unreleased] + +### Added +- Initial monorepo skeleton: pnpm workspaces, Next.js 15 web app, Drizzle + + pgvector DB package, RAG primitives, Vercel AI SDK agent with `search_kb`, + `fetch_doc`, `list_sources` tools. +- Docker Compose stack for Postgres (pgvector) and Ollama. +- Spec docs under `spec/`, gitflow contributing guide, CI workflow. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..1fc68ee --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,90 @@ +# Contributing + +Thanks for your interest. This project follows a lightweight **gitflow** +model with conventional commits. + +## Branching model (gitflow) + +| Branch | Purpose | Lifetime | +| ------------------ | ---------------------------------------------------- | ----------- | +| `main` | Production-ready. Tagged releases live here. | Permanent | +| `develop` | Integration branch for the next release. | Permanent | +| `feature/` | New features. Branch from `develop`, PR into `develop`. | Short-lived | +| `fix/` | Non-urgent bug fixes. Same flow as features. | Short-lived | +| `release/` | Stabilize a release. Branch from `develop`, merge to both `main` and `develop`. Tag from `main`. | Short-lived | +| `hotfix/` | Urgent prod fix. Branch from `main`, merge to both `main` and `develop`. | Short-lived | + +### Typical feature flow +```bash +git checkout develop && git pull +git checkout -b feature/citations-ui +# ...commits... +git push -u origin feature/citations-ui +gh pr create --base develop --title "feat(web): render citations as cards" +``` + +### Release flow +```bash +git checkout develop && git pull +git checkout -b release/0.2.0 +# bump versions, finalize CHANGELOG, last-mile fixes +git checkout main && git merge --no-ff release/0.2.0 +git tag -a v0.2.0 -m "v0.2.0" +git checkout develop && git merge --no-ff release/0.2.0 +git push --all && git push --tags +``` + +### Hotfix flow +```bash +git checkout main && git pull +git checkout -b hotfix/embed-dim-crash +# fix + bump patch version +git checkout main && git merge --no-ff hotfix/embed-dim-crash && git tag v0.2.1 +git checkout develop && git merge --no-ff hotfix/embed-dim-crash +``` + +## Commit messages — Conventional Commits + +``` +(): + +[optional body] +[optional footer(s)] +``` + +Types: `feat`, `fix`, `chore`, `docs`, `refactor`, `perf`, `test`, `build`, `ci`. +Common scopes: `web`, `agent`, `rag`, `db`, `shared`, `infra`, `ci`. + +Examples: +- `feat(agent): add fetch_doc tool` +- `fix(rag): clamp chunk overlap to chunk size` +- `chore(infra): bump pgvector image to pg16` + +## Pull request checklist + +- [ ] Branched from `develop` (or `main` for hotfixes) +- [ ] `pnpm typecheck` passes +- [ ] `pnpm lint` passes +- [ ] `pnpm test` passes (when tests exist) +- [ ] Updated relevant `spec/` doc if behavior or contract changed +- [ ] Added a migration if schema changed (`pnpm --filter @app/db generate`) +- [ ] Manual smoke test described in PR body + +## Local dev + +See `README.md` § Quickstart. Copy `.env.example` to `.env`, run +`pnpm stack:up`, `pnpm ollama:pull`, `pnpm db:migrate`, then `pnpm dev`. + +## Code style + +- Prettier (`pnpm format`) — run before pushing. +- ESLint flat config at the root; per-package overrides allowed. +- TypeScript strict mode is non-negotiable; prefer `unknown` over `any`. +- Validate every external boundary with zod (HTTP bodies, env, tool inputs). + +## Reporting issues + +Use the issue template under `.github/ISSUE_TEMPLATE`. Include: +- What you ran, what you expected, what happened. +- Versions: Node, pnpm, Docker, OS. +- Relevant logs (`pnpm stack:logs`). diff --git a/README.md b/README.md new file mode 100644 index 0000000..b097729 --- /dev/null +++ b/README.md @@ -0,0 +1,135 @@ +# Agentic RAG + +A local-first, full-stack agentic RAG application. The agent decides when to +retrieve from a private knowledge base and cites its sources. + +**Stack:** Next.js 15 · TypeScript · Postgres + pgvector · Ollama (chat + +embeddings) · Vercel AI SDK · Drizzle ORM · Docker Compose · pnpm workspaces. + +> LangChain is intentionally not a dependency. The AI SDK + a thin RAG layer +> covers v1; we'll add LangChain/LangGraph only if branching agent workflows +> or long-tail document loaders justify it. See `spec/spec-1.0.0/00-overview.md`. + +--- + +## Repository layout + +``` +apps/ + web/ Next.js 15 app (UI + API routes) +packages/ + shared/ zod schemas, env loader, shared types + db/ Drizzle schema, client, migrations + rag/ chunker, embedder, ingest, retrieve + agent/ AI SDK agent loop + tools + prompts +docker/postgres/ DB init SQL (pgvector extension) +scripts/ dev helpers +spec/spec-1.0.0/ design docs for v1.0.0 (start at 00-overview.md) +``` + +--- + +## Quickstart + +### 1. Prerequisites +- Docker Desktop (or compatible) with Compose v2 +- Node.js ≥ 20.11 +- pnpm ≥ 10 (`corepack enable && corepack prepare pnpm@latest --activate`) + +### 2. Bootstrap + +```bash +git clone agentic-rag-app +cd agentic-rag-app +cp .env.example .env +pnpm install +``` + +### 3. Start the stack + +```bash +pnpm stack:up # postgres + ollama (via docker compose) +pnpm ollama:pull # pulls llama3.1:8b + nomic-embed-text into the container +pnpm db:migrate # apply Drizzle migrations (after first generate) +``` + +> Migrations: on a fresh repo run `pnpm --filter @app/db generate` once to +> produce the initial SQL under `packages/db/drizzle/`, commit it, then +> `pnpm db:migrate` to apply. + +### 4. Run the app + +```bash +pnpm dev # next dev on http://localhost:3000 +``` + +### 5. Ingest a document + +```bash +curl -X POST http://localhost:3000/api/ingest \ + -H 'content-type: application/json' \ + -d '{ + "source": "demo/readme", + "title": "Project README", + "content": "Agentic RAG is a local-first chat app that grounds answers in your own documents." + }' +``` + +Then ask the chat UI: *"What is Agentic RAG?"* — the agent should call +`search_kb` and cite `[demo/readme:Project README]`. + +--- + +## Common scripts + +| Command | What it does | +| ---------------------- | --------------------------------------------------- | +| `pnpm dev` | Run Next dev server | +| `pnpm build` | Build all workspace packages | +| `pnpm typecheck` | TS check across the workspace | +| `pnpm lint` | Lint all packages | +| `pnpm format` | Prettier write across the repo | +| `pnpm stack:up` | `docker compose up -d` (postgres + ollama) | +| `pnpm stack:down` | Tear down the stack | +| `pnpm db:migrate` | Apply Drizzle migrations | +| `pnpm db:studio` | Open drizzle-kit studio | +| `pnpm ollama:pull` | Pull configured chat + embedding models | + +--- + +## Environment variables + +See `.env.example` for the full list. Required at minimum: + +| Var | Default | +| -------------------- | ----------------------------------------- | +| `DATABASE_URL` | `postgres://rag:rag@localhost:5432/rag` | +| `OLLAMA_BASE_URL` | `http://localhost:11434` | +| `OLLAMA_CHAT_MODEL` | `llama3.1:8b` | +| `OLLAMA_EMBED_MODEL` | `nomic-embed-text` | +| `EMBEDDING_DIM` | `768` (must match the embed model) | + +--- + +## Documentation + +- `spec/spec-1.0.0/00-overview.md` — goals, non-goals, stack rationale +- `spec/spec-1.0.0/01-architecture.md` — component map and request lifecycle +- `spec/spec-1.0.0/02-data-model.md` — tables, indexes, embedding dimension policy +- `spec/spec-1.0.0/03-agent-design.md` — tools, prompt principles, guardrails +- `spec/spec-1.0.0/04-api.md` — HTTP endpoints +- `spec/spec-1.0.0/05-roadmap.md` — what's next + +Future versions get their own `spec/spec-/` directory; older specs are +preserved as historical record. + +--- + +## Contributing + +See `CONTRIBUTING.md` for the gitflow branching model, commit conventions, +and PR checklist. + +## License + +Apache-2.0 — see `LICENSE`. diff --git a/apps/web/.eslintrc.json b/apps/web/.eslintrc.json new file mode 100644 index 0000000..bffb357 --- /dev/null +++ b/apps/web/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile new file mode 100644 index 0000000..0cc09e2 --- /dev/null +++ b/apps/web/Dockerfile @@ -0,0 +1,37 @@ +# syntax=docker/dockerfile:1.7 +FROM node:20-alpine AS base +ENV PNPM_HOME=/pnpm +ENV PATH=$PNPM_HOME:$PATH +RUN corepack enable + +# ---- Deps ---- +FROM base AS deps +WORKDIR /repo +COPY package.json pnpm-workspace.yaml pnpm-lock.yaml* ./ +COPY apps/web/package.json apps/web/package.json +COPY packages/shared/package.json packages/shared/package.json +COPY packages/db/package.json packages/db/package.json +COPY packages/rag/package.json packages/rag/package.json +COPY packages/agent/package.json packages/agent/package.json +RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile=false + +# ---- Build ---- +FROM base AS builder +WORKDIR /repo +COPY --from=deps /repo/node_modules ./node_modules +COPY . . +RUN pnpm --filter @app/web... build + +# ---- Runtime ---- +FROM node:20-alpine AS runner +WORKDIR /app +ENV NODE_ENV=production +ENV PORT=3000 +ENV HOSTNAME=0.0.0.0 +RUN addgroup -S app && adduser -S app -G app +COPY --from=builder --chown=app:app /repo/apps/web/.next/standalone ./ +COPY --from=builder --chown=app:app /repo/apps/web/.next/static ./apps/web/.next/static +COPY --from=builder --chown=app:app /repo/apps/web/public ./apps/web/public +USER app +EXPOSE 3000 +CMD ["node", "apps/web/server.js"] diff --git a/apps/web/next-env.d.ts b/apps/web/next-env.d.ts new file mode 100644 index 0000000..6080add --- /dev/null +++ b/apps/web/next-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs new file mode 100644 index 0000000..126e552 --- /dev/null +++ b/apps/web/next.config.mjs @@ -0,0 +1,11 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + output: 'standalone', + experimental: { + // Workspace packages are bundled via transpilePackages. + }, + transpilePackages: ['@app/shared', '@app/db', '@app/rag', '@app/agent'], + serverExternalPackages: ['postgres'], +}; + +export default nextConfig; diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 0000000..ffdbca1 --- /dev/null +++ b/apps/web/package.json @@ -0,0 +1,31 @@ +{ + "name": "@app/web", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "next dev -p 3000", + "build": "next build", + "start": "next start -p 3000", + "lint": "next lint", + "typecheck": "tsc -p tsconfig.json --noEmit" + }, + "dependencies": { + "@app/agent": "workspace:*", + "@app/db": "workspace:*", + "@app/rag": "workspace:*", + "@app/shared": "workspace:*", + "ai": "catalog:", + "ollama-ai-provider": "catalog:", + "next": "^15.1.4", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "zod": "catalog:" + }, + "devDependencies": { + "@types/react": "^19.0.7", + "@types/react-dom": "^19.0.3", + "eslint-config-next": "^15.1.4", + "typescript": "catalog:" + } +} diff --git a/apps/web/src/app/api/chat/route.ts b/apps/web/src/app/api/chat/route.ts new file mode 100644 index 0000000..c54e1e2 --- /dev/null +++ b/apps/web/src/app/api/chat/route.ts @@ -0,0 +1,30 @@ +import { NextRequest } from 'next/server'; +import { z } from 'zod'; +import { runAgent } from '@app/agent'; +import { env } from '@/lib/env'; +import { embedder } from '@/lib/embedder'; + +export const runtime = 'nodejs'; +export const dynamic = 'force-dynamic'; + +const BodySchema = z.object({ + messages: z.array( + z.object({ + role: z.enum(['system', 'user', 'assistant']), + content: z.string(), + }), + ), +}); + +export async function POST(req: NextRequest) { + const body = BodySchema.parse(await req.json()); + + const result = runAgent({ + baseURL: env.OLLAMA_BASE_URL, + chatModel: env.OLLAMA_CHAT_MODEL, + messages: body.messages, + toolDeps: { embedder, defaultTopK: env.RAG_TOP_K }, + }); + + return result.toDataStreamResponse(); +} diff --git a/apps/web/src/app/api/ingest/route.ts b/apps/web/src/app/api/ingest/route.ts new file mode 100644 index 0000000..e82b5ee --- /dev/null +++ b/apps/web/src/app/api/ingest/route.ts @@ -0,0 +1,22 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { IngestRequestSchema } from '@app/shared'; +import { ingestDocument } from '@app/rag'; +import { env } from '@/lib/env'; +import { embedder } from '@/lib/embedder'; + +export const runtime = 'nodejs'; + +export async function POST(req: NextRequest) { + const parsed = IngestRequestSchema.safeParse(await req.json()); + if (!parsed.success) { + return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 }); + } + + const result = await ingestDocument(parsed.data, { + chunkSize: env.RAG_CHUNK_SIZE, + chunkOverlap: env.RAG_CHUNK_OVERLAP, + embedder, + }); + + return NextResponse.json(result, { status: 201 }); +} diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css new file mode 100644 index 0000000..9fa4a62 --- /dev/null +++ b/apps/web/src/app/globals.css @@ -0,0 +1,16 @@ +:root { + color-scheme: light dark; + font-family: system-ui, -apple-system, 'Segoe UI', sans-serif; +} + +* { + box-sizing: border-box; +} + +html, +body { + margin: 0; + padding: 0; + background: #0b0b0c; + color: #f5f5f5; +} diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx new file mode 100644 index 0000000..2fdb9ac --- /dev/null +++ b/apps/web/src/app/layout.tsx @@ -0,0 +1,15 @@ +import type { Metadata } from 'next'; +import './globals.css'; + +export const metadata: Metadata = { + title: 'Agentic RAG', + description: 'Agentic full-stack RAG over a local knowledge base.', +}; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx new file mode 100644 index 0000000..f2d9c4b --- /dev/null +++ b/apps/web/src/app/page.tsx @@ -0,0 +1,25 @@ +import { Chat } from '@/components/chat'; + +export default function HomePage() { + return ( +
+
+

Agentic RAG

+

+ Local-first knowledge base · Ollama + pgvector + Vercel AI SDK +

+
+ +
+ ); +} diff --git a/apps/web/src/components/chat.tsx b/apps/web/src/components/chat.tsx new file mode 100644 index 0000000..65afbe7 --- /dev/null +++ b/apps/web/src/components/chat.tsx @@ -0,0 +1,81 @@ +'use client'; + +import { useChat } from 'ai/react'; + +export function Chat() { + const { messages, input, handleInputChange, handleSubmit, isLoading, error } = useChat({ + api: '/api/chat', + }); + + return ( +
+
+ {messages.length === 0 && ( +

+ Ask a question. The agent will search the knowledge base via tool calls. +

+ )} + {messages.map((m) => ( +
+ + {m.role}: + {' '} + {m.content} +
+ ))} + {error &&
Error: {error.message}
} +
+ +
+ + +
+
+ ); +} diff --git a/apps/web/src/lib/embedder.ts b/apps/web/src/lib/embedder.ts new file mode 100644 index 0000000..6adb942 --- /dev/null +++ b/apps/web/src/lib/embedder.ts @@ -0,0 +1,7 @@ +import { createEmbedder } from '@app/rag'; +import { env } from './env.js'; + +export const embedder = createEmbedder({ + baseURL: env.OLLAMA_BASE_URL, + model: env.OLLAMA_EMBED_MODEL, +}); diff --git a/apps/web/src/lib/env.ts b/apps/web/src/lib/env.ts new file mode 100644 index 0000000..fab485a --- /dev/null +++ b/apps/web/src/lib/env.ts @@ -0,0 +1,3 @@ +import { loadEnv } from '@app/shared'; + +export const env = loadEnv(); diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json new file mode 100644 index 0000000..e7498c9 --- /dev/null +++ b/apps/web/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "target": "ES2022", + "lib": ["dom", "dom.iterable", "ES2022"], + "jsx": "preserve", + "module": "ESNext", + "moduleResolution": "Bundler", + "allowJs": true, + "noEmit": true, + "incremental": true, + "plugins": [{ "name": "next" }], + "paths": { + "@/*": ["./src/*"] + } + }, + "references": [ + { "path": "../../packages/shared" }, + { "path": "../../packages/db" }, + { "path": "../../packages/rag" }, + { "path": "../../packages/agent" } + ], + "include": ["next-env.d.ts", "src/**/*", ".next/types/**/*.ts"], + "exclude": ["node_modules", ".next"] +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..93275dd --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,59 @@ +services: + postgres: + image: pgvector/pgvector:pg16 + container_name: rag-postgres + restart: unless-stopped + environment: + POSTGRES_USER: ${POSTGRES_USER:-rag} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-rag} + POSTGRES_DB: ${POSTGRES_DB:-rag} + ports: + - "${POSTGRES_PORT:-5432}:5432" + volumes: + - pgdata:/var/lib/postgresql/data + - ./docker/postgres/init.sql:/docker-entrypoint-initdb.d/00-init.sql:ro + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-rag} -d ${POSTGRES_DB:-rag}"] + interval: 5s + timeout: 5s + retries: 10 + + ollama: + image: ollama/ollama:latest + container_name: rag-ollama + restart: unless-stopped + ports: + - "11434:11434" + volumes: + - ollama:/root/.ollama + healthcheck: + test: ["CMD", "ollama", "list"] + interval: 10s + timeout: 5s + retries: 10 + + web: + build: + context: . + dockerfile: apps/web/Dockerfile + container_name: rag-web + profiles: ["app"] + restart: unless-stopped + depends_on: + postgres: + condition: service_healthy + ollama: + condition: service_started + environment: + DATABASE_URL: postgres://${POSTGRES_USER:-rag}:${POSTGRES_PASSWORD:-rag}@postgres:5432/${POSTGRES_DB:-rag} + OLLAMA_BASE_URL: http://ollama:11434 + OLLAMA_CHAT_MODEL: ${OLLAMA_CHAT_MODEL:-llama3.1:8b} + OLLAMA_EMBED_MODEL: ${OLLAMA_EMBED_MODEL:-nomic-embed-text} + EMBEDDING_DIM: ${EMBEDDING_DIM:-768} + NODE_ENV: production + ports: + - "3000:3000" + +volumes: + pgdata: + ollama: diff --git a/docker/postgres/init.sql b/docker/postgres/init.sql new file mode 100644 index 0000000..1a7da1a --- /dev/null +++ b/docker/postgres/init.sql @@ -0,0 +1,3 @@ +-- Enable pgvector extension on database init. +CREATE EXTENSION IF NOT EXISTS vector; +CREATE EXTENSION IF NOT EXISTS pg_trgm; diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..e6c5909 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,21 @@ +// Flat ESLint config shared across the workspace. +// Each package can extend this from its own eslint.config.mjs. +import js from '@eslint/js'; +import tseslint from 'typescript-eslint'; + +export default [ + { + ignores: ['**/node_modules/**', '**/dist/**', '**/.next/**', '**/build/**'], + }, + js.configs.recommended, + ...tseslint.configs.recommended, + { + rules: { + '@typescript-eslint/no-unused-vars': [ + 'warn', + { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, + ], + '@typescript-eslint/consistent-type-imports': 'warn', + }, + }, +]; diff --git a/package.json b/package.json new file mode 100644 index 0000000..f846d77 --- /dev/null +++ b/package.json @@ -0,0 +1,34 @@ +{ + "name": "agentic-rag-app", + "version": "0.1.0", + "private": true, + "description": "Agentic full-stack RAG application using Next.js, Postgres+pgvector, Ollama, and the Vercel AI SDK.", + "license": "Apache-2.0", + "packageManager": "pnpm@10.28.2", + "engines": { + "node": ">=20.11.0", + "pnpm": ">=10" + }, + "scripts": { + "dev": "pnpm --filter @app/web dev", + "build": "pnpm -r build", + "lint": "pnpm -r lint", + "typecheck": "pnpm -r typecheck", + "test": "pnpm -r test", + "format": "prettier --write \"**/*.{ts,tsx,js,json,md}\"", + "format:check": "prettier --check \"**/*.{ts,tsx,js,json,md}\"", + "db:up": "docker compose up -d postgres", + "db:down": "docker compose down", + "db:migrate": "pnpm --filter @app/db migrate", + "db:studio": "pnpm --filter @app/db studio", + "ollama:pull": "bash scripts/ollama-pull.sh", + "stack:up": "docker compose up -d", + "stack:down": "docker compose down", + "stack:logs": "docker compose logs -f" + }, + "devDependencies": { + "@types/node": "^22.10.5", + "prettier": "^3.4.2", + "typescript": "^5.7.3" + } +} diff --git a/packages/agent/package.json b/packages/agent/package.json new file mode 100644 index 0000000..a8ac9ab --- /dev/null +++ b/packages/agent/package.json @@ -0,0 +1,28 @@ +{ + "name": "@app/agent", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", + "exports": { + ".": "./src/index.ts" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "typecheck": "tsc -p tsconfig.json --noEmit", + "lint": "eslint src" + }, + "dependencies": { + "@app/db": "workspace:*", + "@app/rag": "workspace:*", + "@app/shared": "workspace:*", + "ai": "catalog:", + "ollama-ai-provider": "catalog:", + "drizzle-orm": "catalog:", + "zod": "catalog:" + }, + "devDependencies": { + "typescript": "catalog:" + } +} diff --git a/packages/agent/src/agent.ts b/packages/agent/src/agent.ts new file mode 100644 index 0000000..f6b2272 --- /dev/null +++ b/packages/agent/src/agent.ts @@ -0,0 +1,27 @@ +import { streamText, type CoreMessage } from 'ai'; +import { createOllama } from 'ollama-ai-provider'; +import { buildTools, type ToolDeps } from './tools.js'; +import { SYSTEM_PROMPT } from './prompts.js'; + +export interface RunAgentOptions { + baseURL: string; + chatModel: string; + messages: CoreMessage[]; + toolDeps: ToolDeps; + /** Cap on tool-use rounds to prevent runaway loops. */ + maxSteps?: number; +} + +export function runAgent(opts: RunAgentOptions) { + const ollama = createOllama({ baseURL: opts.baseURL }); + const tools = buildTools(opts.toolDeps); + + return streamText({ + model: ollama(opts.chatModel), + system: SYSTEM_PROMPT, + messages: opts.messages, + tools, + maxSteps: opts.maxSteps ?? 5, + temperature: 0.2, + }); +} diff --git a/packages/agent/src/index.ts b/packages/agent/src/index.ts new file mode 100644 index 0000000..002b571 --- /dev/null +++ b/packages/agent/src/index.ts @@ -0,0 +1,3 @@ +export * from './tools.js'; +export * from './agent.js'; +export * from './prompts.js'; diff --git a/packages/agent/src/prompts.ts b/packages/agent/src/prompts.ts new file mode 100644 index 0000000..ebd2bac --- /dev/null +++ b/packages/agent/src/prompts.ts @@ -0,0 +1,10 @@ +export const SYSTEM_PROMPT = `You are an agentic research assistant. + +You have access to a knowledge base via tools. For any non-trivial question: +1. Use \`search_kb\` to retrieve relevant chunks before answering. +2. If you need more context for a hit, call \`fetch_doc\` to read the full document. +3. If the user asks "what do you know about X", call \`list_sources\` first. +4. Cite sources inline as [source:title] after each claim that depends on retrieval. +5. If retrieval returns nothing useful, say so plainly — do not invent facts. + +Keep answers concise. Prefer bullet points for multi-fact responses.`; diff --git a/packages/agent/src/tools.ts b/packages/agent/src/tools.ts new file mode 100644 index 0000000..a9a3f08 --- /dev/null +++ b/packages/agent/src/tools.ts @@ -0,0 +1,86 @@ +import { tool } from 'ai'; +import { z } from 'zod'; +import { eq, sql } from 'drizzle-orm'; +import { getDb, schema } from '@app/db'; +import { retrieve, type Embedder } from '@app/rag'; + +export interface ToolDeps { + embedder: Embedder; + defaultTopK: number; +} + +export function buildTools(deps: ToolDeps) { + return { + search_kb: tool({ + description: + 'Semantic search over the knowledge base. Returns top-k chunks with source and similarity score.', + parameters: z.object({ + query: z.string().min(1).describe('Natural-language search query.'), + topK: z.number().int().min(1).max(20).optional(), + sourceFilter: z + .string() + .optional() + .describe('Optional substring filter on document.source.'), + }), + execute: async ({ query, topK, sourceFilter }) => { + const results = await retrieve(query, { + topK: topK ?? deps.defaultTopK, + embedder: deps.embedder, + sourceFilter, + }); + return { results }; + }, + }), + + fetch_doc: tool({ + description: 'Fetch full content of a document by ID (concatenated chunks in order).', + parameters: z.object({ + documentId: z.string().uuid(), + }), + execute: async ({ documentId }) => { + const db = getDb(); + const [doc] = await db + .select() + .from(schema.documents) + .where(eq(schema.documents.id, documentId)) + .limit(1); + if (!doc) return { error: 'not_found' as const }; + const rows = await db + .select({ ordinal: schema.chunks.ordinal, content: schema.chunks.content }) + .from(schema.chunks) + .where(eq(schema.chunks.documentId, documentId)) + .orderBy(schema.chunks.ordinal); + return { + id: doc.id, + source: doc.source, + title: doc.title, + content: rows.map((r) => r.content).join('\n\n'), + }; + }, + }), + + list_sources: tool({ + description: 'List distinct document sources in the knowledge base with chunk counts.', + parameters: z.object({ + limit: z.number().int().min(1).max(200).optional(), + }), + execute: async ({ limit }) => { + const db = getDb(); + const rows = await db.execute<{ source: string; document_count: number; chunk_count: number }>(sql` + SELECT + d.source AS source, + COUNT(DISTINCT d.id)::int AS document_count, + COUNT(c.id)::int AS chunk_count + FROM ${schema.documents} d + LEFT JOIN ${schema.chunks} c ON c.document_id = d.id + GROUP BY d.source + ORDER BY chunk_count DESC + LIMIT ${limit ?? 50} + `); + return { sources: rows }; + }, + }), + }; +} + +export type AgentTools = ReturnType; diff --git a/packages/agent/tsconfig.json b/packages/agent/tsconfig.json new file mode 100644 index 0000000..a53af91 --- /dev/null +++ b/packages/agent/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "composite": true + }, + "references": [ + { "path": "../shared" }, + { "path": "../db" }, + { "path": "../rag" } + ], + "include": ["src/**/*"] +} diff --git a/packages/db/drizzle.config.ts b/packages/db/drizzle.config.ts new file mode 100644 index 0000000..17a349a --- /dev/null +++ b/packages/db/drizzle.config.ts @@ -0,0 +1,13 @@ +import 'dotenv/config'; +import type { Config } from 'drizzle-kit'; + +export default { + schema: './src/schema.ts', + out: './drizzle', + dialect: 'postgresql', + dbCredentials: { + url: process.env.DATABASE_URL ?? 'postgres://rag:rag@localhost:5432/rag', + }, + strict: true, + verbose: true, +} satisfies Config; diff --git a/packages/db/package.json b/packages/db/package.json new file mode 100644 index 0000000..b9141c5 --- /dev/null +++ b/packages/db/package.json @@ -0,0 +1,30 @@ +{ + "name": "@app/db", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", + "exports": { + ".": "./src/index.ts", + "./schema": "./src/schema.ts" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "typecheck": "tsc -p tsconfig.json --noEmit", + "lint": "eslint src", + "generate": "drizzle-kit generate", + "migrate": "tsx src/migrate.ts", + "studio": "drizzle-kit studio" + }, + "dependencies": { + "@app/shared": "workspace:*", + "drizzle-orm": "catalog:", + "postgres": "catalog:" + }, + "devDependencies": { + "drizzle-kit": "catalog:", + "tsx": "^4.19.2", + "typescript": "catalog:" + } +} diff --git a/packages/db/src/client.ts b/packages/db/src/client.ts new file mode 100644 index 0000000..9d7ede4 --- /dev/null +++ b/packages/db/src/client.ts @@ -0,0 +1,27 @@ +import { drizzle } from 'drizzle-orm/postgres-js'; +import postgres from 'postgres'; +import * as schema from './schema.js'; + +let _client: ReturnType | null = null; +let _db: ReturnType> | null = null; + +export function getDb(connectionString = process.env.DATABASE_URL) { + if (!connectionString) { + throw new Error('DATABASE_URL is not set.'); + } + if (!_db) { + _client = postgres(connectionString, { max: 10, prepare: false }); + _db = drizzle(_client, { schema }); + } + return _db; +} + +export async function closeDb() { + if (_client) { + await _client.end({ timeout: 5 }); + _client = null; + _db = null; + } +} + +export { schema }; diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts new file mode 100644 index 0000000..e76ae78 --- /dev/null +++ b/packages/db/src/index.ts @@ -0,0 +1,2 @@ +export * from './schema.js'; +export * from './client.js'; diff --git a/packages/db/src/migrate.ts b/packages/db/src/migrate.ts new file mode 100644 index 0000000..0478a13 --- /dev/null +++ b/packages/db/src/migrate.ts @@ -0,0 +1,15 @@ +import 'dotenv/config'; +import { migrate } from 'drizzle-orm/postgres-js/migrator'; +import { getDb, closeDb } from './client.js'; + +async function main() { + const db = getDb(); + await migrate(db, { migrationsFolder: './drizzle' }); + console.log('Migrations applied.'); + await closeDb(); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/packages/db/src/schema.ts b/packages/db/src/schema.ts new file mode 100644 index 0000000..d8df32d --- /dev/null +++ b/packages/db/src/schema.ts @@ -0,0 +1,59 @@ +import { sql } from 'drizzle-orm'; +import { + pgTable, + uuid, + text, + jsonb, + integer, + timestamp, + index, + vector, +} from 'drizzle-orm/pg-core'; + +const EMBEDDING_DIM = Number(process.env.EMBEDDING_DIM ?? 768); + +export const documents = pgTable( + 'documents', + { + id: uuid('id').primaryKey().defaultRandom(), + source: text('source').notNull(), + title: text('title'), + metadata: jsonb('metadata').$type>().notNull().default({}), + createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), + }, + (t) => ({ + sourceIdx: index('documents_source_idx').on(t.source), + }), +); + +export const chunks = pgTable( + 'chunks', + { + id: uuid('id').primaryKey().defaultRandom(), + documentId: uuid('document_id') + .notNull() + .references(() => documents.id, { onDelete: 'cascade' }), + ordinal: integer('ordinal').notNull(), + content: text('content').notNull(), + tokens: integer('tokens'), + embedding: vector('embedding', { dimensions: EMBEDDING_DIM }), + metadata: jsonb('metadata').$type>().notNull().default({}), + createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), + }, + (t) => ({ + docOrdinalIdx: index('chunks_doc_ordinal_idx').on(t.documentId, t.ordinal), + // HNSW index using cosine distance for ANN retrieval. + embeddingIdx: index('chunks_embedding_idx') + .using('hnsw', t.embedding.op('vector_cosine_ops')) + .with({ m: 16, ef_construction: 64 }), + contentTrgmIdx: index('chunks_content_trgm_idx').using( + 'gin', + sql`${t.content} gin_trgm_ops`, + ), + }), +); + +export type DocumentRow = typeof documents.$inferSelect; +export type NewDocumentRow = typeof documents.$inferInsert; +export type ChunkRow = typeof chunks.$inferSelect; +export type NewChunkRow = typeof chunks.$inferInsert; diff --git a/packages/db/tsconfig.json b/packages/db/tsconfig.json new file mode 100644 index 0000000..1ae2645 --- /dev/null +++ b/packages/db/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "composite": true + }, + "references": [{ "path": "../shared" }], + "include": ["src/**/*"] +} diff --git a/packages/rag/package.json b/packages/rag/package.json new file mode 100644 index 0000000..57b9211 --- /dev/null +++ b/packages/rag/package.json @@ -0,0 +1,27 @@ +{ + "name": "@app/rag", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", + "exports": { + ".": "./src/index.ts" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "typecheck": "tsc -p tsconfig.json --noEmit", + "lint": "eslint src" + }, + "dependencies": { + "@app/db": "workspace:*", + "@app/shared": "workspace:*", + "drizzle-orm": "catalog:", + "ollama-ai-provider": "catalog:", + "ai": "catalog:", + "zod": "catalog:" + }, + "devDependencies": { + "typescript": "catalog:" + } +} diff --git a/packages/rag/src/chunker.ts b/packages/rag/src/chunker.ts new file mode 100644 index 0000000..44864ae --- /dev/null +++ b/packages/rag/src/chunker.ts @@ -0,0 +1,61 @@ +export interface ChunkOptions { + chunkSize: number; + chunkOverlap: number; +} + +/** + * Recursive character splitter. Walks separators from coarse (paragraph) to + * fine (word) and falls back to hard slicing only when a single span is bigger + * than chunkSize. Overlap is applied between adjacent emitted chunks. + */ +export function chunkText(input: string, opts: ChunkOptions): string[] { + const { chunkSize, chunkOverlap } = opts; + if (chunkOverlap >= chunkSize) { + throw new Error('chunkOverlap must be smaller than chunkSize'); + } + const separators = ['\n\n', '\n', '. ', ' ', '']; + const pieces = splitRecursive(input.trim(), separators, chunkSize); + return mergeWithOverlap(pieces, chunkSize, chunkOverlap); +} + +function splitRecursive(text: string, seps: string[], maxLen: number): string[] { + if (text.length <= maxLen) return [text]; + const [sep, ...rest] = seps; + if (sep === undefined) return [text]; + if (sep === '') { + const out: string[] = []; + for (let i = 0; i < text.length; i += maxLen) out.push(text.slice(i, i + maxLen)); + return out; + } + const parts = text.split(sep); + const out: string[] = []; + for (const part of parts) { + if (part.length === 0) continue; + if (part.length <= maxLen) { + out.push(part); + } else { + out.push(...splitRecursive(part, rest, maxLen)); + } + } + return out; +} + +function mergeWithOverlap(parts: string[], maxLen: number, overlap: number): string[] { + const chunks: string[] = []; + let buf = ''; + for (const part of parts) { + if (buf.length === 0) { + buf = part; + continue; + } + if (buf.length + 1 + part.length <= maxLen) { + buf = `${buf} ${part}`; + } else { + chunks.push(buf); + const tail = buf.slice(Math.max(0, buf.length - overlap)); + buf = tail.length > 0 ? `${tail} ${part}` : part; + } + } + if (buf.length > 0) chunks.push(buf); + return chunks; +} diff --git a/packages/rag/src/embeddings.ts b/packages/rag/src/embeddings.ts new file mode 100644 index 0000000..cc63806 --- /dev/null +++ b/packages/rag/src/embeddings.ts @@ -0,0 +1,26 @@ +import { createOllama } from 'ollama-ai-provider'; +import { embed, embedMany } from 'ai'; + +export interface EmbeddingClientOptions { + baseURL: string; + model: string; +} + +export function createEmbedder(opts: EmbeddingClientOptions) { + const ollama = createOllama({ baseURL: opts.baseURL }); + const model = ollama.embedding(opts.model); + + return { + async embedOne(text: string): Promise { + const { embedding } = await embed({ model, value: text }); + return embedding; + }, + async embedBatch(values: string[]): Promise { + if (values.length === 0) return []; + const { embeddings } = await embedMany({ model, values }); + return embeddings; + }, + }; +} + +export type Embedder = ReturnType; diff --git a/packages/rag/src/index.ts b/packages/rag/src/index.ts new file mode 100644 index 0000000..48d7350 --- /dev/null +++ b/packages/rag/src/index.ts @@ -0,0 +1,4 @@ +export * from './chunker.js'; +export * from './embeddings.js'; +export * from './ingest.js'; +export * from './retrieve.js'; diff --git a/packages/rag/src/ingest.ts b/packages/rag/src/ingest.ts new file mode 100644 index 0000000..393a56c --- /dev/null +++ b/packages/rag/src/ingest.ts @@ -0,0 +1,57 @@ +import { getDb, schema } from '@app/db'; +import type { IngestRequest } from '@app/shared'; +import { chunkText } from './chunker.js'; +import type { Embedder } from './embeddings.js'; + +export interface IngestOptions { + chunkSize: number; + chunkOverlap: number; + embedder: Embedder; +} + +export interface IngestResult { + documentId: string; + chunkCount: number; +} + +export async function ingestDocument( + req: IngestRequest, + opts: IngestOptions, +): Promise { + const db = getDb(); + const pieces = chunkText(req.content, { + chunkSize: opts.chunkSize, + chunkOverlap: opts.chunkOverlap, + }); + if (pieces.length === 0) { + throw new Error('Document produced zero chunks.'); + } + + const embeddings = await opts.embedder.embedBatch(pieces); + + return await db.transaction(async (tx) => { + const [doc] = await tx + .insert(schema.documents) + .values({ + source: req.source, + title: req.title ?? null, + metadata: req.metadata ?? {}, + }) + .returning({ id: schema.documents.id }); + + if (!doc) throw new Error('Failed to insert document.'); + + await tx.insert(schema.chunks).values( + pieces.map((content, i) => ({ + documentId: doc.id, + ordinal: i, + content, + tokens: null, + embedding: embeddings[i] ?? null, + metadata: {}, + })), + ); + + return { documentId: doc.id, chunkCount: pieces.length }; + }); +} diff --git a/packages/rag/src/retrieve.ts b/packages/rag/src/retrieve.ts new file mode 100644 index 0000000..2ac8653 --- /dev/null +++ b/packages/rag/src/retrieve.ts @@ -0,0 +1,56 @@ +import { sql } from 'drizzle-orm'; +import { getDb, schema } from '@app/db'; +import type { RetrievalResult } from '@app/shared'; +import type { Embedder } from './embeddings.js'; + +export interface RetrieveOptions { + topK: number; + embedder: Embedder; + /** Optional source filter (substring match on documents.source). */ + sourceFilter?: string; +} + +/** + * Cosine-distance ANN search over chunks.embedding (HNSW index). + * Returns top-k chunks joined with their parent document metadata. + */ +export async function retrieve( + query: string, + opts: RetrieveOptions, +): Promise { + const db = getDb(); + const queryVec = await opts.embedder.embedOne(query); + const vecLiteral = `[${queryVec.join(',')}]`; + + const rows = await db.execute<{ + chunk_id: string; + document_id: string; + content: string; + score: number; + source: string; + title: string | null; + }>(sql` + SELECT + c.id AS chunk_id, + c.document_id AS document_id, + c.content AS content, + 1 - (c.embedding <=> ${vecLiteral}::vector) AS score, + d.source AS source, + d.title AS title + FROM ${schema.chunks} c + JOIN ${schema.documents} d ON d.id = c.document_id + WHERE c.embedding IS NOT NULL + ${opts.sourceFilter ? sql`AND d.source ILIKE ${'%' + opts.sourceFilter + '%'}` : sql``} + ORDER BY c.embedding <=> ${vecLiteral}::vector + LIMIT ${opts.topK} + `); + + return rows.map((r) => ({ + chunkId: r.chunk_id, + documentId: r.document_id, + content: r.content, + score: Number(r.score), + source: r.source, + title: r.title, + })); +} diff --git a/packages/rag/tsconfig.json b/packages/rag/tsconfig.json new file mode 100644 index 0000000..b00404c --- /dev/null +++ b/packages/rag/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "composite": true + }, + "references": [{ "path": "../shared" }, { "path": "../db" }], + "include": ["src/**/*"] +} diff --git a/packages/shared/package.json b/packages/shared/package.json new file mode 100644 index 0000000..63d59f5 --- /dev/null +++ b/packages/shared/package.json @@ -0,0 +1,22 @@ +{ + "name": "@app/shared", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", + "exports": { + ".": "./src/index.ts" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "typecheck": "tsc -p tsconfig.json --noEmit", + "lint": "eslint src" + }, + "dependencies": { + "zod": "catalog:" + }, + "devDependencies": { + "typescript": "catalog:" + } +} diff --git a/packages/shared/src/env.ts b/packages/shared/src/env.ts new file mode 100644 index 0000000..c106cfd --- /dev/null +++ b/packages/shared/src/env.ts @@ -0,0 +1,26 @@ +import { z } from 'zod'; + +const EnvSchema = z.object({ + DATABASE_URL: z.string().url(), + OLLAMA_BASE_URL: z.string().url().default('http://localhost:11434'), + OLLAMA_CHAT_MODEL: z.string().default('llama3.1:8b'), + OLLAMA_EMBED_MODEL: z.string().default('nomic-embed-text'), + EMBEDDING_DIM: z.coerce.number().int().positive().default(768), + RAG_CHUNK_SIZE: z.coerce.number().int().positive().default(800), + RAG_CHUNK_OVERLAP: z.coerce.number().int().nonnegative().default(120), + RAG_TOP_K: z.coerce.number().int().positive().default(6), + NODE_ENV: z.enum(['development', 'test', 'production']).default('development'), +}); + +export type Env = z.infer; + +let cached: Env | null = null; +export function loadEnv(source: NodeJS.ProcessEnv = process.env): Env { + if (cached) return cached; + const parsed = EnvSchema.safeParse(source); + if (!parsed.success) { + throw new Error(`Invalid environment: ${parsed.error.message}`); + } + cached = parsed.data; + return cached; +} diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts new file mode 100644 index 0000000..5086eb1 --- /dev/null +++ b/packages/shared/src/index.ts @@ -0,0 +1,2 @@ +export * from './schemas.js'; +export * from './env.js'; diff --git a/packages/shared/src/schemas.ts b/packages/shared/src/schemas.ts new file mode 100644 index 0000000..841bef1 --- /dev/null +++ b/packages/shared/src/schemas.ts @@ -0,0 +1,44 @@ +import { z } from 'zod'; + +export const DocumentSchema = z.object({ + id: z.string().uuid(), + source: z.string().min(1), + title: z.string().nullable(), + metadata: z.record(z.unknown()).default({}), + createdAt: z.coerce.date(), +}); +export type Document = z.infer; + +export const ChunkSchema = z.object({ + id: z.string().uuid(), + documentId: z.string().uuid(), + ordinal: z.number().int().nonnegative(), + content: z.string().min(1), + tokens: z.number().int().positive().nullable(), + metadata: z.record(z.unknown()).default({}), +}); +export type Chunk = z.infer; + +export const RetrievalResultSchema = z.object({ + chunkId: z.string().uuid(), + documentId: z.string().uuid(), + content: z.string(), + score: z.number(), + source: z.string(), + title: z.string().nullable(), +}); +export type RetrievalResult = z.infer; + +export const IngestRequestSchema = z.object({ + source: z.string().min(1), + title: z.string().optional(), + content: z.string().min(1), + metadata: z.record(z.unknown()).optional(), +}); +export type IngestRequest = z.infer; + +export const ChatMessageSchema = z.object({ + role: z.enum(['system', 'user', 'assistant', 'tool']), + content: z.string(), +}); +export type ChatMessage = z.infer; diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json new file mode 100644 index 0000000..c49dd21 --- /dev/null +++ b/packages/shared/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "composite": true + }, + "include": ["src/**/*"] +} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..5934c0b --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,13 @@ +packages: + - "apps/*" + - "packages/*" + +# Catalog pins shared dependency versions across the workspace. +catalog: + typescript: ^5.7.3 + zod: ^3.24.1 + ai: ^4.0.30 + ollama-ai-provider: ^1.2.0 + drizzle-orm: ^0.38.3 + drizzle-kit: ^0.30.1 + postgres: ^3.4.5 diff --git a/scripts/ollama-pull.sh b/scripts/ollama-pull.sh new file mode 100755 index 0000000..371b557 --- /dev/null +++ b/scripts/ollama-pull.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# Pull the chat + embedding models into the running Ollama container. +# Usage: pnpm ollama:pull +set -euo pipefail + +CONTAINER="${OLLAMA_CONTAINER:-rag-ollama}" +CHAT_MODEL="${OLLAMA_CHAT_MODEL:-llama3.1:8b}" +EMBED_MODEL="${OLLAMA_EMBED_MODEL:-nomic-embed-text}" + +if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER}$"; then + echo "Ollama container '${CONTAINER}' is not running. Run: pnpm stack:up" >&2 + exit 1 +fi + +echo "Pulling chat model: ${CHAT_MODEL}" +docker exec -it "${CONTAINER}" ollama pull "${CHAT_MODEL}" + +echo "Pulling embedding model: ${EMBED_MODEL}" +docker exec -it "${CONTAINER}" ollama pull "${EMBED_MODEL}" + +echo "Done. Models available:" +docker exec -it "${CONTAINER}" ollama list diff --git a/spec/spec-1.0.0/00-overview.md b/spec/spec-1.0.0/00-overview.md new file mode 100644 index 0000000..21fd196 --- /dev/null +++ b/spec/spec-1.0.0/00-overview.md @@ -0,0 +1,43 @@ +# Agentic RAG — Project Overview + +## Goal +Build a local-first agentic RAG application: a Next.js chat UI backed by an +LLM agent that retrieves grounded answers from a private knowledge base. The +stack runs end-to-end on a single developer machine via Docker. + +## Non-goals (v1) +- Multi-tenant auth / orgs +- Cloud-hosted LLM providers (OpenAI, Anthropic) — hooks left open, not wired +- Realtime collaboration +- Fine-tuning models + +## Stack +| Layer | Choice | Why | +| ---------------- | ------------------------------------------- | ----------------------------------------- | +| Framework | Next.js 15 (App Router, Server Components) | Streaming, edge-ready, single deploy unit | +| Language | TypeScript (strict) | Shared types across server/client/agent | +| LLM runtime | Ollama (chat + embeddings) | Local, no API keys, GPU/CPU friendly | +| Agent / SDK | Vercel AI SDK (`ai` + `ollama-ai-provider`) | First-class streaming, tools, multi-step | +| Vector store | Postgres + pgvector + HNSW | Single DB, ANN, transactional ingest | +| ORM / migrations | Drizzle ORM + drizzle-kit | Type-safe, SQL-first, plays well with pg | +| Container | Docker Compose | Reproducible local stack | +| Pkg manager | pnpm workspaces (catalog versions) | Fast, deterministic, monorepo-native | + +## When LangChain? +LangChain is intentionally **not** a dependency in v1. Drop it in only if/when +we need: production-grade document loaders for many formats, prebuilt agent +patterns we don't want to maintain, or graph orchestration (LangGraph). For +chunking + embed + retrieve + tool-calling, the AI SDK + a thin RAG layer is +sufficient and has lower lock-in. + +## High-level flow +1. **Ingest**: `POST /api/ingest` → chunk → embed (Ollama) → write `documents` + + `chunks` (with vector) in a single transaction. +2. **Chat**: `POST /api/chat` → AI SDK `streamText` with tools + (`search_kb`, `fetch_doc`, `list_sources`) → model decides when to retrieve + → response streams back to the UI. +3. **Retrieve**: cosine ANN over `chunks.embedding` (HNSW index), joined to + `documents` for source attribution. + +See `01-architecture.md` for component diagrams and `02-data-model.md` +for the schema. diff --git a/spec/spec-1.0.0/01-architecture.md b/spec/spec-1.0.0/01-architecture.md new file mode 100644 index 0000000..8ce6edb --- /dev/null +++ b/spec/spec-1.0.0/01-architecture.md @@ -0,0 +1,76 @@ +# Architecture + +## Monorepo layout +``` +apps/ + web/ Next.js 15 app (UI + API routes) +packages/ + shared/ zod schemas, env loader, shared types + db/ Drizzle schema, client, migrate runner + rag/ chunker, embedder, ingest, retrieve + agent/ AI SDK agent loop + tools + prompts +docker/postgres/ DB init SQL (CREATE EXTENSION vector) +scripts/ dev helpers (ollama-pull.sh) +spec/ design docs +``` + +Dependency direction (no cycles): +``` +shared ◄── db ◄── rag ◄── agent ◄── web + │ + └────────────┘ (web also imports rag/db/shared directly) +``` + +## Components + +### `packages/shared` +- `schemas.ts`: zod schemas + inferred types for `Document`, `Chunk`, + `RetrievalResult`, `IngestRequest`, `ChatMessage`. +- `env.ts`: validated env loader, single source of truth for config. + +### `packages/db` +- `schema.ts`: Drizzle table defs for `documents`, `chunks`. `chunks.embedding` + is `vector(EMBEDDING_DIM)`. HNSW index with `vector_cosine_ops`. +- `client.ts`: lazy singleton `postgres-js` connection. +- `migrate.ts`: applies `./drizzle` migrations on boot/CI. + +### `packages/rag` +- `chunker.ts`: recursive character splitter with overlap. +- `embeddings.ts`: thin wrapper over `ollama-ai-provider` + AI SDK + `embed`/`embedMany`. +- `ingest.ts`: `chunk → embed → INSERT documents+chunks` in one tx. +- `retrieve.ts`: cosine ANN search returning top-k with source metadata. + +### `packages/agent` +- `tools.ts`: AI SDK tools — `search_kb`, `fetch_doc`, `list_sources`. +- `prompts.ts`: system prompt enforcing tool-first behavior + citations. +- `agent.ts`: `streamText` with tools, `maxSteps` cap on tool-use rounds. + +### `apps/web` +- `app/api/chat/route.ts`: POST → `runAgent(...)` → `toDataStreamResponse()`. +- `app/api/ingest/route.ts`: POST → `ingestDocument(...)`. +- `components/chat.tsx`: client component using `useChat` from `ai/react`. + +## Request lifecycle (chat) +``` +client (useChat) + ─POST /api/chat──► Next route (Node runtime) + ├─ build embedder (Ollama) + ├─ runAgent → streamText + │ ├─ model decides: tool call? + │ ├─ search_kb → retrieve() → pgvector ANN + │ ├─ (loop up to maxSteps) + │ └─ final assistant message + └─ toDataStreamResponse() ──► client stream +``` + +## Why pgvector + HNSW (not a separate vector DB) +- Single source of truth, transactional ingest with metadata. +- HNSW index gives sub-50ms ANN at low/medium scale (<1M chunks). +- Easy hybrid search later: HNSW + `pg_trgm` GIN index already in place. + +## Future expansion points +- Reranker (e.g. `bge-reranker`) between `retrieve` and the model. +- BM25 hybrid via `tsvector` or `paradedb` for keyword recall. +- LangGraph if multi-agent / branching workflows appear. +- Per-user namespaces via row-level filter on `documents.metadata.user_id`. diff --git a/spec/spec-1.0.0/02-data-model.md b/spec/spec-1.0.0/02-data-model.md new file mode 100644 index 0000000..92148d3 --- /dev/null +++ b/spec/spec-1.0.0/02-data-model.md @@ -0,0 +1,45 @@ +# Data Model + +## Tables + +### `documents` +| Column | Type | Notes | +| ----------- | -------------------------- | ---------------------------------- | +| id | uuid (pk, default random) | | +| source | text not null | URI / path / logical source key | +| title | text null | Human-readable title | +| metadata | jsonb not null default '{}'| Free-form (author, tags, mime) | +| created_at | timestamptz not null now() | | + +Indexes: `documents_source_idx (source)`. + +### `chunks` +| Column | Type | Notes | +| ------------ | ----------------------------------------------------------- | --------------------------- | +| id | uuid (pk, default random) | | +| document_id | uuid not null fk → documents.id ON DELETE CASCADE | | +| ordinal | int not null | Position in source document | +| content | text not null | Chunk text | +| tokens | int null | Optional token count | +| embedding | vector(EMBEDDING_DIM) | nomic-embed-text → 768 | +| metadata | jsonb not null default '{}' | | +| created_at | timestamptz not null now() | | + +Indexes: +- `chunks_doc_ordinal_idx (document_id, ordinal)` — reassemble in order. +- `chunks_embedding_idx HNSW (embedding vector_cosine_ops)` — ANN. +- `chunks_content_trgm_idx GIN (content gin_trgm_ops)` — keyword fallback. + +## Embedding dimension +Set at table creation via `EMBEDDING_DIM`. Changing it requires: +1. Update env + restart. +2. Generate a new migration (drizzle-kit) altering the vector column. +3. Re-embed all chunks (run a backfill script). + +## Cascading +Deleting a document deletes its chunks (FK cascade). No orphan rows. + +## Future tables (sketched, not in v1) +- `conversations` (id, created_at, user_id?) +- `messages` (conversation_id, role, content, tool_calls jsonb) +- `runs` (agent run trace for debugging) diff --git a/spec/spec-1.0.0/03-agent-design.md b/spec/spec-1.0.0/03-agent-design.md new file mode 100644 index 0000000..c0d6595 --- /dev/null +++ b/spec/spec-1.0.0/03-agent-design.md @@ -0,0 +1,54 @@ +# Agent Design + +## Loop +The agent uses the AI SDK `streamText` loop with `maxSteps` (default 5): +each step the model can either emit text or call a tool. Tool results are +fed back in as `tool` messages and the loop continues until the model +emits a terminal text response or the cap is hit. + +## Tools + +### `search_kb` +Vector ANN over the chunk store. +``` +input: { query: string, topK?: number, sourceFilter?: string } +output: { results: RetrievalResult[] } // chunkId, score, source, content +``` +Cosine similarity normalized to `1 - distance` for intuitive ranking. + +### `fetch_doc` +Get the full reassembled content of one document. +``` +input: { documentId: uuid } +output: { id, source, title, content } // chunks joined in ordinal order +``` +Use case: model finds a strong hit but wants surrounding context. + +### `list_sources` +Inventory the KB. +``` +input: { limit?: number } +output: { sources: [{ source, document_count, chunk_count }] } +``` +Use case: open-ended "what do you know about?" or scoping a search. + +## System prompt principles +1. Tool-first for non-trivial questions — don't answer from priors. +2. Cite as `[source:title]` inline. +3. Refuse to invent facts when retrieval is empty. +4. Be concise; bullets for multi-fact answers. + +See `packages/agent/src/prompts.ts` for the live text. + +## Failure modes & guardrails +| Failure | Mitigation | +| ------------------------------ | ------------------------------------------------------- | +| Model loops on tool calls | `maxSteps` cap (default 5) | +| Empty retrieval | Prompt forbids fabrication; model says "I don't know" | +| Long documents in `fetch_doc` | Cap content size (TODO v1.1) or summarize on the way out | +| Off-topic / irrelevant queries | No special handling; user can rephrase | + +## Observability (planned) +- Log every tool call (name, args, result size, latency). +- Persist agent runs to a `runs` table for replay/debugging. +- Surface citation chunks back to the UI as expandable cards. diff --git a/spec/spec-1.0.0/04-api.md b/spec/spec-1.0.0/04-api.md new file mode 100644 index 0000000..5ff5cbc --- /dev/null +++ b/spec/spec-1.0.0/04-api.md @@ -0,0 +1,40 @@ +# API + +All routes live under `apps/web/src/app/api/`. Node runtime (not edge), +because `postgres-js` and `ollama-ai-provider` need Node APIs. + +## `POST /api/ingest` +Ingest a document into the KB. + +Request: +```json +{ + "source": "docs/handbook.md", + "title": "Engineering Handbook", + "content": "# Intro\n…", + "metadata": { "author": "team" } +} +``` + +Response `201`: +```json +{ "documentId": "…uuid…", "chunkCount": 42 } +``` + +Errors: `400` with zod issue list when validation fails. + +## `POST /api/chat` +Stream an agent response. Body matches the AI SDK `useChat` payload: +```json +{ "messages": [{ "role": "user", "content": "What is X?" }] } +``` + +Response: `text/event-stream` data stream consumed by `useChat` on the +client. Includes assistant deltas, tool calls, and tool results. + +## Future routes +| Path | Purpose | +| ---------------------- | ---------------------------------------- | +| `GET /api/sources` | UI listing of `list_sources` | +| `DELETE /api/documents/:id` | Remove a document and its chunks | +| `POST /api/reindex` | Re-embed all chunks (model swap) | diff --git a/spec/spec-1.0.0/05-roadmap.md b/spec/spec-1.0.0/05-roadmap.md new file mode 100644 index 0000000..cb7321e --- /dev/null +++ b/spec/spec-1.0.0/05-roadmap.md @@ -0,0 +1,33 @@ +# Roadmap + +## v0.1 — Skeleton (this commit) +- [x] Monorepo (pnpm workspaces, TS project refs) +- [x] Postgres+pgvector + Ollama via Docker Compose +- [x] Drizzle schema + HNSW index +- [x] RAG primitives: chunk, embed, ingest, retrieve +- [x] Agent loop with 3 tools (Vercel AI SDK) +- [x] Next.js 15 chat UI + ingest endpoint +- [x] Spec docs + gitflow + CI + +## v0.2 — Usable demo +- [ ] Drizzle migration generated + checked in +- [ ] File upload UI (md / txt / pdf via `unpdf`) +- [ ] Citations rendered as collapsible cards in chat +- [ ] Vitest smoke tests for chunker, retrieve, ingest tx +- [ ] Playwright happy-path test (ingest → ask → cite) + +## v0.3 — Quality +- [ ] Hybrid retrieval (HNSW + `pg_trgm` BM-ish merge) +- [ ] Reranker pass (cross-encoder via Ollama or transformers.js) +- [ ] Per-source filtering UI +- [ ] Run trace persistence + `/runs/:id` debug view + +## v0.4 — Multi-user +- [ ] Auth (Auth.js) + per-user namespaces +- [ ] Conversations / messages persistence +- [ ] Share-a-conversation read links + +## Beyond +- LangGraph if branching workflows emerge. +- Optional cloud LLM provider behind same `agent` interface. +- Self-host deploy guide (Fly.io / Coolify / bare metal). diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 0000000..c741c22 --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022"], + "module": "ESNext", + "moduleResolution": "Bundler", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "isolatedModules": true, + "verbatimModuleSyntax": false, + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "exclude": ["node_modules", "dist", ".next", "build"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..ed7d9bf --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.base.json", + "files": [], + "references": [ + { "path": "./apps/web" }, + { "path": "./packages/shared" }, + { "path": "./packages/db" }, + { "path": "./packages/rag" }, + { "path": "./packages/agent" } + ] +} From 9de557dc29551cafbf311562db27ff9e3c6c09d7 Mon Sep 17 00:00:00 2001 From: Daniel David Date: Sun, 26 Apr 2026 12:12:42 +1000 Subject: [PATCH 02/21] fix(infra): make `docker compose up` start the full working stack MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The web service was hidden behind a Compose profile, so `docker compose up` brought up only postgres + ollama. There was also no automation for pulling Ollama models or applying Drizzle migrations, so even running the web container manually would 404 on first chat and crash on first query. Changes: - docker-compose.yml: drop the `app` profile from `web`. Add two one-shot services: `ollama-init` (pulls chat + embed models, idempotent) and `migrate` (runs Drizzle migrations against postgres). `web` now waits on both via `service_completed_successfully`. - apps/web/Dockerfile: add a `migrate` build target that reuses the deps layer and runs `tsx src/migrate.ts` from packages/db. `web` build target is now explicit (`target: runner`). - packages/db/package.json: add `dotenv` as a runtime dependency (migrate.ts and drizzle.config.ts both import `dotenv/config`). - README.md: rewrite quickstart to use a single `docker compose up --build` and document the local-dev variant for hot reload. Result: `docker compose up --build` brings up postgres → ollama → pulls models → applies migrations → starts the web app on :3000, in that order. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 40 ++++++++++++++++++++++++-------- apps/web/Dockerfile | 14 +++++++++--- docker-compose.yml | 49 ++++++++++++++++++++++++++++++++++++---- packages/db/package.json | 1 + 4 files changed, 86 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index b097729..e7beba1 100644 --- a/README.md +++ b/README.md @@ -45,22 +45,42 @@ cp .env.example .env pnpm install ``` -### 3. Start the stack +### 3. Generate the initial migration (one-time) + +On a fresh clone, generate the SQL Drizzle will apply at startup: ```bash -pnpm stack:up # postgres + ollama (via docker compose) -pnpm ollama:pull # pulls llama3.1:8b + nomic-embed-text into the container -pnpm db:migrate # apply Drizzle migrations (after first generate) +pnpm --filter @app/db generate # writes packages/db/drizzle/*.sql ``` -> Migrations: on a fresh repo run `pnpm --filter @app/db generate` once to -> produce the initial SQL under `packages/db/drizzle/`, commit it, then -> `pnpm db:migrate` to apply. +Commit the generated files. Re-run after any change to `packages/db/src/schema.ts`. + +### 4. Start the full stack + +```bash +docker compose up --build +``` + +That brings up, in order: + +1. `postgres` (pgvector/pg16) with `vector` + `pg_trgm` extensions +2. `ollama` daemon +3. `ollama-init` — pulls `OLLAMA_CHAT_MODEL` + `OLLAMA_EMBED_MODEL` (first + run only takes a while; subsequent runs are no-ops) +4. `migrate` — applies Drizzle migrations against postgres, then exits +5. `web` — Next.js, listening on http://localhost:3000 + +The `web` service waits for both `migrate` and `ollama-init` to finish +successfully, so the app is functional the moment it starts accepting +connections. + +### Local-dev variant (faster inner loop) -### 4. Run the app +If you'd rather run Next on the host with hot reload: ```bash -pnpm dev # next dev on http://localhost:3000 +docker compose up -d postgres ollama ollama-init migrate +pnpm dev # http://localhost:3000 ``` ### 5. Ingest a document @@ -89,7 +109,7 @@ Then ask the chat UI: *"What is Agentic RAG?"* — the agent should call | `pnpm typecheck` | TS check across the workspace | | `pnpm lint` | Lint all packages | | `pnpm format` | Prettier write across the repo | -| `pnpm stack:up` | `docker compose up -d` (postgres + ollama) | +| `pnpm stack:up` | `docker compose up -d` (full stack) | | `pnpm stack:down` | Tear down the stack | | `pnpm db:migrate` | Apply Drizzle migrations | | `pnpm db:studio` | Open drizzle-kit studio | diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile index 0cc09e2..3c08208 100644 --- a/apps/web/Dockerfile +++ b/apps/web/Dockerfile @@ -4,7 +4,7 @@ ENV PNPM_HOME=/pnpm ENV PATH=$PNPM_HOME:$PATH RUN corepack enable -# ---- Deps ---- +# ---- Deps (full workspace install, used by builder + migrate) ---- FROM base AS deps WORKDIR /repo COPY package.json pnpm-workspace.yaml pnpm-lock.yaml* ./ @@ -15,14 +15,22 @@ COPY packages/rag/package.json packages/rag/package.json COPY packages/agent/package.json packages/agent/package.json RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile=false -# ---- Build ---- +# ---- Migrate (one-shot, runs Drizzle migrations against $DATABASE_URL) ---- +FROM base AS migrate +WORKDIR /repo +COPY --from=deps /repo/node_modules ./node_modules +COPY . . +WORKDIR /repo/packages/db +CMD ["pnpm", "exec", "tsx", "src/migrate.ts"] + +# ---- Builder ---- FROM base AS builder WORKDIR /repo COPY --from=deps /repo/node_modules ./node_modules COPY . . RUN pnpm --filter @app/web... build -# ---- Runtime ---- +# ---- Runtime (Next.js standalone) ---- FROM node:20-alpine AS runner WORKDIR /app ENV NODE_ENV=production diff --git a/docker-compose.yml b/docker-compose.yml index 93275dd..1371746 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,18 +32,57 @@ services: timeout: 5s retries: 10 + # One-shot: pulls the configured chat + embedding models into the ollama + # volume on first boot. Idempotent — re-running just verifies presence. + ollama-init: + image: ollama/ollama:latest + container_name: rag-ollama-init + depends_on: + ollama: + condition: service_healthy + environment: + OLLAMA_HOST: http://ollama:11434 + OLLAMA_CHAT_MODEL: ${OLLAMA_CHAT_MODEL:-llama3.1:8b} + OLLAMA_EMBED_MODEL: ${OLLAMA_EMBED_MODEL:-nomic-embed-text} + entrypoint: ["/bin/sh", "-c"] + command: + - | + set -e + echo "Pulling chat model: $$OLLAMA_CHAT_MODEL" + ollama pull "$$OLLAMA_CHAT_MODEL" + echo "Pulling embed model: $$OLLAMA_EMBED_MODEL" + ollama pull "$$OLLAMA_EMBED_MODEL" + echo "Ollama models ready." + restart: "no" + + # One-shot: runs Drizzle migrations against the postgres service. Web waits + # for this to complete successfully before starting. + migrate: + build: + context: . + dockerfile: apps/web/Dockerfile + target: migrate + container_name: rag-migrate + depends_on: + postgres: + condition: service_healthy + environment: + DATABASE_URL: postgres://${POSTGRES_USER:-rag}:${POSTGRES_PASSWORD:-rag}@postgres:5432/${POSTGRES_DB:-rag} + EMBEDDING_DIM: ${EMBEDDING_DIM:-768} + restart: "no" + web: build: context: . dockerfile: apps/web/Dockerfile + target: runner container_name: rag-web - profiles: ["app"] restart: unless-stopped depends_on: - postgres: - condition: service_healthy - ollama: - condition: service_started + migrate: + condition: service_completed_successfully + ollama-init: + condition: service_completed_successfully environment: DATABASE_URL: postgres://${POSTGRES_USER:-rag}:${POSTGRES_PASSWORD:-rag}@postgres:5432/${POSTGRES_DB:-rag} OLLAMA_BASE_URL: http://ollama:11434 diff --git a/packages/db/package.json b/packages/db/package.json index b9141c5..5907148 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -19,6 +19,7 @@ }, "dependencies": { "@app/shared": "workspace:*", + "dotenv": "^16.4.7", "drizzle-orm": "catalog:", "postgres": "catalog:" }, From 0bfa7080c0360d0583ab853d039748ad3bfcdbbc Mon Sep 17 00:00:00 2001 From: Daniel David Date: Sun, 26 Apr 2026 12:17:46 +1000 Subject: [PATCH 03/21] fix(infra): copy full pnpm workspace tree from deps stage The previous Dockerfile only copied /repo/node_modules from the deps stage into the builder and migrate stages. With pnpm workspaces the per-package node_modules (e.g. packages/shared/node_modules) contain symlinks into the hoisted store and must also be present, otherwise `tsc -p` in any workspace package fails with TS2307: Cannot find module 'zod'. Copy the entire /repo tree from deps before overlaying source. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/web/Dockerfile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile index 3c08208..3d705e5 100644 --- a/apps/web/Dockerfile +++ b/apps/web/Dockerfile @@ -16,9 +16,11 @@ COPY packages/agent/package.json packages/agent/package.json RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile=false # ---- Migrate (one-shot, runs Drizzle migrations against $DATABASE_URL) ---- +# Copy the entire deps tree (root + per-package node_modules with pnpm +# symlinks) before overlaying source. FROM base AS migrate WORKDIR /repo -COPY --from=deps /repo/node_modules ./node_modules +COPY --from=deps /repo ./ COPY . . WORKDIR /repo/packages/db CMD ["pnpm", "exec", "tsx", "src/migrate.ts"] @@ -26,7 +28,7 @@ CMD ["pnpm", "exec", "tsx", "src/migrate.ts"] # ---- Builder ---- FROM base AS builder WORKDIR /repo -COPY --from=deps /repo/node_modules ./node_modules +COPY --from=deps /repo ./ COPY . . RUN pnpm --filter @app/web... build From e14297c623443be004447c7dc6aed859002a80bb Mon Sep 17 00:00:00 2001 From: Daniel David Date: Sun, 26 Apr 2026 12:18:57 +1000 Subject: [PATCH 04/21] fix(web): teach Next webpack to resolve `.js` imports as `.ts` Workspace TS packages use NodeNext-style imports (`./env.js` resolving to `./env.ts`), which `tsc --moduleResolution=Bundler` accepts but Next's webpack does not. With `transpilePackages` consuming TS source directly, webpack failed every cross-file import with `Module not found`. Add a `resolve.extensionAlias` mapping `.js` -> `.ts/.tsx/.js` (and `.mjs` -> `.mts/.mjs`) so webpack follows the same convention. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/web/next.config.mjs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs index 126e552..c7a8af0 100644 --- a/apps/web/next.config.mjs +++ b/apps/web/next.config.mjs @@ -1,11 +1,19 @@ /** @type {import('next').NextConfig} */ const nextConfig = { output: 'standalone', - experimental: { - // Workspace packages are bundled via transpilePackages. - }, transpilePackages: ['@app/shared', '@app/db', '@app/rag', '@app/agent'], serverExternalPackages: ['postgres'], + webpack: (config) => { + // Workspace packages are TS source written in NodeNext style + // (`./foo.js` imports that resolve to `./foo.ts`). Teach webpack the + // same alias so `transpilePackages` can bundle them. + config.resolve.extensionAlias = { + ...(config.resolve.extensionAlias ?? {}), + '.js': ['.ts', '.tsx', '.js'], + '.mjs': ['.mts', '.mjs'], + }; + return config; + }, }; export default nextConfig; From 6140ccc42b6a487cb792e8394107eddb093f68be Mon Sep 17 00:00:00 2001 From: Daniel David Date: Sun, 26 Apr 2026 12:20:17 +1000 Subject: [PATCH 05/21] fix(web): defer env + embedder initialization to request time `loadEnv()` ran at module import, which Next evaluates during the "Collecting page data" build phase. Build-time has no DATABASE_URL, so the build crashed before producing an image. Replace top-level singletons with lazy `getEnv()` / `getEmbedder()` helpers that initialize on first call inside the route handlers. Also mark /api/ingest as `force-dynamic` for symmetry with /api/chat. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/web/src/app/api/chat/route.ts | 6 ++++-- apps/web/src/app/api/ingest/route.ts | 8 ++++++-- apps/web/src/lib/embedder.ts | 20 ++++++++++++++------ apps/web/src/lib/env.ts | 13 +++++++++++-- 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/apps/web/src/app/api/chat/route.ts b/apps/web/src/app/api/chat/route.ts index c54e1e2..c1af1a0 100644 --- a/apps/web/src/app/api/chat/route.ts +++ b/apps/web/src/app/api/chat/route.ts @@ -1,8 +1,8 @@ import { NextRequest } from 'next/server'; import { z } from 'zod'; import { runAgent } from '@app/agent'; -import { env } from '@/lib/env'; -import { embedder } from '@/lib/embedder'; +import { getEnv } from '@/lib/env'; +import { getEmbedder } from '@/lib/embedder'; export const runtime = 'nodejs'; export const dynamic = 'force-dynamic'; @@ -17,6 +17,8 @@ const BodySchema = z.object({ }); export async function POST(req: NextRequest) { + const env = getEnv(); + const embedder = getEmbedder(); const body = BodySchema.parse(await req.json()); const result = runAgent({ diff --git a/apps/web/src/app/api/ingest/route.ts b/apps/web/src/app/api/ingest/route.ts index e82b5ee..37707db 100644 --- a/apps/web/src/app/api/ingest/route.ts +++ b/apps/web/src/app/api/ingest/route.ts @@ -1,10 +1,11 @@ import { NextRequest, NextResponse } from 'next/server'; import { IngestRequestSchema } from '@app/shared'; import { ingestDocument } from '@app/rag'; -import { env } from '@/lib/env'; -import { embedder } from '@/lib/embedder'; +import { getEnv } from '@/lib/env'; +import { getEmbedder } from '@/lib/embedder'; export const runtime = 'nodejs'; +export const dynamic = 'force-dynamic'; export async function POST(req: NextRequest) { const parsed = IngestRequestSchema.safeParse(await req.json()); @@ -12,6 +13,9 @@ export async function POST(req: NextRequest) { return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 }); } + const env = getEnv(); + const embedder = getEmbedder(); + const result = await ingestDocument(parsed.data, { chunkSize: env.RAG_CHUNK_SIZE, chunkOverlap: env.RAG_CHUNK_OVERLAP, diff --git a/apps/web/src/lib/embedder.ts b/apps/web/src/lib/embedder.ts index 6adb942..713ffdd 100644 --- a/apps/web/src/lib/embedder.ts +++ b/apps/web/src/lib/embedder.ts @@ -1,7 +1,15 @@ -import { createEmbedder } from '@app/rag'; -import { env } from './env.js'; +import { createEmbedder, type Embedder } from '@app/rag'; +import { getEnv } from './env.js'; -export const embedder = createEmbedder({ - baseURL: env.OLLAMA_BASE_URL, - model: env.OLLAMA_EMBED_MODEL, -}); +let _embedder: Embedder | null = null; + +export function getEmbedder(): Embedder { + if (!_embedder) { + const env = getEnv(); + _embedder = createEmbedder({ + baseURL: env.OLLAMA_BASE_URL, + model: env.OLLAMA_EMBED_MODEL, + }); + } + return _embedder; +} diff --git a/apps/web/src/lib/env.ts b/apps/web/src/lib/env.ts index fab485a..0cdcd80 100644 --- a/apps/web/src/lib/env.ts +++ b/apps/web/src/lib/env.ts @@ -1,3 +1,12 @@ -import { loadEnv } from '@app/shared'; +import { loadEnv, type Env } from '@app/shared'; -export const env = loadEnv(); +let _env: Env | null = null; + +/** + * Lazily validate process.env. Called at request time, not import time, so + * Next's build-time page-data collection doesn't crash on missing vars. + */ +export function getEnv(): Env { + if (!_env) _env = loadEnv(); + return _env; +} From 10e8c36686ce0b6c245bd5117136cfcb0921d2de Mon Sep 17 00:00:00 2001 From: Daniel David Date: Sun, 26 Apr 2026 12:21:28 +1000 Subject: [PATCH 06/21] fix(web): create empty public/ so Dockerfile COPY does not fail Next standalone build expects apps/web/public to exist; with no static assets the directory was never created and the runner stage failed at `COPY /repo/apps/web/public`. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/web/public/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/web/public/.gitkeep diff --git a/apps/web/public/.gitkeep b/apps/web/public/.gitkeep new file mode 100644 index 0000000..e69de29 From d502351b3d76d07c89f60957d67f5d67c8b41486 Mon Sep 17 00:00:00 2001 From: Daniel David Date: Sun, 26 Apr 2026 12:29:13 +1000 Subject: [PATCH 07/21] =?UTF-8?q?fix(agent,web,db):=20unbreak=20chat=20?= =?UTF-8?q?=E2=80=94=20generate=20migrations,=20simulate=20streaming,=20un?= =?UTF-8?q?mask=20errors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A multi-agent review found three converging causes of the chat endpoint returning the masked AI SDK error '3:"An error occurred."': 1. Drizzle migrations were never generated. `packages/db/drizzle/` did not exist, so the `migrate` Compose service exited with `Can't find meta/_journal.json file`, postgres had zero tables, and every tool call (search_kb / fetch_doc / list_sources) failed on a missing relation. Generated the initial migration (0000_chilly_union_jack.sql) and committed it. Also commit the root pnpm-lock.yaml so container builds are reproducible. 2. `ollama-ai-provider@1.2.0` + AI SDK v4 `streamText` + tools is broken upstream (vercel/ai#4700) — the model factory needs `simulateStreaming: true` so tool rounds are batched and chunked back to the data stream instead of failing silently. 3. Errors were masked into the data stream. Pass `getErrorMessage` to `toDataStreamResponse` and add an `onError` log inside `streamText` so future failures surface their real message instead of '3:"An error occurred."'. After this commit a fresh `docker compose up --build` should: postgres healthy → ollama-init pulls models → migrate creates schema → web boots and chat works end-to-end (after at least one document is ingested via /api/ingest). Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/web/src/app/api/chat/route.ts | 8 +- packages/agent/src/agent.ts | 9 +- .../db/drizzle/0000_chilly_union_jack.sql | 24 + packages/db/drizzle/meta/0000_snapshot.json | 215 + packages/db/drizzle/meta/_journal.json | 13 + pnpm-lock.yaml | 4698 +++++++++++++++++ 6 files changed, 4965 insertions(+), 2 deletions(-) create mode 100644 packages/db/drizzle/0000_chilly_union_jack.sql create mode 100644 packages/db/drizzle/meta/0000_snapshot.json create mode 100644 packages/db/drizzle/meta/_journal.json create mode 100644 pnpm-lock.yaml diff --git a/apps/web/src/app/api/chat/route.ts b/apps/web/src/app/api/chat/route.ts index c1af1a0..2d12bec 100644 --- a/apps/web/src/app/api/chat/route.ts +++ b/apps/web/src/app/api/chat/route.ts @@ -28,5 +28,11 @@ export async function POST(req: NextRequest) { toolDeps: { embedder, defaultTopK: env.RAG_TOP_K }, }); - return result.toDataStreamResponse(); + return result.toDataStreamResponse({ + getErrorMessage: (error) => { + const message = error instanceof Error ? error.message : String(error); + console.error('[/api/chat] error:', error); + return message; + }, + }); } diff --git a/packages/agent/src/agent.ts b/packages/agent/src/agent.ts index f6b2272..68d5c9c 100644 --- a/packages/agent/src/agent.ts +++ b/packages/agent/src/agent.ts @@ -17,11 +17,18 @@ export function runAgent(opts: RunAgentOptions) { const tools = buildTools(opts.toolDeps); return streamText({ - model: ollama(opts.chatModel), + // `simulateStreaming: true` is required for ollama-ai-provider@1.x + + // AI SDK v4 when using tools — native streaming + tool-calling is broken + // upstream and surfaces as a masked "An error occurred." to the client. + // See: https://github.com/vercel/ai/issues/4700 + model: ollama(opts.chatModel, { simulateStreaming: true }), system: SYSTEM_PROMPT, messages: opts.messages, tools, maxSteps: opts.maxSteps ?? 5, temperature: 0.2, + onError: ({ error }) => { + console.error('[agent] streamText error:', error); + }, }); } diff --git a/packages/db/drizzle/0000_chilly_union_jack.sql b/packages/db/drizzle/0000_chilly_union_jack.sql new file mode 100644 index 0000000..76e2fe8 --- /dev/null +++ b/packages/db/drizzle/0000_chilly_union_jack.sql @@ -0,0 +1,24 @@ +CREATE TABLE "chunks" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "document_id" uuid NOT NULL, + "ordinal" integer NOT NULL, + "content" text NOT NULL, + "tokens" integer, + "embedding" vector(768), + "metadata" jsonb DEFAULT '{}'::jsonb NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "documents" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "source" text NOT NULL, + "title" text, + "metadata" jsonb DEFAULT '{}'::jsonb NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "chunks" ADD CONSTRAINT "chunks_document_id_documents_id_fk" FOREIGN KEY ("document_id") REFERENCES "public"."documents"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "chunks_doc_ordinal_idx" ON "chunks" USING btree ("document_id","ordinal");--> statement-breakpoint +CREATE INDEX "chunks_embedding_idx" ON "chunks" USING hnsw ("embedding" vector_cosine_ops) WITH (m=16,ef_construction=64);--> statement-breakpoint +CREATE INDEX "chunks_content_trgm_idx" ON "chunks" USING gin ("content" gin_trgm_ops);--> statement-breakpoint +CREATE INDEX "documents_source_idx" ON "documents" USING btree ("source"); \ No newline at end of file diff --git a/packages/db/drizzle/meta/0000_snapshot.json b/packages/db/drizzle/meta/0000_snapshot.json new file mode 100644 index 0000000..e66cb7d --- /dev/null +++ b/packages/db/drizzle/meta/0000_snapshot.json @@ -0,0 +1,215 @@ +{ + "id": "36a005e7-2c0f-4d57-b87a-174427bf345f", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.chunks": { + "name": "chunks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "ordinal": { + "name": "ordinal", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tokens": { + "name": "tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "embedding": { + "name": "embedding", + "type": "vector(768)", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chunks_doc_ordinal_idx": { + "name": "chunks_doc_ordinal_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "ordinal", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chunks_embedding_idx": { + "name": "chunks_embedding_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "chunks_content_trgm_idx": { + "name": "chunks_content_trgm_idx", + "columns": [ + { + "expression": "\"content\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "chunks_document_id_documents_id_fk": { + "name": "chunks_document_id_documents_id_fk", + "tableFrom": "chunks", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.documents": { + "name": "documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "documents_source_idx": { + "name": "documents_source_idx", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/drizzle/meta/_journal.json b/packages/db/drizzle/meta/_journal.json new file mode 100644 index 0000000..2db5e5b --- /dev/null +++ b/packages/db/drizzle/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1777170510725, + "tag": "0000_chilly_union_jack", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..4e01256 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,4698 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +catalogs: + default: + ai: + specifier: ^4.0.30 + version: 4.3.19 + drizzle-kit: + specifier: ^0.30.1 + version: 0.30.6 + drizzle-orm: + specifier: ^0.38.3 + version: 0.38.4 + ollama-ai-provider: + specifier: ^1.2.0 + version: 1.2.0 + postgres: + specifier: ^3.4.5 + version: 3.4.9 + typescript: + specifier: ^5.7.3 + version: 5.9.3 + zod: + specifier: ^3.24.1 + version: 3.25.76 + +importers: + + .: + devDependencies: + '@types/node': + specifier: ^22.10.5 + version: 22.19.17 + prettier: + specifier: ^3.4.2 + version: 3.8.3 + typescript: + specifier: ^5.7.3 + version: 5.9.3 + + apps/web: + dependencies: + '@app/agent': + specifier: workspace:* + version: link:../../packages/agent + '@app/db': + specifier: workspace:* + version: link:../../packages/db + '@app/rag': + specifier: workspace:* + version: link:../../packages/rag + '@app/shared': + specifier: workspace:* + version: link:../../packages/shared + ai: + specifier: 'catalog:' + version: 4.3.19(react@19.2.5)(zod@3.25.76) + next: + specifier: ^15.1.4 + version: 15.5.15(@opentelemetry/api@1.9.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + ollama-ai-provider: + specifier: 'catalog:' + version: 1.2.0(zod@3.25.76) + react: + specifier: ^19.0.0 + version: 19.2.5 + react-dom: + specifier: ^19.0.0 + version: 19.2.5(react@19.2.5) + zod: + specifier: 'catalog:' + version: 3.25.76 + devDependencies: + '@types/react': + specifier: ^19.0.7 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.0.3 + version: 19.2.3(@types/react@19.2.14) + eslint-config-next: + specifier: ^15.1.4 + version: 15.5.15(eslint@9.39.4)(typescript@5.9.3) + typescript: + specifier: 'catalog:' + version: 5.9.3 + + packages/agent: + dependencies: + '@app/db': + specifier: workspace:* + version: link:../db + '@app/rag': + specifier: workspace:* + version: link:../rag + '@app/shared': + specifier: workspace:* + version: link:../shared + ai: + specifier: 'catalog:' + version: 4.3.19(react@19.2.5)(zod@3.25.76) + drizzle-orm: + specifier: 'catalog:' + version: 0.38.4(@opentelemetry/api@1.9.0)(@types/react@19.2.14)(postgres@3.4.9)(react@19.2.5) + ollama-ai-provider: + specifier: 'catalog:' + version: 1.2.0(zod@3.25.76) + zod: + specifier: 'catalog:' + version: 3.25.76 + devDependencies: + typescript: + specifier: 'catalog:' + version: 5.9.3 + + packages/db: + dependencies: + '@app/shared': + specifier: workspace:* + version: link:../shared + dotenv: + specifier: ^16.4.7 + version: 16.6.1 + drizzle-orm: + specifier: 'catalog:' + version: 0.38.4(@opentelemetry/api@1.9.0)(@types/react@19.2.14)(postgres@3.4.9)(react@19.2.5) + postgres: + specifier: 'catalog:' + version: 3.4.9 + devDependencies: + drizzle-kit: + specifier: 'catalog:' + version: 0.30.6 + tsx: + specifier: ^4.19.2 + version: 4.21.0 + typescript: + specifier: 'catalog:' + version: 5.9.3 + + packages/rag: + dependencies: + '@app/db': + specifier: workspace:* + version: link:../db + '@app/shared': + specifier: workspace:* + version: link:../shared + ai: + specifier: 'catalog:' + version: 4.3.19(react@19.2.5)(zod@3.25.76) + drizzle-orm: + specifier: 'catalog:' + version: 0.38.4(@opentelemetry/api@1.9.0)(@types/react@19.2.14)(postgres@3.4.9)(react@19.2.5) + ollama-ai-provider: + specifier: 'catalog:' + version: 1.2.0(zod@3.25.76) + zod: + specifier: 'catalog:' + version: 3.25.76 + devDependencies: + typescript: + specifier: 'catalog:' + version: 5.9.3 + + packages/shared: + dependencies: + zod: + specifier: 'catalog:' + version: 3.25.76 + devDependencies: + typescript: + specifier: 'catalog:' + version: 5.9.3 + +packages: + + '@ai-sdk/provider-utils@2.2.8': + resolution: {integrity: sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.23.8 + + '@ai-sdk/provider@1.1.3': + resolution: {integrity: sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg==} + engines: {node: '>=18'} + + '@ai-sdk/react@1.2.12': + resolution: {integrity: sha512-jK1IZZ22evPZoQW3vlkZ7wvjYGYF+tRBKXtrcolduIkQ/m/sOAVcVeVDUDvh1T91xCnWCdUGCPZg2avZ90mv3g==} + engines: {node: '>=18'} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + zod: ^3.23.8 + peerDependenciesMeta: + zod: + optional: true + + '@ai-sdk/ui-utils@1.2.11': + resolution: {integrity: sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.23.8 + + '@drizzle-team/brocli@0.10.2': + resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} + + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + + '@esbuild-kit/core-utils@3.3.2': + resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} + deprecated: 'Merged into tsx: https://tsx.is' + + '@esbuild-kit/esm-loader@2.6.5': + resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==} + deprecated: 'Merged into tsx: https://tsx.is' + + '@esbuild/aix-ppc64@0.19.12': + resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.18.20': + resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.19.12': + resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.18.20': + resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.19.12': + resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.18.20': + resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.19.12': + resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.18.20': + resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.19.12': + resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.18.20': + resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.19.12': + resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.18.20': + resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.19.12': + resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.18.20': + resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.19.12': + resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.18.20': + resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.19.12': + resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.18.20': + resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.19.12': + resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.18.20': + resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.19.12': + resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.18.20': + resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.19.12': + resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.18.20': + resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.19.12': + resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.18.20': + resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.19.12': + resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.18.20': + resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.19.12': + resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.18.20': + resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.19.12': + resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.18.20': + resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.19.12': + resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.18.20': + resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.19.12': + resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.18.20': + resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.19.12': + resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.18.20': + resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.19.12': + resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.18.20': + resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.19.12': + resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.18.20': + resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.19.12': + resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.18.20': + resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.19.12': + resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.2': + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.5': + resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.4': + resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@napi-rs/wasm-runtime@0.2.12': + resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} + + '@next/env@15.5.15': + resolution: {integrity: sha512-vcmyu5/MyFzN7CdqRHO3uHO44p/QPCZkuTUXroeUmhNP8bL5PHFEhik22JUazt+CDDoD6EpBYRCaS2pISL+/hg==} + + '@next/eslint-plugin-next@15.5.15': + resolution: {integrity: sha512-ExQoBfyKMjAUQ2nuF39ryQsG26H374ZfH13dlOZqf6TaE9ycRbIm+qUbUFCliU4BtQhiqtS7cnGA1yWfPMQ+jA==} + + '@next/swc-darwin-arm64@15.5.15': + resolution: {integrity: sha512-6PvFO2Tzt10GFK2Ro9tAVEtacMqRmTarYMFKAnV2vYMdwWc73xzmDQyAV7SwEdMhzmiRoo7+m88DuiXlJlGeaw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@next/swc-darwin-x64@15.5.15': + resolution: {integrity: sha512-G+YNV+z6FDZTp/+IdGyIMFqalBTaQSnvAA+X/hrt+eaTRFSznRMz9K7rTmzvM6tDmKegNtyzgufZW0HwVzEqaQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@next/swc-linux-arm64-gnu@15.5.15': + resolution: {integrity: sha512-eVkrMcVIBqGfXB+QUC7jjZ94Z6uX/dNStbQFabewAnk13Uy18Igd1YZ/GtPRzdhtm7QwC0e6o7zOQecul4iC1w==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@next/swc-linux-arm64-musl@15.5.15': + resolution: {integrity: sha512-RwSHKMQ7InLy5GfkY2/n5PcFycKA08qI1VST78n09nN36nUPqCvGSMiLXlfUmzmpQpF6XeBYP2KRWHi0UW3uNg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@next/swc-linux-x64-gnu@15.5.15': + resolution: {integrity: sha512-nplqvY86LakS+eeiuWsNWvfmK8pFcOEW7ZtVRt4QH70lL+0x6LG/m1OpJ/tvrbwjmR8HH9/fH2jzW1GlL03TIg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@next/swc-linux-x64-musl@15.5.15': + resolution: {integrity: sha512-eAgl9NKQ84/sww0v81DQINl/vL2IBxD7sMybd0cWRw6wqgouVI53brVRBrggqBRP/NWeIAE1dm5cbKYoiMlqDQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@next/swc-win32-arm64-msvc@15.5.15': + resolution: {integrity: sha512-GJVZC86lzSquh0MtvZT+L7G8+jMnJcldloOjA8Kf3wXvBrvb6OGe2MzPuALxFshSm/IpwUtD2mIoof39ymf52A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@next/swc-win32-x64-msvc@15.5.15': + resolution: {integrity: sha512-nFucjVdwlFqxh/JG3hWSJ4p8+YJV7Ii8aPDuBQULB6DzUF4UNZETXLfEUk+oI2zEznWWULPt7MeuTE6xtK1HSA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@nolyfill/is-core-module@1.0.39': + resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} + engines: {node: '>=12.4.0'} + + '@opentelemetry/api@1.9.0': + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + engines: {node: '>=8.0.0'} + + '@petamoriken/float16@3.9.3': + resolution: {integrity: sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g==} + + '@rtsao/scc@1.1.0': + resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + + '@rushstack/eslint-patch@1.16.1': + resolution: {integrity: sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag==} + + '@swc/helpers@0.5.15': + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + + '@types/diff-match-patch@1.0.36': + resolution: {integrity: sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + + '@types/node@22.19.17': + resolution: {integrity: sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.14': + resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + + '@typescript-eslint/eslint-plugin@8.59.0': + resolution: {integrity: sha512-HyAZtpdkgZwpq8Sz3FSUvCR4c+ScbuWa9AksK2Jweub7w4M3yTz4O11AqVJzLYjy/B9ZWPyc81I+mOdJU/bDQw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.59.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.59.0': + resolution: {integrity: sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.59.0': + resolution: {integrity: sha512-Lw5ITrR5s5TbC19YSvlr63ZfLaJoU6vtKTHyB0GQOpX0W7d5/Ir6vUahWi/8Sps/nOukZQ0IB3SmlxZnjaKVnw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.59.0': + resolution: {integrity: sha512-UzR16Ut8IpA3Mc4DbgAShlPPkVm8xXMWafXxB0BocaVRHs8ZGakAxGRskF7FId3sdk9lgGD73GSFaWmWFDE4dg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.59.0': + resolution: {integrity: sha512-91Sbl3s4Kb3SybliIY6muFBmHVv+pYXfybC4Oolp3dvk8BvIE3wOPc+403CWIT7mJNkfQRGtdqghzs2+Z91Tqg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.59.0': + resolution: {integrity: sha512-3TRiZaQSltGqGeNrJzzr1+8YcEobKH9rHnqIp/1psfKFmhRQDNMGP5hBufanYTGznwShzVLs3Mz+gDN7HkWfXg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.59.0': + resolution: {integrity: sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.59.0': + resolution: {integrity: sha512-O9Re9P1BmBLFJyikRbQpLku/QA3/AueZNO9WePLBwQrvkixTmDe8u76B6CYUAITRl/rHawggEqUGn5QIkVRLMw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.59.0': + resolution: {integrity: sha512-I1R/K7V07XsMJ12Oaxg/O9GfrysGTmCRhvZJBv0RE0NcULMzjqVpR5kRRQjHsz3J/bElU7HwCO7zkqL+MSUz+g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.59.0': + resolution: {integrity: sha512-/uejZt4dSere1bx12WLlPfv8GktzcaDtuJ7s42/HEZ5zGj9oxRaD4bj7qwSunXkf+pbAhFt2zjpHYUiT5lHf0Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@unrs/resolver-binding-android-arm-eabi@1.11.1': + resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} + cpu: [arm] + os: [android] + + '@unrs/resolver-binding-android-arm64@1.11.1': + resolution: {integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==} + cpu: [arm64] + os: [android] + + '@unrs/resolver-binding-darwin-arm64@1.11.1': + resolution: {integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==} + cpu: [arm64] + os: [darwin] + + '@unrs/resolver-binding-darwin-x64@1.11.1': + resolution: {integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==} + cpu: [x64] + os: [darwin] + + '@unrs/resolver-binding-freebsd-x64@1.11.1': + resolution: {integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==} + cpu: [x64] + os: [freebsd] + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': + resolution: {integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': + resolution: {integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': + resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-arm64-musl@1.11.1': + resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': + resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': + resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': + resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': + resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-x64-gnu@1.11.1': + resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-x64-musl@1.11.1': + resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-wasm32-wasi@1.11.1': + resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': + resolution: {integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==} + cpu: [arm64] + os: [win32] + + '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': + resolution: {integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==} + cpu: [ia32] + os: [win32] + + '@unrs/resolver-binding-win32-x64-msvc@1.11.1': + resolution: {integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==} + cpu: [x64] + os: [win32] + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ai@4.3.19: + resolution: {integrity: sha512-dIE2bfNpqHN3r6IINp9znguYdhIOheKW2LDigAMrgt/upT3B8eBGPSCblENvaZGoq+hxaN9fSMzjWpbqloP+7Q==} + engines: {node: '>=18'} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + zod: ^3.23.8 + peerDependenciesMeta: + react: + optional: true + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-includes@3.1.9: + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlastindex@1.2.6: + resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + + array.prototype.tosorted@1.1.4: + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + ast-types-flow@0.0.8: + resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} + + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + axe-core@4.11.3: + resolution: {integrity: sha512-zBQouZixDTbo3jMGqHKyePxYxr1e5W8UdTmBQ7sNtaA9M2bE32daxxPLS/jojhKOHxQ7LWwPjfiwf/fhaJWzlg==} + engines: {node: '>=4'} + + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + brace-expansion@1.1.14: + resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} + + brace-expansion@5.0.5: + resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + engines: {node: 18 || 20 || >=22} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001790: + resolution: {integrity: sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + damerau-levenshtein@1.0.8: + resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + diff-match-patch@1.0.5: + resolution: {integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==} + + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + + drizzle-kit@0.30.6: + resolution: {integrity: sha512-U4wWit0fyZuGuP7iNmRleQyK2V8wCuv57vf5l3MnG4z4fzNTjY/U13M8owyQ5RavqvqxBifWORaR3wIUzlN64g==} + hasBin: true + + drizzle-orm@0.38.4: + resolution: {integrity: sha512-s7/5BpLKO+WJRHspvpqTydxFob8i1vo2rEx4pY6TGY7QSMuUfWUuzaY0DIpXCkgHOo37BaFC+SJQb99dDUXT3Q==} + peerDependencies: + '@aws-sdk/client-rds-data': '>=3' + '@cloudflare/workers-types': '>=4' + '@electric-sql/pglite': '>=0.2.0' + '@libsql/client': '>=0.10.0' + '@libsql/client-wasm': '>=0.10.0' + '@neondatabase/serverless': '>=0.10.0' + '@op-engineering/op-sqlite': '>=2' + '@opentelemetry/api': ^1.4.1 + '@planetscale/database': '>=1' + '@prisma/client': '*' + '@tidbcloud/serverless': '*' + '@types/better-sqlite3': '*' + '@types/pg': '*' + '@types/react': '>=18' + '@types/sql.js': '*' + '@vercel/postgres': '>=0.8.0' + '@xata.io/client': '*' + better-sqlite3: '>=7' + bun-types: '*' + expo-sqlite: '>=14.0.0' + knex: '*' + kysely: '*' + mysql2: '>=2' + pg: '>=8' + postgres: '>=3' + prisma: '*' + react: '>=18' + sql.js: '>=1' + sqlite3: '>=5' + peerDependenciesMeta: + '@aws-sdk/client-rds-data': + optional: true + '@cloudflare/workers-types': + optional: true + '@electric-sql/pglite': + optional: true + '@libsql/client': + optional: true + '@libsql/client-wasm': + optional: true + '@neondatabase/serverless': + optional: true + '@op-engineering/op-sqlite': + optional: true + '@opentelemetry/api': + optional: true + '@planetscale/database': + optional: true + '@prisma/client': + optional: true + '@tidbcloud/serverless': + optional: true + '@types/better-sqlite3': + optional: true + '@types/pg': + optional: true + '@types/react': + optional: true + '@types/sql.js': + optional: true + '@vercel/postgres': + optional: true + '@xata.io/client': + optional: true + better-sqlite3: + optional: true + bun-types: + optional: true + expo-sqlite: + optional: true + knex: + optional: true + kysely: + optional: true + mysql2: + optional: true + pg: + optional: true + postgres: + optional: true + prisma: + optional: true + react: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + env-paths@3.0.0: + resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + es-abstract@1.24.2: + resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-iterator-helpers@1.3.2: + resolution: {integrity: sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + + esbuild-register@3.6.0: + resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==} + peerDependencies: + esbuild: '>=0.12 <1' + + esbuild@0.18.20: + resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.19.12: + resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-next@15.5.15: + resolution: {integrity: sha512-mI5KIONOIosjF3jK2z9a8fY2LePNeW5C4lRJ+XZoJHAKkwx2MQjMPQ2/kL7tsMRPcQPZc/UBtCfqxElluL1CBg==} + peerDependencies: + eslint: ^7.23.0 || ^8.0.0 || ^9.0.0 + typescript: '>=3.3.1' + peerDependenciesMeta: + typescript: + optional: true + + eslint-import-resolver-node@0.3.10: + resolution: {integrity: sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==} + + eslint-import-resolver-typescript@3.10.1: + resolution: {integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' + eslint-plugin-import-x: '*' + peerDependenciesMeta: + eslint-plugin-import: + optional: true + eslint-plugin-import-x: + optional: true + + eslint-module-utils@2.12.1: + resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + + eslint-plugin-import@2.32.0: + resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + + eslint-plugin-jsx-a11y@6.10.2: + resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==} + engines: {node: '>=4.0'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 + + eslint-plugin-react-hooks@5.2.0: + resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react@7.37.5: + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@9.39.4: + resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.1: + resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + gel@2.2.0: + resolution: {integrity: sha512-q0ma7z2swmoamHQusey8ayo8+ilVdzDt4WTxSPzq/yRqvucWRfymRVMvNgmSC0XK7eNjjEZEcplxpgaNojKdmQ==} + engines: {node: '>= 18.0.0'} + hasBin: true + + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.3: + resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} + engines: {node: '>= 0.4'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-bun-module@2.0.0: + resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isexe@3.1.5: + resolution: {integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==} + engines: {node: '>=18'} + + iterator.prototype@1.1.5: + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} + engines: {node: '>= 0.4'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + + jsondiffpatch@0.6.0: + resolution: {integrity: sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + + jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + language-subtag-registry@0.3.23: + resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} + + language-tags@1.0.9: + resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} + engines: {node: '>=0.10'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + napi-postinstall@0.3.4: + resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + next@15.5.15: + resolution: {integrity: sha512-VSqCrJwtLVGwAVE0Sb/yikrQfkwkZW9p+lL/J4+xe+G3ZA+QnWPqgcfH1tDUEuk9y+pthzzVFp4L/U8JerMfMQ==} + engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.51.1 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + + node-exports-info@1.6.0: + resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==} + engines: {node: '>= 0.4'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.entries@1.1.9: + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + + ollama-ai-provider@1.2.0: + resolution: {integrity: sha512-jTNFruwe3O/ruJeppI/quoOUxG7NA6blG3ZyQj3lei4+NnJo7bi3eIRWqlVpRlu/mbzbFXeJSBuYQWF6pzGKww==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.0.0 + peerDependenciesMeta: + zod: + optional: true + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + partial-json@0.1.7: + resolution: {integrity: sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} + + postgres@3.4.9: + resolution: {integrity: sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw==} + engines: {node: '>=12'} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier@3.8.3: + resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} + engines: {node: '>=14'} + hasBin: true + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + react-dom@19.2.5: + resolution: {integrity: sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==} + peerDependencies: + react: ^19.2.5 + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react@19.2.5: + resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==} + engines: {node: '>=0.10.0'} + + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + resolve@2.0.0-next.6: + resolution: {integrity: sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==} + engines: {node: '>= 0.4'} + hasBin: true + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-array-concat@1.1.4: + resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} + engines: {node: '>=0.4'} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + secure-json-parse@2.7.0: + resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shell-quote@1.8.3: + resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} + engines: {node: '>= 0.4'} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + stable-hash@0.0.5: + resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} + + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + + string.prototype.includes@2.0.1: + resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} + engines: {node: '>= 0.4'} + + string.prototype.matchall@4.0.12: + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + engines: {node: '>= 0.4'} + + string.prototype.repeat@1.0.0: + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + styled-jsx@5.1.6: + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + swr@2.4.1: + resolution: {integrity: sha512-2CC6CiKQtEwaEeNiqWTAw9PGykW8SR5zZX8MZk6TeAvEAnVS7Visz8WzphqgtQ8v2xz/4Q5K+j+SeMaKXeeQIA==} + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + throttleit@2.1.0: + resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==} + engines: {node: '>=18'} + + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + unrs-resolver@1.11.1: + resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.20: + resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + which@4.0.0: + resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==} + engines: {node: ^16.13.0 || >=18.0.0} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} + peerDependencies: + zod: ^3.25.28 || ^4 + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + +snapshots: + + '@ai-sdk/provider-utils@2.2.8(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 1.1.3 + nanoid: 3.3.11 + secure-json-parse: 2.7.0 + zod: 3.25.76 + + '@ai-sdk/provider@1.1.3': + dependencies: + json-schema: 0.4.0 + + '@ai-sdk/react@1.2.12(react@19.2.5)(zod@3.25.76)': + dependencies: + '@ai-sdk/provider-utils': 2.2.8(zod@3.25.76) + '@ai-sdk/ui-utils': 1.2.11(zod@3.25.76) + react: 19.2.5 + swr: 2.4.1(react@19.2.5) + throttleit: 2.1.0 + optionalDependencies: + zod: 3.25.76 + + '@ai-sdk/ui-utils@1.2.11(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 1.1.3 + '@ai-sdk/provider-utils': 2.2.8(zod@3.25.76) + zod: 3.25.76 + zod-to-json-schema: 3.25.2(zod@3.25.76) + + '@drizzle-team/brocli@0.10.2': {} + + '@emnapi/core@1.10.0': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild-kit/core-utils@3.3.2': + dependencies: + esbuild: 0.18.20 + source-map-support: 0.5.21 + + '@esbuild-kit/esm-loader@2.6.5': + dependencies: + '@esbuild-kit/core-utils': 3.3.2 + get-tsconfig: 4.14.0 + + '@esbuild/aix-ppc64@0.19.12': + optional: true + + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.18.20': + optional: true + + '@esbuild/android-arm64@0.19.12': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm@0.18.20': + optional: true + + '@esbuild/android-arm@0.19.12': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-x64@0.18.20': + optional: true + + '@esbuild/android-x64@0.19.12': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.18.20': + optional: true + + '@esbuild/darwin-arm64@0.19.12': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.18.20': + optional: true + + '@esbuild/darwin-x64@0.19.12': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.18.20': + optional: true + + '@esbuild/freebsd-arm64@0.19.12': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.18.20': + optional: true + + '@esbuild/freebsd-x64@0.19.12': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.18.20': + optional: true + + '@esbuild/linux-arm64@0.19.12': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm@0.18.20': + optional: true + + '@esbuild/linux-arm@0.19.12': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.18.20': + optional: true + + '@esbuild/linux-ia32@0.19.12': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.18.20': + optional: true + + '@esbuild/linux-loong64@0.19.12': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.18.20': + optional: true + + '@esbuild/linux-mips64el@0.19.12': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.18.20': + optional: true + + '@esbuild/linux-ppc64@0.19.12': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.18.20': + optional: true + + '@esbuild/linux-riscv64@0.19.12': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.18.20': + optional: true + + '@esbuild/linux-s390x@0.19.12': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-x64@0.18.20': + optional: true + + '@esbuild/linux-x64@0.19.12': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.18.20': + optional: true + + '@esbuild/netbsd-x64@0.19.12': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.18.20': + optional: true + + '@esbuild/openbsd-x64@0.19.12': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.18.20': + optional: true + + '@esbuild/sunos-x64@0.19.12': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.18.20': + optional: true + + '@esbuild/win32-arm64@0.19.12': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.18.20': + optional: true + + '@esbuild/win32-ia32@0.19.12': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-x64@0.18.20': + optional: true + + '@esbuild/win32-x64@0.19.12': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4)': + dependencies: + eslint: 9.39.4 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.2': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.5': + dependencies: + ajv: 6.15.0 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.4': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@img/colour@1.1.0': + optional: true + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.10.0 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + + '@napi-rs/wasm-runtime@0.2.12': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.1 + optional: true + + '@next/env@15.5.15': {} + + '@next/eslint-plugin-next@15.5.15': + dependencies: + fast-glob: 3.3.1 + + '@next/swc-darwin-arm64@15.5.15': + optional: true + + '@next/swc-darwin-x64@15.5.15': + optional: true + + '@next/swc-linux-arm64-gnu@15.5.15': + optional: true + + '@next/swc-linux-arm64-musl@15.5.15': + optional: true + + '@next/swc-linux-x64-gnu@15.5.15': + optional: true + + '@next/swc-linux-x64-musl@15.5.15': + optional: true + + '@next/swc-win32-arm64-msvc@15.5.15': + optional: true + + '@next/swc-win32-x64-msvc@15.5.15': + optional: true + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@nolyfill/is-core-module@1.0.39': {} + + '@opentelemetry/api@1.9.0': {} + + '@petamoriken/float16@3.9.3': {} + + '@rtsao/scc@1.1.0': {} + + '@rushstack/eslint-patch@1.16.1': {} + + '@swc/helpers@0.5.15': + dependencies: + tslib: 2.8.1 + + '@tybys/wasm-util@0.10.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/diff-match-patch@1.0.36': {} + + '@types/estree@1.0.8': {} + + '@types/json-schema@7.0.15': {} + + '@types/json5@0.0.29': {} + + '@types/node@22.19.17': + dependencies: + undici-types: 6.21.0 + + '@types/react-dom@19.2.3(@types/react@19.2.14)': + dependencies: + '@types/react': 19.2.14 + + '@types/react@19.2.14': + dependencies: + csstype: 3.2.3 + + '@typescript-eslint/eslint-plugin@8.59.0(@typescript-eslint/parser@8.59.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.59.0(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.59.0 + '@typescript-eslint/type-utils': 8.59.0(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/utils': 8.59.0(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.59.0 + eslint: 9.39.4 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.59.0(eslint@9.39.4)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.59.0 + '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/typescript-estree': 8.59.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.59.0 + debug: 4.4.3 + eslint: 9.39.4 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.59.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.59.0(typescript@5.9.3) + '@typescript-eslint/types': 8.59.0 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.59.0': + dependencies: + '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/visitor-keys': 8.59.0 + + '@typescript-eslint/tsconfig-utils@8.59.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.59.0(eslint@9.39.4)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/typescript-estree': 8.59.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.59.0(eslint@9.39.4)(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.4 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.59.0': {} + + '@typescript-eslint/typescript-estree@8.59.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.59.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.59.0(typescript@5.9.3) + '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/visitor-keys': 8.59.0 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.7.4 + tinyglobby: 0.2.16 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.59.0(eslint@9.39.4)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) + '@typescript-eslint/scope-manager': 8.59.0 + '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/typescript-estree': 8.59.0(typescript@5.9.3) + eslint: 9.39.4 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.59.0': + dependencies: + '@typescript-eslint/types': 8.59.0 + eslint-visitor-keys: 5.0.1 + + '@unrs/resolver-binding-android-arm-eabi@1.11.1': + optional: true + + '@unrs/resolver-binding-android-arm64@1.11.1': + optional: true + + '@unrs/resolver-binding-darwin-arm64@1.11.1': + optional: true + + '@unrs/resolver-binding-darwin-x64@1.11.1': + optional: true + + '@unrs/resolver-binding-freebsd-x64@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm64-musl@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-x64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-x64-musl@1.11.1': + optional: true + + '@unrs/resolver-binding-wasm32-wasi@1.11.1': + dependencies: + '@napi-rs/wasm-runtime': 0.2.12 + optional: true + + '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': + optional: true + + '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': + optional: true + + '@unrs/resolver-binding-win32-x64-msvc@1.11.1': + optional: true + + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + ai@4.3.19(react@19.2.5)(zod@3.25.76): + dependencies: + '@ai-sdk/provider': 1.1.3 + '@ai-sdk/provider-utils': 2.2.8(zod@3.25.76) + '@ai-sdk/react': 1.2.12(react@19.2.5)(zod@3.25.76) + '@ai-sdk/ui-utils': 1.2.11(zod@3.25.76) + '@opentelemetry/api': 1.9.0 + jsondiffpatch: 0.6.0 + zod: 3.25.76 + optionalDependencies: + react: 19.2.5 + + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + argparse@2.0.1: {} + + aria-query@5.3.2: {} + + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-includes@3.1.9: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + is-string: 1.1.1 + math-intrinsics: 1.1.0 + + array.prototype.findlast@1.2.5: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + + array.prototype.findlastindex@1.2.6: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + + array.prototype.flat@1.3.3: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-shim-unscopables: 1.1.0 + + array.prototype.flatmap@1.3.3: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-shim-unscopables: 1.1.0 + + array.prototype.tosorted@1.1.4: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-shim-unscopables: 1.1.0 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + ast-types-flow@0.0.8: {} + + async-function@1.0.0: {} + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + axe-core@4.11.3: {} + + axobject-query@4.1.0: {} + + balanced-match@1.0.2: {} + + balanced-match@4.0.4: {} + + brace-expansion@1.1.14: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@5.0.5: + dependencies: + balanced-match: 4.0.4 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + buffer-from@1.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.9: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001790: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.6.2: {} + + client-only@0.0.1: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + concat-map@0.0.1: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + csstype@3.2.3: {} + + damerau-levenshtein@1.0.8: {} + + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + debug@3.2.7: + dependencies: + ms: 2.1.3 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + dequal@2.0.3: {} + + detect-libc@2.1.2: + optional: true + + diff-match-patch@1.0.5: {} + + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + + dotenv@16.6.1: {} + + drizzle-kit@0.30.6: + dependencies: + '@drizzle-team/brocli': 0.10.2 + '@esbuild-kit/esm-loader': 2.6.5 + esbuild: 0.19.12 + esbuild-register: 3.6.0(esbuild@0.19.12) + gel: 2.2.0 + transitivePeerDependencies: + - supports-color + + drizzle-orm@0.38.4(@opentelemetry/api@1.9.0)(@types/react@19.2.14)(postgres@3.4.9)(react@19.2.5): + optionalDependencies: + '@opentelemetry/api': 1.9.0 + '@types/react': 19.2.14 + postgres: 3.4.9 + react: 19.2.5 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + emoji-regex@9.2.2: {} + + env-paths@3.0.0: {} + + es-abstract@1.24.2: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.3 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.4 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.20 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-iterator-helpers@1.3.2: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-set-tostringtag: 2.1.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + iterator.prototype: 1.1.5 + math-intrinsics: 1.1.0 + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.3 + + es-shim-unscopables@1.1.0: + dependencies: + hasown: 2.0.3 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + + esbuild-register@3.6.0(esbuild@0.19.12): + dependencies: + debug: 4.4.3 + esbuild: 0.19.12 + transitivePeerDependencies: + - supports-color + + esbuild@0.18.20: + optionalDependencies: + '@esbuild/android-arm': 0.18.20 + '@esbuild/android-arm64': 0.18.20 + '@esbuild/android-x64': 0.18.20 + '@esbuild/darwin-arm64': 0.18.20 + '@esbuild/darwin-x64': 0.18.20 + '@esbuild/freebsd-arm64': 0.18.20 + '@esbuild/freebsd-x64': 0.18.20 + '@esbuild/linux-arm': 0.18.20 + '@esbuild/linux-arm64': 0.18.20 + '@esbuild/linux-ia32': 0.18.20 + '@esbuild/linux-loong64': 0.18.20 + '@esbuild/linux-mips64el': 0.18.20 + '@esbuild/linux-ppc64': 0.18.20 + '@esbuild/linux-riscv64': 0.18.20 + '@esbuild/linux-s390x': 0.18.20 + '@esbuild/linux-x64': 0.18.20 + '@esbuild/netbsd-x64': 0.18.20 + '@esbuild/openbsd-x64': 0.18.20 + '@esbuild/sunos-x64': 0.18.20 + '@esbuild/win32-arm64': 0.18.20 + '@esbuild/win32-ia32': 0.18.20 + '@esbuild/win32-x64': 0.18.20 + + esbuild@0.19.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.19.12 + '@esbuild/android-arm': 0.19.12 + '@esbuild/android-arm64': 0.19.12 + '@esbuild/android-x64': 0.19.12 + '@esbuild/darwin-arm64': 0.19.12 + '@esbuild/darwin-x64': 0.19.12 + '@esbuild/freebsd-arm64': 0.19.12 + '@esbuild/freebsd-x64': 0.19.12 + '@esbuild/linux-arm': 0.19.12 + '@esbuild/linux-arm64': 0.19.12 + '@esbuild/linux-ia32': 0.19.12 + '@esbuild/linux-loong64': 0.19.12 + '@esbuild/linux-mips64el': 0.19.12 + '@esbuild/linux-ppc64': 0.19.12 + '@esbuild/linux-riscv64': 0.19.12 + '@esbuild/linux-s390x': 0.19.12 + '@esbuild/linux-x64': 0.19.12 + '@esbuild/netbsd-x64': 0.19.12 + '@esbuild/openbsd-x64': 0.19.12 + '@esbuild/sunos-x64': 0.19.12 + '@esbuild/win32-arm64': 0.19.12 + '@esbuild/win32-ia32': 0.19.12 + '@esbuild/win32-x64': 0.19.12 + + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + + escape-string-regexp@4.0.0: {} + + eslint-config-next@15.5.15(eslint@9.39.4)(typescript@5.9.3): + dependencies: + '@next/eslint-plugin-next': 15.5.15 + '@rushstack/eslint-patch': 1.16.1 + '@typescript-eslint/eslint-plugin': 8.59.0(@typescript-eslint/parser@8.59.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/parser': 8.59.0(eslint@9.39.4)(typescript@5.9.3) + eslint: 9.39.4 + eslint-import-resolver-node: 0.3.10 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.0(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4) + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4) + eslint-plugin-react: 7.37.5(eslint@9.39.4) + eslint-plugin-react-hooks: 5.2.0(eslint@9.39.4) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - eslint-import-resolver-webpack + - eslint-plugin-import-x + - supports-color + + eslint-import-resolver-node@0.3.10: + dependencies: + debug: 3.2.7 + is-core-module: 2.16.1 + resolve: 2.0.0-next.6 + transitivePeerDependencies: + - supports-color + + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4): + dependencies: + '@nolyfill/is-core-module': 1.0.39 + debug: 4.4.3 + eslint: 9.39.4 + get-tsconfig: 4.14.0 + is-bun-module: 2.0.0 + stable-hash: 0.0.5 + tinyglobby: 0.2.16 + unrs-resolver: 1.11.1 + optionalDependencies: + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.0(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4) + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.59.0(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.59.0(eslint@9.39.4)(typescript@5.9.3) + eslint: 9.39.4 + eslint-import-resolver-node: 0.3.10 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4) + transitivePeerDependencies: + - supports-color + + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.0(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.9 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 9.39.4 + eslint-import-resolver-node: 0.3.10 + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.59.0(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4) + hasown: 2.0.3 + is-core-module: 2.16.1 + is-glob: 4.0.3 + minimatch: 3.1.5 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 + semver: 6.3.1 + string.prototype.trimend: 1.0.9 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.59.0(eslint@9.39.4)(typescript@5.9.3) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.4): + dependencies: + aria-query: 5.3.2 + array-includes: 3.1.9 + array.prototype.flatmap: 1.3.3 + ast-types-flow: 0.0.8 + axe-core: 4.11.3 + axobject-query: 4.1.0 + damerau-levenshtein: 1.0.8 + emoji-regex: 9.2.2 + eslint: 9.39.4 + hasown: 2.0.3 + jsx-ast-utils: 3.3.5 + language-tags: 1.0.9 + minimatch: 3.1.5 + object.fromentries: 2.0.8 + safe-regex-test: 1.1.0 + string.prototype.includes: 2.0.1 + + eslint-plugin-react-hooks@5.2.0(eslint@9.39.4): + dependencies: + eslint: 9.39.4 + + eslint-plugin-react@7.37.5(eslint@9.39.4): + dependencies: + array-includes: 3.1.9 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.3 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.3.2 + eslint: 9.39.4 + estraverse: 5.3.0 + hasown: 2.0.3 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.5 + object.entries: 1.1.9 + object.fromentries: 2.0.8 + object.values: 1.2.1 + prop-types: 15.8.1 + resolve: 2.0.0-next.6 + semver: 6.3.1 + string.prototype.matchall: 4.0.12 + string.prototype.repeat: 1.0.0 + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@9.39.4: + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.2 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.5 + '@eslint/js': 9.39.4 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.15.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.1: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + + flatted@3.4.2: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.3 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + + gel@2.2.0: + dependencies: + '@petamoriken/float16': 3.9.3 + debug: 4.4.3 + env-paths: 3.0.0 + semver: 7.7.4 + shell-quote: 1.8.3 + which: 4.0.0 + transitivePeerDependencies: + - supports-color + + generator-function@2.0.1: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.3 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + get-tsconfig@4.14.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@14.0.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + gopd@1.2.0: {} + + has-bigints@1.1.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.3: + dependencies: + function-bind: 1.1.2 + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.3 + side-channel: 1.1.0 + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-bun-module@2.0.0: + dependencies: + semver: 7.7.4 + + is-callable@1.2.7: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.3 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-generator-function@1.1.2: + dependencies: + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.3 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.20 + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + isexe@3.1.5: {} + + iterator.prototype@1.1.5: + dependencies: + define-data-property: 1.1.4 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + has-symbols: 1.1.0 + set-function-name: 2.0.2 + + js-tokens@4.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema@0.4.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@1.0.2: + dependencies: + minimist: 1.2.8 + + jsondiffpatch@0.6.0: + dependencies: + '@types/diff-match-patch': 1.0.36 + chalk: 5.6.2 + diff-match-patch: 1.0.5 + + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.9 + array.prototype.flat: 1.3.3 + object.assign: 4.1.7 + object.values: 1.2.1 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + language-subtag-registry@0.3.23: {} + + language-tags@1.0.9: + dependencies: + language-subtag-registry: 0.3.23 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + math-intrinsics@1.1.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.5 + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.14 + + minimist@1.2.8: {} + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + napi-postinstall@0.3.4: {} + + natural-compare@1.4.0: {} + + next@15.5.15(@opentelemetry/api@1.9.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5): + dependencies: + '@next/env': 15.5.15 + '@swc/helpers': 0.5.15 + caniuse-lite: 1.0.30001790 + postcss: 8.4.31 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + styled-jsx: 5.1.6(react@19.2.5) + optionalDependencies: + '@next/swc-darwin-arm64': 15.5.15 + '@next/swc-darwin-x64': 15.5.15 + '@next/swc-linux-arm64-gnu': 15.5.15 + '@next/swc-linux-arm64-musl': 15.5.15 + '@next/swc-linux-x64-gnu': 15.5.15 + '@next/swc-linux-x64-musl': 15.5.15 + '@next/swc-win32-arm64-msvc': 15.5.15 + '@next/swc-win32-x64-msvc': 15.5.15 + '@opentelemetry/api': 1.9.0 + sharp: 0.34.5 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + node-exports-info@1.6.0: + dependencies: + array.prototype.flatmap: 1.3.3 + es-errors: 1.3.0 + object.entries: 1.1.9 + semver: 6.3.1 + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.entries@1.1.9: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.1 + + object.groupby@1.0.3: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + + object.values@1.2.1: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + ollama-ai-provider@1.2.0(zod@3.25.76): + dependencies: + '@ai-sdk/provider': 1.1.3 + '@ai-sdk/provider-utils': 2.2.8(zod@3.25.76) + partial-json: 0.1.7 + optionalDependencies: + zod: 3.25.76 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + partial-json@0.1.7: {} + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.4: {} + + possible-typed-array-names@1.1.0: {} + + postcss@8.4.31: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + postgres@3.4.9: {} + + prelude-ls@1.2.1: {} + + prettier@3.8.3: {} + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + react-dom@19.2.5(react@19.2.5): + dependencies: + react: 19.2.5 + scheduler: 0.27.0 + + react-is@16.13.1: {} + + react@19.2.5: {} + + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + resolve-from@4.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + resolve@2.0.0-next.6: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.1 + node-exports-info: 1.6.0 + object-keys: 1.1.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.1.0: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-array-concat@1.1.4: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + scheduler@0.27.0: {} + + secure-json-parse@2.7.0: {} + + semver@6.3.1: {} + + semver@7.7.4: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + + sharp@0.34.5: + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.7.4 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + optional: true + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shell-quote@1.8.3: {} + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + source-map-js@1.2.1: {} + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + stable-hash@0.0.5: {} + + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + + string.prototype.includes@2.0.1: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + + string.prototype.matchall@4.0.12: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + regexp.prototype.flags: 1.5.4 + set-function-name: 2.0.2 + side-channel: 1.1.0 + + string.prototype.repeat@1.0.0: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.24.2 + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + strip-bom@3.0.0: {} + + strip-json-comments@3.1.1: {} + + styled-jsx@5.1.6(react@19.2.5): + dependencies: + client-only: 0.0.1 + react: 19.2.5 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + swr@2.4.1(react@19.2.5): + dependencies: + dequal: 2.0.3 + react: 19.2.5 + use-sync-external-store: 1.6.0(react@19.2.5) + + throttleit@2.1.0: {} + + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + ts-api-utils@2.5.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + tsconfig-paths@3.15.0: + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tslib@2.8.1: {} + + tsx@4.21.0: + dependencies: + esbuild: 0.27.7 + get-tsconfig: 4.14.0 + optionalDependencies: + fsevents: 2.3.3 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.7: + dependencies: + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typescript@5.9.3: {} + + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + + undici-types@6.21.0: {} + + unrs-resolver@1.11.1: + dependencies: + napi-postinstall: 0.3.4 + optionalDependencies: + '@unrs/resolver-binding-android-arm-eabi': 1.11.1 + '@unrs/resolver-binding-android-arm64': 1.11.1 + '@unrs/resolver-binding-darwin-arm64': 1.11.1 + '@unrs/resolver-binding-darwin-x64': 1.11.1 + '@unrs/resolver-binding-freebsd-x64': 1.11.1 + '@unrs/resolver-binding-linux-arm-gnueabihf': 1.11.1 + '@unrs/resolver-binding-linux-arm-musleabihf': 1.11.1 + '@unrs/resolver-binding-linux-arm64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-arm64-musl': 1.11.1 + '@unrs/resolver-binding-linux-ppc64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-riscv64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-riscv64-musl': 1.11.1 + '@unrs/resolver-binding-linux-s390x-gnu': 1.11.1 + '@unrs/resolver-binding-linux-x64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-x64-musl': 1.11.1 + '@unrs/resolver-binding-wasm32-wasi': 1.11.1 + '@unrs/resolver-binding-win32-arm64-msvc': 1.11.1 + '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 + '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + use-sync-external-store@1.6.0(react@19.2.5): + dependencies: + react: 19.2.5 + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.2 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.20 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.20: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + which@4.0.0: + dependencies: + isexe: 3.1.5 + + word-wrap@1.2.5: {} + + yocto-queue@0.1.0: {} + + zod-to-json-schema@3.25.2(zod@3.25.76): + dependencies: + zod: 3.25.76 + + zod@3.25.76: {} From 5e199eab3375eb73887ab5b9b510b4e11300f8f2 Mon Sep 17 00:00:00 2001 From: Daniel David Date: Sun, 26 Apr 2026 12:37:37 +1000 Subject: [PATCH 08/21] fix(agent,rag): append /api to Ollama base URL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ollama-ai-provider's default baseURL is http://localhost:11434/api — the trailing /api is mandatory because the provider concatenates routes like /chat and /embeddings onto it. Passing the bare host (http://ollama:11434) made every request 404, surfacing as `3:"Not Found"` in the chat stream. Add normalizeOllamaBaseURL() in @app/rag and use it from both the agent and the embedder. Accepts bare-host or already-suffixed values. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/agent/src/agent.ts | 3 ++- packages/rag/src/embeddings.ts | 12 +++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/agent/src/agent.ts b/packages/agent/src/agent.ts index 68d5c9c..5a386e0 100644 --- a/packages/agent/src/agent.ts +++ b/packages/agent/src/agent.ts @@ -1,5 +1,6 @@ import { streamText, type CoreMessage } from 'ai'; import { createOllama } from 'ollama-ai-provider'; +import { normalizeOllamaBaseURL } from '@app/rag'; import { buildTools, type ToolDeps } from './tools.js'; import { SYSTEM_PROMPT } from './prompts.js'; @@ -13,7 +14,7 @@ export interface RunAgentOptions { } export function runAgent(opts: RunAgentOptions) { - const ollama = createOllama({ baseURL: opts.baseURL }); + const ollama = createOllama({ baseURL: normalizeOllamaBaseURL(opts.baseURL) }); const tools = buildTools(opts.toolDeps); return streamText({ diff --git a/packages/rag/src/embeddings.ts b/packages/rag/src/embeddings.ts index cc63806..b0ff4ba 100644 --- a/packages/rag/src/embeddings.ts +++ b/packages/rag/src/embeddings.ts @@ -7,7 +7,7 @@ export interface EmbeddingClientOptions { } export function createEmbedder(opts: EmbeddingClientOptions) { - const ollama = createOllama({ baseURL: opts.baseURL }); + const ollama = createOllama({ baseURL: normalizeOllamaBaseURL(opts.baseURL) }); const model = ollama.embedding(opts.model); return { @@ -24,3 +24,13 @@ export function createEmbedder(opts: EmbeddingClientOptions) { } export type Embedder = ReturnType; + +/** + * `ollama-ai-provider` expects the base URL to point at Ollama's `/api` + * prefix (its default is `http://localhost:11434/api`). Accept either form + * from env and normalize so callers can pass the bare host. + */ +export function normalizeOllamaBaseURL(baseURL: string): string { + const trimmed = baseURL.replace(/\/+$/, ''); + return trimmed.endsWith('/api') ? trimmed : `${trimmed}/api`; +} From fdda9f9d4ce1aa191b97eb36b6bfe89b6043ecb9 Mon Sep 17 00:00:00 2001 From: Daniel David Date: Sun, 26 Apr 2026 12:46:01 +1000 Subject: [PATCH 09/21] chore(infra): point Compose at remote Ollama, drop in-stack ollama service Confirmed working end-to-end against an Ollama server on the LAN (http://192.168.4.90:11434) hosting llama3.1:8b + nomic-embed-text: ingested a doc, asked a question, got a tool-grounded cited response. - docker-compose.yml: remove `ollama` and `ollama-init` services. Web no longer waits on ollama-init. Add OLLAMA_BASE_URL pass-through with the LAN host as the default. Also pass through RAG_TOP_K / RAG_CHUNK_SIZE / RAG_CHUNK_OVERLAP so /api tuning works without code changes. - .env / .env.example: switch OLLAMA_BASE_URL to the LAN URL and document the alternatives (localhost, host.docker.internal, LAN host). Co-Authored-By: Claude Opus 4.7 (1M context) --- .env.example | 13 +++++++++---- docker-compose.yml | 47 ++++++---------------------------------------- 2 files changed, 15 insertions(+), 45 deletions(-) diff --git a/.env.example b/.env.example index a6c71c1..b6d3010 100644 --- a/.env.example +++ b/.env.example @@ -6,11 +6,16 @@ POSTGRES_PORT=5432 DATABASE_URL=postgres://rag:rag@localhost:5432/rag # ---- Ollama ---- -# Base URL for the Ollama HTTP API (host machine: http://localhost:11434). -OLLAMA_BASE_URL=http://localhost:11434 -# Chat / generation model. Pull with: ollama pull llama3.1:8b +# Base URL for the Ollama HTTP API. Examples: +# - LAN host: http://192.168.4.90:11434 +# - Same host: http://localhost:11434 +# - Docker host (web container reaching host Ollama on macOS/Windows): +# http://host.docker.internal:11434 +# The /api suffix is appended automatically. +OLLAMA_BASE_URL=http://192.168.4.90:11434 +# Chat / generation model (must be pulled on the Ollama host). OLLAMA_CHAT_MODEL=llama3.1:8b -# Embedding model. Pull with: ollama pull nomic-embed-text +# Embedding model (must be pulled on the Ollama host). OLLAMA_EMBED_MODEL=nomic-embed-text # Embedding dimension must match the model (nomic-embed-text = 768). EMBEDDING_DIM=768 diff --git a/docker-compose.yml b/docker-compose.yml index 1371746..895e22f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,43 +18,6 @@ services: timeout: 5s retries: 10 - ollama: - image: ollama/ollama:latest - container_name: rag-ollama - restart: unless-stopped - ports: - - "11434:11434" - volumes: - - ollama:/root/.ollama - healthcheck: - test: ["CMD", "ollama", "list"] - interval: 10s - timeout: 5s - retries: 10 - - # One-shot: pulls the configured chat + embedding models into the ollama - # volume on first boot. Idempotent — re-running just verifies presence. - ollama-init: - image: ollama/ollama:latest - container_name: rag-ollama-init - depends_on: - ollama: - condition: service_healthy - environment: - OLLAMA_HOST: http://ollama:11434 - OLLAMA_CHAT_MODEL: ${OLLAMA_CHAT_MODEL:-llama3.1:8b} - OLLAMA_EMBED_MODEL: ${OLLAMA_EMBED_MODEL:-nomic-embed-text} - entrypoint: ["/bin/sh", "-c"] - command: - - | - set -e - echo "Pulling chat model: $$OLLAMA_CHAT_MODEL" - ollama pull "$$OLLAMA_CHAT_MODEL" - echo "Pulling embed model: $$OLLAMA_EMBED_MODEL" - ollama pull "$$OLLAMA_EMBED_MODEL" - echo "Ollama models ready." - restart: "no" - # One-shot: runs Drizzle migrations against the postgres service. Web waits # for this to complete successfully before starting. migrate: @@ -81,18 +44,20 @@ services: depends_on: migrate: condition: service_completed_successfully - ollama-init: - condition: service_completed_successfully environment: DATABASE_URL: postgres://${POSTGRES_USER:-rag}:${POSTGRES_PASSWORD:-rag}@postgres:5432/${POSTGRES_DB:-rag} - OLLAMA_BASE_URL: http://ollama:11434 + # Ollama runs on a LAN host, not in this Compose stack. Override + # OLLAMA_BASE_URL in .env if your Ollama lives elsewhere. + OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-http://192.168.4.90:11434} OLLAMA_CHAT_MODEL: ${OLLAMA_CHAT_MODEL:-llama3.1:8b} OLLAMA_EMBED_MODEL: ${OLLAMA_EMBED_MODEL:-nomic-embed-text} EMBEDDING_DIM: ${EMBEDDING_DIM:-768} + RAG_TOP_K: ${RAG_TOP_K:-6} + RAG_CHUNK_SIZE: ${RAG_CHUNK_SIZE:-800} + RAG_CHUNK_OVERLAP: ${RAG_CHUNK_OVERLAP:-120} NODE_ENV: production ports: - "3000:3000" volumes: pgdata: - ollama: From 1240fb0b255a7609e925970d223fb0ec6c4aa09a Mon Sep 17 00:00:00 2001 From: Daniel David Date: Sun, 26 Apr 2026 13:02:58 +1000 Subject: [PATCH 10/21] feat(web,rag,db): document upload UI for txt / md / pdf MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a sidebar where users can ingest documents into the knowledge base without leaving the chat UI. Backend: - packages/rag: add unpdf dep + extractPdfText() helper. Joins pages with blank lines so the existing chunker treats them as paragraph breaks. Throws on image-only / encrypted PDFs (callers map to 422). - packages/db: add listDocuments() and deleteDocument() helpers so the web layer doesn't import drizzle-orm directly. The web bundle already failed to build when /api/documents/[id] reached for `eq` from drizzle-orm — keep ORM use inside @app/db. - apps/web/api/sources GET: returns documents (id, source, title, chunk count, metadata, created_at) newest-first. - apps/web/api/ingest/upload POST: accepts multipart with a `file` field. Detects .txt / .md / .markdown / text/* as text; .pdf / application/pdf as PDF. 25 MB cap. 422 on extraction failure. - apps/web/api/documents/[id] DELETE: cascade-deletes via the helper, 204 on success / 404 on miss. Frontend: - New SourcesPanel component (left sidebar): file picker, paste-text form, list of ingested documents with chunk counts and a delete button. Auto-refreshes after each mutation. - Home page restructured to a two-column grid (sidebar | chat). Verified end-to-end against the LAN Ollama: ingested .txt / .md / .pdf files, asked questions, agent retrieved + cited each, delete removed the row from the listing. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/web/src/app/api/documents/[id]/route.ts | 20 + apps/web/src/app/api/ingest/upload/route.ts | 106 +++++ apps/web/src/app/api/sources/route.ts | 12 + apps/web/src/app/page.tsx | 20 +- apps/web/src/components/sources-panel.tsx | 268 +++++++++++ packages/db/src/index.ts | 1 + packages/db/src/queries.ts | 56 +++ packages/rag/package.json | 1 + packages/rag/src/index.ts | 1 + packages/rag/src/pdf.ts | 26 + pnpm-lock.yaml | 469 +++++++++++++++++++ 11 files changed, 977 insertions(+), 3 deletions(-) create mode 100644 apps/web/src/app/api/documents/[id]/route.ts create mode 100644 apps/web/src/app/api/ingest/upload/route.ts create mode 100644 apps/web/src/app/api/sources/route.ts create mode 100644 apps/web/src/components/sources-panel.tsx create mode 100644 packages/db/src/queries.ts create mode 100644 packages/rag/src/pdf.ts diff --git a/apps/web/src/app/api/documents/[id]/route.ts b/apps/web/src/app/api/documents/[id]/route.ts new file mode 100644 index 0000000..5bf4d7d --- /dev/null +++ b/apps/web/src/app/api/documents/[id]/route.ts @@ -0,0 +1,20 @@ +import { NextResponse } from 'next/server'; +import { z } from 'zod'; +import { deleteDocument } from '@app/db'; + +export const runtime = 'nodejs'; +export const dynamic = 'force-dynamic'; + +const ParamsSchema = z.object({ id: z.string().uuid() }); + +export async function DELETE(_req: Request, ctx: { params: Promise<{ id: string }> }) { + const parsed = ParamsSchema.safeParse(await ctx.params); + if (!parsed.success) { + return NextResponse.json({ error: 'Invalid id' }, { status: 400 }); + } + const deletedId = await deleteDocument(parsed.data.id); + if (!deletedId) { + return NextResponse.json({ error: 'Not found' }, { status: 404 }); + } + return new NextResponse(null, { status: 204 }); +} diff --git a/apps/web/src/app/api/ingest/upload/route.ts b/apps/web/src/app/api/ingest/upload/route.ts new file mode 100644 index 0000000..267e96e --- /dev/null +++ b/apps/web/src/app/api/ingest/upload/route.ts @@ -0,0 +1,106 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { extractPdfText, ingestDocument } from '@app/rag'; +import { getEnv } from '@/lib/env'; +import { getEmbedder } from '@/lib/embedder'; + +export const runtime = 'nodejs'; +export const dynamic = 'force-dynamic'; +// Allow large PDFs. +export const maxDuration = 60; + +const MAX_BYTES = 25 * 1024 * 1024; // 25 MB + +type Kind = 'text' | 'pdf'; + +function detectKind(filename: string, mime: string): Kind | null { + const lower = filename.toLowerCase(); + if (lower.endsWith('.pdf') || mime === 'application/pdf') return 'pdf'; + if ( + lower.endsWith('.txt') || + lower.endsWith('.md') || + lower.endsWith('.markdown') || + mime.startsWith('text/') + ) { + return 'text'; + } + return null; +} + +export async function POST(req: NextRequest) { + let form: FormData; + try { + form = await req.formData(); + } catch { + return NextResponse.json({ error: 'Expected multipart/form-data' }, { status: 400 }); + } + + const file = form.get('file'); + if (!(file instanceof File)) { + return NextResponse.json({ error: 'Missing `file` field' }, { status: 400 }); + } + if (file.size === 0) { + return NextResponse.json({ error: 'Empty file' }, { status: 400 }); + } + if (file.size > MAX_BYTES) { + return NextResponse.json( + { error: `File too large (${file.size} > ${MAX_BYTES} bytes)` }, + { status: 413 }, + ); + } + + const kind = detectKind(file.name, file.type); + if (!kind) { + return NextResponse.json( + { error: `Unsupported file type: ${file.name} (${file.type || 'unknown'}). Allowed: .txt, .md, .pdf` }, + { status: 415 }, + ); + } + + let content: string; + const metadata: Record = { + filename: file.name, + mimeType: file.type || (kind === 'pdf' ? 'application/pdf' : 'text/plain'), + bytes: file.size, + }; + + try { + if (kind === 'pdf') { + const buf = new Uint8Array(await file.arrayBuffer()); + const { text, pageCount } = await extractPdfText(buf); + content = text; + metadata.pageCount = pageCount; + } else { + content = (await file.text()).trim(); + if (content.length === 0) { + return NextResponse.json({ error: 'File contains no text' }, { status: 400 }); + } + } + } catch (err) { + const message = err instanceof Error ? err.message : 'Failed to extract text'; + return NextResponse.json({ error: message }, { status: 422 }); + } + + const titleField = form.get('title'); + const title = typeof titleField === 'string' && titleField.trim().length > 0 ? titleField.trim() : file.name; + + const env = getEnv(); + const embedder = getEmbedder(); + const result = await ingestDocument( + { + source: file.name, + title, + content, + metadata, + }, + { + chunkSize: env.RAG_CHUNK_SIZE, + chunkOverlap: env.RAG_CHUNK_OVERLAP, + embedder, + }, + ); + + return NextResponse.json( + { ...result, source: file.name, kind, bytes: file.size }, + { status: 201 }, + ); +} diff --git a/apps/web/src/app/api/sources/route.ts b/apps/web/src/app/api/sources/route.ts new file mode 100644 index 0000000..6b72c4a --- /dev/null +++ b/apps/web/src/app/api/sources/route.ts @@ -0,0 +1,12 @@ +import { NextResponse } from 'next/server'; +import { listDocuments } from '@app/db'; + +export const runtime = 'nodejs'; +export const dynamic = 'force-dynamic'; + +export async function GET() { + const documents = await listDocuments(); + return NextResponse.json({ + documents: documents.map((d) => ({ ...d, createdAt: d.createdAt.toISOString() })), + }); +} diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx index f2d9c4b..f3277e9 100644 --- a/apps/web/src/app/page.tsx +++ b/apps/web/src/app/page.tsx @@ -1,12 +1,15 @@ +'use client'; + import { Chat } from '@/components/chat'; +import { SourcesPanel } from '@/components/sources-panel'; export default function HomePage() { return (
- +
+ + +
); } diff --git a/apps/web/src/components/sources-panel.tsx b/apps/web/src/components/sources-panel.tsx new file mode 100644 index 0000000..47edaad --- /dev/null +++ b/apps/web/src/components/sources-panel.tsx @@ -0,0 +1,268 @@ +'use client'; + +import { useCallback, useEffect, useRef, useState } from 'react'; + +interface DocumentRow { + id: string; + source: string; + title: string | null; + chunkCount: number; + metadata: Record; + createdAt: string; +} + +type Status = + | { kind: 'idle' } + | { kind: 'busy'; message: string } + | { kind: 'ok'; message: string } + | { kind: 'error'; message: string }; + +export function SourcesPanel({ onChange }: { onChange?: () => void }) { + const [docs, setDocs] = useState([]); + const [status, setStatus] = useState({ kind: 'idle' }); + const [pasteOpen, setPasteOpen] = useState(false); + const [pasteSource, setPasteSource] = useState(''); + const [pasteText, setPasteText] = useState(''); + const fileInput = useRef(null); + + const refresh = useCallback(async () => { + const r = await fetch('/api/sources', { cache: 'no-store' }); + if (!r.ok) { + setStatus({ kind: 'error', message: `Sources fetch failed: ${r.status}` }); + return; + } + const json = (await r.json()) as { documents: DocumentRow[] }; + setDocs(json.documents); + }, []); + + useEffect(() => { + void refresh(); + }, [refresh]); + + const onUpload = useCallback( + async (file: File) => { + setStatus({ kind: 'busy', message: `Uploading ${file.name}…` }); + const fd = new FormData(); + fd.append('file', file); + const r = await fetch('/api/ingest/upload', { method: 'POST', body: fd }); + if (!r.ok) { + const err = await r.json().catch(() => ({ error: r.statusText })); + setStatus({ kind: 'error', message: err.error ?? `Upload failed: ${r.status}` }); + return; + } + const json = (await r.json()) as { chunkCount: number }; + setStatus({ kind: 'ok', message: `Ingested ${file.name} (${json.chunkCount} chunks)` }); + await refresh(); + onChange?.(); + }, + [refresh, onChange], + ); + + const onPaste = useCallback(async () => { + const source = pasteSource.trim(); + const content = pasteText.trim(); + if (!source || !content) { + setStatus({ kind: 'error', message: 'Source and content are required.' }); + return; + } + setStatus({ kind: 'busy', message: `Ingesting ${source}…` }); + const r = await fetch('/api/ingest', { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ source, title: source, content }), + }); + if (!r.ok) { + const err = await r.json().catch(() => ({ error: r.statusText })); + setStatus({ kind: 'error', message: err.error ?? `Ingest failed: ${r.status}` }); + return; + } + const json = (await r.json()) as { chunkCount: number }; + setStatus({ kind: 'ok', message: `Ingested ${source} (${json.chunkCount} chunks)` }); + setPasteText(''); + setPasteSource(''); + setPasteOpen(false); + await refresh(); + onChange?.(); + }, [pasteSource, pasteText, refresh, onChange]); + + const onDelete = useCallback( + async (doc: DocumentRow) => { + if (!confirm(`Delete ${doc.source}?`)) return; + setStatus({ kind: 'busy', message: `Deleting ${doc.source}…` }); + const r = await fetch(`/api/documents/${doc.id}`, { method: 'DELETE' }); + if (!r.ok && r.status !== 204) { + setStatus({ kind: 'error', message: `Delete failed: ${r.status}` }); + return; + } + setStatus({ kind: 'ok', message: `Deleted ${doc.source}` }); + await refresh(); + onChange?.(); + }, + [refresh, onChange], + ); + + return ( +