From 19d89f261649f01a4c6914114326be1c808b9464 Mon Sep 17 00:00:00 2001 From: Yash Date: Tue, 2 Jun 2026 23:58:15 +0530 Subject: [PATCH 01/46] feat: add AISettings component and related AI service functionality --- .../settings/components/AISettings.tsx | 335 ++++++++++++++++++ src/features/settings/components/index.tsx | 3 +- src/pages/Settings.tsx | 5 +- src/services/bridge/ai.ts | 126 +++++++ 4 files changed, 467 insertions(+), 2 deletions(-) create mode 100644 src/features/settings/components/AISettings.tsx create mode 100644 src/services/bridge/ai.ts diff --git a/src/features/settings/components/AISettings.tsx b/src/features/settings/components/AISettings.tsx new file mode 100644 index 0000000..db3b460 --- /dev/null +++ b/src/features/settings/components/AISettings.tsx @@ -0,0 +1,335 @@ +import { useState, useEffect } from "react"; +import { Bot, Eye, EyeOff, CheckCircle2, XCircle, Loader2, ChevronDown } from "lucide-react"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { Label } from "@/components/ui/label"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { cn } from "@/lib/utils"; +import { + AIProviderName, + type AISettings as AISettingsData, + loadAISettings, + saveAISettings, + aiService, +} from "@/services/bridge/ai"; + +// ── Provider metadata ───────────────────────────────────────────────────── + +interface ProviderMeta { + name: AIProviderName; + label: string; + description: string; + requiresKey: boolean; + keyField?: keyof AISettingsData; + keyPlaceholder?: string; + extraFields?: Array<{ + field: keyof AISettingsData; + label: string; + placeholder: string; + type?: string; + }>; +} + +const PROVIDERS: ProviderMeta[] = [ + { + name: "anthropic", + label: "Claude (Anthropic)", + description: "claude-3-5-haiku-20241022", + requiresKey: true, + keyField: "anthropicApiKey", + keyPlaceholder: "sk-ant-api03-…", + }, + { + name: "openai", + label: "OpenAI", + description: "gpt-4o-mini", + requiresKey: true, + keyField: "openaiApiKey", + keyPlaceholder: "sk-proj-…", + }, + { + name: "gemini", + label: "Gemini (Google)", + description: "gemini-1.5-flash", + requiresKey: true, + keyField: "geminiApiKey", + keyPlaceholder: "AIzaSy…", + }, + { + name: "groq", + label: "Groq", + description: "llama-3.3-70b-versatile", + requiresKey: true, + keyField: "groqApiKey", + keyPlaceholder: "gsk_…", + }, + { + name: "mistral", + label: "Mistral", + description: "mistral-small-latest", + requiresKey: true, + keyField: "mistralApiKey", + keyPlaceholder: "…", + }, + { + name: "ollama", + label: "Ollama (Local)", + description: "Runs entirely on your machine", + requiresKey: false, + extraFields: [ + { + field: "ollamaBaseUrl", + label: "Base URL", + placeholder: "http://localhost:11434", + }, + { + field: "ollamaModel", + label: "Model", + placeholder: "llama3.2", + }, + ], + }, +]; + +// ── Connection status indicator ─────────────────────────────────────────── + +type ConnectionStatus = "idle" | "testing" | "ok" | "error"; + +function StatusBadge({ status, message }: { status: ConnectionStatus; message?: string }) { + if (status === "idle") return null; + return ( +
+ {status === "testing" && } + {status === "ok" && } + {status === "error" && } + {status === "testing" ? "Testing…" : status === "ok" ? "Connected" : (message ?? "Failed")} +
+ ); +} + +// ── Password field with show/hide ───────────────────────────────────────── + +function SecretInput({ + value, + onChange, + placeholder, + id, +}: { + value: string; + onChange: (v: string) => void; + placeholder?: string; + id: string; +}) { + const [show, setShow] = useState(false); + return ( +
+ onChange(e.target.value)} + placeholder={placeholder} + className="pr-9 h-8 text-xs font-mono border-border/40" + autoComplete="off" + spellCheck={false} + /> + +
+ ); +} + +// ── Main component ──────────────────────────────────────────────────────── + +export default function AISettings() { + const [settings, setSettings] = useState(loadAISettings); + const [dirty, setDirty] = useState(false); + const [status, setStatus] = useState("idle"); + const [statusMessage, setStatusMessage] = useState(); + + // Load from storage on mount + useEffect(() => { + setSettings(loadAISettings()); + }, []); + + const activeProvider = PROVIDERS.find((p) => p.name === settings.defaultProvider) ?? PROVIDERS[0]; + + const update = (patch: Partial) => { + setSettings((prev) => ({ ...prev, ...patch })); + setDirty(true); + setStatus("idle"); + }; + + const handleSave = () => { + saveAISettings(settings); + setDirty(false); + setStatus("idle"); + }; + + const handleTest = async () => { + // Save first so the bridge gets the latest values + saveAISettings(settings); + setDirty(false); + setStatus("testing"); + setStatusMessage(undefined); + const result = await aiService.testConnection(settings); + if (result.connected) { + setStatus("ok"); + } else { + setStatus("error"); + setStatusMessage(result.message); + } + }; + + return ( +
+ {/* Section header */} +
+
+ +
+
+

AI Settings

+

+ Configure your AI provider. Keys stay on your machine. +

+
+
+ +
+ {/* Provider selector */} +
+
+ +

{activeProvider.description}

+
+ + + + + + {PROVIDERS.map((p) => ( + update({ defaultProvider: p.name })} + > + {p.label} + {p.description} + + ))} + + +
+ + {/* Credential fields for the active provider */} +
+ {activeProvider.requiresKey && activeProvider.keyField && ( +
+ + update({ [activeProvider.keyField!]: v })} + placeholder={activeProvider.keyPlaceholder} + /> +
+ )} + + {activeProvider.extraFields?.map((field) => ( +
+ + update({ [field.field]: e.target.value })} + placeholder={field.placeholder} + className="h-8 text-xs border-border/40" + spellCheck={false} + /> +
+ ))} +
+ + {/* Actions */} +
+ +
+ + +
+
+
+ + {/* All providers key summary (collapsed) */} +
+ + + Configure other providers + +
+ {PROVIDERS.filter((p) => p.name !== settings.defaultProvider && p.requiresKey).map((provider) => ( +
+ + update({ [provider.keyField!]: v })} + placeholder={provider.keyPlaceholder} + /> +
+ ))} +
+
+
+ ); +} diff --git a/src/features/settings/components/index.tsx b/src/features/settings/components/index.tsx index 70ae1b9..6f0366c 100644 --- a/src/features/settings/components/index.tsx +++ b/src/features/settings/components/index.tsx @@ -4,4 +4,5 @@ export { default as ColorVariant } from './ColorVariant' export {default as DeveloperMode} from './DeveloperMode' export {default as CheckForUpdates} from './CheckForUpdates' export {default as Version} from './Version' -export {default as Preview} from './Preview' \ No newline at end of file +export {default as Preview} from './Preview' +export {default as AISettings} from './AISettings' \ No newline at end of file diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index d8867f0..c876eca 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -1,4 +1,4 @@ -import { CheckForUpdates, ColorVariant, DeveloperMode, Header, Preview, ThemeMode, Version } from "@/features/settings/components"; +import { AISettings, CheckForUpdates, ColorVariant, DeveloperMode, Header, Preview, ThemeMode, Version } from "@/features/settings/components"; const Settings = () => { return ( @@ -19,6 +19,9 @@ const Settings = () => { {/* Developer Mode Section */} + {/* AI Settings Section */} + + {/* Updates Section */} diff --git a/src/services/bridge/ai.ts b/src/services/bridge/ai.ts new file mode 100644 index 0000000..d011cf9 --- /dev/null +++ b/src/services/bridge/ai.ts @@ -0,0 +1,126 @@ +import { bridgeRequest } from "./bridgeClient"; + +// ── Re-export the types that the frontend needs ─────────────────────────── +// (These mirror the bridge types but kept local to avoid importing from bridge) + +export type AIProviderName = + | "anthropic" + | "openai" + | "gemini" + | "groq" + | "mistral" + | "ollama"; + +export interface AISettings { + defaultProvider: AIProviderName; + anthropicApiKey?: string; + openaiApiKey?: string; + geminiApiKey?: string; + groqApiKey?: string; + mistralApiKey?: string; + ollamaBaseUrl?: string; + ollamaModel?: string; +} + +export interface SchemaAnalysisInput { + tables: Array<{ + name: string; + schema?: string; + columns: Array<{ + name: string; + type: string; + nullable?: boolean; + isPrimaryKey?: boolean; + isForeignKey?: boolean; + references?: { table: string; column: string }; + }>; + indexes?: string[]; + foreignKeys?: string[]; + constraints?: string[]; + }>; + databaseType?: string; +} + +export interface QueryExplanationInput { + sql: string; + schema?: SchemaAnalysisInput["tables"]; + databaseType?: string; +} + +export interface ChartRecommendationInput { + tableName: string; + columns: Array<{ + name: string; + type: string; + isPrimaryKey?: boolean; + sampleValues?: string[]; + }>; +} + +export interface ChartRecommendation { + chartType: "bar" | "line" | "area" | "pie"; + xAxis: string; + yAxis: string; + reasoning: string; +} + +// ── AI Settings storage ─────────────────────────────────────────────────── + +const AI_SETTINGS_KEY = "relwave:ai-settings"; + +export function loadAISettings(): AISettings { + try { + const raw = localStorage.getItem(AI_SETTINGS_KEY); + if (raw) return JSON.parse(raw) as AISettings; + } catch { /* ignore */ } + return { defaultProvider: "ollama" }; +} + +export function saveAISettings(settings: AISettings): void { + localStorage.setItem(AI_SETTINGS_KEY, JSON.stringify(settings)); +} + +// ── Bridge service class ────────────────────────────────────────────────── + +class AIService { + /** + * Test whether the configured provider is reachable. + */ + async testConnection(settings: AISettings): Promise<{ connected: boolean; message?: string }> { + try { + const result = await bridgeRequest("ai.testConnection", { settings }); + return { connected: true }; + } catch (err: any) { + return { connected: false, message: err?.message ?? String(err) }; + } + } + + /** + * Analyze a database schema. Returns a markdown string. + */ + async analyzeSchema(settings: AISettings, input: SchemaAnalysisInput): Promise { + const result = await bridgeRequest("ai.analyzeSchema", { settings, input }); + return result?.data?.markdown ?? ""; + } + + /** + * Explain a SQL query. Returns a markdown string. + */ + async explainQuery(settings: AISettings, input: QueryExplanationInput): Promise { + const result = await bridgeRequest("ai.explainQuery", { settings, input }); + return result?.data?.markdown ?? ""; + } + + /** + * Recommend a chart type and axes for the given table metadata. + */ + async recommendChart( + settings: AISettings, + input: ChartRecommendationInput + ): Promise { + const result = await bridgeRequest("ai.recommendChart", { settings, input }); + return result?.data as ChartRecommendation; + } +} + +export const aiService = new AIService(); From 06f22174166165ae17059f8344dc9fe7be4c56e7 Mon Sep 17 00:00:00 2001 From: Yash Date: Wed, 3 Jun 2026 00:09:46 +0530 Subject: [PATCH 02/46] feat: add shared types and interfaces for AI integration layer and providers --- bridge/src/ai/ai.types.ts | 92 ++++++++++++++++++++++++++++++++ bridge/src/ai/prompts/shared.ts | 18 +++++++ bridge/src/ai/providers/types.ts | 85 +++++++++++++++++++++++++++++ 3 files changed, 195 insertions(+) create mode 100644 bridge/src/ai/ai.types.ts create mode 100644 bridge/src/ai/prompts/shared.ts create mode 100644 bridge/src/ai/providers/types.ts diff --git a/bridge/src/ai/ai.types.ts b/bridge/src/ai/ai.types.ts new file mode 100644 index 0000000..b5e97a9 --- /dev/null +++ b/bridge/src/ai/ai.types.ts @@ -0,0 +1,92 @@ +// ── Shared types for the AI integration layer ───────────────────────────── + +export type AIProviderName = + | "anthropic" + | "openai" + | "gemini" + | "groq" + | "mistral" + | "ollama"; + +/** + * User-facing AI settings — stored client-side and passed on every RPC call. + * The bridge is stateless regarding API keys. + */ +export interface AISettings { + defaultProvider: AIProviderName; + anthropicApiKey?: string; + openaiApiKey?: string; + geminiApiKey?: string; + groqApiKey?: string; + mistralApiKey?: string; + ollamaBaseUrl?: string; + ollamaModel?: string; +} + +// ── Feature input/output types ──────────────────────────────────────────── + +export interface SchemaColumn { + name: string; + type: string; + nullable?: boolean; + isPrimaryKey?: boolean; + isForeignKey?: boolean; + references?: { table: string; column: string }; +} + +export interface SchemaTable { + name: string; + schema?: string; + columns: SchemaColumn[]; + indexes?: string[]; + foreignKeys?: string[]; + constraints?: string[]; +} + +export interface SchemaAnalysisInput { + tables: SchemaTable[]; + databaseType?: string; +} + +export interface QueryExplanationInput { + sql: string; + schema?: SchemaTable[]; + databaseType?: string; +} + +export interface ChartRecommendationInput { + tableName: string; + columns: Array<{ + name: string; + type: string; + isPrimaryKey?: boolean; + sampleValues?: string[]; + }>; +} + +export interface ChartRecommendation { + chartType: "bar" | "line" | "area" | "pie"; + xAxis: string; + yAxis: string; + reasoning: string; +} + +// ── RPC param shapes ────────────────────────────────────────────────────── + +export interface AIRequestBase { + settings: AISettings; +} + +export interface AIAnalyzeSchemaParams extends AIRequestBase { + input: SchemaAnalysisInput; +} + +export interface AIExplainQueryParams extends AIRequestBase { + input: QueryExplanationInput; +} + +export interface AIRecommendChartParams extends AIRequestBase { + input: ChartRecommendationInput; +} + +export interface AITestConnectionParams extends AIRequestBase {} diff --git a/bridge/src/ai/prompts/shared.ts b/bridge/src/ai/prompts/shared.ts new file mode 100644 index 0000000..2a7bcc6 --- /dev/null +++ b/bridge/src/ai/prompts/shared.ts @@ -0,0 +1,18 @@ +/** + * Shared prompt fragments used across all AI providers. + * Keep these provider-independent — no SDK-specific formatting here. + */ + +export const SYSTEM_CONTEXT = `You are RelWave AI, an expert database assistant embedded in the RelWave desktop application. +RelWave helps developers manage PostgreSQL, MySQL, MariaDB, and SQLite databases. +Always respond in clear, well-structured Markdown unless instructed otherwise. +Be concise, practical, and actionable. Avoid boilerplate preambles.`; + +export const MARKDOWN_INSTRUCTION = `Format your response in Markdown with: +- Level 2 headings (##) for major sections +- Bullet points for lists of items +- Code blocks (\`\`\`sql) for SQL examples +- Bold text for key terms and important warnings +Keep responses focused and under 1000 words unless the complexity demands more.`; + +export const NO_DATA_PROMPT = "No structured data was provided."; diff --git a/bridge/src/ai/providers/types.ts b/bridge/src/ai/providers/types.ts new file mode 100644 index 0000000..8abeddc --- /dev/null +++ b/bridge/src/ai/providers/types.ts @@ -0,0 +1,85 @@ +import { + SchemaAnalysisInput, + QueryExplanationInput, + ChartRecommendationInput, + ChartRecommendation, +} from "../ai.types"; + +// ── Provider interface ──────────────────────────────────────────────────── + +/** + * All AI providers must implement this interface. + * Frontend code never knows which concrete provider is active. + */ +export interface AIProvider { + /** Analyze a database schema and return a markdown report. */ + analyzeSchema(input: SchemaAnalysisInput): Promise; + + /** Explain a SQL query in plain language, returning markdown. */ + explainQuery(input: QueryExplanationInput): Promise; + + /** Recommend a chart type + axes for the given table. */ + recommendChart(input: ChartRecommendationInput): Promise; + + /** + * Verify that the provider is reachable with the supplied credentials. + * Resolves with an empty string on success, a user-facing message on failure. + */ + testConnection(): Promise; +} + +// ── Standardized error type ─────────────────────────────────────────────── + +export type AIErrorCode = + | "MISSING_API_KEY" + | "INVALID_API_KEY" + | "PROVIDER_UNAVAILABLE" + | "INVALID_MODEL" + | "NETWORK_FAILURE" + | "TIMEOUT" + | "RATE_LIMIT" + | "PARSE_ERROR" + | "UNKNOWN"; + +export class AIError extends Error { + readonly code: AIErrorCode; + readonly provider: string; + readonly originalMessage: string; + + constructor(code: AIErrorCode, provider: string, message: string) { + super(`[${provider}] ${message}`); + this.name = "AIError"; + this.code = code; + this.provider = provider; + this.originalMessage = message; + } +} + +/** + * Classify a raw SDK/network error into an AIErrorCode. + */ +export function classifyError(err: unknown, provider: string): AIError { + const msg = err instanceof Error ? err.message : String(err); + const lower = msg.toLowerCase(); + + if (lower.includes("api key") || lower.includes("apikey") || lower.includes("authentication") || lower.includes("unauthorized") || lower.includes("401")) { + return new AIError("INVALID_API_KEY", provider, msg); + } + if (lower.includes("rate limit") || lower.includes("429") || lower.includes("too many requests")) { + return new AIError("RATE_LIMIT", provider, msg); + } + if (lower.includes("timeout") || lower.includes("timed out")) { + return new AIError("TIMEOUT", provider, msg); + } + if (lower.includes("econnrefused") || lower.includes("enotfound") || lower.includes("fetch failed") || lower.includes("network")) { + return new AIError("NETWORK_FAILURE", provider, msg); + } + if (lower.includes("model") && (lower.includes("not found") || lower.includes("invalid"))) { + return new AIError("INVALID_MODEL", provider, msg); + } + if (lower.includes("unavailable") || lower.includes("503") || lower.includes("overloaded")) { + return new AIError("PROVIDER_UNAVAILABLE", provider, msg); + } + + return new AIError("UNKNOWN", provider, msg); +} From 0eef9b94ccba4339bedc6c331f7e994244f2b696 Mon Sep 17 00:00:00 2001 From: Yash Date: Wed, 3 Jun 2026 00:11:24 +0530 Subject: [PATCH 03/46] feat: add prompt builders for chart recommendation, query explanation, and schema analysis --- bridge/src/ai/prompts/chart-recommendation.ts | 66 +++++++++++++++++++ bridge/src/ai/prompts/query-explanation.ts | 42 ++++++++++++ bridge/src/ai/prompts/schema-analysis.ts | 55 ++++++++++++++++ 3 files changed, 163 insertions(+) create mode 100644 bridge/src/ai/prompts/chart-recommendation.ts create mode 100644 bridge/src/ai/prompts/query-explanation.ts create mode 100644 bridge/src/ai/prompts/schema-analysis.ts diff --git a/bridge/src/ai/prompts/chart-recommendation.ts b/bridge/src/ai/prompts/chart-recommendation.ts new file mode 100644 index 0000000..da19a0e --- /dev/null +++ b/bridge/src/ai/prompts/chart-recommendation.ts @@ -0,0 +1,66 @@ +import { ChartRecommendationInput, ChartRecommendation } from "../ai.types"; +import { SYSTEM_CONTEXT } from "./shared"; +import { AIError } from "../providers/types"; + +export function buildChartRecommendationPrompt(input: ChartRecommendationInput): { + system: string; + user: string; +} { + const columnList = input.columns + .map((c) => { + const flags: string[] = [c.type]; + if (c.isPrimaryKey) flags.push("PK"); + if (c.sampleValues?.length) flags.push(`samples: ${c.sampleValues.slice(0, 3).join(", ")}`); + return `- ${c.name} (${flags.join(", ")})`; + }) + .join("\n"); + + const user = `Given the table "${input.tableName}" with the following columns, recommend the best chart visualization: + +## Columns +${columnList} + +## Instructions +Respond ONLY with a valid JSON object — no markdown fences, no explanation text. +The JSON must match exactly this shape: +{ + "chartType": "bar" | "line" | "area" | "pie", + "xAxis": "", + "yAxis": "", + "reasoning": "" +} + +Choose the most insightful combination. Prefer grouping a categorical/text column on X and counting a numeric/PK column on Y.`; + + return { system: SYSTEM_CONTEXT, user }; +} + +/** + * Parse the raw LLM text response into a ChartRecommendation. + * The model is instructed to return bare JSON, but defensively strip + * any markdown fences if the model adds them anyway. + */ +export function parseChartRecommendation(raw: string): ChartRecommendation { + // Strip markdown code fences if present + const cleaned = raw + .replace(/```json\s*/gi, "") + .replace(/```\s*/g, "") + .trim(); + + let parsed: any; + try { + parsed = JSON.parse(cleaned); + } catch { + throw new AIError("PARSE_ERROR", "chart-recommendation", `Failed to parse chart recommendation JSON: ${raw.slice(0, 200)}`); + } + + const validTypes = ["bar", "line", "area", "pie"]; + const chartType = validTypes.includes(parsed.chartType) ? parsed.chartType : "bar"; + + return { + chartType: chartType as ChartRecommendation["chartType"], + xAxis: String(parsed.xAxis ?? ""), + yAxis: String(parsed.yAxis ?? ""), + reasoning: String(parsed.reasoning ?? ""), + }; +} diff --git a/bridge/src/ai/prompts/query-explanation.ts b/bridge/src/ai/prompts/query-explanation.ts new file mode 100644 index 0000000..09106d7 --- /dev/null +++ b/bridge/src/ai/prompts/query-explanation.ts @@ -0,0 +1,42 @@ +import { QueryExplanationInput } from "../ai.types"; +import { SYSTEM_CONTEXT, MARKDOWN_INSTRUCTION } from "./shared"; + +export function buildQueryExplanationPrompt(input: QueryExplanationInput): { + system: string; + user: string; +} { + const schemaContext = + input.schema && input.schema.length > 0 + ? input.schema + .map((t) => { + const cols = t.columns + .map((c) => `${c.name} ${c.type}${c.isPrimaryKey ? " PK" : ""}${c.isForeignKey ? " FK" : ""}`) + .join(", "); + return `- ${t.name}(${cols})`; + }) + .join("\n") + : "Schema not provided."; + + const dbType = input.databaseType ? ` (${input.databaseType})` : ""; + + const user = `Explain the following SQL query${dbType}: + +\`\`\`sql +${input.sql} +\`\`\` + +## Relevant Schema +${schemaContext} + +## What to cover +1. **Query Purpose** — What does this query do in plain English? +2. **Joins** — Explain any joins and the relationships they traverse +3. **Filters** — What data is being filtered and why +4. **Aggregations** — Any GROUP BY, COUNT, SUM, etc., and what they compute +5. **Performance Concerns** — Potential bottlenecks, missing indexes, full table scans +6. **Suggested Improvements** — Rewritten or optimized version if applicable + +${MARKDOWN_INSTRUCTION}`; + + return { system: SYSTEM_CONTEXT, user }; +} diff --git a/bridge/src/ai/prompts/schema-analysis.ts b/bridge/src/ai/prompts/schema-analysis.ts new file mode 100644 index 0000000..9042a24 --- /dev/null +++ b/bridge/src/ai/prompts/schema-analysis.ts @@ -0,0 +1,55 @@ +import { SchemaAnalysisInput } from "../ai.types"; +import { SYSTEM_CONTEXT, MARKDOWN_INSTRUCTION } from "./shared"; + +export function buildSchemaAnalysisPrompt(input: SchemaAnalysisInput): { + system: string; + user: string; +} { + const tableDescriptions = input.tables + .map((t) => { + const columns = t.columns + .map((c) => { + const flags: string[] = []; + if (c.isPrimaryKey) flags.push("PK"); + if (c.isForeignKey) flags.push("FK"); + if (!c.nullable) flags.push("NOT NULL"); + if (c.references) flags.push(`→ ${c.references.table}.${c.references.column}`); + return ` - ${c.name} (${c.type})${flags.length ? " [" + flags.join(", ") + "]" : ""}`; + }) + .join("\n"); + + const extras: string[] = []; + if (t.indexes?.length) extras.push(`Indexes: ${t.indexes.join(", ")}`); + if (t.foreignKeys?.length) extras.push(`Foreign keys: ${t.foreignKeys.join(", ")}`); + if (t.constraints?.length) extras.push(`Constraints: ${t.constraints.join(", ")}`); + + return [ + `### Table: ${t.schema ? `${t.schema}.` : ""}${t.name}`, + columns, + extras.length ? extras.map((e) => ` * ${e}`).join("\n") : "", + ] + .filter(Boolean) + .join("\n"); + }) + .join("\n\n"); + + const dbType = input.databaseType ? ` (${input.databaseType})` : ""; + + const user = `Analyze the following database schema${dbType} and provide: + +## What to cover +1. **Purpose** — What does this database appear to be for? +2. **Architecture** — Key design decisions and table relationships +3. **Missing Indexes** — Columns that should be indexed but aren't +4. **Schema Smells** — Anti-patterns, naming issues, or poor design choices +5. **Normalization Concerns** — Over/under-normalization issues +6. **Scalability Concerns** — Issues that may cause problems at scale +7. **Suggested Improvements** — Concrete, actionable recommendations + +## Schema +${tableDescriptions || "No tables provided."} + +${MARKDOWN_INSTRUCTION}`; + + return { system: SYSTEM_CONTEXT, user }; +} From 3b94bea2d7a4b25499fe85f6f3dbfc1125d4a924 Mon Sep 17 00:00:00 2001 From: Yash Date: Wed, 3 Jun 2026 00:21:18 +0530 Subject: [PATCH 04/46] feat: implement AI provider classes for Anthropic, Gemini, Groq, Mistral, Ollama, and OpenAI --- bridge/src/ai/providers/anthropic.provider.ts | 65 +++++++++++++++++ bridge/src/ai/providers/gemini.provider.ts | 60 ++++++++++++++++ bridge/src/ai/providers/groq.provider.ts | 66 +++++++++++++++++ bridge/src/ai/providers/mistral.provider.ts | 72 +++++++++++++++++++ bridge/src/ai/providers/ollama.provider.ts | 68 ++++++++++++++++++ bridge/src/ai/providers/openai.provider.ts | 66 +++++++++++++++++ 6 files changed, 397 insertions(+) create mode 100644 bridge/src/ai/providers/anthropic.provider.ts create mode 100644 bridge/src/ai/providers/gemini.provider.ts create mode 100644 bridge/src/ai/providers/groq.provider.ts create mode 100644 bridge/src/ai/providers/mistral.provider.ts create mode 100644 bridge/src/ai/providers/ollama.provider.ts create mode 100644 bridge/src/ai/providers/openai.provider.ts diff --git a/bridge/src/ai/providers/anthropic.provider.ts b/bridge/src/ai/providers/anthropic.provider.ts new file mode 100644 index 0000000..6591c8e --- /dev/null +++ b/bridge/src/ai/providers/anthropic.provider.ts @@ -0,0 +1,65 @@ +import Anthropic from "@anthropic-ai/sdk"; +import { AIProvider, classifyError } from "./types"; +import { + SchemaAnalysisInput, + QueryExplanationInput, + ChartRecommendationInput, + ChartRecommendation, +} from "../ai.types"; +import { buildSchemaAnalysisPrompt } from "../prompts/schema-analysis"; +import { buildQueryExplanationPrompt } from "../prompts/query-explanation"; +import { buildChartRecommendationPrompt, parseChartRecommendation } from "../prompts/chart-recommendation"; + +const DEFAULT_MODEL = "claude-3-5-haiku-20241022"; + +export class AnthropicProvider implements AIProvider { + private client: Anthropic; + + constructor(apiKey: string) { + this.client = new Anthropic({ apiKey }); + } + + private async complete(system: string, user: string): Promise { + try { + const msg = await this.client.messages.create({ + model: DEFAULT_MODEL, + max_tokens: 2048, + system, + messages: [{ role: "user", content: user }], + }); + const block = msg.content[0]; + return block.type === "text" ? block.text : ""; + } catch (err) { + throw classifyError(err, "anthropic"); + } + } + + async analyzeSchema(input: SchemaAnalysisInput): Promise { + const { system, user } = buildSchemaAnalysisPrompt(input); + return this.complete(system, user); + } + + async explainQuery(input: QueryExplanationInput): Promise { + const { system, user } = buildQueryExplanationPrompt(input); + return this.complete(system, user); + } + + async recommendChart(input: ChartRecommendationInput): Promise { + const { system, user } = buildChartRecommendationPrompt(input); + const raw = await this.complete(system, user); + return parseChartRecommendation(raw); + } + + async testConnection(): Promise { + try { + await this.client.messages.create({ + model: DEFAULT_MODEL, + max_tokens: 10, + messages: [{ role: "user", content: "ping" }], + }); + return ""; + } catch (err) { + throw classifyError(err, "anthropic"); + } + } +} diff --git a/bridge/src/ai/providers/gemini.provider.ts b/bridge/src/ai/providers/gemini.provider.ts new file mode 100644 index 0000000..3867366 --- /dev/null +++ b/bridge/src/ai/providers/gemini.provider.ts @@ -0,0 +1,60 @@ +import { GoogleGenerativeAI } from "@google/generative-ai"; +import { AIProvider, classifyError } from "./types"; +import { + SchemaAnalysisInput, + QueryExplanationInput, + ChartRecommendationInput, + ChartRecommendation, +} from "../ai.types"; +import { buildSchemaAnalysisPrompt } from "../prompts/schema-analysis"; +import { buildQueryExplanationPrompt } from "../prompts/query-explanation"; +import { buildChartRecommendationPrompt, parseChartRecommendation } from "../prompts/chart-recommendation"; + +const DEFAULT_MODEL = "gemini-1.5-flash"; + +export class GeminiProvider implements AIProvider { + private genAI: GoogleGenerativeAI; + + constructor(apiKey: string) { + this.genAI = new GoogleGenerativeAI(apiKey); + } + + private async complete(system: string, user: string): Promise { + try { + const model = this.genAI.getGenerativeModel({ + model: DEFAULT_MODEL, + systemInstruction: system, + }); + const result = await model.generateContent(user); + return result.response.text(); + } catch (err) { + throw classifyError(err, "gemini"); + } + } + + async analyzeSchema(input: SchemaAnalysisInput): Promise { + const { system, user } = buildSchemaAnalysisPrompt(input); + return this.complete(system, user); + } + + async explainQuery(input: QueryExplanationInput): Promise { + const { system, user } = buildQueryExplanationPrompt(input); + return this.complete(system, user); + } + + async recommendChart(input: ChartRecommendationInput): Promise { + const { system, user } = buildChartRecommendationPrompt(input); + const raw = await this.complete(system, user); + return parseChartRecommendation(raw); + } + + async testConnection(): Promise { + try { + const model = this.genAI.getGenerativeModel({ model: DEFAULT_MODEL }); + await model.generateContent("ping"); + return ""; + } catch (err) { + throw classifyError(err, "gemini"); + } + } +} diff --git a/bridge/src/ai/providers/groq.provider.ts b/bridge/src/ai/providers/groq.provider.ts new file mode 100644 index 0000000..d089b02 --- /dev/null +++ b/bridge/src/ai/providers/groq.provider.ts @@ -0,0 +1,66 @@ +import Groq from "groq-sdk"; +import { AIProvider, classifyError } from "./types"; +import { + SchemaAnalysisInput, + QueryExplanationInput, + ChartRecommendationInput, + ChartRecommendation, +} from "../ai.types"; +import { buildSchemaAnalysisPrompt } from "../prompts/schema-analysis"; +import { buildQueryExplanationPrompt } from "../prompts/query-explanation"; +import { buildChartRecommendationPrompt, parseChartRecommendation } from "../prompts/chart-recommendation"; + +const DEFAULT_MODEL = "llama-3.3-70b-versatile"; + +export class GroqProvider implements AIProvider { + private client: Groq; + + constructor(apiKey: string) { + this.client = new Groq({ apiKey }); + } + + private async complete(system: string, user: string): Promise { + try { + const res = await this.client.chat.completions.create({ + model: DEFAULT_MODEL, + messages: [ + { role: "system", content: system }, + { role: "user", content: user }, + ], + max_tokens: 2048, + }); + return res.choices[0]?.message?.content ?? ""; + } catch (err) { + throw classifyError(err, "groq"); + } + } + + async analyzeSchema(input: SchemaAnalysisInput): Promise { + const { system, user } = buildSchemaAnalysisPrompt(input); + return this.complete(system, user); + } + + async explainQuery(input: QueryExplanationInput): Promise { + const { system, user } = buildQueryExplanationPrompt(input); + return this.complete(system, user); + } + + async recommendChart(input: ChartRecommendationInput): Promise { + const { system, user } = buildChartRecommendationPrompt(input); + const raw = await this.complete(system, user); + return parseChartRecommendation(raw); + } + + async testConnection(): Promise { + try { + await this.client.chat.completions.create({ + model: DEFAULT_MODEL, + messages: [{ role: "user", content: "ping" }], + max_tokens: 5, + }); + return ""; + } catch (err) { + throw classifyError(err, "groq"); + } + } +} diff --git a/bridge/src/ai/providers/mistral.provider.ts b/bridge/src/ai/providers/mistral.provider.ts new file mode 100644 index 0000000..5a7ed7b --- /dev/null +++ b/bridge/src/ai/providers/mistral.provider.ts @@ -0,0 +1,72 @@ +import { Mistral } from "@mistralai/mistralai"; +import { AIProvider, classifyError } from "./types"; +import { + SchemaAnalysisInput, + QueryExplanationInput, + ChartRecommendationInput, + ChartRecommendation, +} from "../ai.types"; +import { buildSchemaAnalysisPrompt } from "../prompts/schema-analysis"; +import { buildQueryExplanationPrompt } from "../prompts/query-explanation"; +import { buildChartRecommendationPrompt, parseChartRecommendation } from "../prompts/chart-recommendation"; + +const DEFAULT_MODEL = "mistral-small-latest"; + +export class MistralProvider implements AIProvider { + private client: Mistral; + + constructor(apiKey: string) { + this.client = new Mistral({ apiKey }); + } + + private async complete(system: string, user: string): Promise { + try { + const res = await this.client.chat.complete({ + model: DEFAULT_MODEL, + messages: [ + { role: "system", content: system }, + { role: "user", content: user }, + ], + maxTokens: 2048, + }); + const choice = res.choices?.[0]; + const content = choice?.message?.content; + if (typeof content === "string") return content; + if (Array.isArray(content)) { + return content.map((c: any) => c.text ?? "").join(""); + } + return ""; + } catch (err) { + throw classifyError(err, "mistral"); + } + } + + async analyzeSchema(input: SchemaAnalysisInput): Promise { + const { system, user } = buildSchemaAnalysisPrompt(input); + return this.complete(system, user); + } + + async explainQuery(input: QueryExplanationInput): Promise { + const { system, user } = buildQueryExplanationPrompt(input); + return this.complete(system, user); + } + + async recommendChart(input: ChartRecommendationInput): Promise { + const { system, user } = buildChartRecommendationPrompt(input); + const raw = await this.complete(system, user); + return parseChartRecommendation(raw); + } + + async testConnection(): Promise { + try { + await this.client.chat.complete({ + model: DEFAULT_MODEL, + messages: [{ role: "user", content: "ping" }], + maxTokens: 5, + }); + return ""; + } catch (err) { + throw classifyError(err, "mistral"); + } + } +} diff --git a/bridge/src/ai/providers/ollama.provider.ts b/bridge/src/ai/providers/ollama.provider.ts new file mode 100644 index 0000000..f6b6b22 --- /dev/null +++ b/bridge/src/ai/providers/ollama.provider.ts @@ -0,0 +1,68 @@ +import { Ollama } from "ollama"; +import { AIProvider, classifyError } from "./types"; +import { + SchemaAnalysisInput, + QueryExplanationInput, + ChartRecommendationInput, + ChartRecommendation, +} from "../ai.types"; +import { buildSchemaAnalysisPrompt } from "../prompts/schema-analysis"; +import { buildQueryExplanationPrompt } from "../prompts/query-explanation"; +import { buildChartRecommendationPrompt, parseChartRecommendation } from "../prompts/chart-recommendation"; + +const DEFAULT_MODEL = "llama3.2"; + +export class OllamaProvider implements AIProvider { + private client: Ollama; + private model: string; + + constructor(baseUrl?: string, model?: string) { + this.client = new Ollama({ host: baseUrl ?? "http://localhost:11434" }); + this.model = model?.trim() || DEFAULT_MODEL; + } + + private async complete(system: string, user: string): Promise { + try { + const res = await this.client.chat({ + model: this.model, + messages: [ + { role: "system", content: system }, + { role: "user", content: user }, + ], + }); + return res.message?.content ?? ""; + } catch (err) { + throw classifyError(err, "ollama"); + } + } + + async analyzeSchema(input: SchemaAnalysisInput): Promise { + const { system, user } = buildSchemaAnalysisPrompt(input); + return this.complete(system, user); + } + + async explainQuery(input: QueryExplanationInput): Promise { + const { system, user } = buildQueryExplanationPrompt(input); + return this.complete(system, user); + } + + async recommendChart(input: ChartRecommendationInput): Promise { + const { system, user } = buildChartRecommendationPrompt(input); + const raw = await this.complete(system, user); + return parseChartRecommendation(raw); + } + + async testConnection(): Promise { + try { + // List models to verify Ollama is reachable and the model exists + const list = await this.client.list(); + const available = list.models.map((m: any) => m.name); + if (!available.some((n: string) => n.startsWith(this.model.split(":")[0]))) { + throw new Error(`Model "${this.model}" not found. Available: ${available.join(", ") || "none"}`); + } + return ""; + } catch (err) { + throw classifyError(err, "ollama"); + } + } +} diff --git a/bridge/src/ai/providers/openai.provider.ts b/bridge/src/ai/providers/openai.provider.ts new file mode 100644 index 0000000..33fe6b0 --- /dev/null +++ b/bridge/src/ai/providers/openai.provider.ts @@ -0,0 +1,66 @@ +import OpenAI from "openai"; +import { AIProvider, classifyError } from "./types"; +import { + SchemaAnalysisInput, + QueryExplanationInput, + ChartRecommendationInput, + ChartRecommendation, +} from "../ai.types"; +import { buildSchemaAnalysisPrompt } from "../prompts/schema-analysis"; +import { buildQueryExplanationPrompt } from "../prompts/query-explanation"; +import { buildChartRecommendationPrompt, parseChartRecommendation } from "../prompts/chart-recommendation"; + +const DEFAULT_MODEL = "gpt-4o-mini"; + +export class OpenAIProvider implements AIProvider { + private client: OpenAI; + + constructor(apiKey: string) { + this.client = new OpenAI({ apiKey }); + } + + private async complete(system: string, user: string): Promise { + try { + const res = await this.client.chat.completions.create({ + model: DEFAULT_MODEL, + messages: [ + { role: "system", content: system }, + { role: "user", content: user }, + ], + max_tokens: 2048, + }); + return res.choices[0]?.message?.content ?? ""; + } catch (err) { + throw classifyError(err, "openai"); + } + } + + async analyzeSchema(input: SchemaAnalysisInput): Promise { + const { system, user } = buildSchemaAnalysisPrompt(input); + return this.complete(system, user); + } + + async explainQuery(input: QueryExplanationInput): Promise { + const { system, user } = buildQueryExplanationPrompt(input); + return this.complete(system, user); + } + + async recommendChart(input: ChartRecommendationInput): Promise { + const { system, user } = buildChartRecommendationPrompt(input); + const raw = await this.complete(system, user); + return parseChartRecommendation(raw); + } + + async testConnection(): Promise { + try { + await this.client.chat.completions.create({ + model: DEFAULT_MODEL, + messages: [{ role: "user", content: "ping" }], + max_tokens: 5, + }); + return ""; + } catch (err) { + throw classifyError(err, "openai"); + } + } +} From 532194afa044d000f04bda6444cdeff368566215 Mon Sep 17 00:00:00 2001 From: Yash Date: Wed, 3 Jun 2026 01:02:42 +0530 Subject: [PATCH 05/46] feat: implement AIHandlers and AIService classes for enhanced AI provider functionality --- bridge/src/ai/providers/types.ts | 2 +- bridge/src/handlers/aiHandlers.ts | 79 ++++++++++++++++++++++ bridge/src/services/ai.impl.ts | 60 ++++++++++++++++ bridge/src/services/aiService.ts | 7 ++ bridge/src/{ai/ai.types.ts => types/ai.ts} | 4 +- bridge/src/types/index.ts | 1 + 6 files changed, 150 insertions(+), 3 deletions(-) create mode 100644 bridge/src/handlers/aiHandlers.ts create mode 100644 bridge/src/services/ai.impl.ts create mode 100644 bridge/src/services/aiService.ts rename bridge/src/{ai/ai.types.ts => types/ai.ts} (91%) diff --git a/bridge/src/ai/providers/types.ts b/bridge/src/ai/providers/types.ts index 8abeddc..2d4bdb4 100644 --- a/bridge/src/ai/providers/types.ts +++ b/bridge/src/ai/providers/types.ts @@ -3,7 +3,7 @@ import { QueryExplanationInput, ChartRecommendationInput, ChartRecommendation, -} from "../ai.types"; +} from "../../types/ai"; // ── Provider interface ──────────────────────────────────────────────────── diff --git a/bridge/src/handlers/aiHandlers.ts b/bridge/src/handlers/aiHandlers.ts new file mode 100644 index 0000000..cce9d03 --- /dev/null +++ b/bridge/src/handlers/aiHandlers.ts @@ -0,0 +1,79 @@ +import { Logger } from "pino"; +import { Rpc } from "../types"; +import { AIService } from "../services/aiService"; +import { AIError } from "../ai/providers/types"; +import { + AIAnalyzeSchemaParams, + AIExplainQueryParams, + AIRecommendChartParams, + AITestConnectionParams, +} from "../types/ai"; + +export class AIHandlers { + private aiService: AIService; + + constructor(private rpc: Rpc, private logger: Logger) { + this.aiService = new AIService(); + } + + async handleTestConnection(params: AITestConnectionParams, id: number | string) { + try { + const provider = this.aiService.resolveProvider(params.settings); + await provider.testConnection(); + this.rpc.sendResponse(id, { ok: true, data: { connected: true } }); + } catch (err) { + this.logger?.warn({ err }, "ai.testConnection failed"); + const msg = err instanceof AIError ? err.originalMessage : String(err); + const code = err instanceof AIError ? err.code : "UNKNOWN"; + this.rpc.sendError(id, { code, message: msg }); + } + } + + async handleAnalyzeSchema(params: AIAnalyzeSchemaParams, id: number | string) { + try { + if (!params?.input?.tables?.length) { + return this.rpc.sendError(id, { code: "BAD_REQUEST", message: "No tables provided." }); + } + const provider = this.aiService.resolveProvider(params.settings); + const markdown = await provider.analyzeSchema(params.input); + this.rpc.sendResponse(id, { ok: true, data: { markdown } }); + } catch (err) { + this.logger?.error({ err }, "ai.analyzeSchema failed"); + const msg = err instanceof AIError ? err.originalMessage : String(err); + const code = err instanceof AIError ? err.code : "UNKNOWN"; + this.rpc.sendError(id, { code, message: msg }); + } + } + + async handleExplainQuery(params: AIExplainQueryParams, id: number | string) { + try { + if (!params?.input?.sql?.trim()) { + return this.rpc.sendError(id, { code: "BAD_REQUEST", message: "No SQL provided." }); + } + const provider = this.aiService.resolveProvider(params.settings); + const markdown = await provider.explainQuery(params.input); + this.rpc.sendResponse(id, { ok: true, data: { markdown } }); + } catch (err) { + this.logger?.error({ err }, "ai.explainQuery failed"); + const msg = err instanceof AIError ? err.originalMessage : String(err); + const code = err instanceof AIError ? err.code : "UNKNOWN"; + this.rpc.sendError(id, { code, message: msg }); + } + } + + async handleRecommendChart(params: AIRecommendChartParams, id: number | string) { + try { + if (!params?.input?.tableName || !params?.input?.columns?.length) { + return this.rpc.sendError(id, { code: "BAD_REQUEST", message: "Missing tableName or columns." }); + } + const provider = this.aiService.resolveProvider(params.settings); + const recommendation = await provider.recommendChart(params.input); + this.rpc.sendResponse(id, { ok: true, data: recommendation }); + } catch (err) { + this.logger?.error({ err }, "ai.recommendChart failed"); + const msg = err instanceof AIError ? err.originalMessage : String(err); + const code = err instanceof AIError ? err.code : "UNKNOWN"; + this.rpc.sendError(id, { code, message: msg }); + } + } +} diff --git a/bridge/src/services/ai.impl.ts b/bridge/src/services/ai.impl.ts new file mode 100644 index 0000000..3554216 --- /dev/null +++ b/bridge/src/services/ai.impl.ts @@ -0,0 +1,60 @@ +import { AISettings, AIProviderName } from "../types/ai"; +import { AIProvider, AIError } from "../ai/providers/types"; +import { AnthropicProvider } from "../ai/providers/anthropic.provider"; +import { OpenAIProvider } from "../ai/providers/openai.provider"; +import { GeminiProvider } from "../ai/providers/gemini.provider"; +import { GroqProvider } from "../ai/providers/groq.provider"; +import { MistralProvider } from "../ai/providers/mistral.provider"; +import { OllamaProvider } from "../ai/providers/ollama.provider"; + +/** + * Factory — creates the correct provider from user settings. + * Throws AIError("MISSING_API_KEY") if the required credential is absent. + */ +export class AIServiceImpl { + /** + * Resolve the active provider from the supplied settings. + */ + resolveProvider(settings: AISettings): AIProvider { + const provider = settings.defaultProvider; + return this.createProvider(provider, settings); + } + + private createProvider(name: AIProviderName, settings: AISettings): AIProvider { + switch (name) { + case "anthropic": { + const key = settings.anthropicApiKey?.trim(); + if (!key) throw new AIError("MISSING_API_KEY", "anthropic", "Anthropic API key is not configured."); + return new AnthropicProvider(key); + } + case "openai": { + const key = settings.openaiApiKey?.trim(); + if (!key) throw new AIError("MISSING_API_KEY", "openai", "OpenAI API key is not configured."); + return new OpenAIProvider(key); + } + case "gemini": { + const key = settings.geminiApiKey?.trim(); + if (!key) throw new AIError("MISSING_API_KEY", "gemini", "Gemini API key is not configured."); + return new GeminiProvider(key); + } + case "groq": { + const key = settings.groqApiKey?.trim(); + if (!key) throw new AIError("MISSING_API_KEY", "groq", "Groq API key is not configured."); + return new GroqProvider(key); + } + case "mistral": { + const key = settings.mistralApiKey?.trim(); + if (!key) throw new AIError("MISSING_API_KEY", "mistral", "Mistral API key is not configured."); + return new MistralProvider(key); + } + case "ollama": { + return new OllamaProvider(settings.ollamaBaseUrl, settings.ollamaModel); + } + default: { + throw new AIError("PROVIDER_UNAVAILABLE", name as string, `Unknown provider: ${name}`); + } + } + } +} + +export const aiImpl = new AIServiceImpl(); diff --git a/bridge/src/services/aiService.ts b/bridge/src/services/aiService.ts new file mode 100644 index 0000000..09036ac --- /dev/null +++ b/bridge/src/services/aiService.ts @@ -0,0 +1,7 @@ +import { AIServiceImpl } from "./ai.impl"; + +// Re-export the AIService class for discoverability under services/ +export class AIService extends AIServiceImpl {} + +// Also provide a singleton instance for callers that prefer a shared object +export const aiService = new AIService(); diff --git a/bridge/src/ai/ai.types.ts b/bridge/src/types/ai.ts similarity index 91% rename from bridge/src/ai/ai.types.ts rename to bridge/src/types/ai.ts index b5e97a9..aedecb2 100644 --- a/bridge/src/ai/ai.types.ts +++ b/bridge/src/types/ai.ts @@ -1,4 +1,4 @@ -// ── Shared types for the AI integration layer ───────────────────────────── +// ── Shared types for the AI integration layer (moved from src/ai/ai.types.ts) export type AIProviderName = | "anthropic" @@ -71,7 +71,7 @@ export interface ChartRecommendation { reasoning: string; } -// ── RPC param shapes ────────────────────────────────────────────────────── +// ── RPC param shapes ───────────────────────────────────────────────────── export interface AIRequestBase { settings: AISettings; diff --git a/bridge/src/types/index.ts b/bridge/src/types/index.ts index 8c5e236..dcfa1e9 100644 --- a/bridge/src/types/index.ts +++ b/bridge/src/types/index.ts @@ -14,6 +14,7 @@ export * from './common'; export * from './mysql'; export * from './postgres'; export * from './sqlite'; +export * from './ai'; export { SSHConfig } from './common'; export enum DBType { From 0d02221e8246075d266712299c4d2afadefe9494 Mon Sep 17 00:00:00 2001 From: Yash Date: Wed, 3 Jun 2026 01:05:51 +0530 Subject: [PATCH 06/46] feat(ai): restructure AI integration and update imports --- bridge/package.json | 13 +- bridge/pnpm-lock.yaml | 1170 +++++++++-------- bridge/src/ai/README.md | 23 + bridge/src/ai/prompts/chart-recommendation.ts | 2 +- bridge/src/ai/prompts/query-explanation.ts | 2 +- bridge/src/ai/prompts/schema-analysis.ts | 2 +- bridge/src/ai/providers/anthropic.provider.ts | 2 +- bridge/src/ai/providers/gemini.provider.ts | 2 +- bridge/src/ai/providers/groq.provider.ts | 2 +- bridge/src/ai/providers/mistral.provider.ts | 2 +- bridge/src/ai/providers/ollama.provider.ts | 2 +- bridge/src/ai/providers/openai.provider.ts | 2 +- 12 files changed, 698 insertions(+), 526 deletions(-) create mode 100644 bridge/src/ai/README.md diff --git a/bridge/package.json b/bridge/package.json index 1fb23b9..9e1b42c 100644 --- a/bridge/package.json +++ b/bridge/package.json @@ -7,21 +7,28 @@ "dev": "ts-node-dev --respawn --transpile-only src/index.ts", "start": "node dist/index.cjs", "build": "tsc --project tsconfig.json && esbuild src/index.ts --bundle --platform=node --outfile=dist/index.cjs --format=cjs --packages=external", + "build:bundle-for-pkg": "esbuild src/index.ts --bundle --platform=node --outfile=dist/index.bundled.cjs --format=cjs --external:better-sqlite3 --external:@napi-rs/keyring --external:ssh2 --external:cpu-features --external:pg-native", "copy:native": "node scripts/copy-native.js", "rebuild:native": "npm rebuild better-sqlite3", - "build:pkg:win": "npm run build && npm run rebuild:native && npm run copy:native && npx @yao-pkg/pkg . --target node22-win-x64 --output ../src-tauri/resources/bridge-x86_64-pc-windows-msvc.exe", - "build:pkg:linux": "npm run build && npm run rebuild:native && npm run copy:native && npx @yao-pkg/pkg . --target node22-linux-x64 --output ../src-tauri/resources/bridge-x86_64-unknown-linux-gnu", + "build:pkg:win": "npm run build && npm run build:bundle-for-pkg && npm run rebuild:native && npm run copy:native && npx @yao-pkg/pkg dist/index.bundled.cjs --target node22-win-x64 --output ../src-tauri/resources/bridge-x86_64-pc-windows-msvc.exe", + "build:pkg:linux": "npm run build && npm run build:bundle-for-pkg && npm run rebuild:native && npm run copy:native && npx @yao-pkg/pkg dist/index.bundled.cjs --target node22-linux-x64 --output ../src-tauri/resources/bridge-x86_64-unknown-linux-gnu", "test": "jest", "test:watch": "jest --watchAll --detectOpenHandles" }, "dependencies": { + "@anthropic-ai/sdk": "^0.100.1", + "@google/generative-ai": "^0.24.1", "@jest/globals": "^30.2.0", + "@mistralai/mistralai": "^2.2.5", "@napi-rs/keyring": "^1.2.0", "@types/ssh2": "^1.15.5", "bcryptjs": "^3.0.3", "better-sqlite3": "^11.9.0", "dotenv": "^17.2.3", + "groq-sdk": "^1.2.1", "mysql2": "^3.15.3", + "ollama": "^0.6.3", + "openai": "^6.41.0", "pg": "^8.16.3", "pg-query-stream": "^4.10.3", "pino": "^9.14.0", @@ -53,4 +60,4 @@ "ts-node-dev": "^2.0.0", "typescript": "^5.0.0" } -} +} \ No newline at end of file diff --git a/bridge/pnpm-lock.yaml b/bridge/pnpm-lock.yaml index 9f5d41d..f00a3ba 100644 --- a/bridge/pnpm-lock.yaml +++ b/bridge/pnpm-lock.yaml @@ -8,9 +8,18 @@ importers: .: dependencies: + '@anthropic-ai/sdk': + specifier: ^0.100.1 + version: 0.100.1(zod@4.4.3) + '@google/generative-ai': + specifier: ^0.24.1 + version: 0.24.1 '@jest/globals': specifier: ^30.2.0 version: 30.2.0 + '@mistralai/mistralai': + specifier: ^2.2.5 + version: 2.2.5 '@napi-rs/keyring': specifier: ^1.2.0 version: 1.2.0 @@ -22,19 +31,28 @@ importers: version: 3.0.3 better-sqlite3: specifier: ^11.9.0 - version: 11.9.0 + version: 11.10.0 dotenv: specifier: ^17.2.3 - version: 17.2.3 + version: 17.4.2 + groq-sdk: + specifier: ^1.2.1 + version: 1.2.1 mysql2: specifier: ^3.15.3 version: 3.15.3 + ollama: + specifier: ^0.6.3 + version: 0.6.3 + openai: + specifier: ^6.41.0 + version: 6.41.0(ws@8.20.1)(zod@4.4.3) pg: specifier: ^8.16.3 - version: 8.16.3 + version: 8.21.0 pg-query-stream: specifier: ^4.10.3 - version: 4.10.3(pg@8.16.3) + version: 4.10.3(pg@8.21.0) pino: specifier: ^9.14.0 version: 9.14.0 @@ -46,7 +64,7 @@ importers: version: 8.3.2 ws: specifier: ^8.19.0 - version: 8.19.0 + version: 8.20.1 devDependencies: '@types/better-sqlite3': specifier: ^7.6.13 @@ -56,70 +74,79 @@ importers: version: 30.0.0 '@types/node': specifier: ^20.0.0 - version: 20.19.25 + version: 20.19.41 '@types/pg': specifier: ^8.15.6 - version: 8.15.6 + version: 8.20.0 '@yao-pkg/pkg': specifier: ^5.12.0 version: 5.16.1 esbuild: specifier: ^0.27.0 - version: 0.27.0 + version: 0.27.7 jest: specifier: ^30.2.0 - version: 30.2.0(@types/node@20.19.25)(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)) + version: 30.2.0(@types/node@20.19.41)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)) ts-jest: specifier: ^29.4.6 - version: 29.4.6(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(esbuild@0.27.0)(jest-util@30.2.0)(jest@30.2.0(@types/node@20.19.25)(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)))(typescript@5.9.3) + version: 29.4.6(@babel/core@7.29.0)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.29.0))(esbuild@0.27.7)(jest-util@30.2.0)(jest@30.2.0(@types/node@20.19.41)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)))(typescript@5.9.3) ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@20.19.25)(typescript@5.9.3) + version: 10.9.2(@types/node@20.19.41)(typescript@5.9.3) ts-node-dev: specifier: ^2.0.0 - version: 2.0.0(@types/node@20.19.25)(typescript@5.9.3) + version: 2.0.0(@types/node@20.19.41)(typescript@5.9.3) typescript: specifier: ^5.0.0 version: 5.9.3 packages: - '@babel/code-frame@7.27.1': - resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + '@anthropic-ai/sdk@0.100.1': + resolution: {integrity: sha512-RANcEe7LpiLczkKGOwoXOTuFdPhuubS0i4xaAKOMpcqc55YO0mukgxppV7eygx3DXNjxWT6RYOLPyOy0aIAmwg==} + hasBin: true + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + peerDependenciesMeta: + zod: + optional: true + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.28.5': - resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} engines: {node: '>=6.9.0'} - '@babel/core@7.28.5': - resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} engines: {node: '>=6.9.0'} - '@babel/generator@7.28.5': - resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.27.2': - resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} engines: {node: '>=6.9.0'} '@babel/helper-globals@7.28.0': resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.27.1': - resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.28.3': - resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-plugin-utils@7.27.1': - resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} engines: {node: '>=6.9.0'} '@babel/helper-string-parser@7.27.1': @@ -134,12 +161,12 @@ packages: resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.28.4': - resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} + '@babel/helpers@7.28.6': + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} engines: {node: '>=6.9.0'} - '@babel/parser@7.28.5': - resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} + '@babel/parser@7.29.0': + resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} engines: {node: '>=6.0.0'} hasBin: true @@ -180,8 +207,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-jsx@7.27.1': - resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} + '@babel/plugin-syntax-jsx@7.28.6': + resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -228,22 +255,26 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-typescript@7.27.1': - resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} + '@babel/plugin-syntax-typescript@7.28.6': + resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/template@7.27.2': - resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.28.5': - resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} engines: {node: '>=6.9.0'} - '@babel/types@7.28.5': - resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} '@bcoe/v8-coverage@0.2.3': @@ -253,171 +284,175 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} - '@emnapi/core@1.7.1': - resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==} + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} - '@emnapi/runtime@1.7.1': - resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} - '@emnapi/wasi-threads@1.1.0': - resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} - '@esbuild/aix-ppc64@0.27.0': - resolution: {integrity: sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==} + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.27.0': - resolution: {integrity: sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==} + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.27.0': - resolution: {integrity: sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==} + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.27.0': - resolution: {integrity: sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==} + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.27.0': - resolution: {integrity: sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==} + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.27.0': - resolution: {integrity: sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==} + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.27.0': - resolution: {integrity: sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==} + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.27.0': - resolution: {integrity: sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==} + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.27.0': - resolution: {integrity: sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==} + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.27.0': - resolution: {integrity: sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==} + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.27.0': - resolution: {integrity: sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==} + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.27.0': - resolution: {integrity: sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==} + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.27.0': - resolution: {integrity: sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==} + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.27.0': - resolution: {integrity: sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==} + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.27.0': - resolution: {integrity: sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==} + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.27.0': - resolution: {integrity: sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==} + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.27.0': - resolution: {integrity: sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==} + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.27.0': - resolution: {integrity: sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==} + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.27.0': - resolution: {integrity: sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==} + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.27.0': - resolution: {integrity: sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==} + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.27.0': - resolution: {integrity: sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==} + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.27.0': - resolution: {integrity: sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==} + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.27.0': - resolution: {integrity: sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==} + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.27.0': - resolution: {integrity: sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==} + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.27.0': - resolution: {integrity: sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==} + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.27.0': - resolution: {integrity: sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==} + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} engines: {node: '>=18'} cpu: [x64] os: [win32] + '@google/generative-ai@0.24.1': + resolution: {integrity: sha512-MqO+MLfM6kjxcKoy0p1wRzG3b4ZZXtPI+z2IE26UogS2Cm/XHO+7gGRBh6gcJsOiIVoH93UwKvW4HdgiOZCy9Q==} + engines: {node: '>=18.0.0'} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -531,6 +566,9 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@mistralai/mistralai@2.2.5': + resolution: {integrity: sha512-ATbWzKkNzNAZ+gtw9MI/c/ULTMG80tKUiRNIbQFfg4OP0uEZZpTfXZeBCNfs5Dq0uqMQ/tQWc4o6RRJQtMrpDA==} + '@napi-rs/keyring-darwin-arm64@1.2.0': resolution: {integrity: sha512-CA83rDeyONDADO25JLZsh3eHY8yTEtm/RS6ecPsY+1v+dSawzT9GywBMu2r6uOp1IEhQs/xAfxgybGAFr17lSA==} engines: {node: '>= 10'} @@ -630,6 +668,9 @@ packages: '@sinonjs/fake-timers@13.0.5': resolution: {integrity: sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==} + '@stablelib/base64@1.0.1': + resolution: {integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==} + '@tsconfig/node10@1.0.12': resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==} @@ -675,11 +716,11 @@ packages: '@types/node@18.19.130': resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==} - '@types/node@20.19.25': - resolution: {integrity: sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==} + '@types/node@20.19.41': + resolution: {integrity: sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==} - '@types/pg@8.15.6': - resolution: {integrity: sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==} + '@types/pg@8.20.0': + resolution: {integrity: sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==} '@types/ssh2@1.15.5': resolution: {integrity: sha512-N1ASjp/nXH3ovBHddRJpli4ozpk6UdDYIX4RJWFa9L1YKnzdhTlVmiGHm4DZnj/jLbqZpes4aeR30EFGQtvhQQ==} @@ -809,8 +850,8 @@ packages: resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} engines: {node: '>=0.4.0'} - acorn@8.15.0: - resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} engines: {node: '>=0.4.0'} hasBin: true @@ -894,8 +935,9 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.9.5: - resolution: {integrity: sha512-D5vIoztZOq1XM54LUdttJVc96ggEsIfju2JBvht06pSzpckp3C7HReun67Bghzrtdsq9XdMGbSSB3v3GhMNmAA==} + baseline-browser-mapping@2.10.31: + resolution: {integrity: sha512-MujYO3eP72uvmSE0i4wltsodRfIpZATP3jvzRNRGGxgzId7aVocVJJV3nf01qnzzKFGxQVC9bpWxl5cjxTr/7Q==} + engines: {node: '>=6.0.0'} hasBin: true bcrypt-pbkdf@1.0.2: @@ -905,8 +947,8 @@ packages: resolution: {integrity: sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==} hasBin: true - better-sqlite3@11.9.0: - resolution: {integrity: sha512-4b9xYnoaskj8eIkke9ZCB42p5bOPabptSku8Rl4Yww70Jf+aHeLvrIjXDJrKQxUEjdppsFb+fdJSjoH4TklROA==} + better-sqlite3@11.10.0: + resolution: {integrity: sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==} binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} @@ -918,18 +960,18 @@ packages: bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - brace-expansion@1.1.12: - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + brace-expansion@1.1.14: + resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} - brace-expansion@2.0.2: - resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@2.1.0: + resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.28.1: - resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -962,8 +1004,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001760: - resolution: {integrity: sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==} + caniuse-lite@1.0.30001793: + resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} @@ -980,8 +1022,8 @@ packages: chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} - ci-info@4.3.1: - resolution: {integrity: sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==} + ci-info@4.4.0: + resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} engines: {node: '>=8'} cjs-module-lexer@2.1.1: @@ -1041,8 +1083,8 @@ packages: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} - dedent@1.7.0: - resolution: {integrity: sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==} + dedent@1.7.2: + resolution: {integrity: sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==} peerDependencies: babel-plugin-macros: ^3.1.0 peerDependenciesMeta: @@ -1069,12 +1111,12 @@ packages: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} - diff@4.0.2: - resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + diff@4.0.4: + resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==} engines: {node: '>=0.3.1'} - dotenv@17.2.3: - resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} + dotenv@17.4.2: + resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} engines: {node: '>=12'} dynamic-dedupe@0.3.0: @@ -1083,8 +1125,8 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - electron-to-chromium@1.5.267: - resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} + electron-to-chromium@1.5.361: + resolution: {integrity: sha512-Q6Hts7N9FnJc5LeGRINFvLhCI9xZmNtTDe5ZbcVezQz7cU4a8Aua3GH1b8J2XY8Al9PF+OCwYqhgsOOheMdvkA==} emittery@0.13.1: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} @@ -1102,8 +1144,12 @@ packages: error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} - esbuild@0.27.0: - resolution: {integrity: sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==} + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} engines: {node: '>=18'} hasBin: true @@ -1139,6 +1185,9 @@ packages: fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + fast-sha256@1.3.0: + resolution: {integrity: sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==} + fb-watchman@2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} @@ -1220,6 +1269,10 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + groq-sdk@1.2.1: + resolution: {integrity: sha512-dsDSWJRJf+n2dPiCv7zU3IsJbrh7jfSPqi6vc1q0TTK1oUF6bn+wv4P2VFdynkHpuJ0TTJ57vlpT87judPgVPA==} + hasBin: true + handlebars@4.7.8: resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} engines: {node: '>=0.4.7'} @@ -1244,8 +1297,8 @@ packages: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} - iconv-lite@0.7.0: - resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} engines: {node: '>=0.10.0'} ieee754@1.2.1: @@ -1484,6 +1537,10 @@ packages: json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json-schema-to-ts@3.1.1: + resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==} + engines: {node: '>=16'} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -1512,12 +1569,8 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - lru-cache@7.18.3: - resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} - engines: {node: '>=12'} - - lru.min@1.1.3: - resolution: {integrity: sha512-Lkk/vx6ak3rYkRR0Nhu4lFUT2VDnQSxBe8Hbl7f36358p6ow8Bnvr8lrLt98H8J1aGxfhbX4Fs5tYg2+FTwr5Q==} + lru.min@1.1.4: + resolution: {integrity: sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==} engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} make-dir@4.0.0: @@ -1545,11 +1598,11 @@ packages: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} engines: {node: '>=16 || 14 >=14.17'} minimist@1.2.8: @@ -1577,9 +1630,9 @@ packages: resolution: {integrity: sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==} engines: {node: '>= 8.0'} - named-placeholders@1.1.3: - resolution: {integrity: sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==} - engines: {node: '>=12.0.0'} + named-placeholders@1.1.6: + resolution: {integrity: sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==} + engines: {node: '>=8.0.0'} nan@2.25.0: resolution: {integrity: sha512-0M90Ag7Xn5KMLLZ7zliPWP3rT90P6PN+IzVFS0VqmnPktBk3700xUVv8Ikm9EUaUE5SDWdp/BIxdENzVznpm1g==} @@ -1598,8 +1651,8 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - node-abi@3.85.0: - resolution: {integrity: sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==} + node-abi@3.87.0: + resolution: {integrity: sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==} engines: {node: '>=10'} node-fetch@2.7.0: @@ -1614,8 +1667,9 @@ packages: node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} - node-releases@2.0.27: - resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + node-releases@2.0.46: + resolution: {integrity: sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==} + engines: {node: '>=18'} normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} @@ -1625,6 +1679,9 @@ packages: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} + ollama@0.6.3: + resolution: {integrity: sha512-KEWEhIqE5wtfzEIZbDCLH51VFZ6Z3ZSa6sIOg/E/tBV8S51flyqBOXi+bRxlOYKDf8i327zG9eSTb8IJxvm3Zg==} + on-exit-leak-free@2.1.2: resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} engines: {node: '>=14.0.0'} @@ -1636,6 +1693,17 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} + openai@6.41.0: + resolution: {integrity: sha512-IGWPopZq6Rjoynjfb3NSLf/z2MTw7UiOsm9TAjPGAjUESH7Uq41Trg4QWehBEn58p74i+m7uoRPV2vXcpPXhyA==} + peerDependencies: + ws: ^8.18.0 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + ws: + optional: true + zod: + optional: true + p-is-promise@3.0.0: resolution: {integrity: sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==} engines: {node: '>=8'} @@ -1682,11 +1750,11 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} - pg-cloudflare@1.2.7: - resolution: {integrity: sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==} + pg-cloudflare@1.4.0: + resolution: {integrity: sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==} - pg-connection-string@2.9.1: - resolution: {integrity: sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==} + pg-connection-string@2.13.0: + resolution: {integrity: sha512-EMnU9E2fSULdsbErBbMaXJvFeD9B4+nPcM3f+4lsiCR0BHLPrLVjv3DbyM2hgQQviKJaTWIRRTjKjWlHg3p2ig==} pg-cursor@2.15.3: resolution: {integrity: sha512-eHw63TsiGtFEfAd7tOTZ+TLy+i/2ePKS20H84qCQ+aQ60pve05Okon9tKMC+YN3j6XyeFoHnaim7Lt9WVafQsA==} @@ -1697,13 +1765,13 @@ packages: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} - pg-pool@3.10.1: - resolution: {integrity: sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==} + pg-pool@3.14.0: + resolution: {integrity: sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==} peerDependencies: pg: '>=8.0' - pg-protocol@1.10.3: - resolution: {integrity: sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==} + pg-protocol@1.14.0: + resolution: {integrity: sha512-n5taZ1kO3s9ngDTVxsEznOqCyToTgz0FLuPq0B33COy5pPpuWJpY3/2oRBVETuOgzdqRXfWpM9HIhp2LBBT1BA==} pg-query-stream@4.10.3: resolution: {integrity: sha512-h2utrzpOIzeT9JfaqfvBbVuvCfBjH86jNfVrGGTbyepKAIOyTfDew0lAt8bbJjs9n/I5bGDl7S2sx6h5hPyJxw==} @@ -1714,8 +1782,8 @@ packages: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} engines: {node: '>=4'} - pg@8.16.3: - resolution: {integrity: sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==} + pg@8.21.0: + resolution: {integrity: sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA==} engines: {node: '>= 16.0.0'} peerDependencies: pg-native: '>=3.0.1' @@ -1729,12 +1797,12 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} engines: {node: '>=8.6'} - picomatch@4.0.3: - resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} pino-abstract-transport@2.0.0: @@ -1759,8 +1827,8 @@ packages: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} engines: {node: '>=4'} - postgres-bytea@1.0.0: - resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} + postgres-bytea@1.0.1: + resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==} engines: {node: '>=0.10.0'} postgres-date@1.0.7: @@ -1791,8 +1859,8 @@ packages: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} engines: {node: '>=0.4.0'} - pump@3.0.3: - resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} pure-rand@7.0.1: resolution: {integrity: sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==} @@ -1834,8 +1902,8 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} - resolve@1.22.11: - resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} engines: {node: '>= 0.4'} hasBin: true @@ -1861,8 +1929,8 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.7.3: - resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + semver@7.8.1: + resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==} engines: {node: '>=10'} hasBin: true @@ -1926,6 +1994,9 @@ packages: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} + standardwebhooks@1.0.0: + resolution: {integrity: sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==} + stream-meter@1.0.4: resolution: {integrity: sha512-4sOEtrbgFotXwnEuzzsQBYEV1elAeFSO8rSGeTwabuX1RRn/kEq9JVH7I0MRBhKVRR0sJkr0M0QCH7yOLf9fhQ==} @@ -1951,8 +2022,8 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-ansi@7.1.2: - resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} engines: {node: '>=12'} strip-bom@3.0.0: @@ -2005,8 +2076,8 @@ packages: thread-stream@3.1.0: resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} - tinyglobby@0.2.15: - resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} engines: {node: '>=12.0.0'} tmpl@1.0.5: @@ -2023,6 +2094,9 @@ packages: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true + ts-algebra@2.0.0: + resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==} + ts-jest@29.4.6: resolution: {integrity: sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==} engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} @@ -2118,8 +2192,8 @@ packages: unrs-resolver@1.11.1: resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} - update-browserslist-db@1.2.2: - resolution: {integrity: sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==} + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -2129,6 +2203,7 @@ packages: uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true v8-compile-cache-lib@3.0.1: @@ -2144,6 +2219,9 @@ packages: webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + whatwg-fetch@3.6.20: + resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==} + whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} @@ -2170,8 +2248,8 @@ packages: resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - ws@8.19.0: - resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} + ws@8.20.1: + resolution: {integrity: sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -2217,27 +2295,42 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zod-to-json-schema@3.25.1: + resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} + peerDependencies: + zod: ^3.25 || ^4 + + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + snapshots: - '@babel/code-frame@7.27.1': + '@anthropic-ai/sdk@0.100.1(zod@4.4.3)': + dependencies: + json-schema-to-ts: 3.1.1 + standardwebhooks: 1.0.0 + optionalDependencies: + zod: 4.4.3 + + '@babel/code-frame@7.29.0': dependencies: '@babel/helper-validator-identifier': 7.28.5 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.28.5': {} + '@babel/compat-data@7.29.0': {} - '@babel/core@7.28.5': + '@babel/core@7.29.0': dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.5 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) - '@babel/helpers': 7.28.4 - '@babel/parser': 7.28.5 - '@babel/template': 7.27.2 - '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@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.4.3 @@ -2247,41 +2340,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.28.5': + '@babel/generator@7.29.1': dependencies: - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 - '@babel/helper-compilation-targets@7.27.2': + '@babel/helper-compilation-targets@7.28.6': dependencies: - '@babel/compat-data': 7.28.5 + '@babel/compat-data': 7.29.0 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.28.1 + browserslist: 4.28.2 lru-cache: 5.1.1 semver: 6.3.1 '@babel/helper-globals@7.28.0': {} - '@babel/helper-module-imports@7.27.1': + '@babel/helper-module-imports@7.28.6': dependencies: - '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-imports': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.28.5 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helper-plugin-utils@7.27.1': {} + '@babel/helper-plugin-utils@7.28.6': {} '@babel/helper-string-parser@7.27.1': {} @@ -2289,119 +2382,121 @@ snapshots: '@babel/helper-validator-option@7.27.1': {} - '@babel/helpers@7.28.4': + '@babel/helpers@7.28.6': dependencies: - '@babel/template': 7.27.2 - '@babel/types': 7.28.5 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 - '@babel/parser@7.28.5': + '@babel/parser@7.29.0': dependencies: - '@babel/types': 7.28.5 + '@babel/types': 7.29.0 - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.5)': + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.5)': + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.5)': + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.5)': + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.5)': + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.5)': + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.5)': + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.5)': + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.5)': + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.5)': + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.5)': + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.5)': + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.5)': + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.5)': + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/template@7.27.2': + '@babel/runtime@7.29.2': {} + + '@babel/template@7.28.6': dependencies: - '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 - '@babel/traverse@7.28.5': + '@babel/traverse@7.29.0': dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.5 + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.5 - '@babel/template': 7.27.2 - '@babel/types': 7.28.5 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 debug: 4.4.3 transitivePeerDependencies: - supports-color - '@babel/types@7.28.5': + '@babel/types@7.29.0': dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 @@ -2412,105 +2507,107 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@emnapi/core@1.7.1': + '@emnapi/core@1.10.0': dependencies: - '@emnapi/wasi-threads': 1.1.0 + '@emnapi/wasi-threads': 1.2.1 tslib: 2.8.1 optional: true - '@emnapi/runtime@1.7.1': + '@emnapi/runtime@1.10.0': dependencies: tslib: 2.8.1 optional: true - '@emnapi/wasi-threads@1.1.0': + '@emnapi/wasi-threads@1.2.1': dependencies: tslib: 2.8.1 optional: true - '@esbuild/aix-ppc64@0.27.0': + '@esbuild/aix-ppc64@0.27.7': optional: true - '@esbuild/android-arm64@0.27.0': + '@esbuild/android-arm64@0.27.7': optional: true - '@esbuild/android-arm@0.27.0': + '@esbuild/android-arm@0.27.7': optional: true - '@esbuild/android-x64@0.27.0': + '@esbuild/android-x64@0.27.7': optional: true - '@esbuild/darwin-arm64@0.27.0': + '@esbuild/darwin-arm64@0.27.7': optional: true - '@esbuild/darwin-x64@0.27.0': + '@esbuild/darwin-x64@0.27.7': optional: true - '@esbuild/freebsd-arm64@0.27.0': + '@esbuild/freebsd-arm64@0.27.7': optional: true - '@esbuild/freebsd-x64@0.27.0': + '@esbuild/freebsd-x64@0.27.7': optional: true - '@esbuild/linux-arm64@0.27.0': + '@esbuild/linux-arm64@0.27.7': optional: true - '@esbuild/linux-arm@0.27.0': + '@esbuild/linux-arm@0.27.7': optional: true - '@esbuild/linux-ia32@0.27.0': + '@esbuild/linux-ia32@0.27.7': optional: true - '@esbuild/linux-loong64@0.27.0': + '@esbuild/linux-loong64@0.27.7': optional: true - '@esbuild/linux-mips64el@0.27.0': + '@esbuild/linux-mips64el@0.27.7': optional: true - '@esbuild/linux-ppc64@0.27.0': + '@esbuild/linux-ppc64@0.27.7': optional: true - '@esbuild/linux-riscv64@0.27.0': + '@esbuild/linux-riscv64@0.27.7': optional: true - '@esbuild/linux-s390x@0.27.0': + '@esbuild/linux-s390x@0.27.7': optional: true - '@esbuild/linux-x64@0.27.0': + '@esbuild/linux-x64@0.27.7': optional: true - '@esbuild/netbsd-arm64@0.27.0': + '@esbuild/netbsd-arm64@0.27.7': optional: true - '@esbuild/netbsd-x64@0.27.0': + '@esbuild/netbsd-x64@0.27.7': optional: true - '@esbuild/openbsd-arm64@0.27.0': + '@esbuild/openbsd-arm64@0.27.7': optional: true - '@esbuild/openbsd-x64@0.27.0': + '@esbuild/openbsd-x64@0.27.7': optional: true - '@esbuild/openharmony-arm64@0.27.0': + '@esbuild/openharmony-arm64@0.27.7': optional: true - '@esbuild/sunos-x64@0.27.0': + '@esbuild/sunos-x64@0.27.7': optional: true - '@esbuild/win32-arm64@0.27.0': + '@esbuild/win32-arm64@0.27.7': optional: true - '@esbuild/win32-ia32@0.27.0': + '@esbuild/win32-ia32@0.27.7': optional: true - '@esbuild/win32-x64@0.27.0': + '@esbuild/win32-x64@0.27.7': optional: true + '@google/generative-ai@0.24.1': {} + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 strip-ansi-cjs: strip-ansi@6.0.1 wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 @@ -2528,13 +2625,13 @@ snapshots: '@jest/console@30.2.0': dependencies: '@jest/types': 30.2.0 - '@types/node': 20.19.25 + '@types/node': 20.19.41 chalk: 4.1.2 jest-message-util: 30.2.0 jest-util: 30.2.0 slash: 3.0.0 - '@jest/core@30.2.0(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3))': + '@jest/core@30.2.0(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3))': dependencies: '@jest/console': 30.2.0 '@jest/pattern': 30.0.1 @@ -2542,14 +2639,14 @@ snapshots: '@jest/test-result': 30.2.0 '@jest/transform': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 20.19.25 + '@types/node': 20.19.41 ansi-escapes: 4.3.2 chalk: 4.1.2 - ci-info: 4.3.1 + ci-info: 4.4.0 exit-x: 0.2.2 graceful-fs: 4.2.11 jest-changed-files: 30.2.0 - jest-config: 30.2.0(@types/node@20.19.25)(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)) + jest-config: 30.2.0(@types/node@20.19.41)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)) jest-haste-map: 30.2.0 jest-message-util: 30.2.0 jest-regex-util: 30.0.1 @@ -2576,7 +2673,7 @@ snapshots: dependencies: '@jest/fake-timers': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 20.19.25 + '@types/node': 20.19.41 jest-mock: 30.2.0 '@jest/expect-utils@30.2.0': @@ -2594,7 +2691,7 @@ snapshots: dependencies: '@jest/types': 30.2.0 '@sinonjs/fake-timers': 13.0.5 - '@types/node': 20.19.25 + '@types/node': 20.19.41 jest-message-util: 30.2.0 jest-mock: 30.2.0 jest-util: 30.2.0 @@ -2612,7 +2709,7 @@ snapshots: '@jest/pattern@30.0.1': dependencies: - '@types/node': 20.19.25 + '@types/node': 20.19.41 jest-regex-util: 30.0.1 '@jest/reporters@30.2.0': @@ -2623,7 +2720,7 @@ snapshots: '@jest/transform': 30.2.0 '@jest/types': 30.2.0 '@jridgewell/trace-mapping': 0.3.31 - '@types/node': 20.19.25 + '@types/node': 20.19.41 chalk: 4.1.2 collect-v8-coverage: 1.0.3 exit-x: 0.2.2 @@ -2676,7 +2773,7 @@ snapshots: '@jest/transform@30.2.0': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@jest/types': 30.2.0 '@jridgewell/trace-mapping': 0.3.31 babel-plugin-istanbul: 7.0.1 @@ -2700,7 +2797,7 @@ snapshots: '@jest/schemas': 30.0.5 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.19.25 + '@types/node': 20.19.41 '@types/yargs': 17.0.35 chalk: 4.1.2 @@ -2728,6 +2825,15 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@mistralai/mistralai@2.2.5': + dependencies: + ws: 8.20.1 + zod: 4.4.3 + zod-to-json-schema: 3.25.1(zod@4.4.3) + transitivePeerDependencies: + - bufferutil + - utf-8-validate + '@napi-rs/keyring-darwin-arm64@1.2.0': optional: true @@ -2781,8 +2887,8 @@ snapshots: '@napi-rs/wasm-runtime@0.2.12': dependencies: - '@emnapi/core': 1.7.1 - '@emnapi/runtime': 1.7.1 + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 '@tybys/wasm-util': 0.10.1 optional: true @@ -2803,6 +2909,8 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 + '@stablelib/base64@1.0.1': {} + '@tsconfig/node10@1.0.12': {} '@tsconfig/node12@1.0.11': {} @@ -2818,28 +2926,28 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.28.0 '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.28.5 + '@babel/types': 7.29.0 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 '@types/babel__traverse@7.28.0': dependencies: - '@babel/types': 7.28.5 + '@babel/types': 7.29.0 '@types/better-sqlite3@7.6.13': dependencies: - '@types/node': 20.19.25 + '@types/node': 20.19.41 '@types/istanbul-lib-coverage@2.0.6': {} @@ -2860,14 +2968,14 @@ snapshots: dependencies: undici-types: 5.26.5 - '@types/node@20.19.25': + '@types/node@20.19.41': dependencies: undici-types: 6.21.0 - '@types/pg@8.15.6': + '@types/pg@8.20.0': dependencies: - '@types/node': 20.19.25 - pg-protocol: 1.10.3 + '@types/node': 20.19.41 + pg-protocol: 1.14.0 pg-types: 2.2.0 '@types/ssh2@1.15.5': @@ -2953,7 +3061,7 @@ snapshots: node-fetch: 2.7.0 picocolors: 1.1.1 progress: 2.0.3 - semver: 7.7.3 + semver: 7.8.1 tar-fs: 2.1.4 yargs: 16.2.0 transitivePeerDependencies: @@ -2962,28 +3070,28 @@ snapshots: '@yao-pkg/pkg@5.16.1': dependencies: - '@babel/generator': 7.28.5 - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 + '@babel/generator': 7.29.1 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 '@yao-pkg/pkg-fetch': 3.5.16 into-stream: 6.0.0 minimist: 1.2.8 multistream: 4.1.0 picocolors: 1.1.1 - picomatch: 4.0.3 + picomatch: 4.0.4 prebuild-install: 7.1.3 - resolve: 1.22.11 + resolve: 1.22.12 stream-meter: 1.0.4 - tinyglobby: 0.2.15 + tinyglobby: 0.2.16 transitivePeerDependencies: - encoding - supports-color acorn-walk@8.3.4: dependencies: - acorn: 8.15.0 + acorn: 8.16.0 - acorn@8.15.0: {} + acorn@8.16.0: {} agent-base@6.0.2: dependencies: @@ -3010,7 +3118,7 @@ snapshots: anymatch@3.1.3: dependencies: normalize-path: 3.0.0 - picomatch: 2.3.1 + picomatch: 2.3.2 arg@4.1.3: {} @@ -3026,13 +3134,13 @@ snapshots: aws-ssl-profiles@1.1.2: {} - babel-jest@30.2.0(@babel/core@7.28.5): + babel-jest@30.2.0(@babel/core@7.29.0): dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@jest/transform': 30.2.0 '@types/babel__core': 7.20.5 babel-plugin-istanbul: 7.0.1 - babel-preset-jest: 30.2.0(@babel/core@7.28.5) + babel-preset-jest: 30.2.0(@babel/core@7.29.0) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 @@ -3041,7 +3149,7 @@ snapshots: babel-plugin-istanbul@7.0.1: dependencies: - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 '@istanbuljs/load-nyc-config': 1.1.0 '@istanbuljs/schema': 0.1.3 istanbul-lib-instrument: 6.0.3 @@ -3053,36 +3161,36 @@ snapshots: dependencies: '@types/babel__core': 7.20.5 - babel-preset-current-node-syntax@1.2.0(@babel/core@7.28.5): - dependencies: - '@babel/core': 7.28.5 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.5) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.28.5) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.28.5) - '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.5) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.28.5) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.28.5) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.5) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.5) - - babel-preset-jest@30.2.0(@babel/core@7.28.5): - dependencies: - '@babel/core': 7.28.5 + babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.0) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.0) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.0) + '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.0) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.0) + + babel-preset-jest@30.2.0(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 babel-plugin-jest-hoist: 30.2.0 - babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.5) + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) balanced-match@1.0.2: {} base64-js@1.5.1: {} - baseline-browser-mapping@2.9.5: {} + baseline-browser-mapping@2.10.31: {} bcrypt-pbkdf@1.0.2: dependencies: @@ -3090,7 +3198,7 @@ snapshots: bcryptjs@3.0.3: {} - better-sqlite3@11.9.0: + better-sqlite3@11.10.0: dependencies: bindings: 1.5.0 prebuild-install: 7.1.3 @@ -3107,12 +3215,12 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - brace-expansion@1.1.12: + brace-expansion@1.1.14: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.0.2: + brace-expansion@2.1.0: dependencies: balanced-match: 1.0.2 @@ -3120,13 +3228,13 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.28.1: + browserslist@4.28.2: dependencies: - baseline-browser-mapping: 2.9.5 - caniuse-lite: 1.0.30001760 - electron-to-chromium: 1.5.267 - node-releases: 2.0.27 - update-browserslist-db: 1.2.2(browserslist@4.28.1) + baseline-browser-mapping: 2.10.31 + caniuse-lite: 1.0.30001793 + electron-to-chromium: 1.5.361 + node-releases: 2.0.46 + update-browserslist-db: 1.2.3(browserslist@4.28.2) bs-logger@0.2.6: dependencies: @@ -3152,7 +3260,7 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001760: {} + caniuse-lite@1.0.30001793: {} chalk@4.1.2: dependencies: @@ -3175,7 +3283,7 @@ snapshots: chownr@1.1.4: {} - ci-info@4.3.1: {} + ci-info@4.4.0: {} cjs-module-lexer@2.1.1: {} @@ -3229,7 +3337,7 @@ snapshots: dependencies: mimic-response: 3.1.0 - dedent@1.7.0: {} + dedent@1.7.2: {} deep-extend@0.6.0: {} @@ -3241,9 +3349,9 @@ snapshots: detect-newline@3.1.0: {} - diff@4.0.2: {} + diff@4.0.4: {} - dotenv@17.2.3: {} + dotenv@17.4.2: {} dynamic-dedupe@0.3.0: dependencies: @@ -3251,7 +3359,7 @@ snapshots: eastasianwidth@0.2.0: {} - electron-to-chromium@1.5.267: {} + electron-to-chromium@1.5.361: {} emittery@0.13.1: {} @@ -3267,34 +3375,36 @@ snapshots: dependencies: is-arrayish: 0.2.1 - esbuild@0.27.0: + es-errors@1.3.0: {} + + esbuild@0.27.7: optionalDependencies: - '@esbuild/aix-ppc64': 0.27.0 - '@esbuild/android-arm': 0.27.0 - '@esbuild/android-arm64': 0.27.0 - '@esbuild/android-x64': 0.27.0 - '@esbuild/darwin-arm64': 0.27.0 - '@esbuild/darwin-x64': 0.27.0 - '@esbuild/freebsd-arm64': 0.27.0 - '@esbuild/freebsd-x64': 0.27.0 - '@esbuild/linux-arm': 0.27.0 - '@esbuild/linux-arm64': 0.27.0 - '@esbuild/linux-ia32': 0.27.0 - '@esbuild/linux-loong64': 0.27.0 - '@esbuild/linux-mips64el': 0.27.0 - '@esbuild/linux-ppc64': 0.27.0 - '@esbuild/linux-riscv64': 0.27.0 - '@esbuild/linux-s390x': 0.27.0 - '@esbuild/linux-x64': 0.27.0 - '@esbuild/netbsd-arm64': 0.27.0 - '@esbuild/netbsd-x64': 0.27.0 - '@esbuild/openbsd-arm64': 0.27.0 - '@esbuild/openbsd-x64': 0.27.0 - '@esbuild/openharmony-arm64': 0.27.0 - '@esbuild/sunos-x64': 0.27.0 - '@esbuild/win32-arm64': 0.27.0 - '@esbuild/win32-ia32': 0.27.0 - '@esbuild/win32-x64': 0.27.0 + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 escalade@3.2.0: {} @@ -3329,13 +3439,15 @@ snapshots: fast-json-stable-stringify@2.1.0: {} + fast-sha256@1.3.0: {} + fb-watchman@2.0.2: dependencies: bser: 2.1.1 - fdir@6.5.0(picomatch@4.0.3): + fdir@6.5.0(picomatch@4.0.4): optionalDependencies: - picomatch: 4.0.3 + picomatch: 4.0.4 file-uri-to-path@1.0.0: {} @@ -3389,7 +3501,7 @@ snapshots: dependencies: foreground-child: 3.3.1 jackspeak: 3.4.3 - minimatch: 9.0.5 + minimatch: 9.0.9 minipass: 7.1.2 package-json-from-dist: 1.0.1 path-scurry: 1.11.1 @@ -3399,12 +3511,14 @@ snapshots: fs.realpath: 1.0.0 inflight: 1.0.6 inherits: 2.0.4 - minimatch: 3.1.2 + minimatch: 3.1.5 once: 1.4.0 path-is-absolute: 1.0.1 graceful-fs@4.2.11: {} + groq-sdk@1.2.1: {} + handlebars@4.7.8: dependencies: minimist: 1.2.8 @@ -3431,7 +3545,7 @@ snapshots: human-signals@2.1.0: {} - iconv-lite@0.7.0: + iconv-lite@0.7.2: dependencies: safer-buffer: 2.1.2 @@ -3492,11 +3606,11 @@ snapshots: istanbul-lib-instrument@6.0.3: dependencies: - '@babel/core': 7.28.5 - '@babel/parser': 7.28.5 + '@babel/core': 7.29.0 + '@babel/parser': 7.29.0 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 - semver: 7.7.3 + semver: 7.8.1 transitivePeerDependencies: - supports-color @@ -3537,10 +3651,10 @@ snapshots: '@jest/expect': 30.2.0 '@jest/test-result': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 20.19.25 + '@types/node': 20.19.41 chalk: 4.1.2 co: 4.6.0 - dedent: 1.7.0 + dedent: 1.7.2 is-generator-fn: 2.1.0 jest-each: 30.2.0 jest-matcher-utils: 30.2.0 @@ -3557,15 +3671,15 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@30.2.0(@types/node@20.19.25)(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)): + jest-cli@30.2.0(@types/node@20.19.41)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)): dependencies: - '@jest/core': 30.2.0(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)) + '@jest/core': 30.2.0(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)) '@jest/test-result': 30.2.0 '@jest/types': 30.2.0 chalk: 4.1.2 exit-x: 0.2.2 import-local: 3.2.0 - jest-config: 30.2.0(@types/node@20.19.25)(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)) + jest-config: 30.2.0(@types/node@20.19.41)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)) jest-util: 30.2.0 jest-validate: 30.2.0 yargs: 17.7.2 @@ -3576,16 +3690,16 @@ snapshots: - supports-color - ts-node - jest-config@30.2.0(@types/node@20.19.25)(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)): + jest-config@30.2.0(@types/node@20.19.41)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)): dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@jest/get-type': 30.1.0 '@jest/pattern': 30.0.1 '@jest/test-sequencer': 30.2.0 '@jest/types': 30.2.0 - babel-jest: 30.2.0(@babel/core@7.28.5) + babel-jest: 30.2.0(@babel/core@7.29.0) chalk: 4.1.2 - ci-info: 4.3.1 + ci-info: 4.4.0 deepmerge: 4.3.1 glob: 10.5.0 graceful-fs: 4.2.11 @@ -3603,8 +3717,8 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 20.19.25 - ts-node: 10.9.2(@types/node@20.19.25)(typescript@5.9.3) + '@types/node': 20.19.41 + ts-node: 10.9.2(@types/node@20.19.41)(typescript@5.9.3) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -3633,7 +3747,7 @@ snapshots: '@jest/environment': 30.2.0 '@jest/fake-timers': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 20.19.25 + '@types/node': 20.19.41 jest-mock: 30.2.0 jest-util: 30.2.0 jest-validate: 30.2.0 @@ -3641,7 +3755,7 @@ snapshots: jest-haste-map@30.2.0: dependencies: '@jest/types': 30.2.0 - '@types/node': 20.19.25 + '@types/node': 20.19.41 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -3667,7 +3781,7 @@ snapshots: jest-message-util@30.2.0: dependencies: - '@babel/code-frame': 7.27.1 + '@babel/code-frame': 7.29.0 '@jest/types': 30.2.0 '@types/stack-utils': 2.0.3 chalk: 4.1.2 @@ -3680,7 +3794,7 @@ snapshots: jest-mock@30.2.0: dependencies: '@jest/types': 30.2.0 - '@types/node': 20.19.25 + '@types/node': 20.19.41 jest-util: 30.2.0 jest-pnp-resolver@1.2.3(jest-resolve@30.2.0): @@ -3714,7 +3828,7 @@ snapshots: '@jest/test-result': 30.2.0 '@jest/transform': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 20.19.25 + '@types/node': 20.19.41 chalk: 4.1.2 emittery: 0.13.1 exit-x: 0.2.2 @@ -3743,7 +3857,7 @@ snapshots: '@jest/test-result': 30.2.0 '@jest/transform': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 20.19.25 + '@types/node': 20.19.41 chalk: 4.1.2 cjs-module-lexer: 2.1.1 collect-v8-coverage: 1.0.3 @@ -3763,17 +3877,17 @@ snapshots: jest-snapshot@30.2.0: dependencies: - '@babel/core': 7.28.5 - '@babel/generator': 7.28.5 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5) - '@babel/types': 7.28.5 + '@babel/core': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) + '@babel/types': 7.29.0 '@jest/expect-utils': 30.2.0 '@jest/get-type': 30.1.0 '@jest/snapshot-utils': 30.2.0 '@jest/transform': 30.2.0 '@jest/types': 30.2.0 - babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.5) + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) chalk: 4.1.2 expect: 30.2.0 graceful-fs: 4.2.11 @@ -3782,7 +3896,7 @@ snapshots: jest-message-util: 30.2.0 jest-util: 30.2.0 pretty-format: 30.2.0 - semver: 7.7.3 + semver: 7.8.1 synckit: 0.11.11 transitivePeerDependencies: - supports-color @@ -3790,11 +3904,11 @@ snapshots: jest-util@30.2.0: dependencies: '@jest/types': 30.2.0 - '@types/node': 20.19.25 + '@types/node': 20.19.41 chalk: 4.1.2 - ci-info: 4.3.1 + ci-info: 4.4.0 graceful-fs: 4.2.11 - picomatch: 4.0.3 + picomatch: 4.0.4 jest-validate@30.2.0: dependencies: @@ -3809,7 +3923,7 @@ snapshots: dependencies: '@jest/test-result': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 20.19.25 + '@types/node': 20.19.41 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -3818,18 +3932,18 @@ snapshots: jest-worker@30.2.0: dependencies: - '@types/node': 20.19.25 + '@types/node': 20.19.41 '@ungap/structured-clone': 1.3.0 jest-util: 30.2.0 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@30.2.0(@types/node@20.19.25)(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)): + jest@30.2.0(@types/node@20.19.41)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)): dependencies: - '@jest/core': 30.2.0(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)) + '@jest/core': 30.2.0(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)) '@jest/types': 30.2.0 import-local: 3.2.0 - jest-cli: 30.2.0(@types/node@20.19.25)(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)) + jest-cli: 30.2.0(@types/node@20.19.41)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -3848,6 +3962,11 @@ snapshots: json-parse-even-better-errors@2.3.1: {} + json-schema-to-ts@3.1.1: + dependencies: + '@babel/runtime': 7.29.2 + ts-algebra: 2.0.0 + json5@2.2.3: {} leven@3.1.0: {} @@ -3868,13 +3987,11 @@ snapshots: dependencies: yallist: 3.1.1 - lru-cache@7.18.3: {} - - lru.min@1.1.3: {} + lru.min@1.1.4: {} make-dir@4.0.0: dependencies: - semver: 7.7.3 + semver: 7.8.1 make-error@1.3.6: {} @@ -3887,19 +4004,19 @@ snapshots: micromatch@4.0.8: dependencies: braces: 3.0.3 - picomatch: 2.3.1 + picomatch: 2.3.2 mimic-fn@2.1.0: {} mimic-response@3.1.0: {} - minimatch@3.1.2: + minimatch@3.1.5: dependencies: - brace-expansion: 1.1.12 + brace-expansion: 1.1.14 - minimatch@9.0.5: + minimatch@9.0.9: dependencies: - brace-expansion: 2.0.2 + brace-expansion: 2.1.0 minimist@1.2.8: {} @@ -3921,16 +4038,16 @@ snapshots: aws-ssl-profiles: 1.1.2 denque: 2.1.0 generate-function: 2.3.1 - iconv-lite: 0.7.0 + iconv-lite: 0.7.2 long: 5.3.2 - lru.min: 1.1.3 - named-placeholders: 1.1.3 + lru.min: 1.1.4 + named-placeholders: 1.1.6 seq-queue: 0.0.5 sqlstring: 2.3.3 - named-placeholders@1.1.3: + named-placeholders@1.1.6: dependencies: - lru-cache: 7.18.3 + lru.min: 1.1.4 nan@2.25.0: optional: true @@ -3943,9 +4060,9 @@ snapshots: neo-async@2.6.2: {} - node-abi@3.85.0: + node-abi@3.87.0: dependencies: - semver: 7.7.3 + semver: 7.8.1 node-fetch@2.7.0: dependencies: @@ -3953,7 +4070,7 @@ snapshots: node-int64@0.4.0: {} - node-releases@2.0.27: {} + node-releases@2.0.46: {} normalize-path@3.0.0: {} @@ -3961,6 +4078,10 @@ snapshots: dependencies: path-key: 3.1.1 + ollama@0.6.3: + dependencies: + whatwg-fetch: 3.6.20 + on-exit-leak-free@2.1.2: {} once@1.4.0: @@ -3971,6 +4092,11 @@ snapshots: dependencies: mimic-fn: 2.1.0 + openai@6.41.0(ws@8.20.1)(zod@4.4.3): + optionalDependencies: + ws: 8.20.1 + zod: 4.4.3 + p-is-promise@3.0.0: {} p-limit@2.3.0: @@ -3991,7 +4117,7 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.27.1 + '@babel/code-frame': 7.29.0 error-ex: 1.3.4 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -4009,45 +4135,45 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 - pg-cloudflare@1.2.7: + pg-cloudflare@1.4.0: optional: true - pg-connection-string@2.9.1: {} + pg-connection-string@2.13.0: {} - pg-cursor@2.15.3(pg@8.16.3): + pg-cursor@2.15.3(pg@8.21.0): dependencies: - pg: 8.16.3 + pg: 8.21.0 pg-int8@1.0.1: {} - pg-pool@3.10.1(pg@8.16.3): + pg-pool@3.14.0(pg@8.21.0): dependencies: - pg: 8.16.3 + pg: 8.21.0 - pg-protocol@1.10.3: {} + pg-protocol@1.14.0: {} - pg-query-stream@4.10.3(pg@8.16.3): + pg-query-stream@4.10.3(pg@8.21.0): dependencies: - pg: 8.16.3 - pg-cursor: 2.15.3(pg@8.16.3) + pg: 8.21.0 + pg-cursor: 2.15.3(pg@8.21.0) pg-types@2.2.0: dependencies: pg-int8: 1.0.1 postgres-array: 2.0.0 - postgres-bytea: 1.0.0 + postgres-bytea: 1.0.1 postgres-date: 1.0.7 postgres-interval: 1.2.0 - pg@8.16.3: + pg@8.21.0: dependencies: - pg-connection-string: 2.9.1 - pg-pool: 3.10.1(pg@8.16.3) - pg-protocol: 1.10.3 + pg-connection-string: 2.13.0 + pg-pool: 3.14.0(pg@8.21.0) + pg-protocol: 1.14.0 pg-types: 2.2.0 pgpass: 1.0.5 optionalDependencies: - pg-cloudflare: 1.2.7 + pg-cloudflare: 1.4.0 pgpass@1.0.5: dependencies: @@ -4055,9 +4181,9 @@ snapshots: picocolors@1.1.1: {} - picomatch@2.3.1: {} + picomatch@2.3.2: {} - picomatch@4.0.3: {} + picomatch@4.0.4: {} pino-abstract-transport@2.0.0: dependencies: @@ -4087,7 +4213,7 @@ snapshots: postgres-array@2.0.0: {} - postgres-bytea@1.0.0: {} + postgres-bytea@1.0.1: {} postgres-date@1.0.7: {} @@ -4103,8 +4229,8 @@ snapshots: minimist: 1.2.8 mkdirp-classic: 0.5.3 napi-build-utils: 2.0.0 - node-abi: 3.85.0 - pump: 3.0.3 + node-abi: 3.87.0 + pump: 3.0.4 rc: 1.2.8 simple-get: 4.0.1 tar-fs: 2.1.4 @@ -4122,7 +4248,7 @@ snapshots: progress@2.0.3: {} - pump@3.0.3: + pump@3.0.4: dependencies: end-of-stream: 1.4.5 once: 1.4.0 @@ -4158,7 +4284,7 @@ snapshots: readdirp@3.6.0: dependencies: - picomatch: 2.3.1 + picomatch: 2.3.2 real-require@0.2.0: {} @@ -4170,8 +4296,9 @@ snapshots: resolve-from@5.0.0: {} - resolve@1.22.11: + resolve@1.22.12: dependencies: + es-errors: 1.3.0 is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -4190,7 +4317,7 @@ snapshots: semver@6.3.1: {} - semver@7.7.3: {} + semver@7.8.1: {} seq-queue@0.0.5: {} @@ -4248,6 +4375,11 @@ snapshots: dependencies: escape-string-regexp: 2.0.0 + standardwebhooks@1.0.0: + dependencies: + '@stablelib/base64': 1.0.1 + fast-sha256: 1.3.0 + stream-meter@1.0.4: dependencies: readable-stream: 2.3.8 @@ -4267,7 +4399,7 @@ snapshots: dependencies: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 string_decoder@1.1.1: dependencies: @@ -4281,7 +4413,7 @@ snapshots: dependencies: ansi-regex: 5.0.1 - strip-ansi@7.1.2: + strip-ansi@7.2.0: dependencies: ansi-regex: 6.2.2 @@ -4313,7 +4445,7 @@ snapshots: dependencies: chownr: 1.1.4 mkdirp-classic: 0.5.3 - pump: 3.0.3 + pump: 3.0.4 tar-stream: 2.2.0 tar-stream@2.2.0: @@ -4328,16 +4460,16 @@ snapshots: dependencies: '@istanbuljs/schema': 0.1.3 glob: 7.2.3 - minimatch: 3.1.2 + minimatch: 3.1.5 thread-stream@3.1.0: dependencies: real-require: 0.2.0 - tinyglobby@0.2.15: + tinyglobby@0.2.16: dependencies: - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 tmpl@1.0.5: {} @@ -4349,38 +4481,40 @@ snapshots: tree-kill@1.2.2: {} - ts-jest@29.4.6(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(esbuild@0.27.0)(jest-util@30.2.0)(jest@30.2.0(@types/node@20.19.25)(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)))(typescript@5.9.3): + ts-algebra@2.0.0: {} + + ts-jest@29.4.6(@babel/core@7.29.0)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.29.0))(esbuild@0.27.7)(jest-util@30.2.0)(jest@30.2.0(@types/node@20.19.41)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)))(typescript@5.9.3): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 handlebars: 4.7.8 - jest: 30.2.0(@types/node@20.19.25)(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)) + jest: 30.2.0(@types/node@20.19.41)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3)) json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 - semver: 7.7.3 + semver: 7.8.1 type-fest: 4.41.0 typescript: 5.9.3 yargs-parser: 21.1.1 optionalDependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@jest/transform': 30.2.0 '@jest/types': 30.2.0 - babel-jest: 30.2.0(@babel/core@7.28.5) - esbuild: 0.27.0 + babel-jest: 30.2.0(@babel/core@7.29.0) + esbuild: 0.27.7 jest-util: 30.2.0 - ts-node-dev@2.0.0(@types/node@20.19.25)(typescript@5.9.3): + ts-node-dev@2.0.0(@types/node@20.19.41)(typescript@5.9.3): dependencies: chokidar: 3.6.0 dynamic-dedupe: 0.3.0 minimist: 1.2.8 mkdirp: 1.0.4 - resolve: 1.22.11 + resolve: 1.22.12 rimraf: 2.7.1 source-map-support: 0.5.21 tree-kill: 1.2.2 - ts-node: 10.9.2(@types/node@20.19.25)(typescript@5.9.3) + ts-node: 10.9.2(@types/node@20.19.41)(typescript@5.9.3) tsconfig: 7.0.0 typescript: 5.9.3 transitivePeerDependencies: @@ -4388,19 +4522,19 @@ snapshots: - '@swc/wasm' - '@types/node' - ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3): + ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.12 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.19.25 - acorn: 8.15.0 + '@types/node': 20.19.41 + acorn: 8.16.0 acorn-walk: 8.3.4 arg: 4.1.3 create-require: 1.1.1 - diff: 4.0.2 + diff: 4.0.4 make-error: 1.3.6 typescript: 5.9.3 v8-compile-cache-lib: 3.0.1 @@ -4461,9 +4595,9 @@ snapshots: '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 - update-browserslist-db@1.2.2(browserslist@4.28.1): + update-browserslist-db@1.2.3(browserslist@4.28.2): dependencies: - browserslist: 4.28.1 + browserslist: 4.28.2 escalade: 3.2.0 picocolors: 1.1.1 @@ -4485,6 +4619,8 @@ snapshots: webidl-conversions@3.0.1: {} + whatwg-fetch@3.6.20: {} + whatwg-url@5.0.0: dependencies: tr46: 0.0.3 @@ -4506,7 +4642,7 @@ snapshots: dependencies: ansi-styles: 6.2.3 string-width: 5.1.2 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 wrappy@1.0.2: {} @@ -4515,7 +4651,7 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 4.1.0 - ws@8.19.0: {} + ws@8.20.1: {} xtend@4.0.2: {} @@ -4550,3 +4686,9 @@ snapshots: yn@3.1.1: {} yocto-queue@0.1.0: {} + + zod-to-json-schema@3.25.1(zod@4.4.3): + dependencies: + zod: 4.4.3 + + zod@4.4.3: {} diff --git a/bridge/src/ai/README.md b/bridge/src/ai/README.md new file mode 100644 index 0000000..0746cdf --- /dev/null +++ b/bridge/src/ai/README.md @@ -0,0 +1,23 @@ +# AI Integration (Bridge) + +This folder contains AI provider implementations and prompt templates used by the bridge. + +Layout +- `providers/` — concrete provider adapters (OpenAI, Anthropic, Gemini, Mistral, Ollama, Groq). Implement the `AIProvider` interface in `providers/types.ts`. +- `prompts/` — prompt builders and parsers for schema analysis, query explanation and chart recommendation. + +What moved +- The public RPC entry points and handler logic live under `src/handlers/aiHandlers.ts`. +- The service factory implementation was moved to `src/services/ai.impl.ts` and a shim `src/services/aiService.ts` exposes `AIService` and `aiService` for consistency with other bridge services. +- Shared AI types were moved to `src/types/ai.ts` and are re-exported from the central `src/types/index.ts`. + +How to add a provider +1. Create a new file under `providers/` implementing the `AIProvider` interface. +2. Add the provider into `src/services/ai.impl.ts` in the `createProvider` switch. +3. Add any provider-specific configuration notes to this README. + +Testing +- No tests currently exist for AI. To exercise the integration locally, use the frontend UI features that call `ai.*` RPC methods or create a test that uses the RPC registrar. + +Notes +- Keep providers small and avoid direct network retries; the bridge surface should translate provider errors into `AIError` using `providers/types.ts`. diff --git a/bridge/src/ai/prompts/chart-recommendation.ts b/bridge/src/ai/prompts/chart-recommendation.ts index da19a0e..bb5316e 100644 --- a/bridge/src/ai/prompts/chart-recommendation.ts +++ b/bridge/src/ai/prompts/chart-recommendation.ts @@ -1,4 +1,4 @@ -import { ChartRecommendationInput, ChartRecommendation } from "../ai.types"; +import { ChartRecommendationInput, ChartRecommendation } from "../../types/"; import { SYSTEM_CONTEXT } from "./shared"; import { AIError } from "../providers/types"; diff --git a/bridge/src/ai/prompts/query-explanation.ts b/bridge/src/ai/prompts/query-explanation.ts index 09106d7..1897456 100644 --- a/bridge/src/ai/prompts/query-explanation.ts +++ b/bridge/src/ai/prompts/query-explanation.ts @@ -1,4 +1,4 @@ -import { QueryExplanationInput } from "../ai.types"; +import { QueryExplanationInput } from "../../types/"; import { SYSTEM_CONTEXT, MARKDOWN_INSTRUCTION } from "./shared"; export function buildQueryExplanationPrompt(input: QueryExplanationInput): { diff --git a/bridge/src/ai/prompts/schema-analysis.ts b/bridge/src/ai/prompts/schema-analysis.ts index 9042a24..a530893 100644 --- a/bridge/src/ai/prompts/schema-analysis.ts +++ b/bridge/src/ai/prompts/schema-analysis.ts @@ -1,4 +1,4 @@ -import { SchemaAnalysisInput } from "../ai.types"; +import { SchemaAnalysisInput } from "../../types/"; import { SYSTEM_CONTEXT, MARKDOWN_INSTRUCTION } from "./shared"; export function buildSchemaAnalysisPrompt(input: SchemaAnalysisInput): { diff --git a/bridge/src/ai/providers/anthropic.provider.ts b/bridge/src/ai/providers/anthropic.provider.ts index 6591c8e..a366468 100644 --- a/bridge/src/ai/providers/anthropic.provider.ts +++ b/bridge/src/ai/providers/anthropic.provider.ts @@ -5,7 +5,7 @@ import { QueryExplanationInput, ChartRecommendationInput, ChartRecommendation, -} from "../ai.types"; +} from "../../types"; import { buildSchemaAnalysisPrompt } from "../prompts/schema-analysis"; import { buildQueryExplanationPrompt } from "../prompts/query-explanation"; import { buildChartRecommendationPrompt, parseChartRecommendation } from "../prompts/chart-recommendation"; diff --git a/bridge/src/ai/providers/gemini.provider.ts b/bridge/src/ai/providers/gemini.provider.ts index 3867366..a5f37c9 100644 --- a/bridge/src/ai/providers/gemini.provider.ts +++ b/bridge/src/ai/providers/gemini.provider.ts @@ -5,7 +5,7 @@ import { QueryExplanationInput, ChartRecommendationInput, ChartRecommendation, -} from "../ai.types"; +} from "../../types/"; import { buildSchemaAnalysisPrompt } from "../prompts/schema-analysis"; import { buildQueryExplanationPrompt } from "../prompts/query-explanation"; import { buildChartRecommendationPrompt, parseChartRecommendation } from "../prompts/chart-recommendation"; diff --git a/bridge/src/ai/providers/groq.provider.ts b/bridge/src/ai/providers/groq.provider.ts index d089b02..f433e40 100644 --- a/bridge/src/ai/providers/groq.provider.ts +++ b/bridge/src/ai/providers/groq.provider.ts @@ -5,7 +5,7 @@ import { QueryExplanationInput, ChartRecommendationInput, ChartRecommendation, -} from "../ai.types"; +} from "../../types/"; import { buildSchemaAnalysisPrompt } from "../prompts/schema-analysis"; import { buildQueryExplanationPrompt } from "../prompts/query-explanation"; import { buildChartRecommendationPrompt, parseChartRecommendation } from "../prompts/chart-recommendation"; diff --git a/bridge/src/ai/providers/mistral.provider.ts b/bridge/src/ai/providers/mistral.provider.ts index 5a7ed7b..2c6d1c7 100644 --- a/bridge/src/ai/providers/mistral.provider.ts +++ b/bridge/src/ai/providers/mistral.provider.ts @@ -5,7 +5,7 @@ import { QueryExplanationInput, ChartRecommendationInput, ChartRecommendation, -} from "../ai.types"; +} from "../../types/"; import { buildSchemaAnalysisPrompt } from "../prompts/schema-analysis"; import { buildQueryExplanationPrompt } from "../prompts/query-explanation"; import { buildChartRecommendationPrompt, parseChartRecommendation } from "../prompts/chart-recommendation"; diff --git a/bridge/src/ai/providers/ollama.provider.ts b/bridge/src/ai/providers/ollama.provider.ts index f6b6b22..b109927 100644 --- a/bridge/src/ai/providers/ollama.provider.ts +++ b/bridge/src/ai/providers/ollama.provider.ts @@ -5,7 +5,7 @@ import { QueryExplanationInput, ChartRecommendationInput, ChartRecommendation, -} from "../ai.types"; +} from "../../types/"; import { buildSchemaAnalysisPrompt } from "../prompts/schema-analysis"; import { buildQueryExplanationPrompt } from "../prompts/query-explanation"; import { buildChartRecommendationPrompt, parseChartRecommendation } from "../prompts/chart-recommendation"; diff --git a/bridge/src/ai/providers/openai.provider.ts b/bridge/src/ai/providers/openai.provider.ts index 33fe6b0..c09da72 100644 --- a/bridge/src/ai/providers/openai.provider.ts +++ b/bridge/src/ai/providers/openai.provider.ts @@ -5,7 +5,7 @@ import { QueryExplanationInput, ChartRecommendationInput, ChartRecommendation, -} from "../ai.types"; +} from "../../types"; import { buildSchemaAnalysisPrompt } from "../prompts/schema-analysis"; import { buildQueryExplanationPrompt } from "../prompts/query-explanation"; import { buildChartRecommendationPrompt, parseChartRecommendation } from "../prompts/chart-recommendation"; From 068d0f372ee63dcd96e32c441003265457513246 Mon Sep 17 00:00:00 2001 From: Yash Date: Wed, 3 Jun 2026 01:06:00 +0530 Subject: [PATCH 07/46] feat(ai): add AIHandlers and register AI-related RPC methods --- bridge/src/jsonRpcHandler.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/bridge/src/jsonRpcHandler.ts b/bridge/src/jsonRpcHandler.ts index 8b90f6c..ccd76dc 100644 --- a/bridge/src/jsonRpcHandler.ts +++ b/bridge/src/jsonRpcHandler.ts @@ -69,6 +69,7 @@ export function registerDbHandlers( const gitHandlers = new GitHandlers(rpc, logger); const gitAdvancedHandlers = new GitAdvancedHandlers(rpc, logger); const monitoringHandlers = new MonitoringHandlers(rpc, logger, dbService, monitoringService); + const aiHandlers = new (require("./handlers/aiHandlers")).AIHandlers(rpc, logger); // ========================================== // SESSION MANAGEMENT HANDLERS @@ -322,6 +323,22 @@ export function registerDbHandlers( } }); + // ========================================== + // AI HANDLERS + // ========================================== + rpcRegister(rpc, "ai.testConnection", (p, id) => + aiHandlers.handleTestConnection(p, id) + ); + rpcRegister(rpc, "ai.analyzeSchema", (p, id) => + aiHandlers.handleAnalyzeSchema(p, id) + ); + rpcRegister(rpc, "ai.explainQuery", (p, id) => + aiHandlers.handleExplainQuery(p, id) + ); + rpcRegister(rpc, "ai.recommendChart", (p, id) => + aiHandlers.handleRecommendChart(p, id) + ); + logger?.info("All RPC handlers registered successfully"); } From 2f1d381488a527e75e1641a5f240e08bb51df462 Mon Sep 17 00:00:00 2001 From: Yash Date: Wed, 3 Jun 2026 14:02:27 +0530 Subject: [PATCH 08/46] feat: implement AI-powered chart suggestions and interactive query result dialogs --- src/features/ai/components/AIResultDialog.tsx | 226 ++++++++++++++++++ src/features/ai/hooks/useAISettings.ts | 22 ++ .../components/AnalyzeSchemaButton.tsx | 106 ++++++++ .../components/ExplainQueryButton.tsx | 71 ++++++ 4 files changed, 425 insertions(+) create mode 100644 src/features/ai/components/AIResultDialog.tsx create mode 100644 src/features/ai/hooks/useAISettings.ts create mode 100644 src/features/schema-explorer/components/AnalyzeSchemaButton.tsx create mode 100644 src/features/workspace/components/ExplainQueryButton.tsx diff --git a/src/features/ai/components/AIResultDialog.tsx b/src/features/ai/components/AIResultDialog.tsx new file mode 100644 index 0000000..032efa7 --- /dev/null +++ b/src/features/ai/components/AIResultDialog.tsx @@ -0,0 +1,226 @@ +import { useState } from "react"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, +} from "@/components/ui/dialog"; +import { Loader2, Bot, AlertCircle, Sparkles, Copy, Check } from "lucide-react"; +import { cn } from "@/lib/utils"; + +interface AIResultDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + title: string; + description?: string; + markdown?: string; + loading?: boolean; + error?: string | null; +} + +/** + * Reusable dialog that renders AI-generated markdown output. + * Used by Schema Analysis and Query Explanation features. + */ +export function AIResultDialog({ + open, + onOpenChange, + title, + description, + markdown, + loading, + error, +}: AIResultDialogProps) { + return ( + + + + +
+ +
+ {title} +
+ {description && ( + {description} + )} +
+ +
+ {loading ? ( +
+
+ +
+

Analyzing with AI…

+
+ ) : error ? ( +
+
+ +
+

Analysis failed

+

+ {error} +

+

+ Make sure your AI provider is configured in{" "} + Settings → AI Settings. +

+
+ ) : markdown ? ( + + ) : ( +
+
+ +
+

No content yet.

+
+ )} +
+
+
+ ); +} + +/** + * Lightweight markdown renderer — no heavy library needed. + * Handles headings, bold, code blocks, bullet lists, and paragraphs. + */ +function MarkdownRenderer({ content }: { content: string }) { + const lines = content.split("\n"); + const elements: React.ReactNode[] = []; + let i = 0; + + while (i < lines.length) { + const line = lines[i]; + + // Fenced code block + if (line.trim().startsWith("```")) { + const lang = line.trim().slice(3).trim(); + const codeLines: string[] = []; + i++; + while (i < lines.length && !lines[i].trim().startsWith("```")) { + codeLines.push(lines[i]); + i++; + } + elements.push( + + ); + i++; + continue; + } + + // H1–H3 headings + const h3 = line.match(/^###\s+(.+)$/); + const h2 = line.match(/^##\s+(.+)$/); + const h1 = line.match(/^#\s+(.+)$/); + if (h1) { + elements.push(

{renderInline(h1[1])}

); + i++; continue; + } + if (h2) { + elements.push(

{renderInline(h2[1])}

); + i++; continue; + } + if (h3) { + elements.push(
{renderInline(h3[1])}
); + i++; continue; + } + + // Bullet list items + const bullet = line.match(/^[-*]\s+(.+)$/); + if (bullet) { + const items: string[] = [bullet[1]]; + i++; + while (i < lines.length && lines[i].match(/^[-*]\s+(.+)$/)) { + items.push(lines[i].match(/^[-*]\s+(.+)$/)![1]); + i++; + } + elements.push( +
    + {items.map((item, idx) => ( +
  • + {renderInline(item)} +
  • + ))} +
+ ); + continue; + } + + // Blank line + if (!line.trim()) { + i++; + continue; + } + + // Paragraph + elements.push( +

+ {renderInline(line)} +

+ ); + i++; + } + + return
{elements}
; +} + +/** Render bold (**text**) and inline code (`code`) within a text node. */ +function renderInline(text: string): React.ReactNode { + const parts = text.split(/(\*\*[^*]+\*\*|`[^`]+`)/g); + return parts.map((part, i) => { + if (part.startsWith("**") && part.endsWith("**")) { + return {part.slice(2, -2)}; + } + if (part.startsWith("`") && part.endsWith("`")) { + return {part.slice(1, -1)}; + } + return part; + }); +} + +function CodeBlock({ code, lang }: { code: string; lang: string }) { + const [copied, setCopied] = useState(false); + + const handleCopy = async () => { + try { + await navigator.clipboard.writeText(code); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch (err) { + console.error("Failed to copy code: ", err); + } + }; + + return ( +
+
+        {code}
+      
+ +
+ ); +} diff --git a/src/features/ai/hooks/useAISettings.ts b/src/features/ai/hooks/useAISettings.ts new file mode 100644 index 0000000..92045d3 --- /dev/null +++ b/src/features/ai/hooks/useAISettings.ts @@ -0,0 +1,22 @@ +import { loadAISettings, type AISettings } from "@/services/bridge/ai"; +import { useEffect, useState } from "react"; + +/** + * Reads AISettings from localStorage and stays in sync when + * the user updates them in the Settings page during the same session. + */ +export function useAISettings(): AISettings { + const [settings, setSettings] = useState(loadAISettings); + + useEffect(() => { + const onStorage = (e: StorageEvent) => { + if (e.key === "relwave:ai-settings") { + setSettings(loadAISettings()); + } + }; + window.addEventListener("storage", onStorage); + return () => window.removeEventListener("storage", onStorage); + }, []); + + return settings; +} diff --git a/src/features/schema-explorer/components/AnalyzeSchemaButton.tsx b/src/features/schema-explorer/components/AnalyzeSchemaButton.tsx new file mode 100644 index 0000000..7e1ab76 --- /dev/null +++ b/src/features/schema-explorer/components/AnalyzeSchemaButton.tsx @@ -0,0 +1,106 @@ +import { useState } from "react"; +import { Bot, Loader2 } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { AIResultDialog } from "@/features/ai/components/AIResultDialog"; +import { useAISettings } from "@/features/ai/hooks/useAISettings"; +import { aiService, type SchemaAnalysisInput } from "@/services/bridge/ai"; +import { DatabaseSchemaDetails } from "@/features/database/types"; + +interface AnalyzeSchemaButtonProps { + schemaData: DatabaseSchemaDetails & { + schemas: Array<{ + name?: string; + tables: Array<{ + name: string; + columns: Array<{ + name: string; + type: string; + nullable?: boolean; + isPrimaryKey?: boolean; + isForeignKey?: boolean; + }>; + }>; + }>; + }; + databaseType?: string; +} + +export function AnalyzeSchemaButton({ schemaData, databaseType }: AnalyzeSchemaButtonProps) { + const settings = useAISettings(); + const [open, setOpen] = useState(false); + const [loading, setLoading] = useState(false); + const [markdown, setMarkdown] = useState(); + const [error, setError] = useState(null); + + const tableCount = schemaData.schemas?.flatMap((s) => s.tables).length ?? 0; + + const handleAnalyze = async () => { + setOpen(true); + if (markdown) return; // Already analyzed — reuse result + setLoading(true); + setError(null); + + try { + const input: SchemaAnalysisInput = { + databaseType, + tables: schemaData.schemas.flatMap((schema) => + schema.tables.map((table) => ({ + name: table.name, + schema: schema.name, + columns: table.columns.map((col) => ({ + name: col.name, + type: col.type, + nullable: col.nullable, + isPrimaryKey: col.isPrimaryKey, + isForeignKey: col.isForeignKey, + })), + })) + ), + }; + const result = await aiService.analyzeSchema(settings, input); + setMarkdown(result); + } catch (err: any) { + setError(err?.message ?? String(err)); + } finally { + setLoading(false); + } + }; + + // Reset cached result when dialog closes so next open re-fetches + const handleOpenChange = (next: boolean) => { + setOpen(next); + if (!next) { + setMarkdown(undefined); + setError(null); + } + }; + + return ( + <> + + + + + ); +} diff --git a/src/features/workspace/components/ExplainQueryButton.tsx b/src/features/workspace/components/ExplainQueryButton.tsx new file mode 100644 index 0000000..b2e863e --- /dev/null +++ b/src/features/workspace/components/ExplainQueryButton.tsx @@ -0,0 +1,71 @@ +import { useState } from "react"; +import { Bot, Loader2 } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { AIResultDialog } from "@/features/ai/components/AIResultDialog"; +import { useAISettings } from "@/features/ai/hooks/useAISettings"; +import { aiService } from "@/services/bridge/ai"; + +interface ExplainQueryButtonProps { + sql: string; + disabled?: boolean; +} + +export function ExplainQueryButton({ sql, disabled }: ExplainQueryButtonProps) { + const settings = useAISettings(); + const [open, setOpen] = useState(false); + const [loading, setLoading] = useState(false); + const [markdown, setMarkdown] = useState(); + const [error, setError] = useState(null); + + const handleExplain = async () => { + setOpen(true); + setMarkdown(undefined); + setError(null); + setLoading(true); + + try { + const result = await aiService.explainQuery(settings, { + sql: sql.trim(), + }); + setMarkdown(result); + } catch (err: any) { + setError(err?.message ?? String(err)); + } finally { + setLoading(false); + } + }; + + const shortSQL = + sql.trim().length > 60 + ? sql.trim().slice(0, 60).replace(/\s+/g, " ") + "…" + : sql.trim().replace(/\s+/g, " "); + + return ( + <> + + + + + ); +} From 079dac2dfe652589eb2c81097a6cba51da15cb7a Mon Sep 17 00:00:00 2001 From: Yash Date: Wed, 3 Jun 2026 17:26:48 +0530 Subject: [PATCH 09/46] feat(ai): add AI suggestion functionality to ChartVisualization and integrate AI analysis in SchemaExplorerHeader --- .../chart/components/ChartVisualization.tsx | 47 +++++++++++++++++++ .../components/SchemaExplorerHeader.tsx | 15 ++++++ .../components/SQLWorkspacePanel.tsx | 1 + .../workspace/components/WorkspaceHeader.tsx | 9 ++++ 4 files changed, 72 insertions(+) diff --git a/src/features/chart/components/ChartVisualization.tsx b/src/features/chart/components/ChartVisualization.tsx index 8f1245d..f470f4f 100644 --- a/src/features/chart/components/ChartVisualization.tsx +++ b/src/features/chart/components/ChartVisualization.tsx @@ -6,6 +6,7 @@ import { ChevronDown, Sparkles, AlertCircle, + Bot, } from "lucide-react"; import { DropdownMenu, @@ -19,6 +20,10 @@ import ChartRenderer from "./ChartRenderer"; import { useChartVisualization } from "../hooks/useChartVisualization"; import { SelectedTable } from "@/features/database/types"; import { cn } from "@/lib/utils"; +import { useState } from "react"; +import { useAISettings } from "@/features/ai/hooks/useAISettings"; +import { aiService } from "@/services/bridge/ai"; +import { toast } from "sonner"; interface ChartVisualizationProps { selectedTable: SelectedTable; @@ -29,6 +34,9 @@ export const ChartVisualization = ({ selectedTable, dbId, }: ChartVisualizationProps) => { + const aiSettings = useAISettings(); + const [aiLoading, setAiLoading] = useState(false); + const { handleExport, chartType, @@ -45,6 +53,29 @@ export const ChartVisualization = ({ rowData, } = useChartVisualization(selectedTable, dbId); + const handleAISuggest = async () => { + if (!columnData.length) return; + setAiLoading(true); + try { + const rec = await aiService.recommendChart(aiSettings, { + tableName: selectedTable?.name ?? "table", + columns: columnData.map((c) => ({ + name: c.name, + type: c.type, + isPrimaryKey: c.isPrimaryKey, + })), + }); + setChartType(rec.chartType); + setXAxis(rec.xAxis); + setYAxis(rec.yAxis); + toast.success("AI suggestion applied", { description: rec.reasoning }); + } catch (err: any) { + toast.error("AI suggestion failed", { description: err?.message ?? String(err) }); + } finally { + setAiLoading(false); + } + }; + const hasData = rowData.length > 0; const isReady = !isExecuting && !errorMessage && hasData; @@ -72,6 +103,22 @@ export const ChartVisualization = ({ )} + {/* AI Suggest button */} + + + + + + +
+                {entry.prompt}
+              
+
+ + + {/* Response */} +
+
+ Response + +
+
+ +
+
+ + {/* Delete action */} + {onDelete && ( +
+ + + + + + + Delete this analysis? + + This will permanently remove this AI analysis from your local history. + This action cannot be undone. + + + + Cancel + { + onDelete(entry.id); + onOpenChange(false); + }} + > + Delete + + + + +
+ )} + + + + ); +} + +function MetaItem({ + icon, + label, + value, +}: { + icon: React.ReactNode; + label: string; + value: string; +}) { + return ( +
+ {icon} +
+

{label}

+

{value}

+
+
+ ); +} diff --git a/src/features/ai/components/AIHistoryPanel.tsx b/src/features/ai/components/AIHistoryPanel.tsx new file mode 100644 index 0000000..c435ae2 --- /dev/null +++ b/src/features/ai/components/AIHistoryPanel.tsx @@ -0,0 +1,359 @@ +import { useState, useEffect, useCallback } from "react"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@/components/ui/alert-dialog"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { History, Trash2, ChevronLeft, ChevronRight, Loader2 } from "lucide-react"; +import { aiService, type AIHistoryListItem, type AIHistoryEntry } from "@/services/bridge/ai"; +import { AIHistoryDetailDialog } from "./AIHistoryDetailDialog"; +import { cn } from "@/lib/utils"; + +const PAGE_SIZE = 10; + +const FEATURE_OPTIONS = [ + { value: "all", label: "All Features" }, + { value: "schema-analysis", label: "Schema Analysis" }, + { value: "query-explanation", label: "Query Explanation" }, + { value: "chart-recommendation", label: "Chart Recommendation" }, +]; + +const PROVIDER_OPTIONS = [ + { value: "all", label: "All Providers" }, + { value: "anthropic", label: "Anthropic" }, + { value: "openai", label: "OpenAI" }, + { value: "gemini", label: "Gemini" }, + { value: "groq", label: "Groq" }, + { value: "mistral", label: "Mistral" }, + { value: "ollama", label: "Ollama" }, +]; + +const FEATURE_LABELS: Record = { + "schema-analysis": "Schema Analysis", + "query-explanation": "Query Explanation", + "chart-recommendation": "Chart Recommendation", +}; + +const FEATURE_COLORS: Record = { + "schema-analysis": "border-violet-500/30 text-violet-600 bg-violet-500/8", + "query-explanation": "border-blue-500/30 text-blue-600 bg-blue-500/8", + "chart-recommendation": "border-amber-500/30 text-amber-600 bg-amber-500/8", +}; + +const PROVIDER_LABELS: Record = { + anthropic: "Anthropic", + openai: "OpenAI", + gemini: "Gemini", + groq: "Groq", + mistral: "Mistral", + ollama: "Ollama", +}; + +function timeAgo(isoDate: string): string { + const diff = Date.now() - new Date(isoDate).getTime(); + const seconds = Math.floor(diff / 1000); + if (seconds < 60) return "just now"; + const minutes = Math.floor(seconds / 60); + if (minutes < 60) return `${minutes}m ago`; + const hours = Math.floor(minutes / 60); + if (hours < 24) return `${hours}h ago`; + const days = Math.floor(hours / 24); + if (days < 30) return `${days}d ago`; + const months = Math.floor(days / 30); + return `${months}mo ago`; +} + +export default function AIHistoryPanel() { + const [items, setItems] = useState([]); + const [total, setTotal] = useState(0); + const [page, setPage] = useState(0); + const [loading, setLoading] = useState(false); + const [featureFilter, setFeatureFilter] = useState("all"); + const [providerFilter, setProviderFilter] = useState("all"); + + // Detail dialog + const [detailOpen, setDetailOpen] = useState(false); + const [detailEntry, setDetailEntry] = useState(null); + const [detailLoading, setDetailLoading] = useState(false); + + const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE)); + + const fetchHistory = useCallback(async () => { + setLoading(true); + try { + const result = await aiService.getHistory({ + feature: featureFilter !== "all" ? featureFilter : undefined, + provider: providerFilter !== "all" ? providerFilter : undefined, + limit: PAGE_SIZE, + offset: page * PAGE_SIZE, + }); + setItems(result.items); + setTotal(result.total); + } catch (err) { + console.error("Failed to load AI history:", err); + } finally { + setLoading(false); + } + }, [featureFilter, providerFilter, page]); + + useEffect(() => { + fetchHistory(); + }, [fetchHistory]); + + // Reset page when filters change + useEffect(() => { + setPage(0); + }, [featureFilter, providerFilter]); + + const handleRowClick = async (item: AIHistoryListItem) => { + setDetailLoading(true); + setDetailOpen(true); + try { + const entry = await aiService.getHistoryById(item.id); + setDetailEntry(entry); + } catch (err) { + console.error("Failed to load history detail:", err); + } finally { + setDetailLoading(false); + } + }; + + const handleDelete = async (id: number) => { + try { + await aiService.deleteHistory(id); + fetchHistory(); + } catch (err) { + console.error("Failed to delete history entry:", err); + } + }; + + const handleClearAll = async () => { + try { + await aiService.clearHistory(); + setItems([]); + setTotal(0); + setPage(0); + } catch (err) { + console.error("Failed to clear history:", err); + } + }; + + return ( + <> + + +
+ +
+ +
+ AI History + {total > 0 && ( + + {total} + + )} +
+ + {total > 0 && ( + + + + + + + Clear all AI history? + + This will permanently delete all {total} AI analysis entries from your local history. + Cached results will no longer be available. This action cannot be undone. + + + + Cancel + + Clear All History + + + + + )} +
+ + {/* Filters */} +
+ + +
+
+ + + {loading ? ( +
+ +
+ ) : items.length === 0 ? ( +
+ +

No AI analysis history yet.

+

+ Results will appear here after you use AI features. +

+
+ ) : ( + <> + + + + + Feature + Database + Provider + Tokens + Created + + + + {items.map((item) => ( + handleRowClick(item)} + > + + + {FEATURE_LABELS[item.feature] ?? item.feature} + + + + {item.datasource_id || ( + + )} + + + {PROVIDER_LABELS[item.provider] ?? item.provider} + + + {item.tokens_used != null ? ( + ~{item.tokens_used.toLocaleString()} + ) : ( + + )} + + + {timeAgo(item.created_at)} + + + ))} + +
+
+ + {/* Pagination */} + {totalPages > 1 && ( +
+ + Page {page + 1} of {totalPages} · {total} entries + +
+ + +
+
+ )} + + )} +
+
+ + {/* Detail dialog */} + { + setDetailOpen(next); + if (!next) setDetailEntry(null); + }} + entry={detailLoading ? null : detailEntry} + onDelete={handleDelete} + /> + + ); +} diff --git a/src/features/ai/components/AIResultDialog.tsx b/src/features/ai/components/AIResultDialog.tsx index 032efa7..f17f247 100644 --- a/src/features/ai/components/AIResultDialog.tsx +++ b/src/features/ai/components/AIResultDialog.tsx @@ -6,7 +6,9 @@ import { DialogTitle, DialogDescription, } from "@/components/ui/dialog"; -import { Loader2, Bot, AlertCircle, Sparkles, Copy, Check } from "lucide-react"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Loader2, Bot, AlertCircle, Sparkles, Copy, Check, RefreshCw } from "lucide-react"; import { cn } from "@/lib/utils"; interface AIResultDialogProps { @@ -17,6 +19,29 @@ interface AIResultDialogProps { markdown?: string; loading?: boolean; error?: string | null; + /** Whether the result came from cache. */ + cached?: boolean; + /** ISO timestamp when the cached result was originally generated. */ + createdAt?: string; + /** Callback to force a fresh AI call (skip cache). */ + onReanalyze?: () => void; +} + +/** + * Format a relative time string from an ISO date. + */ +function timeAgo(isoDate: string): string { + const diff = Date.now() - new Date(isoDate).getTime(); + const seconds = Math.floor(diff / 1000); + if (seconds < 60) return "just now"; + const minutes = Math.floor(seconds / 60); + if (minutes < 60) return `${minutes}m ago`; + const hours = Math.floor(minutes / 60); + if (hours < 24) return `${hours}h ago`; + const days = Math.floor(hours / 24); + if (days < 30) return `${days}d ago`; + const months = Math.floor(days / 30); + return `${months}mo ago`; } /** @@ -31,6 +56,9 @@ export function AIResultDialog({ markdown, loading, error, + cached, + createdAt, + onReanalyze, }: AIResultDialogProps) { return ( @@ -41,10 +69,43 @@ export function AIResultDialog({ {title} + {/* Cached / Fresh badge */} + {markdown && !loading && !error && cached !== undefined && ( + + {cached ? "Cached" : "Fresh"} + + )} - {description && ( - {description} - )} + {/* Description row with optional timestamp + re-analyze */} +
+ {description && ( + {description} + )} + {markdown && !loading && cached && createdAt && ( + + Generated {timeAgo(createdAt)} + + )} + {markdown && !loading && cached && onReanalyze && ( + + )} +
@@ -88,8 +149,10 @@ export function AIResultDialog({ /** * Lightweight markdown renderer — no heavy library needed. * Handles headings, bold, code blocks, bullet lists, and paragraphs. + * + * Exported so the history detail dialog can reuse it. */ -function MarkdownRenderer({ content }: { content: string }) { +export function MarkdownRenderer({ content }: { content: string }) { const lines = content.split("\n"); const elements: React.ReactNode[] = []; let i = 0; diff --git a/src/features/schema-explorer/components/AnalyzeSchemaButton.tsx b/src/features/schema-explorer/components/AnalyzeSchemaButton.tsx index 7e1ab76..acb8289 100644 --- a/src/features/schema-explorer/components/AnalyzeSchemaButton.tsx +++ b/src/features/schema-explorer/components/AnalyzeSchemaButton.tsx @@ -31,34 +31,43 @@ export function AnalyzeSchemaButton({ schemaData, databaseType }: AnalyzeSchemaB const [loading, setLoading] = useState(false); const [markdown, setMarkdown] = useState(); const [error, setError] = useState(null); + const [cached, setCached] = useState(); + const [createdAt, setCreatedAt] = useState(); const tableCount = schemaData.schemas?.flatMap((s) => s.tables).length ?? 0; - const handleAnalyze = async () => { + const buildInput = (): SchemaAnalysisInput => ({ + databaseType, + tables: schemaData.schemas.flatMap((schema) => + schema.tables.map((table) => ({ + name: table.name, + schema: schema.name, + columns: table.columns.map((col) => ({ + name: col.name, + type: col.type, + nullable: col.nullable, + isPrimaryKey: col.isPrimaryKey, + isForeignKey: col.isForeignKey, + })), + })) + ), + }); + + const handleAnalyze = async (skipCache = false) => { setOpen(true); - if (markdown) return; // Already analyzed — reuse result + if (markdown && !skipCache) return; // Already analyzed — reuse result setLoading(true); setError(null); try { - const input: SchemaAnalysisInput = { - databaseType, - tables: schemaData.schemas.flatMap((schema) => - schema.tables.map((table) => ({ - name: table.name, - schema: schema.name, - columns: table.columns.map((col) => ({ - name: col.name, - type: col.type, - nullable: col.nullable, - isPrimaryKey: col.isPrimaryKey, - isForeignKey: col.isForeignKey, - })), - })) - ), - }; - const result = await aiService.analyzeSchema(settings, input); - setMarkdown(result); + const input = buildInput(); + const result = await aiService.analyzeSchema(settings, input, { + skipCache, + datasourceName: schemaData.name, + }); + setMarkdown(result.markdown); + setCached(result.cached); + setCreatedAt(result.createdAt); } catch (err: any) { setError(err?.message ?? String(err)); } finally { @@ -66,12 +75,21 @@ export function AnalyzeSchemaButton({ schemaData, databaseType }: AnalyzeSchemaB } }; + const handleReanalyze = () => { + setMarkdown(undefined); + setCached(undefined); + setCreatedAt(undefined); + handleAnalyze(true); + }; + // Reset cached result when dialog closes so next open re-fetches const handleOpenChange = (next: boolean) => { setOpen(next); if (!next) { setMarkdown(undefined); setError(null); + setCached(undefined); + setCreatedAt(undefined); } }; @@ -81,7 +99,7 @@ export function AnalyzeSchemaButton({ schemaData, databaseType }: AnalyzeSchemaB variant="outline" size="sm" className="gap-1.5 h-8 text-xs border-border/40" - onClick={handleAnalyze} + onClick={() => handleAnalyze()} disabled={tableCount === 0} > {loading ? ( @@ -100,6 +118,9 @@ export function AnalyzeSchemaButton({ schemaData, databaseType }: AnalyzeSchemaB markdown={markdown} loading={loading} error={error} + cached={cached} + createdAt={createdAt} + onReanalyze={handleReanalyze} /> ); diff --git a/src/features/settings/components/index.tsx b/src/features/settings/components/index.tsx index 6f0366c..9b243f2 100644 --- a/src/features/settings/components/index.tsx +++ b/src/features/settings/components/index.tsx @@ -5,4 +5,5 @@ export {default as DeveloperMode} from './DeveloperMode' export {default as CheckForUpdates} from './CheckForUpdates' export {default as Version} from './Version' export {default as Preview} from './Preview' -export {default as AISettings} from './AISettings' \ No newline at end of file +export {default as AISettings} from './AISettings' +export { default as AIHistoryPanel } from '../../ai/components/AIHistoryPanel' \ No newline at end of file diff --git a/src/features/workspace/components/ExplainQueryButton.tsx b/src/features/workspace/components/ExplainQueryButton.tsx index b2e863e..717c03d 100644 --- a/src/features/workspace/components/ExplainQueryButton.tsx +++ b/src/features/workspace/components/ExplainQueryButton.tsx @@ -8,16 +8,19 @@ import { aiService } from "@/services/bridge/ai"; interface ExplainQueryButtonProps { sql: string; disabled?: boolean; + databaseName?: string; } -export function ExplainQueryButton({ sql, disabled }: ExplainQueryButtonProps) { +export function ExplainQueryButton({ sql, disabled, databaseName }: ExplainQueryButtonProps) { const settings = useAISettings(); const [open, setOpen] = useState(false); const [loading, setLoading] = useState(false); const [markdown, setMarkdown] = useState(); const [error, setError] = useState(null); + const [cached, setCached] = useState(); + const [createdAt, setCreatedAt] = useState(); - const handleExplain = async () => { + const doExplain = async (skipCache = false) => { setOpen(true); setMarkdown(undefined); setError(null); @@ -26,8 +29,10 @@ export function ExplainQueryButton({ sql, disabled }: ExplainQueryButtonProps) { try { const result = await aiService.explainQuery(settings, { sql: sql.trim(), - }); - setMarkdown(result); + }, { skipCache, datasourceName: databaseName }); + setMarkdown(result.markdown); + setCached(result.cached); + setCreatedAt(result.createdAt); } catch (err: any) { setError(err?.message ?? String(err)); } finally { @@ -35,6 +40,12 @@ export function ExplainQueryButton({ sql, disabled }: ExplainQueryButtonProps) { } }; + const handleReanalyze = () => { + setCached(undefined); + setCreatedAt(undefined); + doExplain(true); + }; + const shortSQL = sql.trim().length > 60 ? sql.trim().slice(0, 60).replace(/\s+/g, " ") + "…" @@ -46,7 +57,7 @@ export function ExplainQueryButton({ sql, disabled }: ExplainQueryButtonProps) { variant="outline" size="sm" className="gap-1.5 h-8 text-xs border-border/40" - onClick={handleExplain} + onClick={() => doExplain()} disabled={disabled || !sql.trim()} > {loading ? ( @@ -65,6 +76,9 @@ export function ExplainQueryButton({ sql, disabled }: ExplainQueryButtonProps) { markdown={markdown} loading={loading} error={error} + cached={cached} + createdAt={createdAt} + onReanalyze={handleReanalyze} /> ); diff --git a/src/features/workspace/components/WorkspaceHeader.tsx b/src/features/workspace/components/WorkspaceHeader.tsx index 8f0b5de..12b859a 100644 --- a/src/features/workspace/components/WorkspaceHeader.tsx +++ b/src/features/workspace/components/WorkspaceHeader.tsx @@ -45,6 +45,7 @@ export function WorkspaceHeader({ {/* Run/Stop buttons */} diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index c876eca..017369e 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -1,4 +1,4 @@ -import { AISettings, CheckForUpdates, ColorVariant, DeveloperMode, Header, Preview, ThemeMode, Version } from "@/features/settings/components"; +import { AISettings, AIHistoryPanel, CheckForUpdates, ColorVariant, DeveloperMode, Header, Preview, ThemeMode, Version } from "@/features/settings/components"; const Settings = () => { return ( @@ -22,6 +22,9 @@ const Settings = () => { {/* AI Settings Section */} + {/* AI History Section */} + + {/* Updates Section */} diff --git a/src/services/bridge/ai.ts b/src/services/bridge/ai.ts index d011cf9..e3e3784 100644 --- a/src/services/bridge/ai.ts +++ b/src/services/bridge/ai.ts @@ -64,6 +64,51 @@ export interface ChartRecommendation { reasoning: string; } +// ── Cache-aware response types ──────────────────────────────────────────── + +export interface AIAnalysisResult { + markdown: string; + cached: boolean; + createdAt?: string; +} + +export interface AIChartResult extends ChartRecommendation { + cached: boolean; + createdAt?: string; +} + +// ── History types ───────────────────────────────────────────────────────── + +export interface AIHistoryListItem { + id: number; + feature: string; + datasource_id: string | null; + table_name: string | null; + provider: string; + model: string; + tokens_used: number | null; + created_at: string; +} + +export interface AIHistoryEntry { + id: number; + feature: string; + datasource_id: string | null; + table_name: string | null; + content_hash: string | null; + provider: string; + model: string; + prompt: string; + response: string; + tokens_used: number | null; + created_at: string; +} + +export interface AIHistoryListResult { + items: AIHistoryListItem[]; + total: number; +} + // ── AI Settings storage ─────────────────────────────────────────────────── const AI_SETTINGS_KEY = "relwave:ai-settings"; @@ -88,7 +133,7 @@ class AIService { */ async testConnection(settings: AISettings): Promise<{ connected: boolean; message?: string }> { try { - const result = await bridgeRequest("ai.testConnection", { settings }); + await bridgeRequest("ai.testConnection", { settings }); return { connected: true }; } catch (err: any) { return { connected: false, message: err?.message ?? String(err) }; @@ -96,19 +141,45 @@ class AIService { } /** - * Analyze a database schema. Returns a markdown string. + * Analyze a database schema. Returns markdown + cache metadata. */ - async analyzeSchema(settings: AISettings, input: SchemaAnalysisInput): Promise { - const result = await bridgeRequest("ai.analyzeSchema", { settings, input }); - return result?.data?.markdown ?? ""; + async analyzeSchema( + settings: AISettings, + input: SchemaAnalysisInput, + opts?: { skipCache?: boolean; datasourceName?: string } + ): Promise { + const result = await bridgeRequest("ai.analyzeSchema", { + settings, + input, + skipCache: opts?.skipCache, + datasourceName: opts?.datasourceName, + }); + return { + markdown: result?.data?.markdown ?? "", + cached: result?.data?.cached ?? false, + createdAt: result?.data?.createdAt, + }; } /** - * Explain a SQL query. Returns a markdown string. + * Explain a SQL query. Returns markdown + cache metadata. */ - async explainQuery(settings: AISettings, input: QueryExplanationInput): Promise { - const result = await bridgeRequest("ai.explainQuery", { settings, input }); - return result?.data?.markdown ?? ""; + async explainQuery( + settings: AISettings, + input: QueryExplanationInput, + opts?: { skipCache?: boolean; datasourceName?: string } + ): Promise { + const result = await bridgeRequest("ai.explainQuery", { + settings, + input, + skipCache: opts?.skipCache, + datasourceName: opts?.datasourceName, + }); + return { + markdown: result?.data?.markdown ?? "", + cached: result?.data?.cached ?? false, + createdAt: result?.data?.createdAt, + }; } /** @@ -116,10 +187,64 @@ class AIService { */ async recommendChart( settings: AISettings, - input: ChartRecommendationInput - ): Promise { - const result = await bridgeRequest("ai.recommendChart", { settings, input }); - return result?.data as ChartRecommendation; + input: ChartRecommendationInput, + opts?: { skipCache?: boolean; datasourceName?: string } + ): Promise { + const result = await bridgeRequest("ai.recommendChart", { + settings, + input, + skipCache: opts?.skipCache, + datasourceName: opts?.datasourceName, + tableName: input.tableName, + }); + const data = result?.data; + return { + chartType: data?.chartType ?? "bar", + xAxis: data?.xAxis ?? "", + yAxis: data?.yAxis ?? "", + reasoning: data?.reasoning ?? "", + cached: data?.cached ?? false, + createdAt: data?.createdAt, + }; + } + + // ── History methods ───────────────────────────────────────────────────── + + /** + * List AI analysis history with optional filters and pagination. + */ + async getHistory(params?: { + feature?: string; + provider?: string; + limit?: number; + offset?: number; + }): Promise { + const result = await bridgeRequest("ai.getHistory", params ?? {}); + return result?.data as AIHistoryListResult; + } + + /** + * Get a single history entry by ID (full record with prompt/response). + */ + async getHistoryById(id: number): Promise { + const result = await bridgeRequest("ai.getHistoryById", { id }); + return result?.data as AIHistoryEntry; + } + + /** + * Delete a single history entry by ID. + */ + async deleteHistory(id: number): Promise { + const result = await bridgeRequest("ai.deleteHistory", { id }); + return result?.data?.deleted ?? false; + } + + /** + * Clear all history entries. + */ + async clearHistory(): Promise { + const result = await bridgeRequest("ai.clearHistory", {}); + return result?.data?.deletedCount ?? 0; } } From 0040077b89dcb6bcc9aae5545aa4a312a4eecb35 Mon Sep 17 00:00:00 2001 From: Yash Date: Fri, 5 Jun 2026 18:14:09 +0530 Subject: [PATCH 12/46] feat: enhance project management features with import/export functionality and UI updates --- src/components/layout/CommandPalette.tsx | 9 ++- src/components/layout/VerticalIconBar.tsx | 3 +- .../home/components/ConnectionList.tsx | 15 ++++- .../home/components/DatabaseDetail.tsx | 46 ++++++++++++++ .../home/components/DatabaseOverviewPanel.tsx | 62 ++++++++++++++++++- src/features/home/types.ts | 1 + src/pages/Index.tsx | 14 +++++ 7 files changed, 139 insertions(+), 11 deletions(-) diff --git a/src/components/layout/CommandPalette.tsx b/src/components/layout/CommandPalette.tsx index bf86693..99f97ab 100644 --- a/src/components/layout/CommandPalette.tsx +++ b/src/components/layout/CommandPalette.tsx @@ -189,7 +189,7 @@ export function CommandPalette() { const dbId = React.useMemo(() => { const parts = location.pathname.split("/"); - if (parts.length === 2 && parts[1] && !["projects", "settings"].includes(parts[1])) { + if (parts.length === 2 && parts[1] && !["settings"].includes(parts[1])) { return parts[1]; } return null; @@ -248,7 +248,6 @@ export function CommandPalette() { const pages = React.useMemo(() => [ { icon: Home, label: "Dashboard", path: "/" }, - { icon: FolderOpen, label: "Projects", path: "/projects" }, { icon: Settings, label: "Settings", path: "/settings" }, ], []); @@ -407,7 +406,7 @@ export function CommandPalette() { )} - {/* Projects */} + {/* Projects — navigate to linked database */} {projects.length > 0 && ( }> {projects.map((project) => ( @@ -418,8 +417,8 @@ export function CommandPalette() { type="project" query={search} onSelect={() => - runCommand(() => navigate("/projects"), { - id: project.id, type: "project", label: project.name, path: "/projects", + runCommand(() => navigate(`/${project.databaseId}`), { + id: project.id, type: "project", label: project.name, path: `/${project.databaseId}`, }) } /> diff --git a/src/components/layout/VerticalIconBar.tsx b/src/components/layout/VerticalIconBar.tsx index 10aca4c..f2d233f 100644 --- a/src/components/layout/VerticalIconBar.tsx +++ b/src/components/layout/VerticalIconBar.tsx @@ -1,4 +1,4 @@ -import { Activity, Home, Database, Search, GitBranch, GitCommitHorizontal, Settings, Layers, Terminal, FolderOpen } from 'lucide-react'; +import { Activity, Home, Database, Search, GitBranch, GitCommitHorizontal, Settings, Layers, Terminal } from 'lucide-react'; import { Link, useLocation } from 'react-router-dom'; import { Button } from '@/components/ui/button'; import { @@ -18,7 +18,6 @@ interface VerticalIconBarProps { const globalNavigationItems = [ { icon: Home, label: 'Dashboard', path: '/' }, - { icon: FolderOpen, label: 'Projects', path: '/projects' }, { icon: Settings, label: 'Settings', path: '/settings' }, ]; diff --git a/src/features/home/components/ConnectionList.tsx b/src/features/home/components/ConnectionList.tsx index 0b77b85..8a61f91 100644 --- a/src/features/home/components/ConnectionList.tsx +++ b/src/features/home/components/ConnectionList.tsx @@ -1,5 +1,5 @@ import React, { useState, useMemo } from "react"; -import { Plus, Database, Search, Trash2, Zap, Folder, ChevronRight, ChevronDown, MoreVertical, Edit2, FolderPlus, GripVertical, FolderMinus, Shield } from "lucide-react"; +import { Plus, Database, Search, Trash2, Zap, Folder, ChevronRight, ChevronDown, MoreVertical, Edit2, FolderPlus, GripVertical, FolderMinus, Shield, FolderInput } from "lucide-react"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { @@ -302,6 +302,7 @@ export function ConnectionList({ onDatabaseHover, onDelete, onTest, + onImportClick, }: ConnectionListProps) { const { groups, @@ -377,6 +378,18 @@ export function ConnectionList({

New Group

+ {onImportClick && ( + + + + + +

Import Project

+
+
+ )} + {project && ( + <> + + + Export Project + + + + )} 0} + queryCount={queriesData?.queries?.length} />
diff --git a/src/features/home/components/DatabaseOverviewPanel.tsx b/src/features/home/components/DatabaseOverviewPanel.tsx index e317feb..c028816 100644 --- a/src/features/home/components/DatabaseOverviewPanel.tsx +++ b/src/features/home/components/DatabaseOverviewPanel.tsx @@ -1,5 +1,5 @@ -import { Card, CardAction, CardContent, CardHeader } from "@/components/ui/card"; -import { Clock, Database, HardDrive, Table2 } from "lucide-react"; +import { Card, CardAction, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; +import { Clock, Database, HardDrive, Table2, Layers, GitBranch, FileCode2 } from "lucide-react"; import { formatRelativeTime } from "../utils"; import { DatabaseConnection } from "@/features/database/types"; import { ConnectionDetails } from "./ConnectionDetails"; @@ -9,13 +9,20 @@ interface DatabaseOverviewPanelProps { database: DatabaseConnection; tables: number | string | undefined; size: string | number | undefined; + // Project data props + schemaCount?: number; + hasERLayout?: boolean; + queryCount?: number; } export function DatabaseOverviewPanel({ database, size, - tables + tables, + schemaCount, + hasERLayout, + queryCount, }: DatabaseOverviewPanelProps) { return ( @@ -69,6 +76,55 @@ export function DatabaseOverviewPanel({ + {/* Project Data */} +

Project Data

+
+ + + + + + Schema Cache + + + {schemaCount ?? "—"} + + + Cached schemas + + + + + + + + + ER Diagram + + + {hasERLayout ? "Saved" : "—"} + + + Diagram layout + + + + + + + + + Saved Queries + + + {queryCount ?? "—"} + + + Stored queries + + +
+ {/* Connection Details */} diff --git a/src/features/home/types.ts b/src/features/home/types.ts index 6543769..5f8b327 100644 --- a/src/features/home/types.ts +++ b/src/features/home/types.ts @@ -16,6 +16,7 @@ export interface ConnectionListProps { onDatabaseHover: (dbId: string) => void; onDelete: (dbId: string, dbName: string) => void; onTest: (dbId: string, dbName: string) => void; + onImportClick?: () => void; } export interface DatabaseDetailProps { diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index 5b4e8d5..7eae3fd 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -8,6 +8,7 @@ import { AddConnectionDialog, DeleteDialog, } from "@/features/home/components"; +import { ImportProjectDialog } from "@/features/project/components"; import BridgeLoader from "@/components/feedback/BridgeLoader"; import BridgeFailed from "@/components/feedback/BridgeFailed"; import { useIndexPage } from "@/features/home/hooks/useIndexPage"; @@ -74,6 +75,10 @@ const IndexContent = ({ bridgeReady, onShortcutsClick }: { bridgeReady: boolean, handleDiscoveredDatabaseAdd, handleDialogClose, openDeleteDialog, + + // Import + isImportOpen, + setIsImportOpen, } = useIndexPage(bridgeReady); return ( @@ -97,6 +102,7 @@ const IndexContent = ({ bridgeReady, onShortcutsClick }: { bridgeReady: boolean, onDatabaseHover={handleDatabaseHover} onDelete={openDeleteDialog} onTest={handleTestConnection} + onImportClick={() => setIsImportOpen(true)} /> {/* Right Panel */} @@ -156,6 +162,14 @@ const IndexContent = ({ bridgeReady, onShortcutsClick }: { bridgeReady: boolean, databaseName={dbToDelete?.name} onConfirm={handleDeleteDatabase} /> + + { + setIsImportOpen(false); + }} + /> ); }; From 0f01edf7ae2ee3283cbeaeb11d7c60693fe0d539 Mon Sep 17 00:00:00 2001 From: Yash Date: Fri, 5 Jun 2026 18:17:17 +0530 Subject: [PATCH 13/46] feat(project): integrate project auto-creation on database addition and remove unused project components --- src/features/home/hooks/useIndexPage.ts | 22 +- .../components/CreateProjectDialog.tsx | 172 --------------- .../components/DeleteProjectDialog.tsx | 48 ---- .../project/components/ProjectDetailView.tsx | 206 ------------------ .../project/components/ProjectList.tsx | 183 ---------------- .../project/components/ProjectsEmptyState.tsx | 65 ------ src/features/project/components/index.ts | 4 - src/main.tsx | 4 +- src/pages/Projects.tsx | 117 ---------- 9 files changed, 22 insertions(+), 799 deletions(-) delete mode 100644 src/features/project/components/CreateProjectDialog.tsx delete mode 100644 src/features/project/components/DeleteProjectDialog.tsx delete mode 100644 src/features/project/components/ProjectDetailView.tsx delete mode 100644 src/features/project/components/ProjectList.tsx delete mode 100644 src/features/project/components/ProjectsEmptyState.tsx delete mode 100644 src/pages/Projects.tsx diff --git a/src/features/home/hooks/useIndexPage.ts b/src/features/home/hooks/useIndexPage.ts index c237e0e..13038eb 100644 --- a/src/features/home/hooks/useIndexPage.ts +++ b/src/features/home/hooks/useIndexPage.ts @@ -8,6 +8,7 @@ import { ConnectionFormData, REQUIRED_FIELDS, SQLITE_REQUIRED_FIELDS } from "@/f import { useDatabaseStats } from "../../database/hooks/useDatabaseStats"; import { useSelectedDbStats } from "../../database/hooks/useSelectedDbStats"; import { databaseService } from "@/services/bridge/database"; +import { projectService } from "@/services/bridge/project"; import { DatabaseConnection } from "@/features/database/types"; import { useWelcomeMessage } from "@/features/database/hooks/useWelcomeMessage"; @@ -52,6 +53,7 @@ export const useIndexPage = (bridgeReady: boolean) => { const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [dbToDelete, setDbToDelete] = useState<{ id: string; name: string } | null>(null); const [prefilledConnectionData, setPrefilledConnectionData] = useState | undefined>(undefined); + const [isImportOpen, setIsImportOpen] = useState(false); // Selected db derived state const selectedDatabase = useMemo( @@ -145,7 +147,21 @@ export const useIndexPage = (bridgeReady: boolean) => { }; } - await addDatabaseMutation.mutateAsync(payload); + const db = await addDatabaseMutation.mutateAsync(payload); + + // Auto-create a linked project so the user gets git, schema cache, + // and saved queries for free — one click, two things. + try { + await projectService.createProject({ + databaseId: db.id, + name: db.name, + defaultSchema: db.type === "postgresql" ? "public" : undefined, + }); + } catch { + // Non-fatal — project auto-creation shouldn't block the database add + console.warn("Auto-project creation failed for", db.name); + } + toast.success("Database connection added"); setIsDialogOpen(false); await Promise.all([refetchDatabases(), refetchStatus()]); @@ -274,5 +290,9 @@ export const useIndexPage = (bridgeReady: boolean) => { handleDiscoveredDatabaseAdd, handleDialogClose, openDeleteDialog, + + // Import + isImportOpen, + setIsImportOpen, }; }; \ No newline at end of file diff --git a/src/features/project/components/CreateProjectDialog.tsx b/src/features/project/components/CreateProjectDialog.tsx deleted file mode 100644 index 8aa54d0..0000000 --- a/src/features/project/components/CreateProjectDialog.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import { useState } from "react"; -import { Database, Link as LinkIcon } from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { Textarea } from "@/components/ui/textarea"; -import { DatabaseConnection } from "@/features/database/types"; - -interface CreateProjectDialogProps { - open: boolean; - onOpenChange: (open: boolean) => void; - onSubmit: (data: { - databaseId: string; - name: string; - description?: string; - defaultSchema?: string; - }) => void; - isLoading?: boolean; - databases: DatabaseConnection[]; -} - -export function CreateProjectDialog({ - open, - onOpenChange, - onSubmit, - isLoading, - databases, -}: CreateProjectDialogProps) { - const [name, setName] = useState(""); - const [description, setDescription] = useState(""); - const [databaseId, setDatabaseId] = useState(""); - const [defaultSchema, setDefaultSchema] = useState(""); - - const resetForm = () => { - setName(""); - setDescription(""); - setDatabaseId(""); - setDefaultSchema(""); - }; - - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - if (!name.trim() || !databaseId) return; - - onSubmit({ - databaseId, - name: name.trim(), - description: description.trim() || undefined, - defaultSchema: defaultSchema.trim() || undefined, - }); - - resetForm(); - }; - - return ( - { - onOpenChange(isOpen); - if (!isOpen) resetForm(); - }} - > - - - - - Create Project - - - Create a project to save schema, ER diagrams, and queries offline. - - - -
- {/* Project Name */} -
- - setName(e.target.value)} - autoFocus - /> -
- - {/* Linked Database */} -
- - -
- - {/* Default Schema */} -
- - setDefaultSchema(e.target.value)} - /> -
- - {/* Description */} -
- -