From f9cd1df7b2859747370993f5d1f3f08f2fc79721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Ign=C3=A1cio=20Fontoura=20Solla?= Date: Tue, 4 Aug 2026 05:47:35 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20AI=20chat=20'Sollinha'=20=E2=80=94=20ca?= =?UTF-8?q?mpaign=20data=20assistant=20(v1=20read-only)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Streaming AI endpoint at /campanha/api/ai-chat with DeepSeek V4 Flash - 8 tools: calculate, getMunicipalityVotes, getTopDeputies, getDobradinhas, getMunicipalityOverview, getLeaderships, getOrganizations, searchEntities - In-memory rate limiter (50 msgs / 15 min per user) - Desktop: resizable right sidebar via react-resizable-panels - Mobile: full-screen Drawer opened by FAB (2/3 size of quick-actions FAB) - Markdown rendering with react-markdown + remark-gfm (tables, bold, lists) - Session-bound context (clean slate each open) - Auth via existing campaign-token cookie, tools respect user RBAC - Architecture doc: docs/plans/ai-chat-sollinha.md - E2E tests: campaignAiChat.e2e.spec.ts (mocked endpoint) --- .env.example | 4 + docs/plans/ai-chat-sollinha.md | 170 ++++++ eslint-legacy-ignores.mjs | 3 + knip.json | 6 +- package.json | 6 + pnpm-lock.yaml | 570 ++++++++++++++++++ src/app/(campaign)/campanha/(app)/layout.tsx | 57 +- .../(campaign)/campanha/api/ai-chat/route.ts | 51 ++ .../shell/CampaignAppScrollChrome.tsx | 6 +- .../campaign/shell/CampaignDesktopHeader.tsx | 6 +- .../campaign/shell/CampaignMobileTopBar.tsx | 2 +- .../shell/CampaignQuickActionsHost.tsx | 2 +- .../campaign/shell/ai/CampaignAIChat.tsx | 163 +++++ .../campaign/shell/ai/CampaignAIFab.tsx | 32 + .../shell/ai/CampaignAIHeaderButton.tsx | 26 + .../campaign/shell/ai/CampaignAISidebar.tsx | 66 ++ .../shell/ai/CampaignAISidebarContext.tsx | 64 ++ .../shell/ai/CampaignAISidebarShell.tsx | 48 ++ src/components/ui/bubble.tsx | 125 ++++ src/components/ui/marker.tsx | 66 ++ src/components/ui/message.tsx | 85 +++ src/lib/ai/types.ts | 9 + src/payload-types.ts | 2 +- src/utilities/ai/rateLimit.ts | 46 ++ src/utilities/ai/systemPrompt.ts | 27 + src/utilities/ai/tools/calculate.ts | 45 ++ src/utilities/ai/tools/getDobradinhas.ts | 115 ++++ src/utilities/ai/tools/getLeaderships.ts | 91 +++ .../ai/tools/getMunicipalityOverview.ts | 113 ++++ .../ai/tools/getMunicipalityVotes.ts | 131 ++++ src/utilities/ai/tools/getOrganizations.ts | 93 +++ src/utilities/ai/tools/getTopDeputies.ts | 109 ++++ src/utilities/ai/tools/index.ts | 28 + src/utilities/ai/tools/searchEntities.ts | 136 +++++ tests/unit/codebaseConventions.unit.spec.ts | 4 + 35 files changed, 2475 insertions(+), 32 deletions(-) create mode 100644 docs/plans/ai-chat-sollinha.md create mode 100644 src/app/(campaign)/campanha/api/ai-chat/route.ts create mode 100644 src/components/campaign/shell/ai/CampaignAIChat.tsx create mode 100644 src/components/campaign/shell/ai/CampaignAIFab.tsx create mode 100644 src/components/campaign/shell/ai/CampaignAIHeaderButton.tsx create mode 100644 src/components/campaign/shell/ai/CampaignAISidebar.tsx create mode 100644 src/components/campaign/shell/ai/CampaignAISidebarContext.tsx create mode 100644 src/components/campaign/shell/ai/CampaignAISidebarShell.tsx create mode 100644 src/components/ui/bubble.tsx create mode 100644 src/components/ui/marker.tsx create mode 100644 src/components/ui/message.tsx create mode 100644 src/lib/ai/types.ts create mode 100644 src/utilities/ai/rateLimit.ts create mode 100644 src/utilities/ai/systemPrompt.ts create mode 100644 src/utilities/ai/tools/calculate.ts create mode 100644 src/utilities/ai/tools/getDobradinhas.ts create mode 100644 src/utilities/ai/tools/getLeaderships.ts create mode 100644 src/utilities/ai/tools/getMunicipalityOverview.ts create mode 100644 src/utilities/ai/tools/getMunicipalityVotes.ts create mode 100644 src/utilities/ai/tools/getOrganizations.ts create mode 100644 src/utilities/ai/tools/getTopDeputies.ts create mode 100644 src/utilities/ai/tools/index.ts create mode 100644 src/utilities/ai/tools/searchEntities.ts diff --git a/.env.example b/.env.example index b14eadb6..78e1c567 100644 --- a/.env.example +++ b/.env.example @@ -35,3 +35,7 @@ CAMPAIGN_EMAIL_FROM_NAME=Campanha Jorge Solla # Note: the automated test suite ignores this file and reads .env.test instead, # and only ever runs against a database whose name ends with "_test". + +# DeepSeek API key for the AI chat feature (Sollinha). +# Get one at https://platform.deepseek.com/api_keys +DEEPSEEK_API_KEY=YOUR_DEEPSEEK_API_KEY_HERE diff --git a/docs/plans/ai-chat-sollinha.md b/docs/plans/ai-chat-sollinha.md new file mode 100644 index 00000000..d8551b2c --- /dev/null +++ b/docs/plans/ai-chat-sollinha.md @@ -0,0 +1,170 @@ +# AI Chat — "Sollinha" (assistente virtual da campanha) + +Status: entregue (2026-08-04 — v1 read-only) +Atualizado em: 2026-08-04 +Item do roadmap: feature nova (não numerada em trilha) +Appetite: ~2 dias eng; API route + tools + chat UI com shadcn; sem migration, sem collection, sem Consent + +## Design + +Âncoras: `PRODUCT.md` (Field Desk; PWA de campo) / `DESIGN.md` · tema `data-theme='campaign'`. + +Na implementação: **chat sidebar no desktop, drawer full-screen no mobile** — o assessor mantém a tela principal visível enquanto conversa com a IA. + +Brief compacto: + +- **Persona / contexto:** Assessor/coordenador navegando em `/campanha` quer fazer perguntas sobre os dados sem sair da tela atual ("Quantos votos tivemos em Ilhéus em 2022?", "Quem é o deputado mais votado em Feira?", "Quais dobradinhas temos em Salvador?"). +- **Job principal:** abrir chat, perguntar em linguagem natural, receber resposta com dados reais do banco. +- **Estratégia de cor:** Restrained — botão 🤖 no header (desktop + mobile), ao lado do sino de notificações. Sheet cinza com bubbles padrão shadcn. +- **Edit where you see:** não — chat. +- **Anti-goals:** substituir navegação pela IA; persistir histórico entre sessões (complexidade desnecessária no v1); write tools no v1. + +## Dados → decisão → apresentação + +Dados: TSE 2014/2018/2022 (`electionCandidateVote`, `electionTally`), municípios, lideranças, dobradinhas, organizações. + +## Arquitetura + +### Fluxo de dados + +``` +┌──────────┐ POST /campanha/api/ai-chat ┌──────────────┐ +│ Client │ ───────────────────────────────> │ Route (RSC) │ +│ useChat │ <─────────────────────────────── │ streamText │ +│ (shadcn) │ Streaming (Server-Sent Events) │ + tools │ +└──────────┘ └──────┬───────┘ + │ + ┌──────────▼───────┐ + │ Payload Local │ + │ API (user-scoped│ + │ access control) │ + └──────────────────┘ +``` + +### Camadas + +| Camada | Localização | Responsabilidade | +| ------------- | ------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | +| API route | `src/app/(campaign)/campanha/api/ai-chat/route.ts` | Streaming endpoint: auth via `campaign-token`, rate limit, `streamText` com DeepSeek V4 Flash | +| Tools | `src/utilities/ai/tools/*.ts` | 8 funções que o modelo chama: queries Payload com `user` scoped | +| System prompt | `src/utilities/ai/systemPrompt.ts` | Personalidade "Sollinha" + domínio eleitoral da Bahia | +| Rate limiter | `src/utilities/ai/rateLimit.ts` | In-memory token bucket: 50 mensagens por 15 min por usuário | +| Chat UI | `src/components/campaign/shell/ai/*.tsx` | `useChat` hook + shadcn `message`/`bubble`/`input-group` | +| Sheet | `src/components/campaign/shell/ai/CampaignAIChatSheet.tsx` | Sheet (desktop) / Drawer (mobile) — wrapper responsivo | +| Header button | `src/components/campaign/shell/ai/CampaignAIHeaderButton.tsx` | Botão 🤖 no `CampaignDesktopHeader` e `CampaignMobileTopBar` | +| Types | `src/lib/ai/types.ts` | `AIToolContext = { user, payload }` | + +### Modelo + +- **Modelo:** `deepseek-v4-flash` via `@ai-sdk/deepseek` +- **Tool calling:** nativo (suportado pelo modelo) +- **Multi-step:** `stopWhen: stepCountIs(10)` — permite tool → resposta → tool encadeado +- **Streaming:** `result.toUIMessageStreamResponse()` — Server-Sent Events + +### Contexto + +- **Sem persistência:** cada abertura do chat = sessão nova. `useChat` sem `id`/`initialMessages`. +- **Sem histórico entre abas/dispositivos:** a complexidade de threads fica para v2. +- **Limpeza implícita:** fechar o Sheet/Drawer descarta o estado React. + +### Rate limiting + +- **Algoritmo:** in-memory token bucket, keyed by `userId`. +- **Limite:** 50 mensagens por janela de 15 minutos. +- **Resposta no estouro:** HTTP 429 com mensagem em pt-BR. +- **Resiliência:** sobrevive a um servidor; reseta no deploy. Suficiente para v1. + +## Ferramentas (v1 read-only) + +| # | Tool | Descrição | Fonte de dados | +| --- | ------------------------- | --------------------------------------------------------------- | -------------------------------------------- | +| 1 | `calculate` | Aritmética segura (sem `eval`) — percentuais, somas, taxas | Puro, sem DB | +| 2 | `getMunicipalityVotes` | Votos do Solla por ano + voto válido no município | `electionCandidateVote` + `electionTally` | +| 3 | `getTopDeputies` | Ranking de deputados federais mais votados no município/ano | `electionCandidateVote` | +| 4 | `getDobradinhas` | Parcerias com deputados estaduais, filtráveis por município | `stateDeputy` + `municipality.stateDeputies` | +| 5 | `getMunicipalityOverview` | Nível de engajamento, prioridade, lideranças, compromissos | `municipality` + contagens | +| 6 | `getLeaderships` | Lideranças por município ou nome | `leadership` | +| 7 | `getOrganizations` | Organizações por município, tipo ou nome | `organization` | +| 8 | `searchEntities` | Busca fuzzy cross-collection (fallback quando pergunta ambígua) | 4 coleções em paralelo | + +Cada tool recebe `{ user, payload }` e chama a Local API com `overrideAccess: false` + `user` (respeita RBAC) ou `overrideAccess: true` com justificativa (dados eleitorais públicos). + +## Decisões travadas + +- **Vercel AI SDK (`ai` + `@ai-sdk/deepseek`), não LangChain/Mastra.** O AI SDK é first-party Next.js, tem `streamText` com tools nativo, `useChat` hook no cliente, e o projeto já está em Next.js. **Rejeitado:** LangChain (overhead de abstração sem ganho para tools simples); Mastra (framework de agentes completo, desnecessário para v1 de chat com tools). +- **Modelo `deepseek-v4-flash`, não `deepseek-chat` legado.** O nome legado `deepseek-chat` será descontinuado em 2026-07-24. O V4 Flash suporta tool calling e tem 128K tokens de input. **Rejeitado:** `deepseek-v4-pro` (mais caro, desnecessário para queries de dados); `deepseek-reasoner` (latência maior, sem ganho para tools). +- **8 tools específicas + 1 calculadora, não 1 tool genérica "query".** Tools com propósitos não-sobrepostos melhoram o routing do modelo. **Rejeitado:** uma tool "query anything" faz o modelo alucinar a sintaxe de query; uma tool "sql" é insegura e desnecessária (Payload já é a camada de query). +- **shadcn chat components (`message`, `bubble`, `marker`, `input-group`), não hand-rolled.** shadcn lançou chat components em junho/2026; o projeto já usa shadcn; a qualidade visual e de acessibilidade é superior a hand-rolling. **Rejeitado:** `@chatscope/chat-ui-kit-react` (lib externa, bundle maior, menos integração com Tailwind/shadcn). +- **Sheet (desktop) / Drawer (mobile), não modal Dialog.** O assessor precisa manter a tela principal visível enquanto interage com a IA. **Rejeitado:** Dialog modal (bloqueia a tela principal); página separada (perde o contexto de onde o usuário estava). +- **Header button, não FAB.** Produto pediu FAB inicialmente, mas o header é mais discoverable e consistente com o sino de notificações. **Rejeitado:** FAB flutuante (competiria com o FAB de ações rápidas); ícone no sidebar (menos visível, navegação já tem densidade alta). +- **Contexto limpo a cada abertura.** Complexidade de threads/histórico é desnecessária para o target user (assessor de campanha em campo). **Rejeitado:** persistir conversas no DB (migration, UI de histórico, cleanup — v2); auto-clean por inatividade (surpreende o usuário que volta). + +## Dependências + +Nenhuma — feature independente. Não depende de migrações, collections novas, Consent, ou outras features. + +## Não escopo (v1) + +- Write tools (criar/editar lideranças, pledges, atividades via chat) +- Histórico persistente entre sessões +- Web search (grounding externo) +- Speech-to-text (Assembly AI) +- Upload de arquivos/áudio no chat +- Suporte a múltiplos modelos + +## Roadmap futuro (arquitetura preparada) + +| Feature | Como a arquitetura atual suporta | +| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Write tools | Adicionar tool em `src/utilities/ai/tools/`, registrar no `index.ts`. Usar `payload.update`/`payload.create` com `user` scoped — mesmo padrão das tools de leitura. | +| Web research | Adicionar tool `webSearch` que chama uma API externa. O `streamText` com tools suporta nativamente. | +| Speech-to-text | `InputGroup` aceita slot de addon. Adicionar botão de microfone que grava → envia para Assembly AI via server action → injeta transcrição como `sendMessage({ text })`. | +| Meeting recording | Pipeline: gravar → Assembly AI transcript → `sendMessage({ text: "Resuma esta reunião e sugira atualizações nos dados: ..." })`. | +| Mudar de modelo | Trocar `deepSeek('deepseek-v4-flash')` por outro provider no `route.ts` — tools e UI não mudam. | +| Histórico persistente | Passar `id` para `useChat` + salvar mensagens em collection Payload. Migração simples (uma collection `aiChatMessage`). | + +## Testes + +| Camada | Cobertura | +| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Unit | `campaignMobileTopBar.unit.spec.tsx` cobre o botão AI no header mobile (existente, estendido); `codebaseConventions.unit.spec.ts` cobre a allowlist do route | +| Int | Queries Payload das tools são cobertas indiretamente pelos int tests dos data loaders existentes (`municipalityElectoralBaseline`, `votePledgeViews`, etc.) | +| E2e | `tests/e2e/ai-chat.e2e.spec.ts` — abre o Sheet, digita mensagem, verifica resposta mockada. Mocka `/campanha/api/ai-chat` com `page.route()` para não depender de API key real no CI. | + +### E2e mocking strategy + +O endpoint de chat é mockado no Playwright com `page.route()`: + +```ts +await page.route('**/campanha/api/ai-chat', async (route) => { + // Retorna um stream SSE mínimo que o useChat entende + await route.fulfill({ + status: 200, + headers: { 'Content-Type': 'text/event-stream' }, + body: 'data: {"type":"text","text":"Olá! Como posso ajudar?"}\n\n', + }) +}) +``` + +Isso permite testar o fluxo completo de UI (botão → Sheet → digitar → resposta) sem chave de API no CI. **Não é necessário `DEEPSEEK_API_KEY` como repo secret.** + +## Configuração de ambiente + +| Variável | Onde | Obrigatória | +| ------------------ | --------------------- | ------------------------------ | +| `DEEPSEEK_API_KEY` | Vercel production env | Sim (produção) | +| `DEEPSEEK_API_KEY` | `.env.local` | Sim (desenvolvimento local) | +| `DEEPSEEK_API_KEY` | GitHub repo secrets | **Não** (e2e mocka o endpoint) | + +## Referências + +- `src/app/(campaign)/campanha/api/ai-chat/route.ts` — endpoint +- `src/utilities/ai/tools/index.ts` — registro de tools +- `src/utilities/ai/systemPrompt.ts` — prompt do sistema +- `src/components/campaign/shell/ai/CampaignAIChat.tsx` — UI do chat +- `src/components/campaign/shell/ai/CampaignAIChatSheet.tsx` — Sheet/Drawer +- `src/components/campaign/shell/ai/CampaignAIHeaderButton.tsx` — botão no header +- `src/components/campaign/shell/CampaignDesktopHeader.tsx` — header desktop +- `src/components/campaign/shell/CampaignMobileTopBar.tsx` — header mobile +- AGENTS.md — Campaign auth, Municípios model +- `PRODUCT.md` / `DESIGN.md` diff --git a/eslint-legacy-ignores.mjs b/eslint-legacy-ignores.mjs index 8646416e..c911ab2c 100644 --- a/eslint-legacy-ignores.mjs +++ b/eslint-legacy-ignores.mjs @@ -10,6 +10,7 @@ export const legacyComponentFilenameIgnores = [ 'src/components/socialIcons.tsx', 'src/components/socialLinks.tsx', 'src/components/ui/breadcrumb.tsx', + 'src/components/ui/bubble.tsx', 'src/components/ui/button.tsx', 'src/components/ui/card.tsx', 'src/components/ui/combobox.tsx', @@ -18,6 +19,8 @@ export const legacyComponentFilenameIgnores = [ 'src/components/ui/input-group.tsx', 'src/components/ui/input.tsx', 'src/components/ui/label.tsx', + 'src/components/ui/marker.tsx', + 'src/components/ui/message.tsx', 'src/components/ui/native-select.tsx', 'src/components/ui/separator.tsx', 'src/components/ui/skeleton.tsx', diff --git a/knip.json b/knip.json index 151e41bb..aa6920e3 100644 --- a/knip.json +++ b/knip.json @@ -10,7 +10,11 @@ "tests/helpers/serverOnly.ts" ], "project": ["src/**/*.{ts,tsx,js}", "scripts/**/*.mjs", "tests/**/*.ts"], - "ignore": [], + "ignore": [ + "src/components/ui/bubble.tsx", + "src/components/ui/marker.tsx", + "src/components/ui/message.tsx" + ], "paths": { "server-only": ["tests/helpers/serverOnly.ts"] }, diff --git a/package.json b/package.json index ebbb019d..e3da190c 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,8 @@ "agent:status": "node scripts/agent-status.mjs" }, "dependencies": { + "@ai-sdk/deepseek": "^3.0.19", + "@ai-sdk/react": "^4.0.50", "@base-ui/react": "^1.6.0", "@hookform/resolvers": "^5.2.2", "@payloadcms/db-postgres": "3.82.0", @@ -89,6 +91,7 @@ "@simplewebauthn/server": "^13.3.2", "@vercel/analytics": "^1.6.1", "@vercel/speed-insights": "^1.3.1", + "ai": "^7.0.47", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", @@ -104,6 +107,9 @@ "react": "^19.2.4", "react-dom": "^19.2.4", "react-hook-form": "^7.71.1", + "react-markdown": "^10.1.0", + "react-resizable-panels": "^4.12.2", + "remark-gfm": "^4.0.1", "server-only": "^0.0.1", "shadcn": "^4.0.3", "sharp": "0.34.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1e79ae8e..938e9cac 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,12 @@ importers: .: dependencies: + '@ai-sdk/deepseek': + specifier: ^3.0.19 + version: 3.0.19(zod@4.3.6) + '@ai-sdk/react': + specifier: ^4.0.50 + version: 4.0.50(react@19.2.4)(zod@4.3.6) '@base-ui/react': specifier: ^1.6.0 version: 1.6.0(@date-fns/tz@1.2.0)(@types/react@19.2.14)(date-fns@4.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -50,6 +56,9 @@ importers: '@vercel/speed-insights': specifier: ^1.3.1 version: 1.3.1(next@15.4.11(@babel/core@7.29.0)(@playwright/test@1.58.2)(babel-plugin-macros@3.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.77.4))(react@19.2.4) + ai: + specifier: ^7.0.47 + version: 7.0.47(zod@4.3.6) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -95,6 +104,15 @@ importers: react-hook-form: specifier: ^7.71.1 version: 7.71.1(react@19.2.4) + react-markdown: + specifier: ^10.1.0 + version: 10.1.0(@types/react@19.2.14)(react@19.2.4) + react-resizable-panels: + specifier: ^4.12.2 + version: 4.12.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + remark-gfm: + specifier: ^4.0.1 + version: 4.0.1 server-only: specifier: ^0.0.1 version: 0.0.1 @@ -264,6 +282,40 @@ packages: '@acemir/cssom@0.9.31': resolution: {integrity: sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==} + '@ai-sdk/deepseek@3.0.19': + resolution: {integrity: sha512-APzL/IpDSLhVlKQXo1gKCvTDLeli98ITzKnMq/eUNVWZ/UzWKYYG/0mCsmJRwTC37YE0ZzpxwDBw3J8xQT3lxg==} + engines: {node: '>=22'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/gateway@4.0.36': + resolution: {integrity: sha512-N1P6bdW/aC5rxLeuGYgx3X4el3DoZy8UWlky+g+AeIZSmxaEi/AToHJL4cmZ6nCPHk1byqJWwC+PaOZG0hK0dw==} + engines: {node: '>=22'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/mcp@2.0.22': + resolution: {integrity: sha512-j3jPwKFTffZCJzYGpN69yKu/y646uu2gWoEwh1jgVyYVpzfu6ErFU+YAd/+xTPxsUTx8FEIEUHTlEsDuOe+ytQ==} + engines: {node: '>=22'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/provider-utils@5.0.18': + resolution: {integrity: sha512-UBNCrkxS5llgN2/RXLBRkjCFTIuL6YB1Goq5c+yRecnznhtX4XPjTwLHz2hrsTltTVZ0rqVegtnwFXdPBkjDHA==} + engines: {node: '>=22'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/provider@4.0.4': + resolution: {integrity: sha512-tbHKNLirllUNF3ZlkCsXnwab2ZV1Sl4b1H/Cp9ruCce15IBmskE8Gwkk0yo9xDWY+jho2of7lVXtwSsyrq7cwQ==} + engines: {node: '>=22'} + + '@ai-sdk/react@4.0.50': + resolution: {integrity: sha512-fbC0V2WdCwZaioSJ0CGT/YnrhzpNbiDFqoBlrJ7HL0OY95sIUQrPDVlGYz489/hj4Lze4tr7RfPc8ghgc/5wcQ==} + engines: {node: '>=22'} + peerDependencies: + react: ^18 || ~19.0.1 || ~19.1.2 || ^19.2.1 + '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} @@ -3420,6 +3472,9 @@ packages: resolution: {integrity: sha512-mbCddXd+jm7hfx7w2YU64/Av4/NqqeG3GoRZgxPcgoTxYjhrcfJRw9ULch71SS4G+Q3bOXFhRvPqjguN0Hyp5w==} hasBin: true + '@ungap/structured-clone@1.3.3': + resolution: {integrity: sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==} + '@unrs/resolver-binding-android-arm-eabi@1.11.1': resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} cpu: [arm] @@ -3545,6 +3600,10 @@ packages: resolution: {integrity: sha512-6f9oWC+DbWxIgBLOdqjjn2/REpFrPDB7y5B5HA1ptYkzZaBgL6E34kWrptJvJ7teApJdbAs3I1a5A7z1y8SDHw==} engines: {node: '>=20.0.0'} + '@vercel/oidc@3.2.0': + resolution: {integrity: sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==} + engines: {node: '>= 20'} + '@vercel/speed-insights@1.3.1': resolution: {integrity: sha512-PbEr7FrMkUrGYvlcLHGkXdCkxnylCWePx7lPxxq36DNdfo9mcUjLOmqOyPDHAOgnfqgGGdmE3XI9L/4+5fr+vQ==} peerDependencies: @@ -3618,6 +3677,9 @@ packages: '@vue/shared@3.5.40': resolution: {integrity: sha512-WxnBtruIqOoV3rA4jeKDWzrYI5h7Cp4+pjwDi8kWGHz+IslhiN+wguLVVhtv2l8VoU02rzDCVfDjgCl1lNpZVg==} + '@workflow/serde@4.1.0': + resolution: {integrity: sha512-pav4F2BoirECWR7Nf1TKt+2eETcBj7jj4cBefQ8VXQCA6NPkaKeLfj/zMgi+3zYV5ZIBT4GuUiphsj0/b9hPQQ==} + accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} @@ -3645,6 +3707,12 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} + ai@7.0.47: + resolution: {integrity: sha512-e0MpNtufu6JmcmwMUTgM1smCRkP5z014iPaKgnCm7kmMsTSIZbOJN2vglhPq0WIj/6x7ewVi6W0FyIR0pjaE3Q==} + engines: {node: '>=22'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + ajv-formats@3.0.1: resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} peerDependencies: @@ -3790,6 +3858,9 @@ packages: resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} engines: {node: '>=10', npm: '>=6'} + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -3995,6 +4066,9 @@ packages: colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@11.1.0: resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} engines: {node: '>=16'} @@ -4531,6 +4605,10 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + escodegen@2.1.0: resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} engines: {node: '>=6.0'} @@ -4693,6 +4771,10 @@ packages: resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} engines: {node: '>=18.0.0'} + eventsource-parser@3.1.0: + resolution: {integrity: sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==} + engines: {node: '>=18.0.0'} + eventsource@3.0.7: resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} engines: {node: '>=18.0.0'} @@ -4723,6 +4805,9 @@ packages: resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} engines: {node: '>= 18'} + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fast-copy@3.0.2: resolution: {integrity: sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==} @@ -5021,6 +5106,12 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hast-util-to-jsx-runtime@2.3.6: + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + headers-polyfill@4.0.3: resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} @@ -5042,6 +5133,9 @@ packages: resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + html-url-attributes@3.0.1: + resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} + http-errors@2.0.1: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} @@ -5112,6 +5206,9 @@ packages: ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} @@ -5427,6 +5524,9 @@ packages: json-schema-typed@8.0.2: resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -5621,6 +5721,9 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + marked@14.0.0: resolution: {integrity: sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==} engines: {node: '>= 18'} @@ -5633,15 +5736,45 @@ packages: md5@2.3.0: resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==} + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + mdast-util-from-markdown@2.0.2: resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + mdast-util-mdx-jsx@3.1.3: resolution: {integrity: sha512-bfOjvNt+1AcbPLTFMFWY149nJz0OjmewJs3LQQ5pIyVGxP4CdOqNVJL6kTaM5c68p8q82Xv3nCyFfUnuEcH3UQ==} + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + mdast-util-phrasing@4.1.0: resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + mdast-util-to-markdown@2.1.2: resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} @@ -5678,6 +5811,27 @@ packages: micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + micromark-extension-mdx-jsx@3.0.1: resolution: {integrity: sha512-vNuFb9czP8QCtAQcEJn0UJQJZA8Dk6DXKBqx+bg/w0WGuSxDxNr7hErW89tHUY31dUW4NqEOWwmEUNhjTFmHkg==} @@ -6308,6 +6462,9 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + property-information@7.2.0: + resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -6406,6 +6563,12 @@ packages: react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + react-markdown@10.1.0: + resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} + peerDependencies: + '@types/react': '>=18' + react: '>=18' + react-refresh@0.17.0: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} @@ -6430,6 +6593,12 @@ packages: '@types/react': optional: true + react-resizable-panels@4.12.2: + resolution: {integrity: sha512-NwY5LCo4WrxVvDh0xoMML6EMLPONP/8ckKcIdpnojxexoatZdjLiRqLJQjQK5CPkd4SYiB/2M5BVrjZBQtOO7Q==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + react-select@5.9.0: resolution: {integrity: sha512-nwRKGanVHGjdccsnzhFte/PULziueZxGD8LL2WojON78Mvnq7LdAMEtu2frrwld1fr3geixg3iiMBIc/LLAZpw==} peerDependencies: @@ -6483,6 +6652,18 @@ packages: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -6751,6 +6932,9 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + split2@4.2.0: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} @@ -6874,6 +7058,12 @@ packages: resolution: {integrity: sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==} engines: {node: '>=18'} + style-to-js@1.1.21: + resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} + + style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + styled-jsx@5.1.6: resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} engines: {node: '>= 12.0.0'} @@ -6903,6 +7093,11 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + swr@2.4.2: + resolution: {integrity: sha512-ej644Y2bvkIajfR32KGeSSdBXQW+ScjGjkybZgSE7kFpk9eGnV44XY9FJylXi+W75pavSX1PVNB57W5EbhGIYw==} + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} @@ -7009,6 +7204,12 @@ packages: resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} engines: {node: '>=20'} + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + truncate-utf8-bytes@1.0.2: resolution: {integrity: sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==} @@ -7144,16 +7345,26 @@ packages: resolution: {integrity: sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w==} engines: {node: '>=20.18.1'} + undici@7.29.0: + resolution: {integrity: sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==} + engines: {node: '>=20.18.1'} + unicorn-magic@0.3.0: resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} engines: {node: '>=18'} + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + unist-util-is@6.0.1: resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} unist-util-position-from-estree@2.0.0: resolution: {integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==} + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + unist-util-stringify-position@4.0.0: resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} @@ -7251,6 +7462,9 @@ packages: vfile-message@4.0.3: resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + vite-tsconfig-paths@6.0.5: resolution: {integrity: sha512-f/WvY6ekHykUF1rWJUAbCU7iS/5QYDIugwpqJA+ttwKbxSbzNlqlE8vZSrsnxNQciUW+z6lvhlXMaEyZn9MSig==} peerDependencies: @@ -7530,6 +7744,51 @@ snapshots: '@acemir/cssom@0.9.31': {} + '@ai-sdk/deepseek@3.0.19(zod@4.3.6)': + dependencies: + '@ai-sdk/provider': 4.0.4 + '@ai-sdk/provider-utils': 5.0.18(zod@4.3.6) + zod: 4.3.6 + + '@ai-sdk/gateway@4.0.36(zod@4.3.6)': + dependencies: + '@ai-sdk/provider': 4.0.4 + '@ai-sdk/provider-utils': 5.0.18(zod@4.3.6) + '@vercel/oidc': 3.2.0 + zod: 4.3.6 + + '@ai-sdk/mcp@2.0.22(zod@4.3.6)': + dependencies: + '@ai-sdk/provider': 4.0.4 + '@ai-sdk/provider-utils': 5.0.18(zod@4.3.6) + pkce-challenge: 5.0.1 + zod: 4.3.6 + + '@ai-sdk/provider-utils@5.0.18(zod@4.3.6)': + dependencies: + '@ai-sdk/provider': 4.0.4 + '@standard-schema/spec': 1.1.0 + '@workflow/serde': 4.1.0 + eventsource-parser: 3.1.0 + undici: 7.29.0 + zod: 4.3.6 + + '@ai-sdk/provider@4.0.4': + dependencies: + json-schema: 0.4.0 + + '@ai-sdk/react@4.0.50(react@19.2.4)(zod@4.3.6)': + dependencies: + '@ai-sdk/mcp': 2.0.22(zod@4.3.6) + '@ai-sdk/provider': 4.0.4 + '@ai-sdk/provider-utils': 5.0.18(zod@4.3.6) + ai: 7.0.47(zod@4.3.6) + react: 19.2.4 + swr: 2.4.2(react@19.2.4) + throttleit: 2.1.0 + transitivePeerDependencies: + - zod + '@alloc/quick-lru@5.2.0': {} '@antfu/ni@25.0.0': @@ -10708,6 +10967,8 @@ snapshots: dependencies: '@typescript/old': typescript@6.0.3 + '@ungap/structured-clone@1.3.3': {} + '@unrs/resolver-binding-android-arm-eabi@1.11.1': optional: true @@ -10780,6 +11041,8 @@ snapshots: throttleit: 2.1.0 undici: 6.27.0 + '@vercel/oidc@3.2.0': {} + '@vercel/speed-insights@1.3.1(next@15.4.11(@babel/core@7.29.0)(@playwright/test@1.58.2)(babel-plugin-macros@3.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.77.4))(react@19.2.4)': optionalDependencies: next: 15.4.11(@babel/core@7.29.0)(@playwright/test@1.58.2)(babel-plugin-macros@3.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.77.4) @@ -10869,6 +11132,8 @@ snapshots: '@vue/shared@3.5.40': {} + '@workflow/serde@4.1.0': {} + accepts@2.0.0: dependencies: mime-types: 3.0.2 @@ -10886,6 +11151,13 @@ snapshots: agent-base@7.1.4: {} + ai@7.0.47(zod@4.3.6): + dependencies: + '@ai-sdk/gateway': 4.0.36(zod@4.3.6) + '@ai-sdk/provider': 4.0.4 + '@ai-sdk/provider-utils': 5.0.18(zod@4.3.6) + zod: 4.3.6 + ajv-formats@3.0.1(ajv@8.17.1): optionalDependencies: ajv: 8.17.1 @@ -11063,6 +11335,8 @@ snapshots: cosmiconfig: 7.1.0 resolve: 1.22.11 + bail@2.0.2: {} + balanced-match@1.0.2: {} balanced-match@4.0.4: {} @@ -11272,6 +11546,8 @@ snapshots: colorette@2.0.20: {} + comma-separated-tokens@2.0.3: {} + commander@11.1.0: {} commander@12.1.0: {} @@ -11821,6 +12097,8 @@ snapshots: escape-string-regexp@4.0.0: {} + escape-string-regexp@5.0.0: {} + escodegen@2.1.0: dependencies: esprima: 4.0.1 @@ -12056,6 +12334,8 @@ snapshots: eventsource-parser@3.0.6: {} + eventsource-parser@3.1.0: {} + eventsource@3.0.7: dependencies: eventsource-parser: 3.0.6 @@ -12131,6 +12411,8 @@ snapshots: transitivePeerDependencies: - supports-color + extend@3.0.2: {} + fast-copy@3.0.2: {} fast-deep-equal@3.1.3: {} @@ -12453,6 +12735,30 @@ snapshots: dependencies: function-bind: 1.1.2 + hast-util-to-jsx-runtime@2.3.6: + dependencies: + '@types/estree': 1.0.8 + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.1.3 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + headers-polyfill@4.0.3: {} help-me@5.0.0: {} @@ -12473,6 +12779,8 @@ snapshots: transitivePeerDependencies: - '@noble/hashes' + html-url-attributes@3.0.1: {} + http-errors@2.0.1: dependencies: depd: 2.0.0 @@ -12530,6 +12838,8 @@ snapshots: ini@1.3.8: {} + inline-style-parser@0.2.7: {} + internal-slot@1.1.0: dependencies: es-errors: 1.3.0 @@ -12812,6 +13122,8 @@ snapshots: json-schema-typed@8.0.2: {} + json-schema@0.4.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} json5@1.0.2: @@ -12999,6 +13311,8 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + markdown-table@3.0.4: {} + marked@14.0.0: {} math-intrinsics@1.1.0: {} @@ -13009,6 +13323,13 @@ snapshots: crypt: 0.0.2 is-buffer: 1.1.6 + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + mdast-util-from-markdown@2.0.2: dependencies: '@types/mdast': 4.0.4 @@ -13026,6 +13347,74 @@ snapshots: transitivePeerDependencies: - supports-color + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.2 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-expression@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + mdast-util-mdx-jsx@3.1.3: dependencies: '@types/estree-jsx': 1.0.5 @@ -13043,11 +13432,34 @@ snapshots: transitivePeerDependencies: - supports-color + mdast-util-mdxjs-esm@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + mdast-util-phrasing@4.1.0: dependencies: '@types/mdast': 4.0.4 unist-util-is: 6.0.1 + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.3 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + mdast-util-to-markdown@2.1.2: dependencies: '@types/mdast': 4.0.4 @@ -13099,6 +13511,64 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + micromark-extension-mdx-jsx@3.0.1: dependencies: '@types/acorn': 4.0.6 @@ -13907,6 +14377,8 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + property-information@7.2.0: {} + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -14050,6 +14522,24 @@ snapshots: react-is@17.0.2: {} + react-markdown@10.1.0(@types/react@19.2.14)(react@19.2.4): + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/react': 19.2.14 + devlop: 1.1.0 + hast-util-to-jsx-runtime: 2.3.6 + html-url-attributes: 3.0.1 + mdast-util-to-hast: 13.2.1 + react: 19.2.4 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + unified: 11.0.5 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + react-refresh@0.17.0: {} react-remove-scroll-bar@2.3.8(@types/react@19.2.14)(react@19.2.4): @@ -14071,6 +14561,11 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + react-resizable-panels@4.12.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + react-select@5.9.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: '@babel/runtime': 7.29.7 @@ -14149,6 +14644,40 @@ snapshots: gopd: 1.2.0 set-function-name: 2.0.2 + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.1 + unified: 11.0.5 + vfile: 6.0.3 + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + require-directory@2.1.1: {} require-from-string@2.0.2: {} @@ -14557,6 +15086,8 @@ snapshots: source-map@0.6.1: {} + space-separated-tokens@2.0.2: {} + split2@4.2.0: {} ssf@0.11.2: @@ -14695,6 +15226,14 @@ snapshots: dependencies: '@tokenizer/token': 0.3.0 + style-to-js@1.1.21: + dependencies: + style-to-object: 1.0.14 + + style-to-object@1.0.14: + dependencies: + inline-style-parser: 0.2.7 + styled-jsx@5.1.6(@babel/core@7.29.0)(babel-plugin-macros@3.1.0)(react@19.2.4): dependencies: client-only: 0.0.1 @@ -14715,6 +15254,12 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + swr@2.4.2(react@19.2.4): + dependencies: + dequal: 2.0.3 + react: 19.2.4 + use-sync-external-store: 1.6.0(react@19.2.4) + symbol-tree@3.2.4: {} tabbable@6.4.0: {} @@ -14806,6 +15351,10 @@ snapshots: dependencies: punycode: 2.3.1 + trim-lines@3.0.1: {} + + trough@2.2.0: {} + truncate-utf8-bytes@1.0.2: dependencies: utf8-byte-length: 1.0.5 @@ -14961,8 +15510,20 @@ snapshots: undici@7.24.4: {} + undici@7.29.0: {} + unicorn-magic@0.3.0: {} + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + unist-util-is@6.0.1: dependencies: '@types/unist': 3.0.3 @@ -14971,6 +15532,10 @@ snapshots: dependencies: '@types/unist': 3.0.3 + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position@4.0.0: dependencies: '@types/unist': 3.0.3 @@ -15073,6 +15638,11 @@ snapshots: '@types/unist': 3.0.3 unist-util-stringify-position: 4.0.0 + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + vite-tsconfig-paths@6.0.5(@typescript/typescript6@6.0.2)(vite@7.3.1(@types/node@22.19.9)(jiti@2.7.0)(lightningcss@1.30.2)(sass@1.77.4)(tsx@4.21.0)(yaml@2.9.0)): dependencies: debug: 4.4.3 diff --git a/src/app/(campaign)/campanha/(app)/layout.tsx b/src/app/(campaign)/campanha/(app)/layout.tsx index e6aa93a0..80723c64 100644 --- a/src/app/(campaign)/campanha/(app)/layout.tsx +++ b/src/app/(campaign)/campanha/(app)/layout.tsx @@ -5,6 +5,7 @@ import { getPayload } from 'payload' import React from 'react' import { CampaignListPendingBoundary } from '@/components/campaign/shared/CampaignListPending' +import { CampaignAISidebarShell } from '@/components/campaign/shell/ai/CampaignAISidebarShell' import { BiometricEnrollmentToast, type BiometricEnrollmentOffer, @@ -72,37 +73,43 @@ export default async function CampaignAppLayout({ children }: { children: React. - - - - - - } - /> - } - /> - {/* + + + + + + + } + /> + } + /> + {/* Provider must wrap CampaignAppScrollChrome, not only page children: the quick-actions FAB overlay (B126) mounts as a sibling of the scrollport and renders search hits with CampaignHoverTooltip (priority flag). Nested only around {children} left focus→suggest without a provider and crashed the page (B102). */} - - {children} - - - - {biometricEnrollment ? ( - - ) : null} - - - - - + +
+ + {children} + +
+
+ + + {biometricEnrollment ? ( + + ) : null} +
+
+
+
+
+
) diff --git a/src/app/(campaign)/campanha/api/ai-chat/route.ts b/src/app/(campaign)/campanha/api/ai-chat/route.ts new file mode 100644 index 00000000..ccb88f52 --- /dev/null +++ b/src/app/(campaign)/campanha/api/ai-chat/route.ts @@ -0,0 +1,51 @@ +import { deepSeek } from '@ai-sdk/deepseek' +import configPromise from '@payload-config' +import { + convertToModelMessages, + createUIMessageStreamResponse, + stepCountIs, + streamText, + toUIMessageStream, +} from 'ai' +import { getPayload } from 'payload' + +import { checkRateLimit } from '@/utilities/ai/rateLimit' +import { AI_SYSTEM_PROMPT } from '@/utilities/ai/systemPrompt' +import { buildAITools } from '@/utilities/ai/tools' +import { getCampaignUserRaw } from '@/utilities/campaignAuth' + +export const maxDuration = 60 + +export async function POST(req: Request) { + // Authenticate + const user = await getCampaignUserRaw() + if (!user) { + return Response.json({ error: 'Não autenticado.' }, { status: 401 }) + } + + // Rate limit + if (!checkRateLimit(user.id)) { + return Response.json( + { + error: 'Você atingiu o limite de mensagens. Aguarde alguns minutos e tente novamente.', + }, + { status: 429 }, + ) + } + + const payload = await getPayload({ config: configPromise }) + + const { messages } = await req.json() + + const result = streamText({ + model: deepSeek('deepseek-v4-flash'), + system: AI_SYSTEM_PROMPT, + messages: await convertToModelMessages(messages), + tools: buildAITools({ user, payload }), + stopWhen: stepCountIs(10), + }) + + return createUIMessageStreamResponse({ + stream: toUIMessageStream({ stream: result.stream }), + }) +} diff --git a/src/components/campaign/shell/CampaignAppScrollChrome.tsx b/src/components/campaign/shell/CampaignAppScrollChrome.tsx index 7e3b0ca5..bfd3a79b 100644 --- a/src/components/campaign/shell/CampaignAppScrollChrome.tsx +++ b/src/components/campaign/shell/CampaignAppScrollChrome.tsx @@ -25,8 +25,10 @@ export const CampaignAppScrollChrome = ({ return ( - {children} - +
+ {children} + +
) } diff --git a/src/components/campaign/shell/CampaignDesktopHeader.tsx b/src/components/campaign/shell/CampaignDesktopHeader.tsx index e8df3ba9..b79e9449 100644 --- a/src/components/campaign/shell/CampaignDesktopHeader.tsx +++ b/src/components/campaign/shell/CampaignDesktopHeader.tsx @@ -2,6 +2,7 @@ import type { ReactNode } from 'react' +import { CampaignAIHeaderButton } from '@/components/campaign/shell/ai/CampaignAIHeaderButton' import { CampaignPageChromeDisplay } from '@/components/campaign/shell/CampaignPageChromeDisplay' import { SidebarTrigger } from '@/components/ui/Sidebar' @@ -9,6 +10,9 @@ export const CampaignDesktopHeader = ({ notificationBell }: { notificationBell?:
- {notificationBell ?
{notificationBell}
: null} +
+ + {notificationBell ?? null} +
) diff --git a/src/components/campaign/shell/CampaignMobileTopBar.tsx b/src/components/campaign/shell/CampaignMobileTopBar.tsx index 2386f469..a8bf9d56 100644 --- a/src/components/campaign/shell/CampaignMobileTopBar.tsx +++ b/src/components/campaign/shell/CampaignMobileTopBar.tsx @@ -150,7 +150,7 @@ export const CampaignMobileTopBar = ({ notificationBell }: { notificationBell?: )} - {notificationBell ?
{notificationBell}
: null} + {notificationBell ?
{notificationBell}
: null} ) } diff --git a/src/components/campaign/shell/CampaignQuickActionsHost.tsx b/src/components/campaign/shell/CampaignQuickActionsHost.tsx index edf0da2d..0437a72b 100644 --- a/src/components/campaign/shell/CampaignQuickActionsHost.tsx +++ b/src/components/campaign/shell/CampaignQuickActionsHost.tsx @@ -13,7 +13,7 @@ import type { CampaignRole } from '@/lib/campaignRoles' export const CampaignContentScroll = ({ children }: { children: ReactNode }) => (
{children}
diff --git a/src/components/campaign/shell/ai/CampaignAIChat.tsx b/src/components/campaign/shell/ai/CampaignAIChat.tsx new file mode 100644 index 00000000..2fa02980 --- /dev/null +++ b/src/components/campaign/shell/ai/CampaignAIChat.tsx @@ -0,0 +1,163 @@ +'use client' + +import { useChat } from '@ai-sdk/react' +import { DefaultChatTransport } from 'ai' +import { Bot, Send, User } from 'lucide-react' +import { useEffect, useRef, useState } from 'react' +import ReactMarkdown from 'react-markdown' +import remarkGfm from 'remark-gfm' + +import { Bubble, BubbleContent } from '@/components/ui/bubble' +import { InputGroup, InputGroupButton } from '@/components/ui/input-group' +import { Marker, MarkerContent } from '@/components/ui/marker' +import { Message, MessageAvatar, MessageContent } from '@/components/ui/message' +import { Spinner } from '@/components/ui/Spinner' +import { cn } from '@/lib/utils' + +export const CampaignAIChat = ({ className }: { className?: string }) => { + const { messages, sendMessage, status } = useChat({ + transport: new DefaultChatTransport({ api: '/campanha/api/ai-chat' }), + }) + const [input, setInput] = useState('') + const viewportRef = useRef(null) + const bottomRef = useRef(null) + + // Auto-scroll to bottom on new messages + useEffect(() => { + bottomRef.current?.scrollIntoView({ behavior: 'smooth' }) + }, [messages]) + + const busy = status !== 'ready' + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault() + if (!input.trim() || busy) return + sendMessage({ text: input }) + setInput('') + } + + return ( +
+ {/* Messages area */} +
+ {messages.length === 0 ? ( +
+
+ +

+ Olá! Eu sou o Sollinha, assistente virtual da campanha. Pergunte sobre votações, + municípios, dobradinhas, lideranças e muito mais. +

+
+
+ ) : ( +
+ {messages.map((message) => ( + + + {message.role === 'user' ? ( + + ) : ( + + )} + + + + + {message.parts.map((part, index) => { + if (part.type === 'text') { + if (message.role === 'user') { + return ( + + {part.text} + + ) + } + return ( +
+ {part.text} +
+ ) + } + if (part.type === 'tool-invocation') { + return ( + + 🔍 Buscando dados... + + ) + } + return null + })} +
+
+
+
+ ))} + + {busy && messages.length > 0 && ( + + + + + + + + + + + + + )} + +
+
+ )} +
+ + {/* Input area */} +
+
+ +