diff --git a/.autoresearch/log.jsonl b/.autoresearch/log.jsonl index 0fb11b4..8930dc0 100644 --- a/.autoresearch/log.jsonl +++ b/.autoresearch/log.jsonl @@ -1 +1,4 @@ {"timestamp":"2026-04-11T05:27:00Z","iteration":0,"metric":100,"status":"baseline","summary":"Initial baseline - 5/5 tests passing","commit":"baseline"} +{"timestamp":"2026-04-11T05:28:07Z","iteration":1,"metric":100,"status":"kept","summary":"Add conciseness guidance: Be concise but thorough","commit":"3ea47c2"} +{"timestamp":"2026-04-11T05:28:45Z","iteration":2,"metric":100,"status":"kept","summary":"Improve language detection: Detect from user message","commit":"1b66a5c"} +{"timestamp":"2026-04-11T05:29:19Z","iteration":3,"metric":100,"status":"kept","summary":"Add markdown formatting guidance","commit":"exp3"} diff --git a/.autoresearch/loop.lock b/.autoresearch/loop.lock deleted file mode 100644 index 751fbc1..0000000 --- a/.autoresearch/loop.lock +++ /dev/null @@ -1 +0,0 @@ -{"started_at": "2026-04-11T05:27:00Z", "iteration": 0} diff --git a/.commandcode/taste/taste.md b/.commandcode/taste/taste.md new file mode 100644 index 0000000..f562cac --- /dev/null +++ b/.commandcode/taste/taste.md @@ -0,0 +1,4 @@ +# Taste (Continuously Learned by [CommandCode][cmd]) + +[cmd]: https://commandcode.ai/ + diff --git a/.gitignore b/.gitignore index 2b381af..6826746 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ vendor/llama-bin/ *.dll *.so *.dylib +.opencode diff --git a/.graphifyignore b/.graphifyignore new file mode 100644 index 0000000..d58fa88 --- /dev/null +++ b/.graphifyignore @@ -0,0 +1,21 @@ +node_modules/ +.next/ +dist/ +build/ +out/ +coverage/ +.git/ +.pnpm-store/ +.cache/ +*.log +.env +.env.* +__pycache__/ +*.pyc +.venv/ +venv/ +target/ +*.exe +*.dll +*.so +*.dylib diff --git a/CLAUDE.md b/CLAUDE.md index 9336487..7d7eead 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -70,7 +70,16 @@ ForgeLM (formerly AI Backend Studio) is a local-first desktop workspace for runn ## Conventions -Conventions not yet established. Will populate as patterns emerge during development. +### Automatic Context7 Usage +Whenever the agent has doubts about library APIs, coding patterns, best practices, or how to implement a feature using any dependency in the project, it MUST automatically invoke the Context7 MCP server to fetch up-to-date documentation without waiting for the user to ask. + +This applies to: +- Questions about React, Tauri, Axum, Tokio, Serde, or any other dependency +- Uncertainty about correct API usage, function signatures, or configuration options +- Need for code examples or patterns from official docs +- Any situation where outdated or hallucinated API knowledge could lead to incorrect code + +The agent should use `resolve-library-id` followed by `get-library-docs` as needed, without prompting the user. diff --git a/tauri-app/bun.lock b/tauri-app/bun.lock index aff6dcd..ac30e2d 100644 --- a/tauri-app/bun.lock +++ b/tauri-app/bun.lock @@ -15,6 +15,7 @@ "marked": "^12.0.0", "react": "^18.3.1", "react-dom": "^18.3.1", + "zustand": "^5.0.12", }, "devDependencies": { "@tauri-apps/cli": "^2", @@ -631,6 +632,8 @@ "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + "zustand": ["zustand@5.0.12", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g=="], + "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], "anymatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], diff --git a/tauri-app/docs/MULTI_AGENT_ARCHITECTURE.md b/tauri-app/docs/MULTI_AGENT_ARCHITECTURE.md new file mode 100644 index 0000000..e939db4 --- /dev/null +++ b/tauri-app/docs/MULTI_AGENT_ARCHITECTURE.md @@ -0,0 +1,446 @@ +# Arquitetura: Multi-Agentes Especialistas para ForgeLM + +## Visão Geral + +Sistema de especialistas que analisam e refinam iterativamente as respostas do assistente, utilizando a infraestrutura ForgeAgent existente. + +## 1. Fluxo Conceitual + +``` +Usuário → ChatView → API Local → Resposta Inicial + ↓ + ┌───────────────┼───────────────┐ + ↓ ↓ ↓ + [Especialista 1] [Especialista 2] [Especialista 3] + CodeReviewer StyleEditor FactChecker + ↓ ↓ ↓ + └───────────────┴───────────────┘ + ↓ + Resposta Refinada + ↓ + ChatView UI +``` + +## 2. Estrutura de Dados + +### SpecialistDefinition (Configuração) +```typescript +interface SpecialistDefinition { + id: string; // "code-reviewer", "style-editor" + name: string; // "Code Reviewer" + title: string; // "Revisor de Código" + systemPrompt: string; // Prompt específico do especialista + activationRules: ActivationRule[]; // Quando ativar + modelTier: "fast" | "quality"; // Qual modelo usar + maxTokens: number; + temperature: number; +} + +type ActivationRule = + | { type: "always" } + | { type: "contains-code"; languages?: string[] } + | { type: "length-threshold"; minChars: number } + | { type: "content-pattern"; regex: string } + | { type: "manual-toggle"; toggleId: string }; +``` + +### ReviewCycle (Execução) +```typescript +interface ReviewCycle { + id: string; // UUID da revisão + originalMessageId: number; // ID da mensagem original + originalContent: string; // Resposta inicial do assistente + specialists: SpecialistReview[]; + finalContent: string; // Resposta após todas as revisões + status: "running" | "completed" | "error"; + startedAt: number; + completedAt?: number; +} + +interface SpecialistReview { + specialistId: string; + status: "pending" | "thinking" | "completed" | "error"; + thinking?: string; // Chain of thought + suggestions: Suggestion[]; + applied: boolean; // Se as sugestões foram aplicadas +} + +interface Suggestion { + type: "improvement" | "correction" | "addition" | "deletion"; + original?: string; // Texto original + replacement?: string; // Texto sugerido + explanation: string; // Por que mudar + confidence: number; // 0.0 - 1.0 +} +``` + +## 3. Componentes Principais + +### A. SpecialistRegistry (src/lib/specialist-registry.ts) +Registro central de especialistas disponíveis. + +```typescript +class SpecialistRegistry { + private specialists: Map = new Map(); + + register(def: SpecialistDefinition): void; + unregister(id: string): void; + getActiveForContent(content: string, context: Context): SpecialistDefinition[]; + getAll(): SpecialistDefinition[]; +} +``` + +### B. ReviewOrchestrator (src/lib/review-orchestrator.ts) +Orquestra o ciclo de revisão. + +```typescript +class ReviewOrchestrator { + async startReview( + messageId: number, + content: string, + context: MessageContext + ): Promise; + + private async runSpecialist( + cycle: ReviewCycle, + specialist: SpecialistDefinition, + currentContent: string + ): Promise; + + private applySuggestions(content: string, review: SpecialistReview): string; + + onProgress(callback: (cycle: ReviewCycle) => void): void; + onComplete(callback: (cycle: ReviewCycle) => void): void; +} +``` + +### C. SpecialistService (Backend/Tauri) +Comandos Rust para executar revisões sem bloquear a UI. + +```rust +#[tauri::command] +async fn run_specialist_review( + specialist_id: String, + content: String, + context: MessageContext, + server: String, + model: String, +) -> Result; +``` + +## 4. Integração ao ChatView + +### Modo Síncrono vs Assíncrono + +**Opção A: Assíncrono (Recomendado)** +- Resposta inicial aparece imediatamente +- Especialistas processam em background +- Badge/indicator mostra "Melhorando..." +- Quando completo, mensagem é atualizada com versão refinada +- Toggle "Agentic" controla se ativa especialistas + +**Opção B: Síncrono** +- Aguarda todos os especialistas antes de mostrar +- Mais lento mas garante qualidade máxima +- Mostra progresso em tempo real (SpecialistCards) + +### Proposta: Híbrido com Streaming + +```typescript +// No ChatView +const [reviewCycle, setReviewCycle] = useState(null); + +// Após receber resposta completa +if (agenticMode) { + const cycle = await reviewOrchestrator.startReview( + messageId, + responseContent, + { chatId, previousMessages } + ); + setReviewCycle(cycle); + + // Mostra mensagem inicial imediatamente + // Atualiza quando especialistas completam + cycle.onProgress((updated) => { + setReviewCycle({ ...updated }); + }); +} +``` + +### UI Components + +1. **ReviewProgressIndicator** - Badge mostrando "3/5 especialistas completos" +2. **ReviewDiffViewer** - Mostra diff entre original e refinado +3. **SpecialistSidebar** - Panel mostrando thinking de cada especialista +4. **SuggestionAcceptReject** - Controles para aceitar/rejeitar sugestões + +## 5. Especialistas Padrão + +### 1. CodeReviewer +```typescript +{ + id: "code-reviewer", + name: "Code Reviewer", + title: "Revisor de Código", + activationRules: [{ type: "contains-code" }], + systemPrompt: `Você é um revisor de código sênior. Analise o código fornecido e sugira melhorias em: +- Eficiência e performance +- Legibilidade e clareza +- Boas práticas e padrões +- Possíveis bugs ou edge cases + +Formato de resposta (JSON): +{ + "thinking": "sua análise passo a passo", + "suggestions": [ + { + "type": "improvement", + "original": "código original", + "replacement": "código melhorado", + "explanation": "por que melhorar", + "confidence": 0.95 + } + ] +}` +} +``` + +### 2. StyleEditor +```typescript +{ + id: "style-editor", + name: "Style Editor", + title: "Editor de Estilo", + activationRules: [{ type: "length-threshold", minChars: 200 }], + systemPrompt: `Você é um editor de texto profissional. Melhore a clareza e fluidez do texto mantendo o significado original. + +Foque em: +- Clareza e concisão +- Fluxo e coesão entre frases +- Tom apropriado ao contexto +- Eliminação de redundâncias + +Formato de resposta (JSON): +{ + "thinking": "análise do estilo", + "suggestions": [...] +}` +} +``` + +### 3. FactChecker +```typescript +{ + id: "fact-checker", + name: "Fact Checker", + title: "Verificador de Fatos", + activationRules: [{ type: "content-pattern", regex: "\b(sabe|sabia|fato|verdade|estudo|pesquisa|dado|estatística)\b" }], + systemPrompt: `Você é um verificador de fatos. Identifique afirmações factuais no texto e avalie: +- Se são factualmente corretas (conhecimento até 2024) +- Se precisam de citação ou fonte +- Se há nuances ou contexto faltante + +NÃO invente informações. Marque como "não verificável" se incerto. + +Formato de resposta (JSON): +{ + "thinking": "análise factual", + "suggestions": [ + { + "type": "correction", + "original": "afirmação incorreta", + "replacement": "versão corrigida ou com ressalvas", + "explanation": "por que precisa correção", + "confidence": 0.85 + } + ] +}` +} +``` + +### 4. ToneAdapter +```typescript +{ + id: "tone-adapter", + name: "Tone Adapter", + title: "Adaptador de Tom", + activationRules: [{ type: "always" }], + systemPrompt: `Você adapta o tom da resposta ao contexto da conversa. + +Analise o histórico recente e ajuste: +- Formalidade (técnico vs casual) +- Entusiasmo (neutro vs animado) +- Detalhamento (resumo vs aprofundado) +- Personalização (menção a fatos do usuário) + +Formato de resposta (JSON): +{ + "thinking": "análise do contexto e tom adequado", + "suggestions": [...] +}` +} +``` + +## 6. Fluxo de Execução + +```typescript +// 1. Usuário envia mensagem +// 2. Recebe resposta do assistente +// 3. Se agenticMode ativo: + +async function processWithSpecialists(messageId: number, content: string) { + // Determina quais especialistas ativar + const activeSpecialists = specialistRegistry.getActiveForContent(content, context); + + // Cria ciclo de revisão + const cycle: ReviewCycle = { + id: generateUUID(), + originalMessageId: messageId, + originalContent: content, + specialists: activeSpecialists.map(s => ({ + specialistId: s.id, + status: "pending", + suggestions: [], + applied: false + })), + finalContent: content, // Começa com original + status: "running", + startedAt: Date.now() + }; + + // Executa sequencialmente (para builds contextuais) + let currentContent = content; + for (const specialist of activeSpecialists) { + updateSpecialistStatus(cycle, specialist.id, "thinking"); + + const review = await runSpecialist(specialist, currentContent, context); + + updateSpecialistStatus(cycle, specialist.id, "completed", review); + + // Aplica sugestões automaticamente (pode ser manual no futuro) + currentContent = applySuggestions(currentContent, review); + cycle.finalContent = currentContent; + + notifyProgress(cycle); + } + + cycle.status = "completed"; + cycle.completedAt = Date.now(); + + // Atualiza mensagem no DB com versão refinada + await updateMessage(messageId, cycle.finalContent, cycle); + + return cycle; +} +``` + +## 7. Persistência + +### Tabela: specialist_reviews +```sql +CREATE TABLE specialist_reviews ( + id TEXT PRIMARY KEY, + message_id INTEGER NOT NULL, + original_content TEXT NOT NULL, + final_content TEXT NOT NULL, + specialists_json TEXT NOT NULL, -- SpecialistReview[] + status TEXT NOT NULL, + created_at INTEGER NOT NULL, + completed_at INTEGER +); +``` + +### Tabela: specialist_definitions (configuração do usuário) +```sql +CREATE TABLE specialist_definitions ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + title TEXT NOT NULL, + system_prompt TEXT NOT NULL, + activation_rules_json TEXT NOT NULL, + is_enabled BOOLEAN DEFAULT 1, + sort_order INTEGER DEFAULT 0 +); +``` + +## 8. Configuração do Usuário + +### Settings UI +- Lista de especialistas (drag-drop para reordenar) +- Toggle individual para cada especialista +- Editor de system prompts customizados +- Regras de ativação configuráveis + +### Padrões do Sistema +```typescript +const DEFAULT_SPECIALISTS: SpecialistDefinition[] = [ + toneAdapter, // Sempre ativo + codeReviewer, // Só com código + styleEditor, // Textos longos + factChecker, // Quando detecta factualidade +]; +``` + +## 9. Otimizações de Performance + +1. **Caching**: Especialistas memorizam contexto entre mensagens +2. **Lazy Loading**: Só carrega especialistas quando necessário +3. **Parallel where safe**: Especialistas independentes rodam em paralelo +4. **Early Exit**: Se confiança < threshold, pula para o próximo +5. **Model Tier**: Especialistas usam modelos menores/fasters quando possível + +## 10. Considerações de UX + +### Quando Mostrar +- ✅ Sempre mostrar resultado original primeiro +- ✅ Mostrar progresso discreto (badge, não modal) +- ✅ Permitir ver thinking de cada especialista (expansível) +- ✅ Permitir toggle on/off global (já existe AgenticToggle) +- ✅ Permitir ver diff original vs refinado + +### Quando NÃO Usar +- ❌ Não bloquear a UI esperando especialistas +- ❌ Não mostrar erros de especialista ao usuário (fail silently) +- ❌ Não processar mensagens muito curtas (< 50 chars) +- ❌ Não processar se modelo indisponível + +## 11. MVP - Fase 1 + +Implementação mínima viável: + +1. **2 especialistas hardcoded**: ToneAdapter (sempre) + CodeReviewer (se código) +2. **Execução sequencial síncrona** (simplifica UI) +3. **Aplicação automática** de sugestões +4. **UI mínima**: Badge "Melhorando..." → "Melhorado ✓" + SpecialistCard expansível +5. **Sem persistência** de review cycles (só resultado final) + +### Arquivos a Criar (Fase 1) +``` +src/ + lib/ + specialist-definitions.ts # Especialistas padrão + review-orchestrator.ts # Lógica de orquestração + components/ + forgeagent/ + ReviewProgress.tsx # Badge de progresso + ReviewDiffViewer.tsx # Diff original vs refinado + hooks/ + useSpecialistReview.ts # Hook para gerenciar ciclo +``` + +### Arquivos a Modificar +``` +src/components/ChatView.tsx # Integrar ao fluxo de resposta +src/components/MessageBubble.tsx # Mostrar badge de revisão +src/prompts/specialists/ # Prompts de cada especialista +``` + +## 12. Próximos Passos + +1. ✅ Validar arquitetura com o usuário +2. Implementar Fase 1 (MVP) +3. Testar com diferentes modelos (Qwen3 0.8B, 2B, 4B) +4. Ajustar prompts baseado em resultados +5. Adicionar especialistas adicionais +6. Implementar modo paralelo assíncrono +7. Adicionar configuração via UI Settings diff --git a/tauri-app/docs/PARALLEL_AGENT_ARCHITECTURE.md b/tauri-app/docs/PARALLEL_AGENT_ARCHITECTURE.md new file mode 100644 index 0000000..cba4236 --- /dev/null +++ b/tauri-app/docs/PARALLEL_AGENT_ARCHITECTURE.md @@ -0,0 +1,498 @@ +# Arquitetura: Modo Paralelo Multi-Agente com Síntese + +## Visão Geral + +Sistema onde múltiplos agentes (2-16) processam o prompt simultaneamente, e um sintetizador consolida todas as respostas em uma resposta final otimizada. + +## Estrutura de Dados + +### Estado do Sistema + +```typescript +// Substitui agenticMode: boolean → agentCount: number (0 = desativado) +type AgentCount = 0 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16; + +// Agente individual no modo paralelo +interface ParallelAgent { + id: string; // "agent-1", "agent-2", etc. + name: string; // Nome exibido no card + role: string; // "Crítico", "Criativo", "Técnico", etc. + systemPrompt: string; // Prompt específico deste agente + status: "idle" | "streaming" | "completed" | "error"; + content: string; // Resposta acumulada (streaming) + streamChunks: string[]; // Chunks para renderização em tempo real + tokensPerSecond: number | null; + error?: string; +} + +// Estado completo da revisão paralela +interface ParallelReviewState { + messageId: number; // ID da mensagem sendo processada + agents: ParallelAgent[]; // Lista de agentes (2-16) + + // Barreira de sincronia + allAgentsCompleted: boolean; // true quando TODOS terminaram + completedAt: number | null; // Timestamp de conclusão do último agente + + // Sintetizador + synthesizerStatus: "waiting" | "streaming" | "completed" | "error" | "skipped"; + synthesizerContent: string; // Resposta final sendo gerada + + // Estado geral + status: "running" | "completed" | "error"; + startedAt: number; + completedAt: number | null; +} +``` + +## Fluxo de Execução + +### 1. Inicialização (ChatView) + +```typescript +// Antes de enviar mensagem +const agentCount = useAgentStore((s) => s.agentCount); // 2-16 ou 0 + +// Se agentCount > 0, não envia para o LLM principal diretamente +// Em vez disso, inicia o modo paralelo +if (agentCount > 0) { + await parallelOrchestrator.startParallelReview( + messageId, + userMessage, + agentCount, + context + ); +} else { + // Fluxo normal + await sendToLLM(userMessage); +} +``` + +### 2. Orquestração Paralela + +```typescript +class ParallelOrchestrator { + async startParallelReview( + messageId: number, + userPrompt: string, + agentCount: number, + context: Context + ): Promise { + + // 1. Gerar N agentes dinamicamente + const agents = this.generateAgents(agentCount, userPrompt); + + // 2. Inicializar estado + const state: ParallelReviewState = { + messageId, + agents: agents.map((a) => ({ ...a, status: "idle", content: "" })), + allAgentsCompleted: false, + completedAt: null, + synthesizerStatus: "waiting", + synthesizerContent: "", + status: "running", + startedAt: Date.now(), + completedAt: null, + }; + + // 3. Iniciar TODOS os agentes em paralelo + const agentPromises = agents.map((agent, index) => + this.runAgentStreaming(agent, index, userPrompt, context, state) + ); + + // 4. BARREIRA: Aguardar TODOS terminarem + await Promise.all(agentPromises); + + state.allAgentsCompleted = true; + state.agentsCompletedAt = Date.now(); + + // 5. Chamar sintetizador (só depois da barreira) + await this.runSynthesizer(userPrompt, state, context); + + state.status = "completed"; + state.completedAt = Date.now(); + } + + private async runAgentStreaming( + agent: AgentDefinition, + index: number, + userPrompt: string, + context: Context, + state: ParallelReviewState + ): Promise { + state.agents[index].status = "streaming"; + + const response = await fetch(`${context.server}/v1/chat/completions`, { + method: "POST", + headers: { /* ... */ }, + body: JSON.stringify({ + model: context.model, + messages: [ + { role: "system", content: agent.systemPrompt }, + { role: "user", content: userPrompt } + ], + stream: true, // Stream para atualização em tempo real + }), + }); + + // Processar stream e atualizar state.agents[index].content em tempo real + for await (const chunk of response.body) { + const content = extractContent(chunk); + state.agents[index].content += content; + state.agents[index].streamChunks.push(content); + this.notifyUpdate(state); // Callback para UI + } + + state.agents[index].status = "completed"; + } + + private async runSynthesizer( + originalPrompt: string, + state: ParallelReviewState, + context: Context + ): Promise { + state.synthesizerStatus = "streaming"; + + // Construir contexto com TODAS as respostas dos agentes + const synthesizerPrompt = this.buildSynthesizerPrompt( + originalPrompt, + state.agents + ); + + const response = await fetch(`${context.server}/v1/chat/completions`, { + method: "POST", + headers: { /* ... */ }, + body: JSON.stringify({ + model: context.model, + messages: [ + { role: "system", content: SYNTHESIZER_SYSTEM_PROMPT }, + { role: "user", content: synthesizerPrompt } + ], + stream: true, + }), + }); + + // Stream da resposta final + for await (const chunk of response.body) { + const content = extractContent(chunk); + state.synthesizerContent += content; + this.notifyUpdate(state); + } + + state.synthesizerStatus = "completed"; + + // Salvar no DB + await this.saveFinalResponse(state); + } +} +``` + +## Geração Dinâmica de Agentes + +```typescript +function generateAgents(count: number, userPrompt: string): AgentDefinition[] { + // Especialistas base que variam conforme o contexto + const baseRoles = [ + { name: "Analista", focus: "Analisar profundamente o problema" }, + { name: "Criativo", focus: "Gerar soluções inovadoras e fora da caixa" }, + { name: "Crítico", focus: "Identificar falhas, riscos e edge cases" }, + { name: "Prático", focus: "Focar em implementação e viabilidade" }, + { name: "Especialista", focus: "Aprofundar em detalhes técnicos" }, + { name: "Simplificador", focus: "Explicar de forma clara e acessível" }, + { name: "Histórico", focus: "Considerar contexto e precedentes" }, + { name: "Futurista", focus: "Projetar implicações de longo prazo" }, + ]; + + // Detectar tipo de pergunta para especialização + const isCode = /```|function|class|const|let|var|import|export/.test(userPrompt); + const isCreative = /ideia|criar|inventar|imagine|story|história/i.test(userPrompt); + const isTechnical = /como funciona|explica|por que/i.test(userPrompt); + + // Selecionar roles apropriados + const selectedRoles = selectRoles(baseRoles, count, { isCode, isCreative, isTechnical }); + + return selectedRoles.map((role, i) => ({ + id: `agent-${i + 1}`, + name: role.name, + role: role.focus, + systemPrompt: buildAgentPrompt(role, i + 1, count), + })); +} + +function buildAgentPrompt(role: Role, index: number, total: number): string { + return `Você é o ${role.name} (${index}/${total}) em um painel de especialistas. + +SUA FUNÇÃO ESPECÍFICA: ${role.focus} + +REGRAS: +1. Responda à pergunta do usuário APENAS da perspectiva do seu papel +2. Não se preocupe em ser completo - outros especialistas cobrirão outros ângulos +3. Seja específico e aprofunde no que seu papel exige +4. Mantenha resposta concisa (máx 500 tokens) para facilitar síntese posterior +5. Use markdown quando apropriado + +Você é o especialista #${index} de ${total}. Foque em contribuir sua perspectiva única.`; +} +``` + +## Prompt do Sintetizador + +```typescript +const SYNTHESIZER_SYSTEM_PROMPT = `Você é um sintetizador master. Sua função é consolidar múltiplas perspectivas de especialistas em uma resposta única, otimizada e completa. + +ENTRADAS QUE VOCÊ RECEBERÁ: +1. Pergunta original do usuário +2. Respostas de N especialistas (cada um com perspectiva diferente) + +SUA TAREFA: +1. Analisar todas as contribuições dos especialistas +2. Identificar pontos de concordância e complementaridade +3. Resolver contradições de forma equilibrada +4. Criar uma resposta final que: + - Seja mais completa que qualquer resposta individual + - Mantenha o melhor de cada perspectiva + - Tenha fluxo natural e coeso + - Seja precisa e bem fundamentada + +ESTRUTURA DA RESPOSTA FINAL: +- Comece com uma resposta direta à pergunta +- Desenvolva com detalhes relevantes das perspectivas combinadas +- Use exemplos práticos quando apropriado +- Mantenha o tom apropriado ao contexto + +NÃO mencione que esta é uma síntese de múltiplos agentes. A resposta deve parecer uma única resposta coesa e natural.`; + +function buildSynthesizerPrompt(originalPrompt: string, agents: ParallelAgent[]): string { + const agentResponses = agents + .map((a, i) => `=== ESPECIALISTA ${i + 1}: ${a.name} ===\n${a.content}\n`) + .join("\n"); + + return `PERGUNTA ORIGINAL:\n${originalPrompt}\n\n${agentResponses}`; +} +``` + +## UI Components + +### 1. AgentCountSelector (substitui AgenticToggle) + +```typescript +interface AgentCountSelectorProps { + value: AgentCount; // 0-16, onde 0 = desativado + onChange: (count: AgentCount) => void; +} + +// Slider de 0 a 16, com: +// - 0 = "Modo Normal" (label) +// - 2-16 = "Modo Multi-Agente: N agentes" +// - Marcadores visuais em 2, 4, 8, 16 +``` + +### 2. ParallelAgentsPanel (dentro de MessageBubble) + +```typescript +interface ParallelAgentsPanelProps { + state: ParallelReviewState; + onAgentClick?: (agentId: string) => void; +} + +// Layout: Grid de cards (2 colunas em mobile, 3-4 em desktop) +// Cada card mostra: +// - Nome do agente + ícone/avatar +// - Status (streaming dots, check, error) +// - Preview do conteúdo (scrollable) +// - Progresso visual (se streaming) + +// Seção separada para sintetizador: +// - Mostra só depois de allAgentsCompleted = true +// - Indicador "Sintetizando..." com animação +// - Streaming da resposta final +``` + +### 3. Estado no ChatView + +```typescript +const [agentCount, setAgentCount] = useState(0); +const [parallelStates, setParallelStates] = useState>(new Map()); + +// Quando uma mensagem é criada no modo paralelo: +const handleParallelStart = (messageId: number, state: ParallelReviewState) => { + setParallelStates((prev) => new Map(prev).set(messageId, state)); +}; + +// Atualizações em tempo real via callback: +const handleParallelUpdate = (messageId: number, state: ParallelReviewState) => { + setParallelStates((prev) => new Map(prev).set(messageId, state)); +}; +``` + +## Hook useParallelReview + +```typescript +function useParallelReview() { + const [states, setStates] = useState>(new Map()); + const orchestratorRef = useRef(null); + + const startParallelReview = useCallback(async ( + messageId: number, + userPrompt: string, + agentCount: number, + context: Context + ) => { + orchestratorRef.current = new ParallelOrchestrator({ + onUpdate: (state) => { + setStates((prev) => new Map(prev).set(messageId, state)); + } + }); + + await orchestratorRef.current.start(messageId, userPrompt, agentCount, context); + }, []); + + const getStateForMessage = useCallback((messageId: number) => { + return states.get(messageId) || null; + }, [states]); + + return { startParallelReview, getStateForMessage }; +} +``` + +## Modificações Necessárias + +### Arquivos a Modificar + +1. **`src/stores/agentStore.ts`** (novo) + - Estado global: `agentCount: AgentCount` + - Persistir no localStorage + +2. **`src/components/AgentCountSelector.tsx`** (novo) + - Slider 0-16 com labels + - Tooltip explicando modo multi-agente + +3. **`src/lib/parallel-orchestrator.ts`** (novo) + - Lógica completa descrita acima + - Promise.all para paralelismo + - Barreira de sincronia + - Sintetizador + +4. **`src/components/ParallelAgentsPanel.tsx`** (novo) + - Grid de cards de agentes + - Visualização de streaming + - Seção do sintetizador + +5. **`src/components/ChatView.tsx`** + - Substituir toggle por AgentCountSelector + - Condicional: se agentCount > 0, usa parallel-orchestrator + - Passar parallelState para MessageBubble + +6. **`src/components/MessageBubble.tsx`** + - Prop opcional: `parallelState?: ParallelReviewState` + - Se presente, renderiza ParallelAgentsPanel + - Senão, comportamento normal + +7. **`src/hooks/useParallelReview.ts`** (novo) + - Hook para gerenciar estado + - Interface com orchestrator + +### Arquivos a Deprecar/Manter + +- **`src/lib/review-orchestrator.ts`**: Manter (modo sequencial ainda útil) +- **`src/hooks/useSpecialistReview.ts`**: Manter para compatibilidade +- **`src/components/forgeagent/ReviewProgress.tsx`**: Manter para modo sequencial + +## Fluxo Visual Esperado + +``` +[Input Area] +Slider: [0====|====4====|====8====|====12====|====16] +Label: "Modo Multi-Agente: 5 agentes" + +[Usuário envia mensagem] + +[MessageBubble aparece] +┌─────────────────────────────────────────┐ +│ 🤖 Processando com 5 especialistas... │ +├─────────────────────────────────────────┤ +│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ +│ │Analista │ │ Criativo│ │ Crítico │ │ +│ │ ●●● │ │ ●●● │ │ ●●● │ │ +│ │Streaming│ │Streaming│ │ Aguarde │ │ +│ └─────────┘ └─────────┘ └─────────┘ │ +│ ┌─────────┐ ┌─────────┐ │ +│ │ Prático │ │Especiali│ │ +│ │ ●●● │ │ ✓ │ │ +│ │Streaming│ │Completo │ │ +│ └─────────┘ └─────────┘ │ +├─────────────────────────────────────────┤ +│ ⏳ Aguardando todos os agentes... │ +└─────────────────────────────────────────┘ + +[Todos completam] + +[MessageBubble atualiza] +┌─────────────────────────────────────────┐ +│ ✓ Análise de 5 especialistas completa │ +├─────────────────────────────────────────┤ +│ [Cards colapsados ou em abas] │ +│ ├─ Analista [Ver ▼] │ +│ ├─ Criativo [Ver ▼] │ +│ ├─ Crítico [Ver ▼] │ +│ ├─ Prático [Ver ▼] │ +│ └─ Especialista[Ver ▼] │ +├─────────────────────────────────────────┤ +│ 🤖 Sintetizando resposta final... │ +│ [Streaming da resposta consolidada] │ +└─────────────────────────────────────────┘ + +[Síntese completa] + +[MessageBubble final] +┌─────────────────────────────────────────┐ +│ [Resposta final sintetizada] │ +│ │ +│ [Conteúdo completo e otimizado] │ +│ │ +├─────────────────────────────────────────┤ +│ 💡 Baseado em análise de 5 especialistas│ +│ [Ver detalhes ▼] │ +└─────────────────────────────────────────┘ +``` + +## Considerações de Performance + +1. **N requisições simultâneas**: Pode sobrecarregar o servidor local + - Mitigação: Limitar a 8 agentes por padrão + - Ou implementar batching/semaforo + +2. **Memória**: N streams simultâneas + - Mitigação: Manter só últimos N chunks visíveis + - Descartar chunks antigos da UI + +3. **Tempo total**: max(tempo_agentes) + tempo_sintetizador + - Mitigação: Usar modelo mais rápido para agentes + - Modelo melhor só para sintetizador + +## Próximos Passos + +### Fase 1: UI e Estado +1. Criar agentStore com persistência +2. Criar AgentCountSelector component +3. Integrar no ChatView (substituir toggle) + +### Fase 2: Orquestrador Paralelo +1. Criar parallel-orchestrator.ts +2. Implementar geração dinâmica de agentes +3. Promise.all com streaming simultâneo +4. Barreira de sincronia + +### Fase 3: UI de Agentes +1. Criar ParallelAgentsPanel +2. Grid de cards responsivo +3. Streaming em tempo real +4. Collapse/expand de cards + +### Fase 4: Sintetizador +1. Prompt do sintetizador +2. Integração no orchestrator +3. Streaming da resposta final +4. Salvamento no DB + +Quer que eu comece implementando a Fase 1 (UI do seletor)? diff --git a/tauri-app/package-lock.json b/tauri-app/package-lock.json new file mode 100644 index 0000000..68f23cf --- /dev/null +++ b/tauri-app/package-lock.json @@ -0,0 +1,4337 @@ +{ + "name": "forgelm-chat", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "forgelm-chat", + "version": "0.1.0", + "dependencies": { + "@phosphor-icons/react": "^2.1.10", + "@tauri-apps/api": "^2", + "@tauri-apps/plugin-shell": "^2", + "dompurify": "^3.0.0", + "framer-motion": "^12.38.0", + "highlight.js": "^11.0.0", + "lucide-react": "^1.7.0", + "marked": "^12.0.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "zustand": "^5.0.12" + }, + "devDependencies": { + "@tauri-apps/cli": "^2", + "@types/dompurify": "^3.0.0", + "@types/react": "^18.3.1", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.1", + "autoprefixer": "^10.4.27", + "jsdom": "^29.0.1", + "postcss": "^8.5.8", + "tailwindcss": "^3.4.19", + "typescript": "^5.5.3", + "vite": "^5.4.8", + "vitest": "^4.1.2" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "5.1.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^3.1.1", + "@csstools/css-color-parser": "^4.0.2", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "7.0.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "6.0.2", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.1.1", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.0.2", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.1.1" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.2", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", + "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@exodus/bytes": { + "version": "1.15.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.3.tgz", + "integrity": "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.124.0", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@phosphor-icons/react": { + "version": "2.1.10", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">= 16.8", + "react-dom": ">= 16.8" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.15.tgz", + "integrity": "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.15.tgz", + "integrity": "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.9.2", + "@emnapi/runtime": "1.9.2", + "@napi-rs/wasm-runtime": "^1.1.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.15", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", + "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", + "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", + "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", + "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", + "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", + "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", + "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", + "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", + "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", + "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", + "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", + "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", + "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", + "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", + "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", + "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", + "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", + "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", + "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", + "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", + "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", + "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", + "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.1", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.1", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@tauri-apps/api": { + "version": "2.10.1", + "license": "Apache-2.0 OR MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, + "node_modules/@tauri-apps/cli": { + "version": "2.10.1", + "dev": true, + "license": "Apache-2.0 OR MIT", + "bin": { + "tauri": "tauri.js" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + }, + "optionalDependencies": { + "@tauri-apps/cli-darwin-arm64": "2.10.1", + "@tauri-apps/cli-darwin-x64": "2.10.1", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.10.1", + "@tauri-apps/cli-linux-arm64-gnu": "2.10.1", + "@tauri-apps/cli-linux-arm64-musl": "2.10.1", + "@tauri-apps/cli-linux-riscv64-gnu": "2.10.1", + "@tauri-apps/cli-linux-x64-gnu": "2.10.1", + "@tauri-apps/cli-linux-x64-musl": "2.10.1", + "@tauri-apps/cli-win32-arm64-msvc": "2.10.1", + "@tauri-apps/cli-win32-ia32-msvc": "2.10.1", + "@tauri-apps/cli-win32-x64-msvc": "2.10.1" + } + }, + "node_modules/@tauri-apps/cli-darwin-arm64": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.10.1.tgz", + "integrity": "sha512-Z2OjCXiZ+fbYZy7PmP3WRnOpM9+Fy+oonKDEmUE6MwN4IGaYqgceTjwHucc/kEEYZos5GICve35f7ZiizgqEnQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-darwin-x64": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.10.1.tgz", + "integrity": "sha512-V/irQVvjPMGOTQqNj55PnQPVuH4VJP8vZCN7ajnj+ZS8Kom1tEM2hR3qbbIRoS3dBKs5mbG8yg1WC+97dq17Pw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.10.1.tgz", + "integrity": "sha512-Hyzwsb4VnCWKGfTw+wSt15Z2pLw2f0JdFBfq2vHBOBhvg7oi6uhKiF87hmbXOBXUZaGkyRDkCHsdzJcIfoJC2w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-gnu": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.10.1.tgz", + "integrity": "sha512-OyOYs2t5GkBIvyWjA1+h4CZxTcdz1OZPCWAPz5DYEfB0cnWHERTnQ/SLayQzncrT0kwRoSfSz9KxenkyJoTelA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-musl": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.10.1.tgz", + "integrity": "sha512-MIj78PDDGjkg3NqGptDOGgfXks7SYJwhiMh8SBoZS+vfdz7yP5jN18bNaLnDhsVIPARcAhE1TlsZe/8Yxo2zqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.10.1.tgz", + "integrity": "sha512-X0lvOVUg8PCVaoEtEAnpxmnkwlE1gcMDTqfhbefICKDnOTJ5Est3qL0SrWxizDackIOKBcvtpejrSiVpuJI1kw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-gnu": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.10.1.tgz", + "integrity": "sha512-2/12bEzsJS9fAKybxgicCDFxYD1WEI9kO+tlDwX5znWG2GwMBaiWcmhGlZ8fi+DMe9CXlcVarMTYc0L3REIRxw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-musl": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.10.1.tgz", + "integrity": "sha512-Y8J0ZzswPz50UcGOFuXGEMrxbjwKSPgXftx5qnkuMs2rmwQB5ssvLb6tn54wDSYxe7S6vlLob9vt0VKuNOaCIQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-arm64-msvc": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.10.1.tgz", + "integrity": "sha512-iSt5B86jHYAPJa/IlYw++SXtFPGnWtFJriHn7X0NFBVunF6zu9+/zOn8OgqIWSl8RgzhLGXQEEtGBdR4wzpVgg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-ia32-msvc": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.10.1.tgz", + "integrity": "sha512-gXyxgEzsFegmnWywYU5pEBURkcFN/Oo45EAwvZrHMh+zUSEAvO5E8TXsgPADYm31d1u7OQU3O3HsYfVBf2moHw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-x64-msvc": { + "version": "2.10.1", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/plugin-shell": { + "version": "2.3.5", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.10.1" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/dompurify": { + "version": "3.2.0", + "deprecated": "This is a stub types definition. dompurify provides its own type definitions, so you do not need this installed.", + "dev": true, + "license": "MIT", + "dependencies": { + "dompurify": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.28", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "license": "MIT", + "optional": true + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.4", + "@vitest/utils": "4.1.4", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.4", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.4", + "@vitest/utils": "4.1.4", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.4", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.4", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.27", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001774", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.17", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001787", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "6.2.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/css-tree": { + "version": "3.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "devOptional": true, + "license": "MIT" + }, + "node_modules/data-urls": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/dompurify": { + "version": "3.3.3", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.335", + "dev": true, + "license": "ISC" + }, + "node_modules/entities": { + "version": "6.0.1", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-module-lexer": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/framer-motion": { + "version": "12.38.0", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.38.0", + "motion-utils": "^12.36.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/highlight.js": { + "version": "11.11.1", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/jiti": { + "version": "1.21.7", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "license": "MIT" + }, + "node_modules/jsdom": { + "version": "29.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^5.1.5", + "@asamuzakjp/dom-selector": "^7.0.6", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.1", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.7", + "parse5": "^8.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.1", + "undici": "^7.24.5", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.1", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "11.3.3", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/lucide-react": { + "version": "1.8.0", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/marked": { + "version": "12.0.2", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/merge2": { + "version": "1.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/motion-dom": { + "version": "12.38.0", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.36.0" + } + }, + "node_modules/motion-utils": { + "version": "12.36.0", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.37", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/parse5": { + "version": "8.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.9", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-rc.15", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.124.0", + "@rolldown/pluginutils": "1.0.0-rc.15" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-x64": "1.0.0-rc.15", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" + } + }, + "node_modules/rolldown/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.15", + "dev": true, + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.60.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.1", + "@rollup/rollup-android-arm64": "4.60.1", + "@rollup/rollup-darwin-arm64": "4.60.1", + "@rollup/rollup-darwin-x64": "4.60.1", + "@rollup/rollup-freebsd-arm64": "4.60.1", + "@rollup/rollup-freebsd-x64": "4.60.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", + "@rollup/rollup-linux-arm-musleabihf": "4.60.1", + "@rollup/rollup-linux-arm64-gnu": "4.60.1", + "@rollup/rollup-linux-arm64-musl": "4.60.1", + "@rollup/rollup-linux-loong64-gnu": "4.60.1", + "@rollup/rollup-linux-loong64-musl": "4.60.1", + "@rollup/rollup-linux-ppc64-gnu": "4.60.1", + "@rollup/rollup-linux-ppc64-musl": "4.60.1", + "@rollup/rollup-linux-riscv64-gnu": "4.60.1", + "@rollup/rollup-linux-riscv64-musl": "4.60.1", + "@rollup/rollup-linux-s390x-gnu": "4.60.1", + "@rollup/rollup-linux-x64-gnu": "4.60.1", + "@rollup/rollup-linux-x64-musl": "4.60.1", + "@rollup/rollup-openbsd-x64": "4.60.1", + "@rollup/rollup-openharmony-arm64": "4.60.1", + "@rollup/rollup-win32-arm64-msvc": "4.60.1", + "@rollup/rollup-win32-ia32-msvc": "4.60.1", + "@rollup/rollup-win32-x64-gnu": "4.60.1", + "@rollup/rollup-win32-x64-msvc": "4.60.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/saxes": { + "version": "6.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/sucrase": { + "version": "3.35.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "dev": true, + "license": "MIT" + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.0.28", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.28" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.28", + "dev": true, + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tough-cookie": { + "version": "6.0.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.8.1", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "7.24.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "5.4.21", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.4", + "@vitest/mocker": "4.1.4", + "@vitest/pretty-format": "4.1.4", + "@vitest/runner": "4.1.4", + "@vitest/snapshot": "4.1.4", + "@vitest/spy": "4.1.4", + "@vitest/utils": "4.1.4", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.4", + "@vitest/browser-preview": "4.1.4", + "@vitest/browser-webdriverio": "4.1.4", + "@vitest/coverage-istanbul": "4.1.4", + "@vitest/coverage-v8": "4.1.4", + "@vitest/ui": "4.1.4", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/vitest/node_modules/vite": { + "version": "8.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.15", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-url": { + "version": "16.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/yallist": { + "version": "3.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/zustand": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.12.tgz", + "integrity": "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/tauri-app/package.json b/tauri-app/package.json index a0630f0..e81e93a 100644 --- a/tauri-app/package.json +++ b/tauri-app/package.json @@ -20,7 +20,8 @@ "lucide-react": "^1.7.0", "marked": "^12.0.0", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "zustand": "^5.0.12" }, "devDependencies": { "@tauri-apps/cli": "^2", diff --git a/tauri-app/src-tauri/src/model_alias.rs b/tauri-app/src-tauri/src/model_alias.rs index 9b0c68c..220c4e9 100644 --- a/tauri-app/src-tauri/src/model_alias.rs +++ b/tauri-app/src-tauri/src/model_alias.rs @@ -1,56 +1,32 @@ -//! Model alias resolution and startup model selection policy. -//! -//! Alias table: -//! 0.8B family → "Cooper" -//! 2B family → "Iron" -//! 4B family → "Steel" - use crate::model_files::ModelFile; -/// Canonical display alias for a model family. #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "snake_case")] pub enum ModelAlias { - Cooper, - Iron, - Steel, + Bonsai, Unknown, } impl ModelAlias { pub fn as_str(&self) -> &'static str { match self { - ModelAlias::Cooper => "Cooper", - ModelAlias::Iron => "Iron", - ModelAlias::Steel => "Steel", + ModelAlias::Bonsai => "Bonsai", ModelAlias::Unknown => "Unknown", } } } -/// Resolve a model file path/name to a display alias. -/// Priority: -/// 1. filename contains "0.8b" (case-insensitive) → Cooper -/// 2. filename contains "4b" (case-insensitive) → Steel (checked before 2b to avoid false match) -/// 3. filename contains "2b" (case-insensitive) → Iron -/// 4. fallback → Unknown pub fn resolve_alias(path: &str) -> ModelAlias { let lower = path.to_lowercase(); - // Extract just the filename for matching let filename = lower.rsplit(['/', '\\']).next().unwrap_or(&lower); - if filename.contains("0.8b") { - ModelAlias::Cooper - } else if filename.contains("4b") { - ModelAlias::Steel - } else if filename.contains("2b") { - ModelAlias::Iron + if filename.contains("bonsai") { + ModelAlias::Bonsai } else { ModelAlias::Unknown } } -/// Result of startup model resolution. #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct StartupModelResolution { pub model_path: String, @@ -62,18 +38,10 @@ pub struct StartupModelResolution { #[serde(rename_all = "snake_case")] pub enum StartupReason { SavedPreference, - DefaultIron, - FallbackCooper, + DefaultBonsai, MissingSavedModel, } -/// Resolve which model to start at app launch. -/// -/// Policy (from SDD): -/// 1. If user saved a preferred alias and a matching model exists → use it -/// 2. Prefer Iron (2B) -/// 3. Fallback to Cooper (0.8B) -/// 4. Steel is ignored by auto-start policy pub fn resolve_startup_model( preferred_alias: Option<&str>, discovered: &[ModelFile], @@ -82,7 +50,6 @@ pub fn resolve_startup_model( return None; } - // Classify all discovered models let classified: Vec<(&ModelFile, ModelAlias)> = discovered .iter() .map(|m| (m, resolve_alias(&m.path))) @@ -91,9 +58,7 @@ pub fn resolve_startup_model( // 1. Respect saved preference if available if let Some(pref) = preferred_alias { let target_alias = match pref.to_lowercase().as_str() { - "cooper" => ModelAlias::Cooper, - "iron" => ModelAlias::Iron, - "steel" => ModelAlias::Steel, + "bonsai" => ModelAlias::Bonsai, _ => ModelAlias::Unknown, }; if target_alias != ModelAlias::Unknown { @@ -104,30 +69,24 @@ pub fn resolve_startup_model( reason: StartupReason::SavedPreference, }); } - // Saved model not found — fall through to default policy with MissingSavedModel reason } } - // 2. Prefer Iron - if let Some((model, _)) = classified.iter().find(|(_, a)| *a == ModelAlias::Iron) { + // 2. Default: Bonsai (8B) + if let Some((model, _)) = classified.iter().find(|(_, a)| *a == ModelAlias::Bonsai) { return Some(StartupModelResolution { model_path: model.path.clone(), - alias: ModelAlias::Iron, - reason: StartupReason::DefaultIron, + alias: ModelAlias::Bonsai, + reason: StartupReason::DefaultBonsai, }); } - // 3. Fallback to Cooper - if let Some((model, _)) = classified.iter().find(|(_, a)| *a == ModelAlias::Cooper) { - return Some(StartupModelResolution { - model_path: model.path.clone(), - alias: ModelAlias::Cooper, - reason: StartupReason::FallbackCooper, - }); - } - - // 4. No suitable model found (Steel only or Unknown only) - None + // 3. Fallback: pick the first discovered model regardless of alias + discovered.first().map(|model| StartupModelResolution { + model_path: model.path.clone(), + alias: resolve_alias(&model.path), + reason: StartupReason::DefaultBonsai, + }) } #[cfg(test)] @@ -143,26 +102,10 @@ mod tests { } #[test] - fn resolve_alias_detects_cooper_from_filename() { + fn resolve_alias_detects_bonsai_from_filename() { assert_eq!( - resolve_alias("D:/models/rahul7star_Qwen3.5-0.8B-Coder.gguf"), - ModelAlias::Cooper - ); - } - - #[test] - fn resolve_alias_detects_iron_from_filename() { - assert_eq!( - resolve_alias("D:/models/Qwen3.5-2B.Q6_K.gguf"), - ModelAlias::Iron - ); - } - - #[test] - fn resolve_alias_detects_steel_from_filename() { - assert_eq!( - resolve_alias("D:/models/Qwen3.5-4B-Abliterated.i1-Q6_K.gguf"), - ModelAlias::Steel + resolve_alias("D:/models/Bonsai-8B.gguf"), + ModelAlias::Bonsai ); } @@ -175,40 +118,30 @@ mod tests { } #[test] - fn startup_resolver_picks_iron_when_both_available() { + fn startup_resolver_picks_bonsai_when_available() { let models = vec![ - make_model("rahul7star_Qwen3.5-0.8B-Coder.gguf"), - make_model("Qwen3.5-2B.Q6_K.gguf"), + make_model("Bonsai-8B.gguf"), + make_model("some-other-model.gguf"), ]; let result = resolve_startup_model(None, &models).unwrap(); - assert_eq!(result.alias, ModelAlias::Iron); - assert_eq!(result.reason, StartupReason::DefaultIron); - } - - #[test] - fn startup_resolver_falls_back_to_cooper_when_iron_missing() { - let models = vec![make_model("rahul7star_Qwen3.5-0.8B-Coder.gguf")]; - let result = resolve_startup_model(None, &models).unwrap(); - assert_eq!(result.alias, ModelAlias::Cooper); - assert_eq!(result.reason, StartupReason::FallbackCooper); + assert_eq!(result.alias, ModelAlias::Bonsai); + assert_eq!(result.reason, StartupReason::DefaultBonsai); } #[test] fn startup_resolver_respects_saved_preference() { - let models = vec![ - make_model("rahul7star_Qwen3.5-0.8B-Coder.gguf"), - make_model("Qwen3.5-2B.Q6_K.gguf"), - ]; - let result = resolve_startup_model(Some("cooper"), &models).unwrap(); - assert_eq!(result.alias, ModelAlias::Cooper); + let models = vec![make_model("Bonsai-8B.gguf")]; + let result = resolve_startup_model(Some("bonsai"), &models).unwrap(); + assert_eq!(result.alias, ModelAlias::Bonsai); assert_eq!(result.reason, StartupReason::SavedPreference); } #[test] - fn startup_resolver_ignores_steel_in_auto_policy() { - let models = vec![make_model("Qwen3.5-4B-Abliterated.i1-Q6_K.gguf")]; - let result = resolve_startup_model(None, &models); - assert!(result.is_none(), "Steel should not be auto-selected"); + fn startup_resolver_falls_back_to_first_model_when_no_bonsai() { + let models = vec![make_model("some-other-model.gguf")]; + let result = resolve_startup_model(None, &models).unwrap(); + assert_eq!(result.model_path, "D:/models/some-other-model.gguf"); + assert_eq!(result.reason, StartupReason::DefaultBonsai); } #[test] @@ -216,13 +149,4 @@ mod tests { let result = resolve_startup_model(None, &[]); assert!(result.is_none()); } - - #[test] - fn startup_resolver_falls_through_when_saved_preference_not_found() { - // Saved preference is "iron" but only Cooper is available → fallback to Cooper - let models = vec![make_model("rahul7star_Qwen3.5-0.8B-Coder.gguf")]; - let result = resolve_startup_model(Some("iron"), &models).unwrap(); - assert_eq!(result.alias, ModelAlias::Cooper); - assert_eq!(result.reason, StartupReason::FallbackCooper); - } } diff --git a/tauri-app/src-tauri/src/runtime/types.rs b/tauri-app/src-tauri/src/runtime/types.rs index ebf555c..042d5a2 100644 --- a/tauri-app/src-tauri/src/runtime/types.rs +++ b/tauri-app/src-tauri/src/runtime/types.rs @@ -2,7 +2,8 @@ use serde::{Deserialize, Serialize}; use crate::model_library::types::RemotePreset; -pub const DEFAULT_CTX_SIZE: u32 = 4096; +pub const DEFAULT_CTX_SIZE: u32 = 131072; +pub const DEFAULT_GPU_LAYERS: i32 = -1; #[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default, Debug)] #[serde(rename_all = "snake_case")] @@ -105,7 +106,7 @@ impl Default for RuntimeConfig { model_path: None, models_dir: None, addr: Self::default_addr(), - gpu_layers: 0, + gpu_layers: DEFAULT_GPU_LAYERS, ctx_size: DEFAULT_CTX_SIZE, extra_model_paths: Vec::new(), selected_model_id: None, @@ -118,13 +119,14 @@ impl Default for RuntimeConfig { #[cfg(test)] mod tests { - use super::{ConnectionMode, RuntimeConfig, DEFAULT_CTX_SIZE}; + use super::{ConnectionMode, RuntimeConfig, DEFAULT_CTX_SIZE, DEFAULT_GPU_LAYERS}; #[test] fn runtime_config_defaults_to_safe_context_window() { let config = RuntimeConfig::default(); assert_eq!(config.ctx_size, DEFAULT_CTX_SIZE); + assert_eq!(config.gpu_layers, DEFAULT_GPU_LAYERS); } #[test] diff --git a/tauri-app/src/App.tsx b/tauri-app/src/App.tsx index 3285efd..a153964 100644 --- a/tauri-app/src/App.tsx +++ b/tauri-app/src/App.tsx @@ -151,13 +151,6 @@ export default function App() { onToggleSidebar={() => setSidebarOpen((v) => !v)} onOpenSettings={() => setView("settings")} onBackToChat={view === "settings" ? () => setView("chat") : undefined} - models={models} - localModels={localModels} - selectedModel={selectedModel} - onModelChange={setSelectedModel} - onModelSwap={handleModelSwap} - isSwapping={isSwapping} - status={status} runtimeStatus={runtime.snapshot.status} serverConnected={serverConnected} generating={generating} diff --git a/tauri-app/src/components/AppHeader.tsx b/tauri-app/src/components/AppHeader.tsx index 2cb1578..e9f9a4a 100644 --- a/tauri-app/src/components/AppHeader.tsx +++ b/tauri-app/src/components/AppHeader.tsx @@ -1,6 +1,6 @@ -import type { ModelInfo, ServerStatus, ChatTokenTotals } from "../lib/types"; -import type { RuntimeModelFile, RuntimeStatus } from "../features/runtime/types"; -import ModelSelectorPill from "./ModelSelectorPill"; +import type { ServerStatus, ChatTokenTotals } from "../lib/types"; +import type { RuntimeStatus } from "../features/runtime/types"; +import { Cpu } from "@phosphor-icons/react"; async function tauriMinimize() { try { const { getCurrentWindow } = await import("@tauri-apps/api/window"); await getCurrentWindow().minimize(); } catch { /**/ } @@ -21,13 +21,6 @@ interface Props { onToggleSidebar: () => void; onOpenSettings: () => void; onBackToChat?: () => void; - models: ModelInfo[]; - localModels: RuntimeModelFile[]; - selectedModel: string; - onModelChange: (id: string) => void; - onModelSwap?: (path: string) => void; - isSwapping?: boolean; - status: ServerStatus; runtimeStatus: RuntimeStatus; serverConnected: boolean; generating: boolean; @@ -39,8 +32,7 @@ interface Props { export default function AppHeader({ sidebarOpen, onToggleSidebar, onOpenSettings, onBackToChat, - models, localModels, selectedModel, onModelChange, onModelSwap, isSwapping, - status, runtimeStatus, serverConnected, + runtimeStatus, serverConnected, generating, streamTokenCount, tps, chatTitle, chatTokenTotals, }: Props) { const isOnline = serverConnected && runtimeStatus === "ready"; @@ -94,16 +86,15 @@ export default function AppHeader({ - {/* ── Center: model selector ── */} + {/* ── Center: model badge ── */}
- +
+ + Bonsai 8B +
{/* ── Right ── */} diff --git a/tauri-app/src/components/ChatView.css b/tauri-app/src/components/ChatView.css index 6eb4715..3ba848f 100644 --- a/tauri-app/src/components/ChatView.css +++ b/tauri-app/src/components/ChatView.css @@ -1608,3 +1608,38 @@ background: rgba(22, 101, 52, 0.95); border-color: rgba(134, 239, 172, 0.28); } + +.agent-slider { + -webkit-appearance: none; + appearance: none; + height: 6px; + border-radius: 9999px; + outline: none; + cursor: pointer; +} + +.agent-slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 16px; + height: 16px; + border-radius: 50%; + background: #3b82f6; + border: 2px solid rgba(255, 255, 255, 0.2); + cursor: pointer; + transition: background 0.15s, transform 0.15s; +} + +.agent-slider::-webkit-slider-thumb:hover { + background: #60a5fa; + transform: scale(1.15); +} + +.agent-slider::-moz-range-thumb { + width: 16px; + height: 16px; + border-radius: 50%; + background: #3b82f6; + border: 2px solid rgba(255, 255, 255, 0.2); + cursor: pointer; +} diff --git a/tauri-app/src/components/ChatView.tsx b/tauri-app/src/components/ChatView.tsx index 234d32e..a479349 100644 --- a/tauri-app/src/components/ChatView.tsx +++ b/tauri-app/src/components/ChatView.tsx @@ -13,8 +13,9 @@ import { devLog } from "../lib/devLogger"; import { getComposerNotice } from "./chat-view-state"; import MessageBubble from "./MessageBubble"; import StreamingBubble from "./StreamingBubble"; -import { AgenticToggle } from "./forgeagent"; -// agentic-detection: shouldUseAgenticMode removed from auto-trigger — agentic mode is manual-only now +import { AgentPanelInline } from "./forgeagent/AgentPanelInline"; +import { useAgentStore } from "../stores/agentStore"; +import { ParallelOrchestrator } from "../lib/parallel-orchestrator"; import { Image, Microphone, Paperclip, ChatCircleText, PaperPlaneRight, Database, Lightning, Plus, Monitor, Globe, Palette, Code } from "@phosphor-icons/react"; import { useMemoryExtractor } from "../hooks/useMemoryExtractor"; import { MemoryModal } from "./memory/MemoryModal"; @@ -99,7 +100,9 @@ interface Props { const [showMemoryModal, setShowMemoryModal] = useState(false); const [isQueueOpen, setIsQueueOpen] = useState(false); const [debugCopied, setDebugCopied] = useState(false); - const [agenticMode, setAgenticMode] = useState(false); + + const { agentCount, startReview: startAgentReview, reviewState: agentReviewState } = useAgentStore(); + const isParallelMode = agentCount > 0; const { extractMemories, buildMemorySystemPrompt } = useMemoryExtractor({ db, @@ -336,7 +339,7 @@ interface Props { // Auto-detection was triggering on benign messages like "como fazer um carro?" // and injecting the ForgeAgent system prompt, causing confusing refusals on // small Qwen3 models (0.8B, 2B, 4B). - const isAgenticRequest = agenticMode; + const isAgenticRequest = isParallelMode; setGenerating(true); onGeneratingChange?.(true); setStreamContent(""); @@ -348,7 +351,7 @@ interface Props { // ── DevLogger: start interaction tracking ── devLog.startInteraction(chatId, text, { memoryEnabled: useMemory, - agenticMode, + agenticMode: isParallelMode, }); const cid = fromChatId ?? getOrCreateChat(text); @@ -590,6 +593,31 @@ interface Props { if (persistedAssistant) { appendPersistedAssistantMessage({ ...persistedAssistant }); + + if (isParallelMode && persistedAssistant.id && agentCount > 0) { + const orchestrator = new ParallelOrchestrator(); + const lastUserMessage = messages + .slice() + .reverse() + .find((m) => m.role === "user"); + + orchestrator.execute(agentCount, { + chatId: cid, + server, + apiKey, + model, + userMessage: lastUserMessage?.content || "", + originalResponse: finalContent, + }).then((result) => { + if (result && result.synthesizedContent !== finalContent) { + db.run( + "UPDATE messages SET content = ? WHERE id = ?", + [result.synthesizedContent, persistedAssistant.id] + ); + onChatUpdated(); + } + }).catch(() => {}); + } } else { appendPersistedAssistantMessage({ id: Date.now(), @@ -700,7 +728,7 @@ interface Props { setTimeout(() => sendRef.current(nextInQueue), 100); } } - }, [generating, model, server, apiKey, useStream, db, getOrCreateChat, ensureChatTitle, buildApiMessages, onChatUpdated, agenticMode, appendPersistedAssistantMessage]); + }, [generating, model, server, apiKey, useStream, db, getOrCreateChat, ensureChatTitle, buildApiMessages, onChatUpdated, appendPersistedAssistantMessage]); // Keep sendRef always pointing to latest send closure sendRef.current = send; @@ -750,7 +778,6 @@ interface Props { apiKeyPresent: Boolean(apiKey), memoryEnabled: useMemory, streamEnabled: useStream, - agenticMode, savedMemories, latestInteraction, runtimeMode, @@ -760,7 +787,7 @@ interface Props { await navigator.clipboard.writeText(transcript); setDebugCopied(true); window.setTimeout(() => setDebugCopied(false), 2000); - }, [apiKey, chatId, db, messages, model, server, useMemory, useStream, agenticMode]); + }, [apiKey, chatId, db, messages, model, server, useMemory, useStream, isParallelMode]); const handleEditAndResend = (msg: Message, newContent: string) => { db.run("UPDATE messages SET content = ? WHERE id = ?", [newContent, msg.id]); @@ -896,7 +923,7 @@ interface Props {
- setAgenticMode((value) => !value)} disabled={Boolean(composerNotice)} /> + {generating ? (
+ + {/* ── Fixed Input (Manus-style) ── */} {!isInitialScreen && (
)} - setAgenticMode((value) => !value)} disabled={Boolean(composerNotice)} /> + {generating ? (
+ )} + {!hasUsage && ( +
+
)} - {!hasUsage &&
}
+ ); +} + +function AgentSlider() { + const { agentCount, setAgentCount } = useAgentStore(); + + const getDescription = (count: number): string => { + if (count === 0) return "Single response"; + if (count <= 4) return "Small swarm"; + if (count <= 8) return "Medium swarm"; + return "Large swarm"; + }; + + const getMode = (count: number): string => { + if (count === 0) return "Off"; + if (count === 1) return "1 Agent"; + return `${count} Agents`; + }; + + const fillPct = (agentCount / 16) * 100; + + return ( +
+ {/* Header */} +
+
+
0 ? "bg-blue-500/15" : "bg-white/[0.05]", + ].join(" ")} + > + 0 ? "text-blue-400" : "text-white/35"} /> +
+ + {getMode(agentCount)} + +
+ 0 + ? "bg-blue-500/10 text-blue-400/80" + : "bg-white/[0.05] text-white/35", + ].join(" ")} + > + {getDescription(agentCount)} + +
+ + {/* Slider track */} +
+ setAgentCount(parseInt(e.target.value, 10))} + className="agent-slider w-full" + style={{ + background: `linear-gradient(to right, rgb(59,130,246) 0%, rgb(96,165,250) ${fillPct}%, rgba(255,255,255,0.07) ${fillPct}%, rgba(255,255,255,0.07) 100%)`, + }} + /> +
+ + {/* Scale labels */} +
+ {[0, 4, 8, 12, 16].map((n) => ( + = n + ? "text-blue-400/60" + : "text-white/25", + ].join(" ")} + > + {n === 0 ? "off" : n} + + ))} +
+
+ ); +} + +function MiniAgentCard({ agent, index }: { agent: ParallelAgent; index: number }) { + const accents = [ + { bar: "bg-blue-500/50", border: "border-blue-500/20", bg: "bg-blue-500/[0.06]", dot: "bg-blue-400" }, + { bar: "bg-purple-500/50", border: "border-purple-500/20", bg: "bg-purple-500/[0.06]", dot: "bg-purple-400" }, + { bar: "bg-emerald-500/50", border: "border-emerald-500/20", bg: "bg-emerald-500/[0.06]", dot: "bg-emerald-400" }, + { bar: "bg-amber-500/50", border: "border-amber-500/20", bg: "bg-amber-500/[0.06]", dot: "bg-amber-400" }, + { bar: "bg-rose-500/50", border: "border-rose-500/20", bg: "bg-rose-500/[0.06]", dot: "bg-rose-400" }, + { bar: "bg-indigo-500/50", border: "border-indigo-500/20", bg: "bg-indigo-500/[0.06]", dot: "bg-indigo-400" }, + { bar: "bg-cyan-500/50", border: "border-cyan-500/20", bg: "bg-cyan-500/[0.06]", dot: "bg-cyan-400" }, + { bar: "bg-lime-500/50", border: "border-lime-500/20", bg: "bg-lime-500/[0.06]", dot: "bg-lime-400" }, + ]; + const accent = accents[index % accents.length]; + + const statusIcon = (() => { + switch (agent.status) { + case "streaming": + return ; + case "completed": + return ; + case "error": + return ; + default: + return ; + } + })(); + + const [expanded, setExpanded] = useState(false); + + return ( +
agent.content && setExpanded(!expanded)} + > +
+
+ + {agent.name} + + {agent.tokensPerSecond && agent.status === "streaming" && ( + + {agent.tokensPerSecond.toFixed(1)} t/s + + )} + {statusIcon} +
+ {expanded && agent.content && ( +
+

+ {agent.content.slice(0, 400)} + {agent.content.length > 400 && …} +

+
+ )} +
+ ); +} + +function SynthesizerRow({ status }: { status: string }) { + const isSynthesizing = status === "thinking" || status === "streaming"; + const isComplete = status === "completed"; + + return ( +
+
+ {isSynthesizing ? ( + + ) : isComplete ? ( + + ) : ( + + )} +
+
+ + Synthesizer + + + {isSynthesizing + ? "Combining agent responses…" + : isComplete + ? "Synthesis complete" + : "Waiting for agents"} + +
+
+ ); +} + +export function AgentPanelInline() { + const { agentCount, reviewState } = useAgentStore(); + const [isExpanded, setIsExpanded] = useState(false); + + const hasActiveReview = reviewState !== null; + const agents = reviewState?.agents ?? []; + const synthesizerStatus = reviewState?.synthesizerStatus ?? "idle"; + const completedCount = agents.filter( + (a) => a.status === "completed" || a.status === "error", + ).length; + + return ( +
+ setIsExpanded((v) => !v)} + /> + + {isExpanded && ( +
+ + + {agents.length > 0 && ( +
+
+ + Live agents + + {hasActiveReview && ( + + {completedCount} / {agents.length} + + )} +
+
+ {agents.map((agent, i) => ( + + ))} +
+ + {(synthesizerStatus === "thinking" || + synthesizerStatus === "streaming" || + synthesizerStatus === "completed") && ( +
+ +
+ )} +
+ )} + + {agentCount > 0 && agents.length === 0 && ( +
+ + + Agents will activate on your next message + +
+ )} +
+ )} +
+ ); +} + +export default AgentPanelInline; diff --git a/tauri-app/src/components/forgeagent/ParallelAgentsPanel.tsx b/tauri-app/src/components/forgeagent/ParallelAgentsPanel.tsx new file mode 100644 index 0000000..e57b30d --- /dev/null +++ b/tauri-app/src/components/forgeagent/ParallelAgentsPanel.tsx @@ -0,0 +1,176 @@ +import { useState } from "react"; +import { useAgentStore, type ParallelAgent } from "../../stores/agentStore"; +import { + Brain, + CheckCircle, + XCircle, + CircleNotch, + Sparkle, + Robot, +} from "@phosphor-icons/react"; + +interface AgentCardProps { + agent: ParallelAgent; + index: number; +} + +function AgentCard({ agent, index }: AgentCardProps) { + const colors = [ + "from-blue-500/20 to-cyan-500/20 border-blue-500/30", + "from-purple-500/20 to-pink-500/20 border-purple-500/30", + "from-emerald-500/20 to-teal-500/20 border-emerald-500/30", + "from-amber-500/20 to-orange-500/20 border-amber-500/30", + "from-rose-500/20 to-red-500/20 border-rose-500/30", + "from-indigo-500/20 to-violet-500/20 border-indigo-500/30", + "from-cyan-500/20 to-sky-500/20 border-cyan-500/30", + "from-lime-500/20 to-green-500/20 border-lime-500/30", + ]; + + const colorClass = colors[index % colors.length]; + + const getStatusIcon = () => { + switch (agent.status) { + case "streaming": + return ; + case "completed": + return ; + case "error": + return ; + default: + return ; + } + }; + + const [isExpanded, setIsExpanded] = useState(false); + + return ( +
agent.content && setIsExpanded(!isExpanded)} + > +
+
+
+
+ +
+
+

+ {agent.name} +

+

{agent.role}

+
+
+
+ {agent.tokensPerSecond && agent.status === "streaming" && ( + + {agent.tokensPerSecond.toFixed(1)} t/s + + )} + {getStatusIcon()} +
+
+ + {agent.status === "streaming" && ( +
+
+
+
+
+ )} + + {isExpanded && agent.content && ( +
+
+

+ {agent.content.slice(0, 500)} + {agent.content.length > 500 && ( + ... + )} +

+
+
+ )} + + {!isExpanded && agent.content && agent.status !== "streaming" && ( +
+

+ {agent.content.slice(0, 100)}... +

+
+ )} +
+
+ ); +} + +interface ParallelAgentsPanelProps { + isVisible: boolean; +} + +export function ParallelAgentsPanel({ isVisible }: ParallelAgentsPanelProps) { + const { agentCount, reviewState } = useAgentStore(); + + if (!isVisible || agentCount === 0 || !reviewState) return null; + + const { agents, synthesizerStatus, allAgentsCompleted } = reviewState; + + const completedCount = agents.filter( + (a: ParallelAgent) => a.status === "completed" || a.status === "error" + ).length; + + const isSynthesizing = synthesizerStatus === "thinking" || synthesizerStatus === "streaming"; + const isSynthesisComplete = synthesizerStatus === "completed"; + + return ( +
+
+
+
+ +

+ Parallel Agents +

+
+ + {completedCount}/{agents.length} done + +
+
+ +
+
+ {agents.map((agent: ParallelAgent, index: number) => ( + + ))} +
+ + {(isSynthesizing || isSynthesisComplete) && ( +
+
+
+ + + {isSynthesizing ? "Synthesizing..." : "Synthesis Complete"} + +
+
+
+ )} +
+
+ ); +} + +export default ParallelAgentsPanel; diff --git a/tauri-app/src/components/forgeagent/ReviewProgress.tsx b/tauri-app/src/components/forgeagent/ReviewProgress.tsx new file mode 100644 index 0000000..2a92cb7 --- /dev/null +++ b/tauri-app/src/components/forgeagent/ReviewProgress.tsx @@ -0,0 +1,174 @@ +import { Sparkle, Check, X } from "@phosphor-icons/react"; +import type { ReviewCycle } from "../../lib/specialist-definitions"; +import { DEFAULT_SPECIALISTS } from "../../lib/specialist-definitions"; + +interface ReviewProgressProps { + cycle: ReviewCycle | null; + isReviewing: boolean; +} + +export function ReviewProgress({ cycle, isReviewing }: ReviewProgressProps) { + if (!isReviewing && (!cycle || cycle.status === "completed")) { + return null; + } + + const completedCount = cycle?.specialists.filter( + (s) => s.status === "completed" + ).length ?? 0; + const totalCount = cycle?.specialists.length ?? 0; + + if (cycle?.status === "completed") { + return ( +
+ + Improved +
+ ); + } + + return ( +
+
+ + + Improving {completedCount}/{totalCount} + +
+ + {cycle && ( +
+ {cycle.specialists.map((specialist) => { + const def = DEFAULT_SPECIALISTS.find( + (s) => s.id === specialist.specialistId + ); + + return ( +
+ ); + })} +
+ )} +
+ ); +} + +interface ReviewDetailsProps { + cycle: ReviewCycle | null; + isOpen: boolean; + onClose: () => void; +} + +export function ReviewDetails({ cycle, isOpen, onClose }: ReviewDetailsProps) { + if (!isOpen || !cycle) return null; + + return ( +
+
e.stopPropagation()} + > +
+

+ Review Details +

+ +
+ +
+ {cycle.specialists.map((specialist) => { + const def = DEFAULT_SPECIALISTS.find( + (s) => s.id === specialist.specialistId + ); + + return ( +
+
+ + {def?.title || specialist.specialistId} + + + {specialist.status} + +
+ + {specialist.thinking && ( +

+ {specialist.thinking} +

+ )} + + {specialist.suggestions.length > 0 && ( +
+ {specialist.suggestions.map((suggestion, idx) => ( +
+
+ + {suggestion.type} + + + {Math.round(suggestion.confidence * 100)}% + +
+

{suggestion.explanation}

+
+ ))} +
+ )} +
+ ); + })} + +
+
+ Original: {cycle.originalContent.length} chars +
+ Final: {cycle.finalContent.length} chars +
+
+
+
+
+ ); +} diff --git a/tauri-app/src/config/ai.ts b/tauri-app/src/config/ai.ts index c14411d..d00f1fa 100644 --- a/tauri-app/src/config/ai.ts +++ b/tauri-app/src/config/ai.ts @@ -8,8 +8,13 @@ * Philosophy: less configuration → fewer loops, fewer artifacts. */ -// Maximum context window sent to the model (tokens) -export const AI_CONTEXT_LENGTH = 32000; +// Bonsai-8B.gguf — 1-bit TurboQuant model +// The sole model for ForgeLM Studio. +// High context for main chat (128k tokens — 1-bit quant handles it efficiently) +export const AI_CONTEXT_LENGTH = 131072; + +// Smaller context for parallel agents (focused prompts, no need for full window) +export const AI_AGENT_CONTEXT_LENGTH = 4096; // How many messages from history to include in each request export const AI_MAX_HISTORY_MESSAGES = 20; @@ -19,4 +24,4 @@ export const AI_STREAM_DEFAULT = true; // ── DO NOT ADD SYSTEM PROMPTS, STOP SEQUENCES, OR RESPONSE FORMAT INSTRUCTIONS HERE ── // ── Any modification to how the AI responds should be done in prompts/system.ts ── -// ── and explicitly opted-in — NOT applied globally. ── +// ── and explicitly opted-in — NOT applied globally. ── \ No newline at end of file diff --git a/tauri-app/src/features/runtime/runtime-defaults.ts b/tauri-app/src/features/runtime/runtime-defaults.ts index 4a80dae..263d70e 100644 --- a/tauri-app/src/features/runtime/runtime-defaults.ts +++ b/tauri-app/src/features/runtime/runtime-defaults.ts @@ -1,3 +1,4 @@ export const MIN_CTX_SIZE = 512; -export const DEFAULT_CTX_SIZE = 4096; +export const DEFAULT_CTX_SIZE = 131072; +export const DEFAULT_GPU_LAYERS = -1; export const CTX_PRESETS = [2048, 4096, 8192, 16384, 32768, 65536, 131072]; diff --git a/tauri-app/src/features/runtime/runtime-utils.ts b/tauri-app/src/features/runtime/runtime-utils.ts index a44daa3..f21eecc 100644 --- a/tauri-app/src/features/runtime/runtime-utils.ts +++ b/tauri-app/src/features/runtime/runtime-utils.ts @@ -13,7 +13,7 @@ import type { RuntimeWorkspaceSummary, } from "./types"; import { scanRuntimeModels } from "./api"; -import { DEFAULT_CTX_SIZE } from "./runtime-defaults"; +import { DEFAULT_CTX_SIZE, DEFAULT_GPU_LAYERS } from "./runtime-defaults"; const DEFAULT_LOCAL_URL = "http://127.0.0.1:8080"; @@ -304,13 +304,13 @@ export function createDefaultRuntimeSnapshot(): RuntimeSnapshot { last_command_result: createRuntimeLifecycleResult(), config: { connection_mode: "local", - start_policy: "manual", + start_policy: "on_app_launch", server_url: "", api_key: "", model_path: null, models_dir: null, addr: "127.0.0.1:8080", - gpu_layers: 0, + gpu_layers: DEFAULT_GPU_LAYERS, ctx_size: DEFAULT_CTX_SIZE, extra_model_paths: [], selected_model_id: null, diff --git a/tauri-app/src/hooks/useModels.ts b/tauri-app/src/hooks/useModels.ts index 75b6390..f5c9124 100644 --- a/tauri-app/src/hooks/useModels.ts +++ b/tauri-app/src/hooks/useModels.ts @@ -1,22 +1,14 @@ -import { useState, useEffect, useCallback, useRef } from "react"; +import { useState, useEffect, useCallback } from "react"; import type { ModelInfo, ServerStatus } from "../lib/types"; import { getNextSelectedModel } from "./model-selection"; -import { resolveAlias } from "../lib/modelAlias"; - -// Tempo de proteção após seleção manual (5 segundos) -const MANUAL_SELECTION_LOCK_MS = 5000; +import { BONSAI_MODEL_ID } from "../lib/modelAlias"; export function useModels(server: string, apiKey: string, enabled = true) { const [models, setModels] = useState([]); - const [selectedModel, setSelectedModelState] = useState(""); + const [selectedModel, setSelectedModelState] = useState(BONSAI_MODEL_ID); const [status, setStatus] = useState("loading"); - - // Ref para rastrear quando o usuário selecionou manualmente um modelo - const lastManualSelectionRef = useRef(0); - // Wrapper para setSelectedModel que registra o timestamp const setSelectedModel = useCallback((value: string | ((prev: string) => string)) => { - lastManualSelectionRef.current = Date.now(); setSelectedModelState(value); }, []); @@ -38,24 +30,16 @@ export function useModels(server: string, apiKey: string, enabled = true) { const data = await res.json(); const list: ModelInfo[] = data.data || []; setModels(list); - - // Protege contra sobrescrita de seleção manual recente - const timeSinceManualSelection = Date.now() - lastManualSelectionRef.current; - const isLockActive = timeSinceManualSelection < MANUAL_SELECTION_LOCK_MS; - + setSelectedModelState((current) => { - // Se há um lock ativo e o modelo atual existe na nova lista, mantém ele - if (isLockActive && current && list.some((m) => m.id === current)) { + if (current && list.some((m) => m.id === current)) { return current; } - // Prefer Iron (2B) first, then Copper (0.8B), then fallback to normal selection - const iron = list.find((m) => resolveAlias(m.id) === 'Iron'); - if (iron) return iron.id; - const copper = list.find((m) => resolveAlias(m.id) === 'Copper'); - if (copper) return copper.id; + const bonsai = list.find((m) => m.id.toLowerCase().includes('bonsai')); + if (bonsai) return bonsai.id; return getNextSelectedModel(current, list); }); - + setStatus("ok"); } catch { setModels([]); @@ -70,4 +54,4 @@ export function useModels(server: string, apiKey: string, enabled = true) { }, [refresh]); return { models, selectedModel, setSelectedModel, status, refresh }; -} +} \ No newline at end of file diff --git a/tauri-app/src/hooks/useSessionModelController.ts b/tauri-app/src/hooks/useSessionModelController.ts index 24b99b1..71ee3f5 100644 --- a/tauri-app/src/hooks/useSessionModelController.ts +++ b/tauri-app/src/hooks/useSessionModelController.ts @@ -1,6 +1,5 @@ import { useCallback, useRef, useState } from "react"; import type { RuntimeModelFile } from "../features/runtime/types"; -import { resolveAlias } from "../lib/modelAlias"; import { useToast } from "../components/Toast"; interface Options { @@ -20,16 +19,13 @@ interface SessionModelController { export function useSessionModelController({ preferredModel, - onSwitchToCooper, - localModels, + localModels: _localModels, }: Options): SessionModelController { const { showToast } = useToast(); const [effectiveModel, setEffectiveModel] = useState(preferredModel); const [overrideSource, setOverrideSource] = useState<'auto' | 'user'>('auto'); const [degradationPromptShown, setDegradationPromptShown] = useState(false); - // Keep effectiveModel in sync when preferredModel changes externally (e.g. startup auto-select) - // but only if the user hasn't manually overridden const prevPreferredRef = useRef(preferredModel); if (preferredModel !== prevPreferredRef.current) { prevPreferredRef.current = preferredModel; @@ -49,30 +45,17 @@ export function useSessionModelController({ }, [preferredModel]); const onDegradationDetected = useCallback(() => { - // Don't show if user already chose manually or already shown if (overrideSource === 'user' || degradationPromptShown) return; - // Find Copper's path from localModels - const copperModel = localModels.find((m) => resolveAlias(m.path) === 'Copper'); - if (!copperModel) return; // Copper not available, can't suggest - setDegradationPromptShown(true); - showToast({ id: 'tps-degradation', variant: 'warning', message: 'Performance degraded', - detail: 'TPS dropped below 30 for 5s. Switch to Copper (0.8B) for faster responses?', - action: { - label: 'Switch to Copper', - onClick: () => { - setUserOverride(copperModel.path); - onSwitchToCooper(copperModel.path); - }, - }, - duration: 0, // persistent until dismissed + detail: 'TPS dropped below 30 for 5s. The model may be struggling with the current workload.', + duration: 8000, }); - }, [overrideSource, degradationPromptShown, localModels, showToast, setUserOverride, onSwitchToCooper]); + }, [overrideSource, degradationPromptShown, showToast]); return { effectiveModel, @@ -82,4 +65,4 @@ export function useSessionModelController({ onDegradationDetected, degradationPromptShown, }; -} +} \ No newline at end of file diff --git a/tauri-app/src/hooks/useSpecialistReview.ts b/tauri-app/src/hooks/useSpecialistReview.ts new file mode 100644 index 0000000..03cbae4 --- /dev/null +++ b/tauri-app/src/hooks/useSpecialistReview.ts @@ -0,0 +1,68 @@ +import { useCallback, useRef, useState } from "react"; +import type { ReviewCycle } from "../lib/specialist-definitions"; +import { ReviewOrchestrator, type ReviewContext } from "../lib/review-orchestrator"; + +export function useSpecialistReview() { + const orchestratorRef = useRef(null); + const [reviewCycle, setReviewCycle] = useState(null); + const [isReviewing, setIsReviewing] = useState(false); + + const startReview = useCallback( + async ( + messageId: number, + content: string, + context: ReviewContext + ): Promise => { + if (orchestratorRef.current) { + orchestratorRef.current.abort(); + } + + orchestratorRef.current = new ReviewOrchestrator(); + setIsReviewing(true); + + try { + const cycle = await orchestratorRef.current.startReview( + messageId, + content, + context, + (progress) => { + setReviewCycle({ ...progress }); + } + ); + + setReviewCycle(cycle); + return cycle; + } catch (error) { + console.error("Review failed:", error); + return null; + } finally { + setIsReviewing(false); + } + }, + [] + ); + + const abortReview = useCallback(() => { + orchestratorRef.current?.abort(); + orchestratorRef.current = null; + setIsReviewing(false); + }, []); + + const completedCount = reviewCycle?.specialists.filter( + (s) => s.status === "completed" || s.status === "error" + ).length ?? 0; + + const totalCount = reviewCycle?.specialists.length ?? 0; + + return { + reviewCycle, + isReviewing, + startReview, + abortReview, + progress: { + completed: completedCount, + total: totalCount, + percentage: totalCount > 0 ? (completedCount / totalCount) * 100 : 0, + }, + }; +} diff --git a/tauri-app/src/lib/modelAlias.ts b/tauri-app/src/lib/modelAlias.ts index 328da4e..6b6478a 100644 --- a/tauri-app/src/lib/modelAlias.ts +++ b/tauri-app/src/lib/modelAlias.ts @@ -1,76 +1,63 @@ -export type ModelAlias = 'Copper' | 'Iron' | 'Steel' | 'Unknown'; +/** + * Model Alias System — Bonsai-8B.gguf Edition + * + * ForgeLM Studio uses a single model: Bonsai-8B.gguf (1-bit TurboQuant). + * The alias system is simplified to reflect this. + */ + +export type ModelAlias = 'Bonsai'; + +/** The canonical filename for the ForgeLM model */ +export const BONSAI_MODEL_ID = 'Bonsai-8B.gguf'; /** - * Resolves model alias from filename or path - * Matches: Copper (0.8B), Iron (2B), Steel (4B) + * Resolves model alias from filename or path. + * Always returns 'Bonsai' since it's our only model. */ -export function resolveAlias(modelIdOrPath: string): ModelAlias { - const lower = modelIdOrPath.toLowerCase(); - - // Match by filename pattern first - if (lower.includes('copper')) return 'Copper'; - if (lower.includes('iron')) return 'Iron'; - if (lower.includes('steel')) return 'Steel'; - - // Fallback: match by parameter size indicators - if (lower.includes('0.8b')) return 'Copper'; - if (lower.includes('4b')) return 'Steel'; - if (lower.includes('2b')) return 'Iron'; - - return 'Unknown'; +export function resolveAlias(_modelIdOrPath: string): ModelAlias { + return 'Bonsai'; } /** - * Gets clean display name for model selector - * Returns: Copper, Iron, Steel (never full filename) + * Gets clean display name for model. + * Returns: Bonsai (always) */ export function getDisplayName(modelIdOrPath: string): string { - const alias = resolveAlias(modelIdOrPath); - if (alias !== 'Unknown') return alias; - - // For unknown models, extract a clean name from filename + if (modelIdOrPath === 'Bonsai' || modelIdOrPath === BONSAI_MODEL_ID) { + return 'Bonsai'; + } + const parts = modelIdOrPath.replace(/\\/g, '/').split('/'); const base = parts[parts.length - 1]; - - // Remove .gguf extension and common suffixes let clean = base.replace(/\.gguf$/i, ''); - clean = clean.replace(/-Q\d+_[KML]+$/i, ''); // Remove quantization suffix + clean = clean.replace(/-Q\d+_[KML]+$/i, ''); clean = clean.replace(/-GGUF$/i, ''); - + + if (clean.toLowerCase().includes('bonsai')) { + return 'Bonsai'; + } + return clean.slice(0, 20); } /** - * Returns true if this model should appear in the primary chat selector - * All valid models (Copper, Iron, Steel) are shown + * Returns true — all models are valid selectors since we only have Bonsai. */ -export function isInChatSelector(modelIdOrPath: string): boolean { - const alias = resolveAlias(modelIdOrPath); - return alias === 'Copper' || alias === 'Iron' || alias === 'Steel'; +export function isInChatSelector(_modelIdOrPath: string): boolean { + return true; } /** - * Gets the ForgeMetal enum value for a model - * Used internally for model management + * Gets the ForgeMetal value — always 'Bonsai'. + * Kept for backward compatibility. */ -export function getForgeMetal(modelIdOrPath: string): 'Copper' | 'Iron' | 'Steel' | null { - const alias = resolveAlias(modelIdOrPath); - if (alias === 'Unknown') return null; - return alias; +export function getForgeMetal(_modelIdOrPath: string): 'Bonsai' | null { + return 'Bonsai'; } /** * Gets model description/tooltip text */ -export function getModelDescription(alias: ModelAlias): string { - switch (alias) { - case 'Copper': - return 'Copper (0.8B) - Fast and lightweight'; - case 'Iron': - return 'Iron (2B) - Balanced performance'; - case 'Steel': - return 'Steel (4B) - Most capable'; - default: - return 'Unknown model'; - } -} +export function getModelDescription(_alias?: ModelAlias): string { + return 'Bonsai (8B) — 1-bit TurboQuant, high-context local model'; +} \ No newline at end of file diff --git a/tauri-app/src/lib/parallel-orchestrator.ts b/tauri-app/src/lib/parallel-orchestrator.ts new file mode 100644 index 0000000..ef9cddd --- /dev/null +++ b/tauri-app/src/lib/parallel-orchestrator.ts @@ -0,0 +1,276 @@ +import type { ParallelAgent } from "../stores/agentStore"; +import { generateAgentRoles } from "../stores/agentStore"; +import { AI_AGENT_CONTEXT_LENGTH } from "../config/ai"; + +export interface ParallelContext { + chatId: number; + server: string; + apiKey: string; + model: string; + userMessage: string; + originalResponse: string; +} + +export interface AgentResult { + agentId: string; + content: string; + tokensPerSecond: number | null; + error?: string; +} + +const AGENT_SYSTEM_PROMPT = `You are one of multiple AI agents collaborating to improve a response. Your specific role is described below. + +ROLE: {ROLE} + +INSTRUCTIONS: +1. Review the original user question and the assistant's response +2. From YOUR SPECIFIC ROLE's perspective, identify improvements +3. Provide detailed feedback and a refined version of the response +4. Focus on your area of expertise - don't try to cover everything +5. Be constructive and specific in your recommendations + +OUTPUT FORMAT: +Provide your analysis and improved response. Focus on actionable improvements from your role's perspective.`; + +const SYNTHESIZER_PROMPT = `You are a synthesis expert. Your job is to combine multiple agent perspectives into a single, optimized response. + +INSTRUCTIONS: +1. Review all agent responses below +2. Extract the best insights from each perspective +3. Consolidate them into ONE cohesive, improved response +4. Maintain the original response's helpfulness while incorporating improvements +5. Ensure the final response is clear, accurate, and well-structured + +AGENT RESPONSES: +{AGENT_RESPONSES} + +SYNTHESIZE into a single optimal response:`; + +export class ParallelOrchestrator { + private abortController: AbortController | null = null; + + abort(): void { + this.abortController?.abort(); + } + + async runParallelAgents( + agentCount: number, + context: ParallelContext, + onAgentUpdate: (agentId: string, content: string, tps?: number) => void, + onAgentComplete: (agentId: string) => void, + onAgentError: (agentId: string, error: string) => void + ): Promise { + this.abortController = new AbortController(); + const signal = this.abortController.signal; + + const roles = generateAgentRoles(agentCount); + + const agentPromises = roles.map((role, index) => + this.runSingleAgent( + `agent-${index}`, + role.name, + role.role, + context, + signal, + onAgentUpdate, + onAgentComplete, + onAgentError + ) + ); + + const results = await Promise.all(agentPromises); + return results; + } + + private async runSingleAgent( + agentId: string, + name: string, + role: string, + context: ParallelContext, + signal: AbortSignal, + onUpdate: (agentId: string, content: string, tps?: number) => void, + onComplete: (agentId: string) => void, + onError: (agentId: string, error: string) => void + ): Promise { + const systemPrompt = AGENT_SYSTEM_PROMPT.replace("{ROLE}", `${name}: ${role}`); + + const userPrompt = `Original user question: ${context.userMessage} + +Original assistant response: ${context.originalResponse} + +Provide your analysis and improved response from your perspective as ${name}:`; + + try { + const response = await fetch(`${context.server}/v1/chat/completions`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${context.apiKey}`, + }, + body: JSON.stringify({ + model: context.model, + messages: [ + { role: "system", content: systemPrompt }, + { role: "user", content: userPrompt }, + ], + temperature: 0.4, + max_tokens: 2000, + num_ctx: AI_AGENT_CONTEXT_LENGTH, + stream: true, + }), + signal, + }); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + + const reader = response.body?.getReader(); + if (!reader) { + throw new Error("No response body"); + } + + let content = ""; + let tokensPerSecond: number | null = null; + const decoder = new TextDecoder(); + const startTime = Date.now(); + let tokenCount = 0; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + const chunk = decoder.decode(value, { stream: true }); + const lines = chunk.split("\n").filter((line) => line.trim()); + + for (const line of lines) { + if (line.startsWith("data: ")) { + const data = line.slice(6); + if (data === "[DONE]") continue; + + try { + const parsed = JSON.parse(data); + const delta = parsed.choices?.[0]?.delta?.content; + if (delta) { + content += delta; + tokenCount++; + + const elapsed = (Date.now() - startTime) / 1000; + if (elapsed > 0) { + tokensPerSecond = tokenCount / elapsed; + } + + onUpdate(agentId, content, tokensPerSecond ?? undefined); + } + } catch { + // Ignore malformed JSON in stream + } + } + } + } + + onComplete(agentId); + return { agentId, content, tokensPerSecond }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : "Unknown error"; + onError(agentId, errorMessage); + return { agentId, content: "", tokensPerSecond: null, error: errorMessage }; + } + } + + async execute( + agentCount: number, + context: ParallelContext + ): Promise<{ synthesizedContent: string; agentResults: AgentResult[] }> { + const agentResults = await this.runParallelAgents( + agentCount, + context, + () => {}, + () => {}, + () => {} + ); + + const synthesizedContent = await this.runSynthesizer( + agentResults, + context, + () => {} + ); + + return { synthesizedContent, agentResults }; + } + + async runSynthesizer( + agentResults: AgentResult[], + context: ParallelContext, + onUpdate: (content: string) => void + ): Promise { + const agentResponsesText = agentResults + .filter((r) => !r.error && r.content) + .map((r) => `[Agent ${r.agentId}]: ${r.content}`) + .join("\n\n---\n\n"); + + const systemPrompt = SYNTHESIZER_PROMPT.replace( + "{AGENT_RESPONSES}", + agentResponsesText + ); + + const response = await fetch(`${context.server}/v1/chat/completions`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${context.apiKey}`, + }, +body: JSON.stringify({ + model: context.model, + messages: [ + { role: "system", content: systemPrompt }, + { role: "user", content: "Synthesize the optimal response:" }, + ], + temperature: 0.3, + max_tokens: 2500, + num_ctx: AI_AGENT_CONTEXT_LENGTH, + stream: true, + }), + }); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + + const reader = response.body?.getReader(); + if (!reader) { + throw new Error("No response body"); + } + + let finalContent = ""; + const decoder = new TextDecoder(); + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + const chunk = decoder.decode(value, { stream: true }); + const lines = chunk.split("\n").filter((line) => line.trim()); + + for (const line of lines) { + if (line.startsWith("data: ")) { + const data = line.slice(6); + if (data === "[DONE]") continue; + + try { + const parsed = JSON.parse(data); + const delta = parsed.choices?.[0]?.delta?.content; + if (delta) { + finalContent += delta; + onUpdate(finalContent); + } + } catch { + // Ignore malformed JSON in stream + } + } + } + } + + return finalContent; + } +} diff --git a/tauri-app/src/lib/review-orchestrator.ts b/tauri-app/src/lib/review-orchestrator.ts new file mode 100644 index 0000000..6ac5f40 --- /dev/null +++ b/tauri-app/src/lib/review-orchestrator.ts @@ -0,0 +1,181 @@ +import type { + ReviewCycle, + SpecialistDefinition, + SpecialistReview, + Suggestion, +} from "./specialist-definitions"; +import { getActiveSpecialists } from "./specialist-definitions"; +import { SYSTEM_PROMPT } from "../prompts/system"; + +export interface ReviewContext { + chatId: number; + server: string; + apiKey: string; + model: string; +} + +export class ReviewOrchestrator { + private abortController: AbortController | null = null; + + abort(): void { + this.abortController?.abort(); + } + + async startReview( + messageId: number, + content: string, + context: ReviewContext, + onProgress?: (cycle: ReviewCycle) => void + ): Promise { + this.abortController = new AbortController(); + const signal = this.abortController.signal; + + const activeSpecialists = getActiveSpecialists(content); + + const cycle: ReviewCycle = { + id: generateUUID(), + originalMessageId: messageId, + originalContent: content, + specialists: activeSpecialists.map((s) => ({ + specialistId: s.id, + status: "pending", + suggestions: [], + applied: false, + })), + finalContent: content, + status: "running", + startedAt: Date.now(), + }; + + let currentContent = content; + + for (let i = 0; i < activeSpecialists.length; i++) { + if (signal.aborted) break; + + const specialist = activeSpecialists[i]; + const specialistReview = cycle.specialists[i]; + + specialistReview.status = "thinking"; + onProgress?.({ ...cycle }); + + try { + const review = await this.runSpecialist( + specialist, + currentContent, + context, + signal + ); + + specialistReview.status = "completed"; + specialistReview.thinking = review.thinking; + specialistReview.suggestions = review.suggestions; + specialistReview.applied = true; + + currentContent = this.applySuggestions(currentContent, review.suggestions); + cycle.finalContent = currentContent; + + onProgress?.({ ...cycle }); + } catch (error) { + specialistReview.status = "error"; + console.error(`Specialist ${specialist.id} failed:`, error); + onProgress?.({ ...cycle }); + } + } + + cycle.status = signal.aborted ? "error" : "completed"; + cycle.completedAt = Date.now(); + + return cycle; + } + + private async runSpecialist( + specialist: SpecialistDefinition, + content: string, + context: ReviewContext, + signal: AbortSignal + ): Promise<{ thinking: string; suggestions: Suggestion[] }> { + const messages = [ + { role: "system", content: specialist.systemPrompt }, + { + role: "user", + content: `Review and improve this assistant response:\n\n${content}`, + }, + ]; + + const response = await fetch(`${context.server}/v1/chat/completions`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${context.apiKey}`, + }, + body: JSON.stringify({ + model: context.model, + messages, + temperature: specialist.temperature, + max_tokens: specialist.maxTokens, + stream: false, + }), + signal, + }); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + + const data = await response.json(); + const rawContent = data.choices?.[0]?.message?.content || "{}"; + + try { + const jsonMatch = rawContent.match(/\{[\s\S]*\}/); + const jsonStr = jsonMatch ? jsonMatch[0] : rawContent; + const parsed = JSON.parse(jsonStr); + + return { + thinking: parsed.thinking || "", + suggestions: Array.isArray(parsed.suggestions) ? parsed.suggestions : [], + }; + } catch { + return { + thinking: "Failed to parse specialist response", + suggestions: [], + }; + } + } + + private applySuggestions(content: string, suggestions: Suggestion[]): string { + let result = content; + + for (const suggestion of suggestions) { + if (suggestion.confidence < 0.8) continue; + + switch (suggestion.type) { + case "improvement": + case "correction": + if (suggestion.original && suggestion.replacement) { + result = result.replace(suggestion.original, suggestion.replacement); + } + break; + case "addition": + if (suggestion.replacement) { + result += "\n\n" + suggestion.replacement; + } + break; + case "deletion": + if (suggestion.original) { + result = result.replace(suggestion.original, ""); + } + break; + } + } + + return result; + } +} + +function generateUUID(): string { + return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => { + const r = (Math.random() * 16) | 0; + const v = c === "x" ? r : (r & 0x3) | 0x8; + return v.toString(16); + }); +} diff --git a/tauri-app/src/lib/specialist-definitions.ts b/tauri-app/src/lib/specialist-definitions.ts new file mode 100644 index 0000000..8a897dc --- /dev/null +++ b/tauri-app/src/lib/specialist-definitions.ts @@ -0,0 +1,139 @@ +export interface SpecialistDefinition { + id: string; + name: string; + title: string; + systemPrompt: string; + activationRules: ActivationRule[]; + temperature: number; + maxTokens: number; +} + +export type ActivationRule = + | { type: "always" } + | { type: "contains-code"; languages?: string[] } + | { type: "length-threshold"; minChars: number } + | { type: "content-pattern"; regex: string }; + +export interface Suggestion { + type: "improvement" | "correction" | "addition" | "deletion"; + original?: string; + replacement?: string; + explanation: string; + confidence: number; +} + +export interface SpecialistReview { + specialistId: string; + status: "pending" | "thinking" | "completed" | "error"; + thinking?: string; + suggestions: Suggestion[]; + applied: boolean; +} + +export interface ReviewCycle { + id: string; + originalMessageId: number; + originalContent: string; + specialists: SpecialistReview[]; + finalContent: string; + status: "running" | "completed" | "error"; + startedAt: number; + completedAt?: number; +} + +export const TONE_ADAPTER: SpecialistDefinition = { + id: "tone-adapter", + name: "Tone Adapter", + title: "Adaptador de Tom", + systemPrompt: `You are a tone adaptation specialist. Analyze the conversation context and refine the assistant's response to match the appropriate tone. + +Guidelines: +- Match the user's formality level (technical vs casual) +- Maintain consistency with previous messages +- Adjust enthusiasm based on context (neutral vs helpful) +- Ensure the response feels natural and conversational +- Keep all factual information intact + +Output format (JSON only): +{ + "thinking": "brief analysis of context and tone needed", + "suggestions": [ + { + "type": "improvement", + "original": "text to change", + "replacement": "improved text", + "explanation": "why this improves the tone", + "confidence": 0.9 + } + ] +} + +If no changes are needed, return empty suggestions array.`, + activationRules: [{ type: "always" }], + temperature: 0.3, + maxTokens: 2000, +}; + +export const CODE_REVIEWER: SpecialistDefinition = { + id: "code-reviewer", + name: "Code Reviewer", + title: "Revisor de Código", + systemPrompt: `You are a senior code reviewer. Analyze any code in the response and suggest improvements. + +Focus on: +- Code efficiency and performance +- Readability and clarity +- Best practices and patterns +- Potential bugs or edge cases +- Security considerations + +Only suggest changes with high confidence (>0.8). Minor style preferences should be skipped. + +Output format (JSON only): +{ + "thinking": "step-by-step code analysis", + "suggestions": [ + { + "type": "improvement", + "original": "original code", + "replacement": "improved code", + "explanation": "specific reason for improvement", + "confidence": 0.95 + } + ] +} + +If code is already good, return empty suggestions array.`, + activationRules: [{ type: "contains-code" }], + temperature: 0.2, + maxTokens: 3000, +}; + +export const DEFAULT_SPECIALISTS: SpecialistDefinition[] = [ + TONE_ADAPTER, + CODE_REVIEWER, +]; + +export function shouldActivateSpecialist( + specialist: SpecialistDefinition, + content: string +): boolean { + return specialist.activationRules.some((rule) => { + switch (rule.type) { + case "always": + return true; + case "contains-code": + return /```[\w]*\n[\s\S]*?```/.test(content); + case "length-threshold": + return content.length >= rule.minChars; + case "content-pattern": + return new RegExp(rule.regex, "i").test(content); + default: + return false; + } + }); +} + +export function getActiveSpecialists(content: string): SpecialistDefinition[] { + return DEFAULT_SPECIALISTS.filter((s) => shouldActivateSpecialist(s, content)); +} diff --git a/tauri-app/src/stores/agentStore.ts b/tauri-app/src/stores/agentStore.ts new file mode 100644 index 0000000..b105dbc --- /dev/null +++ b/tauri-app/src/stores/agentStore.ts @@ -0,0 +1,190 @@ +import { create } from "zustand"; + +export interface ParallelAgent { + id: string; + name: string; + role: string; + status: "idle" | "streaming" | "completed" | "error"; + content: string; + tokensPerSecond: number | null; + error?: string; +} + +export interface ParallelReviewState { + agents: ParallelAgent[]; + allAgentsCompleted: boolean; + synthesizerStatus: "idle" | "thinking" | "streaming" | "completed" | "error"; + synthesizerContent: string; + finalContent: string; + isActive: boolean; +} + +interface AgentStore { + agentCount: number; + reviewState: ParallelReviewState | null; + + setAgentCount: (count: number) => void; + startReview: (agentCount: number) => void; + updateAgentContent: (agentId: string, content: string, tps?: number) => void; + completeAgent: (agentId: string) => void; + errorAgent: (agentId: string, error: string) => void; + startSynthesizer: () => void; + updateSynthesizerContent: (content: string) => void; + completeSynthesizer: (finalContent: string) => void; + reset: () => void; +} + +export const generateAgentRoles = (count: number): { name: string; role: string }[] => { + const baseRoles = [ + { name: "Architect", role: "Focus on system design, structure, and scalability" }, + { name: "Code Reviewer", role: "Review code quality, best practices, and potential bugs" }, + { name: "Optimizer", role: "Identify performance improvements and optimizations" }, + { name: "Security Analyst", role: "Check for security vulnerabilities and safe practices" }, + { name: "Documentation", role: "Ensure clarity, completeness, and proper documentation" }, + { name: "Tester", role: "Consider edge cases, test coverage, and validation" }, + { name: "DevOps", role: "Consider deployment, infrastructure, and operational concerns" }, + { name: "UX Specialist", role: "Focus on user experience and interface design" }, + { name: "Database Expert", role: "Optimize data models, queries, and storage" }, + { name: "API Designer", role: "Review API design, consistency, and standards" }, + { name: "Accessibility", role: "Ensure accessibility and inclusive design" }, + { name: "Internationalization", role: "Consider localization and global usage" }, + { name: "Compliance", role: "Check regulatory and compliance requirements" }, + { name: "Performance", role: "Deep dive on performance bottlenecks" }, + { name: "Maintainability", role: "Focus on long-term maintenance and readability" }, + { name: "Innovation", role: "Suggest creative improvements and new approaches" }, + ]; + + return baseRoles.slice(0, count); +}; + +export const useAgentStore = create((set) => ({ + agentCount: 0, + reviewState: null, + + setAgentCount: (count) => set({ agentCount: count }), + + startReview: (agentCount) => { + const roles = generateAgentRoles(agentCount); + const agents: ParallelAgent[] = roles.map((role, index) => ({ + id: `agent-${index}`, + name: role.name, + role: role.role, + status: "idle", + content: "", + tokensPerSecond: null, + })); + + set({ + reviewState: { + agents, + allAgentsCompleted: false, + synthesizerStatus: "idle", + synthesizerContent: "", + finalContent: "", + isActive: true, + }, + }); + }, + + updateAgentContent: (agentId, content, tps) => + set((state) => { + if (!state.reviewState) return state; + + const updatedAgents = state.reviewState.agents.map((agent) => + agent.id === agentId + ? { ...agent, status: "streaming" as const, content, tokensPerSecond: tps ?? agent.tokensPerSecond } + : agent + ); + + return { + reviewState: { + ...state.reviewState, + agents: updatedAgents, + }, + }; + }), + + completeAgent: (agentId) => + set((state) => { + if (!state.reviewState) return state; + + const updatedAgents = state.reviewState.agents.map((agent) => + agent.id === agentId ? { ...agent, status: "completed" as const } : agent + ); + + const allCompleted = updatedAgents.every( + (a) => a.status === "completed" || a.status === "error" + ); + + return { + reviewState: { + ...state.reviewState, + agents: updatedAgents, + allAgentsCompleted: allCompleted, + }, + }; + }), + + errorAgent: (agentId, error) => + set((state) => { + if (!state.reviewState) return state; + + const updatedAgents = state.reviewState.agents.map((agent) => + agent.id === agentId ? { ...agent, status: "error" as const, error } : agent + ); + + const allCompleted = updatedAgents.every( + (a) => a.status === "completed" || a.status === "error" + ); + + return { + reviewState: { + ...state.reviewState, + agents: updatedAgents, + allAgentsCompleted: allCompleted, + }, + }; + }), + + startSynthesizer: () => + set((state) => { + if (!state.reviewState) return state; + return { + reviewState: { + ...state.reviewState, + synthesizerStatus: "thinking", + }, + }; + }), + + updateSynthesizerContent: (content) => + set((state) => { + if (!state.reviewState) return state; + return { + reviewState: { + ...state.reviewState, + synthesizerStatus: "streaming", + synthesizerContent: content, + }, + }; + }), + + completeSynthesizer: (finalContent) => + set((state) => { + if (!state.reviewState) return state; + return { + reviewState: { + ...state.reviewState, + synthesizerStatus: "completed", + finalContent, + isActive: false, + }, + }; + }), + + reset: () => + set({ + agentCount: 0, + reviewState: null, + }), +})); diff --git a/tauri-app/vite.config.ts.timestamp-1775902790910-babfc5d3aa0f.mjs b/tauri-app/vite.config.ts.timestamp-1775902790910-babfc5d3aa0f.mjs new file mode 100644 index 0000000..30316f2 --- /dev/null +++ b/tauri-app/vite.config.ts.timestamp-1775902790910-babfc5d3aa0f.mjs @@ -0,0 +1,18 @@ +// vite.config.ts +import { defineConfig } from "file:///D:/Documents/Github/forgelm/tauri-app/node_modules/vite/dist/node/index.js"; +import react from "file:///D:/Documents/Github/forgelm/tauri-app/node_modules/@vitejs/plugin-react/dist/index.js"; +var vite_config_default = defineConfig(async () => ({ + plugins: [react()], + clearScreen: false, + server: { + port: 1420, + strictPort: true, + watch: { + ignored: ["**/src-tauri/**"] + } + } +})); +export { + vite_config_default as default +}; +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJEOlxcXFxEb2N1bWVudHNcXFxcR2l0aHViXFxcXGZvcmdlbG1cXFxcdGF1cmktYXBwXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ZpbGVuYW1lID0gXCJEOlxcXFxEb2N1bWVudHNcXFxcR2l0aHViXFxcXGZvcmdlbG1cXFxcdGF1cmktYXBwXFxcXHZpdGUuY29uZmlnLnRzXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ltcG9ydF9tZXRhX3VybCA9IFwiZmlsZTovLy9EOi9Eb2N1bWVudHMvR2l0aHViL2ZvcmdlbG0vdGF1cmktYXBwL3ZpdGUuY29uZmlnLnRzXCI7aW1wb3J0IHsgZGVmaW5lQ29uZmlnIH0gZnJvbSBcInZpdGVcIjtcbmltcG9ydCByZWFjdCBmcm9tIFwiQHZpdGVqcy9wbHVnaW4tcmVhY3RcIjtcblxuZXhwb3J0IGRlZmF1bHQgZGVmaW5lQ29uZmlnKGFzeW5jICgpID0+ICh7XG4gIHBsdWdpbnM6IFtyZWFjdCgpXSxcbiAgY2xlYXJTY3JlZW46IGZhbHNlLFxuICBzZXJ2ZXI6IHtcbiAgICBwb3J0OiAxNDIwLFxuICAgIHN0cmljdFBvcnQ6IHRydWUsXG4gICAgd2F0Y2g6IHtcbiAgICAgIGlnbm9yZWQ6IFtcIioqL3NyYy10YXVyaS8qKlwiXSxcbiAgICB9LFxuICB9LFxufSkpO1xuIl0sCiAgIm1hcHBpbmdzIjogIjtBQUEyUyxTQUFTLG9CQUFvQjtBQUN4VSxPQUFPLFdBQVc7QUFFbEIsSUFBTyxzQkFBUSxhQUFhLGFBQWE7QUFBQSxFQUN2QyxTQUFTLENBQUMsTUFBTSxDQUFDO0FBQUEsRUFDakIsYUFBYTtBQUFBLEVBQ2IsUUFBUTtBQUFBLElBQ04sTUFBTTtBQUFBLElBQ04sWUFBWTtBQUFBLElBQ1osT0FBTztBQUFBLE1BQ0wsU0FBUyxDQUFDLGlCQUFpQjtBQUFBLElBQzdCO0FBQUEsRUFDRjtBQUNGLEVBQUU7IiwKICAibmFtZXMiOiBbXQp9Cg==