From 016192dfbce2f3e3e2c38e4cf5c097f20cd30571 Mon Sep 17 00:00:00 2001 From: PraneshASP Date: Thu, 3 Jul 2025 18:05:14 +0530 Subject: [PATCH 01/25] feat: add ai backend --- .gitignore | 3 +- ai-backend/.env.example | 9 + ai-backend/README.md | 123 +++ ai-backend/package-lock.json | 1434 +++++++++++++++++++++++++ ai-backend/package.json | 28 + ai-backend/pnpm-lock.yaml | 1025 ++++++++++++++++++ ai-backend/src/routes/ai.ts | 41 + ai-backend/src/routes/docs.ts | 67 ++ ai-backend/src/server.ts | 99 ++ ai-backend/src/services/aiService.ts | 468 ++++++++ ai-backend/src/services/mcpService.ts | 163 +++ ai-backend/src/types.ts | 50 + ai-backend/src/utils/errorHandler.ts | 18 + ai-backend/tsconfig.json | 23 + 14 files changed, 3550 insertions(+), 1 deletion(-) create mode 100644 ai-backend/.env.example create mode 100644 ai-backend/README.md create mode 100644 ai-backend/package-lock.json create mode 100644 ai-backend/package.json create mode 100644 ai-backend/pnpm-lock.yaml create mode 100644 ai-backend/src/routes/ai.ts create mode 100644 ai-backend/src/routes/docs.ts create mode 100644 ai-backend/src/server.ts create mode 100644 ai-backend/src/services/aiService.ts create mode 100644 ai-backend/src/services/mcpService.ts create mode 100644 ai-backend/src/types.ts create mode 100644 ai-backend/src/utils/errorHandler.ts create mode 100644 ai-backend/tsconfig.json diff --git a/.gitignore b/.gitignore index c141562..ef9d15c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ build node_modules tmp .vscode/* -.DS_Store \ No newline at end of file +.DS_Store +.env \ No newline at end of file diff --git a/ai-backend/.env.example b/ai-backend/.env.example new file mode 100644 index 0000000..913e922 --- /dev/null +++ b/ai-backend/.env.example @@ -0,0 +1,9 @@ +PORT=3001 +NODE_ENV=development + +GEMINI_API_KEY=your_gemini_api_key_here + +FUEL_DOCS_MCP_PATH= +FUEL_DOCS_VECTRA_INDEX_PATH= + +CORS_ORIGIN=http://localhost:3000 \ No newline at end of file diff --git a/ai-backend/README.md b/ai-backend/README.md new file mode 100644 index 0000000..920b89b --- /dev/null +++ b/ai-backend/README.md @@ -0,0 +1,123 @@ +# Sway Playground AI Backend + +A lightweight Node.js backend service that provides AI-powered code generation and documentation search for the Sway Playground. This service bridges the browser frontend with the fuel-docs MCP server and Gemini AI. + +## Features + +- **AI Code Generation**: Generate Sway smart contracts from user prompts +- **Documentation Search**: Search and retrieve relevant Fuel/Sway documentation +- **Error Analysis**: Analyze compilation errors and provide fix suggestions +- **MCP Integration**: Connects to fuel-docs MCP server for documentation context + +## Quick Start + +### 1. Install Dependencies +```bash +pnpm install +``` + +### 2. Setup Environment +```bash +cp .env.example .env +# Edit .env with your configuration +``` + +Required environment variables: +```env +GEMINI_API_KEY=your_gemini_api_key_here +FUEL_DOCS_MCP_PATH=/path/to/fuel-mcp-server/src/mcp-server.ts +FUEL_DOCS_VECTRA_INDEX_PATH=/path/to/fuel-mcp-server/vectra_index +``` + +### 3. Start Development Server +```bash +pnpm dev +``` + +The server will start on `http://localhost:3001` + +## API Endpoints + +### AI Endpoints + +#### Generate Sway Code +```http +POST /api/ai/generate +Content-Type: application/json + +{ + "prompt": "Create a token contract with minting functionality" +} +``` + +#### Analyze Compilation Error +```http +POST /api/ai/analyze-error +Content-Type: application/json + +{ + "errorMessage": "cannot find function `transfer` in scope", + "sourceCode": "contract MyToken { ... }" +} +``` + +### Documentation Endpoints + +#### Search Documentation +```http +POST /api/docs/search +Content-Type: application/json + +{ + "query": "storage read write", + "maxResults": 5 +} +``` + +#### Get Relevant Documentation +```http +POST /api/docs/relevant +Content-Type: application/json + +{ + "query": "token contract implementation" +} +``` + +#### Health Check +```http +GET /api/docs/health +``` + +### Services + +- **AIService**: Handles Gemini AI integration for code generation and analysis +- **MCPService**: Manages fuel-docs MCP server communication via child process +- **Routes**: Express.js API endpoints for AI and documentation operations + +### MCP Integration + +The backend spawns the fuel-docs MCP server as a child process and communicates via JSON-RPC over stdio: + +```typescript +// MCP server is spawned with: +bun run /path/to/fuel-mcp-server/src/mcp-server.ts + +// Environment passed: +VECTRA_INDEX_PATH=/path/to/vectra_index +``` + +> TODO: Convert docs server into a remote MCP server + + +## Environment Variables + +| Variable | Description | Required | +|----------|-------------|----------| +| `PORT` | Server port (default: 3001) | No | +| `GEMINI_API_KEY` | Google Gemini AI API key | Yes | +| `FUEL_DOCS_MCP_PATH` | Path to MCP server TypeScript file | No* | +| `FUEL_DOCS_VECTRA_INDEX_PATH` | Path to Vectra index directory | No* | +| `NODE_ENV` | Environment (development/production) | No | + +*Required for documentation feature \ No newline at end of file diff --git a/ai-backend/package-lock.json b/ai-backend/package-lock.json new file mode 100644 index 0000000..cec43a8 --- /dev/null +++ b/ai-backend/package-lock.json @@ -0,0 +1,1434 @@ +{ + "name": "sway-playground-ai-backend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sway-playground-ai-backend", + "version": "1.0.0", + "dependencies": { + "@google/generative-ai": "^0.24.1", + "cors": "^2.8.5", + "dotenv": "^16.5.0", + "express": "^4.18.2" + }, + "devDependencies": { + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/node": "^24.0.1", + "tsx": "^4.6.2", + "typescript": "^5.3.3" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", + "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", + "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", + "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", + "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", + "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", + "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", + "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", + "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", + "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", + "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", + "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", + "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", + "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", + "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", + "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", + "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", + "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", + "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", + "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", + "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", + "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", + "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", + "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", + "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", + "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@google/generative-ai": { + "version": "0.24.1", + "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.24.1.tgz", + "integrity": "sha512-MqO+MLfM6kjxcKoy0p1wRzG3b4ZZXtPI+z2IE26UogS2Cm/XHO+7gGRBh6gcJsOiIVoH93UwKvW4HdgiOZCy9Q==", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", + "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, + "node_modules/@types/node": { + "version": "24.0.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.1.tgz", + "integrity": "sha512-MX4Zioh39chHlDJbKmEgydJDS3tspMP/lnQC67G3SWsTnb9NeYVWOjkxpOSy4oMfPs4StcWHwBrvUb4ybfnuaw==", + "dev": true, + "dependencies": { + "undici-types": "~7.8.0" + } + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true + }, + "node_modules/@types/send": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", + "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dotenv": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", + "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.5", + "@esbuild/android-arm": "0.25.5", + "@esbuild/android-arm64": "0.25.5", + "@esbuild/android-x64": "0.25.5", + "@esbuild/darwin-arm64": "0.25.5", + "@esbuild/darwin-x64": "0.25.5", + "@esbuild/freebsd-arm64": "0.25.5", + "@esbuild/freebsd-x64": "0.25.5", + "@esbuild/linux-arm": "0.25.5", + "@esbuild/linux-arm64": "0.25.5", + "@esbuild/linux-ia32": "0.25.5", + "@esbuild/linux-loong64": "0.25.5", + "@esbuild/linux-mips64el": "0.25.5", + "@esbuild/linux-ppc64": "0.25.5", + "@esbuild/linux-riscv64": "0.25.5", + "@esbuild/linux-s390x": "0.25.5", + "@esbuild/linux-x64": "0.25.5", + "@esbuild/netbsd-arm64": "0.25.5", + "@esbuild/netbsd-x64": "0.25.5", + "@esbuild/openbsd-arm64": "0.25.5", + "@esbuild/openbsd-x64": "0.25.5", + "@esbuild/sunos-x64": "0.25.5", + "@esbuild/win32-arm64": "0.25.5", + "@esbuild/win32-ia32": "0.25.5", + "@esbuild/win32-x64": "0.25.5" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tsx": { + "version": "4.20.3", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.3.tgz", + "integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==", + "dev": true, + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "dev": true + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + } + } +} diff --git a/ai-backend/package.json b/ai-backend/package.json new file mode 100644 index 0000000..4dd7a8e --- /dev/null +++ b/ai-backend/package.json @@ -0,0 +1,28 @@ +{ + "name": "sway-playground-ai-backend", + "version": "1.0.0", + "description": "Tiny Node.js backend for AI and MCP integration with Sway Playground", + "main": "dist/server.js", + "scripts": { + "dev": "tsx watch src/server.ts", + "build": "tsc", + "start": "node dist/server.js", + "type-check": "tsc --noEmit" + }, + "dependencies": { + "@google/generative-ai": "^0.24.1", + "cors": "^2.8.5", + "dotenv": "^16.5.0", + "express": "^4.18.2" + }, + "devDependencies": { + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/node": "^24.0.1", + "tsx": "^4.6.2", + "typescript": "^5.3.3" + }, + "engines": { + "node": ">=18.0.0" + } +} diff --git a/ai-backend/pnpm-lock.yaml b/ai-backend/pnpm-lock.yaml new file mode 100644 index 0000000..799a7e3 --- /dev/null +++ b/ai-backend/pnpm-lock.yaml @@ -0,0 +1,1025 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@google/generative-ai': + specifier: ^0.24.1 + version: 0.24.1 + cors: + specifier: ^2.8.5 + version: 2.8.5 + dotenv: + specifier: ^16.3.1 + version: 16.5.0 + express: + specifier: ^4.18.2 + version: 4.21.2 + devDependencies: + '@types/cors': + specifier: ^2.8.17 + version: 2.8.19 + '@types/express': + specifier: ^4.17.21 + version: 4.17.23 + '@types/node': + specifier: ^24.0.1 + version: 24.0.1 + tsx: + specifier: ^4.6.2 + version: 4.20.3 + typescript: + specifier: ^5.3.3 + version: 5.8.3 + +packages: + + '@esbuild/aix-ppc64@0.25.5': + resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.5': + resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.5': + resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.5': + resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.5': + resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.5': + resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.5': + resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.5': + resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.5': + resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.5': + resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.5': + resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.5': + resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.5': + resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.5': + resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.5': + resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.5': + resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.5': + resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.5': + resolution: {integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.5': + resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.5': + resolution: {integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.5': + resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.25.5': + resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.5': + resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.5': + resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.5': + resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==} + 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'} + + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/cors@2.8.19': + resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} + + '@types/express-serve-static-core@4.19.6': + resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==} + + '@types/express@4.17.23': + resolution: {integrity: sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==} + + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + + '@types/mime@1.3.5': + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + + '@types/node@24.0.1': + resolution: {integrity: sha512-MX4Zioh39chHlDJbKmEgydJDS3tspMP/lnQC67G3SWsTnb9NeYVWOjkxpOSy4oMfPs4StcWHwBrvUb4ybfnuaw==} + + '@types/qs@6.14.0': + resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + + '@types/send@0.17.5': + resolution: {integrity: sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==} + + '@types/serve-static@1.15.8': + resolution: {integrity: sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==} + + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + + array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + + body-parser@1.20.3: + resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + cookie-signature@1.0.6: + resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + + cookie@0.7.1: + resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} + engines: {node: '>= 0.6'} + + cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + dotenv@16.5.0: + resolution: {integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==} + engines: {node: '>=12'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + esbuild@0.25.5: + resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==} + engines: {node: '>=18'} + hasBin: true + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + express@4.21.2: + resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} + engines: {node: '>= 0.10.0'} + + finalhandler@1.3.1: + resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} + engines: {node: '>= 0.8'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.10.1: + resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-to-regexp@0.1.12: + resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + qs@6.13.0: + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} + engines: {node: '>=0.6'} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} + engines: {node: '>= 0.8'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + send@0.19.0: + resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} + engines: {node: '>= 0.8.0'} + + serve-static@1.16.2: + resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} + engines: {node: '>= 0.8.0'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + tsx@4.20.3: + resolution: {integrity: sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==} + engines: {node: '>=18.0.0'} + hasBin: true + + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.8.0: + resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + +snapshots: + + '@esbuild/aix-ppc64@0.25.5': + optional: true + + '@esbuild/android-arm64@0.25.5': + optional: true + + '@esbuild/android-arm@0.25.5': + optional: true + + '@esbuild/android-x64@0.25.5': + optional: true + + '@esbuild/darwin-arm64@0.25.5': + optional: true + + '@esbuild/darwin-x64@0.25.5': + optional: true + + '@esbuild/freebsd-arm64@0.25.5': + optional: true + + '@esbuild/freebsd-x64@0.25.5': + optional: true + + '@esbuild/linux-arm64@0.25.5': + optional: true + + '@esbuild/linux-arm@0.25.5': + optional: true + + '@esbuild/linux-ia32@0.25.5': + optional: true + + '@esbuild/linux-loong64@0.25.5': + optional: true + + '@esbuild/linux-mips64el@0.25.5': + optional: true + + '@esbuild/linux-ppc64@0.25.5': + optional: true + + '@esbuild/linux-riscv64@0.25.5': + optional: true + + '@esbuild/linux-s390x@0.25.5': + optional: true + + '@esbuild/linux-x64@0.25.5': + optional: true + + '@esbuild/netbsd-arm64@0.25.5': + optional: true + + '@esbuild/netbsd-x64@0.25.5': + optional: true + + '@esbuild/openbsd-arm64@0.25.5': + optional: true + + '@esbuild/openbsd-x64@0.25.5': + optional: true + + '@esbuild/sunos-x64@0.25.5': + optional: true + + '@esbuild/win32-arm64@0.25.5': + optional: true + + '@esbuild/win32-ia32@0.25.5': + optional: true + + '@esbuild/win32-x64@0.25.5': + optional: true + + '@google/generative-ai@0.24.1': {} + + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 24.0.1 + + '@types/connect@3.4.38': + dependencies: + '@types/node': 24.0.1 + + '@types/cors@2.8.19': + dependencies: + '@types/node': 24.0.1 + + '@types/express-serve-static-core@4.19.6': + dependencies: + '@types/node': 24.0.1 + '@types/qs': 6.14.0 + '@types/range-parser': 1.2.7 + '@types/send': 0.17.5 + + '@types/express@4.17.23': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 4.19.6 + '@types/qs': 6.14.0 + '@types/serve-static': 1.15.8 + + '@types/http-errors@2.0.5': {} + + '@types/mime@1.3.5': {} + + '@types/node@24.0.1': + dependencies: + undici-types: 7.8.0 + + '@types/qs@6.14.0': {} + + '@types/range-parser@1.2.7': {} + + '@types/send@0.17.5': + dependencies: + '@types/mime': 1.3.5 + '@types/node': 24.0.1 + + '@types/serve-static@1.15.8': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 24.0.1 + '@types/send': 0.17.5 + + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + array-flatten@1.1.1: {} + + body-parser@1.20.3: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.13.0 + raw-body: 2.5.2 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + + cookie-signature@1.0.6: {} + + cookie@0.7.1: {} + + cors@2.8.5: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + depd@2.0.0: {} + + destroy@1.2.0: {} + + dotenv@16.5.0: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ee-first@1.1.1: {} + + encodeurl@1.0.2: {} + + encodeurl@2.0.0: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + esbuild@0.25.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.5 + '@esbuild/android-arm': 0.25.5 + '@esbuild/android-arm64': 0.25.5 + '@esbuild/android-x64': 0.25.5 + '@esbuild/darwin-arm64': 0.25.5 + '@esbuild/darwin-x64': 0.25.5 + '@esbuild/freebsd-arm64': 0.25.5 + '@esbuild/freebsd-x64': 0.25.5 + '@esbuild/linux-arm': 0.25.5 + '@esbuild/linux-arm64': 0.25.5 + '@esbuild/linux-ia32': 0.25.5 + '@esbuild/linux-loong64': 0.25.5 + '@esbuild/linux-mips64el': 0.25.5 + '@esbuild/linux-ppc64': 0.25.5 + '@esbuild/linux-riscv64': 0.25.5 + '@esbuild/linux-s390x': 0.25.5 + '@esbuild/linux-x64': 0.25.5 + '@esbuild/netbsd-arm64': 0.25.5 + '@esbuild/netbsd-x64': 0.25.5 + '@esbuild/openbsd-arm64': 0.25.5 + '@esbuild/openbsd-x64': 0.25.5 + '@esbuild/sunos-x64': 0.25.5 + '@esbuild/win32-arm64': 0.25.5 + '@esbuild/win32-ia32': 0.25.5 + '@esbuild/win32-x64': 0.25.5 + + escape-html@1.0.3: {} + + etag@1.8.1: {} + + express@4.21.2: + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.3 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.7.1 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.3.1 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.3 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.12 + proxy-addr: 2.0.7 + qs: 6.13.0 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.19.0 + serve-static: 1.16.2 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + finalhandler@1.3.1: + dependencies: + debug: 2.6.9 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + forwarded@0.2.0: {} + + fresh@0.5.2: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-tsconfig@4.10.1: + dependencies: + resolve-pkg-maps: 1.0.0 + + gopd@1.2.0: {} + + has-symbols@1.1.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + inherits@2.0.4: {} + + ipaddr.js@1.9.1: {} + + math-intrinsics@1.1.0: {} + + media-typer@0.3.0: {} + + merge-descriptors@1.0.3: {} + + methods@1.1.2: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@1.6.0: {} + + ms@2.0.0: {} + + ms@2.1.3: {} + + negotiator@0.6.3: {} + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + parseurl@1.3.3: {} + + path-to-regexp@0.1.12: {} + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + qs@6.13.0: + dependencies: + side-channel: 1.1.0 + + range-parser@1.2.1: {} + + raw-body@2.5.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + resolve-pkg-maps@1.0.0: {} + + safe-buffer@5.2.1: {} + + safer-buffer@2.1.2: {} + + send@0.19.0: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + + serve-static@1.16.2: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.0 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + statuses@2.0.1: {} + + toidentifier@1.0.1: {} + + tsx@4.20.3: + dependencies: + esbuild: 0.25.5 + get-tsconfig: 4.10.1 + optionalDependencies: + fsevents: 2.3.3 + + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + typescript@5.8.3: {} + + undici-types@7.8.0: {} + + unpipe@1.0.0: {} + + utils-merge@1.0.1: {} + + vary@1.1.2: {} diff --git a/ai-backend/src/routes/ai.ts b/ai-backend/src/routes/ai.ts new file mode 100644 index 0000000..5e4245c --- /dev/null +++ b/ai-backend/src/routes/ai.ts @@ -0,0 +1,41 @@ +import { Router, Request, Response } from 'express'; +import { AIService } from '../services/aiService'; +import { handleRouteError, handleValidationError } from '../utils/errorHandler'; + +export function createAIRouter(aiService: AIService): Router { + const router = Router(); + + router.post('/generate', async (req: Request, res: Response) => { + const { prompt } = req.body; + + if (!prompt || typeof prompt !== 'string') { + return handleValidationError(res, 'Missing or invalid prompt'); + } + + try { + const result = await aiService.generateSwayCode({ prompt }); + res.json(result); + } catch (error) { + handleRouteError(res, error, 'Code generation'); + } + }); + + router.post('/analyze-error', async (req: Request, res: Response) => { + const { errorMessage, sourceCode, lineNumber } = req.body; + + if (!errorMessage || !sourceCode) { + return handleValidationError(res, 'Missing errorMessage or sourceCode'); + } + + try { + const result = await aiService.analyzeError({ errorMessage, sourceCode, lineNumber }); + res.json(result); + } catch (error) { + handleRouteError(res, error, 'Error analysis'); + } + }); + + + return router; +} + diff --git a/ai-backend/src/routes/docs.ts b/ai-backend/src/routes/docs.ts new file mode 100644 index 0000000..c26cd21 --- /dev/null +++ b/ai-backend/src/routes/docs.ts @@ -0,0 +1,67 @@ +import { Router, Request, Response } from 'express'; +import { MCPService } from '../services/mcpService'; +import { handleRouteError, handleValidationError, handleServiceUnavailable } from '../utils/errorHandler'; + +export function createDocsRouter(mcpService: MCPService): Router { + const router = Router(); + + router.post('/search', async (req: Request, res: Response) => { + const { query } = req.body; + + if (!query || typeof query !== 'string') { + return handleValidationError(res, 'Missing or invalid query'); + } + + if (!mcpService.isAvailable()) { + return handleServiceUnavailable(res, 'Documentation'); + } + + try { + const result = await mcpService.searchDocs({ query, maxResults: req.body.maxResults }); + res.json(result); + } catch (error) { + handleRouteError(res, error, 'Documentation search'); + } + }); + + router.post('/relevant', async (req: Request, res: Response) => { + const { query } = req.body; + + if (!query || typeof query !== 'string') { + return handleValidationError(res, 'Missing or invalid query'); + } + + if (!mcpService.isAvailable()) { + return handleServiceUnavailable(res, 'Documentation'); + } + + try { + const result = await mcpService.getRelevantDocs(query); + res.json(result); + } catch (error) { + handleRouteError(res, error, 'Relevant documentation'); + } + }); + + router.get('/std-context', async (_req: Request, res: Response) => { + if (!mcpService.isAvailable()) { + return handleServiceUnavailable(res, 'Documentation'); + } + + try { + const context = await mcpService.getStdContext(); + res.json({ context }); + } catch (error) { + handleRouteError(res, error, 'Standard library context'); + } + }); + + router.get('/health', (_req: Request, res: Response) => { + res.json({ + available: mcpService.isAvailable(), + status: mcpService.isAvailable() ? 'connected' : 'disconnected' + }); + }); + + return router; +} \ No newline at end of file diff --git a/ai-backend/src/server.ts b/ai-backend/src/server.ts new file mode 100644 index 0000000..437917c --- /dev/null +++ b/ai-backend/src/server.ts @@ -0,0 +1,99 @@ +import express from 'express'; +import cors from 'cors'; +import * as dotenv from 'dotenv'; +import { MCPService } from './services/mcpService'; +import { AIService } from './services/aiService'; +import { createAIRouter } from './routes/ai'; +import { createDocsRouter } from './routes/docs'; + +dotenv.config(); + +const app = express(); +const PORT = process.env.PORT || 3001; + +const mcpService = new MCPService( + process.env.FUEL_DOCS_MCP_PATH || '', + process.env.FUEL_DOCS_VECTRA_INDEX_PATH || '' +); + +const aiService = new AIService( + process.env.GEMINI_API_KEY || '', + mcpService +); + +app.use(express.json({ limit: '10mb' })); +app.use(cors({ + origin: process.env.CORS_ORIGIN || 'http://localhost:3000', + credentials: true +})); + +app.get('/health', (req, res) => { + res.json({ + status: 'healthy', + timestamp: new Date().toISOString(), + version: '1.0.0' + }); +}); + +async function startServer() { + try { + console.log('Starting AI backend server...'); + + if (process.env.FUEL_DOCS_MCP_PATH && process.env.FUEL_DOCS_VECTRA_INDEX_PATH) { + try { + console.log('Initializing MCP service...'); + await mcpService.initialize(); + console.log('MCP service initialized successfully'); + } catch (error) { + console.warn('Failed to initialize MCP service:', error); + console.warn('MCP-dependent features will be unavailable'); + } + } else { + console.warn('MCP configuration missing, documentation search will be unavailable'); + } + + app.use('/api/ai', createAIRouter(aiService)); + app.use('/api/docs', createDocsRouter(mcpService)); + + app.use((error: Error, _req: express.Request, res: express.Response, _next: express.NextFunction) => { + console.error('Unhandled error:', error); + res.status(500).json({ + error: 'Internal server error', + message: process.env.NODE_ENV === 'development' ? error.message : 'Something went wrong' + }); + }); + + app.all('*', (req, res) => { + res.status(404).json({ error: 'Endpoint not found' }); + }); + + app.listen(PORT, () => { + console.log(`🚀 Sway Playground AI Backend running on port ${PORT}`); + console.log(`📚 API endpoints:`); + console.log(` POST /api/ai/generate - Generate Sway code`); + console.log(` POST /api/ai/analyze-error - Analyze compilation errors`); + console.log(` POST /api/docs/search - Search documentation`); + console.log(` POST /api/docs/relevant - Get relevant docs`); + console.log(` GET /api/docs/health - MCP service health`); + console.log(` GET /health - Server health check`); + }); + + process.on('SIGTERM', () => { + console.log('SIGTERM received, shutting down gracefully...'); + mcpService.destroy(); + process.exit(0); + }); + + process.on('SIGINT', () => { + console.log('SIGINT received, shutting down gracefully...'); + mcpService.destroy(); + process.exit(0); + }); + + } catch (error) { + console.error('Failed to start server:', error); + process.exit(1); + } +} + +startServer(); \ No newline at end of file diff --git a/ai-backend/src/services/aiService.ts b/ai-backend/src/services/aiService.ts new file mode 100644 index 0000000..6d778da --- /dev/null +++ b/ai-backend/src/services/aiService.ts @@ -0,0 +1,468 @@ +import { GoogleGenerativeAI } from '@google/generative-ai'; +import { + SwayCodeGenerationRequest, + SwayCodeGenerationResponse, + ErrorAnalysisRequest, + ErrorAnalysisResponse +} from '../types'; + +export class AIService { + private genai: GoogleGenerativeAI | null = null; + private model: any = null; + private functionDeclarations: any[] = []; + private documentationSearchRequired = true; + + constructor(apiKey: string, private mcpService?: any) { + if (apiKey) { + this.genai = new GoogleGenerativeAI(apiKey); + + this.functionDeclarations = [ + { + name: "searchDocumentation", + description: "Search Fuel/Sway documentation for relevant information", + parameters: { + type: "object", + properties: { + query: { + type: "string", + description: "Search query for documentation" + }, + maxResults: { + type: "number", + description: "Maximum number of results to return", + default: 5 + } + }, + required: ["query"] + } + }, + { + name: "getRelevantDocumentation", + description: "Get relevant documentation context for a specific topic or code", + parameters: { + type: "object", + properties: { + topic: { + type: "string", + description: "The topic or code to get relevant documentation for" + } + }, + required: ["topic"] + } + } + ]; + + this.model = this.genai.getGenerativeModel({ + model: "gemini-2.5-flash-preview-05-20", + tools: [{ functionDeclarations: this.functionDeclarations }], + toolConfig: { + functionCallingConfig: { + mode: "auto" as any + } + }, + generationConfig: { + temperature: 0.7, + topK: 40, + topP: 0.95, + maxOutputTokens: 8192, + } + }); + } + } + + private isAvailable(): boolean { + return this.genai !== null && this.model !== null; + } + + public setMCPService(mcpService: any): void { + this.mcpService = mcpService; + } + + + async generateSwayCode(request: SwayCodeGenerationRequest): Promise { + if (!this.isAvailable()) { + throw new Error('AI service not available. Please check your API key configuration.'); + } + + const systemPrompt = `You are an expert Sway smart contract developer. Generate secure, efficient Sway contracts. + +MANDATORY: ALWAYS call 'searchDocumentation' BEFORE generating code. + +SWAY SYNTAX ESSENTIALS: +- Contract: 'contract;' +- ABI: 'abi ContractName { ... }' +- Storage: 'storage { field: Type = default_value, }' (trailing comma required) +- Implementation: 'impl AbiName for Contract { ... }' +- Storage access: '#[storage(read)]' or '#[storage(read, write)]' on both ABI and implementation +- Payable: '#[payable]' on both ABI and implementation +- StorageMap: storage.map.get(key).try_read().unwrap_or(0) +- Validation: assert(condition) or require(condition, "message") +- Identity: Identity::Address(addr) +- No need to import AssetId - Included in prelude. + + +IMPORTS: +- use std::{asset::{mint_to, transfer}, call_frames::msg_asset_id, context::msg_amount, auth::msg_sender, block::timestamp, asset::transfer}; +- use standards::{src3::SRC3, src5::SRC5, src20::SRC20}; + +FALLBACK: If documentation search fails, direct users to docs.fuel.network/docs/sway/`; + + const userPrompt = `Generate a Sway smart contract for: ${request.prompt} + +STEPS: +1. Call 'searchDocumentation' with relevant keywords +2. Generate complete, working Sway contract code +3. Provide brief explanation + +SEARCH KEYWORDS: +- Tokens: "SRC20", "token", "mint", "transfer" +- NFTs: "SRC3", "NFT" +- Access control: "SRC5", "ownership" +- DeFi: "asset management", "swap" +- Basic: "contract", "storage", "functions"` + try { + const result = await this.model.generateContent({ + contents: [{ + role: "user", + parts: [{ text: `${systemPrompt}\n\n${userPrompt}` }] + }] + }); + + const response = result.response; + const functionCalls = response.functionCalls(); + + if (functionCalls && functionCalls.length > 0) { + + const functionResponses = await Promise.all( + functionCalls.map(async (call: any) => { + return await this.handleFunctionCall(call); + }) + ); + + const followUpContents = [ + { role: "user", parts: [{ text: `${systemPrompt}\n\n${userPrompt}` }] }, + { role: "model", parts: response.candidates[0].content.parts }, + { + role: "function", + parts: functionResponses.map((resp, index) => ({ + functionResponse: { + name: functionCalls[index].name, + response: resp + } + })) + } + ]; + + const followUpResult = await this.model.generateContent({ + contents: followUpContents + }); + + const followUpText = followUpResult.response.text(); + if (!followUpText?.trim()) { + throw new Error('AI response was empty. Please try again with a more specific prompt.'); + } + + return this.parseCodeGenerationResponse(followUpText); + } else { + const response_text = response.text(); + return this.parseCodeGenerationResponse(response_text); + } + } catch (error) { + console.error('AI code generation error:', error); + throw new Error('Failed to generate Sway code. Please try again.'); + } + } + + + async analyzeError(request: ErrorAnalysisRequest): Promise { + if (!this.isAvailable()) { + throw new Error('AI service not available. Please check your API key configuration.'); + } + + const systemPrompt = `You are an expert Sway compiler error analyst. Fix Sway compilation errors with accurate, working code. + +MANDATORY: Always call 'searchDocumentation' before analyzing errors. Go one by one and fix errors. + +CRITICAL SWAY SYNTAX RULES: +1. Context imports: use std::{context::{msg_sender, msg_amount}, call_frames::msg_asset_id}; +2. Storage syntax: storage { field: Type = default_value, } (trailing comma required) +3. Validation: Use assert() not require() +4. Identity type: Identity::Address(addr) for addresses +5. ABI functions: Must match impl exactly +6. Storage attributes: #[storage(read)] or #[storage(read, write)] + +IMPORTANT CORRECTIONS: +- Identity::zero() is NOT a method. Use Identity::Address(Address::zero()). +- Option pattern-match limitation: + // GOOD + if storage.highest_bidder.read().is_some() { … } + // BAD (will not compile) + if let Option::Some(x) = storage.highest_bidder.read() { … } +- assert has ONE parameter; use require for message strings. +- Never import or call transfer_inner; only transfer() is public. +- Always unwrap msg_sender() once: + let sender = msg_sender().expect("unauthenticated"); +- Built-ins for time & value: + msg_amount() // std::context + block_timestamp() // std::context + Never import them from anywhere else. +- There is NO transfer_to_contract. + To move tokens into the contract, call + transfer(this_contract_id(), asset_id, amount); +- Do NOT import StorageMap. + Just use it inside the storage { … } block, e.g. + sales: StorageMap = StorageMap {}, + and access via storage.sales. +- Replace unwrap_or_revert("msg") ➜ expect("msg") (same semantics). +- self is a *type parameter* in Sway ABIs, not a variable. + Call sibling fns directly: + let price = get_current_auction_price(id); + +COMMON ERROR FIXES: +- "No storage has been declared" + - insert a storage { … } block and ensure every .read() / .write() target is declared there. +- "symbol transfer_inner / msg_amount / block_height not found" + - remove the bad import; use the std::context versions shown above. +- "Identity::zero() not found" - replace with Identity::Address(Address::zero()). +- "Option::Some cannot be matched" - read into a variable and use .is_some() / .unwrap() instead of pattern matching. +- "assert expects 1 argument" - change to require(cond,"msg"). +- "No method .write / .read" - make sure the field is declared as a StorageValue (or StorageMap) and the type matches exactly. +- "Could not find symbol transfer_to_contract / msg_amount / block_timestamp" + - Use the import list shown above and call transfer(this_contract_id(), …). +- "Mismatched types – expected Identity, found u64" + - Your parameter order in transfer is wrong. + Correct: (to: Identity, asset_id: AssetId, amount: u64) +- "Function assert expects 1 argument" + - change to require(condition, "explanation") +- "Option::Some cannot be matched" + - use .is_some() / .unwrap() instead of pattern matching. +- "unwrap_or_revert not found" + - use .expect("msg") (same effect). +- "Field access requires a struct" + - The storage field or local struct wasn't declared; verify your + Auction struct and storage map types. +- "cannot find msg_sender": Add use std::auth::msg_sender; +- "cannot find assert": Use assert() instead of require() +- "type mismatch Identity": Use Identity::Address(addr) +- "storage field not found": Check storage block syntax +- "ABI mismatch": Ensure impl matches abi exactly + - insert: storage.my_map.insert(key, value); + - read : storage.my_map.get(key).try_read().unwrap_or(default); +- Nested map read/write: + storage.nested.get(k1).insert(k2, v); // write + let v = storage.nested.get(k1).get(k2).try_read(); // read + +PROVEN SWAY PATTERNS: +- Basic contract structure: + contract; + use std::context::msg_sender; + abi MyContract { fn my_function(); } + impl MyContract for Contract { fn my_function() { } } + +- Storage with validation: + storage { owner: Identity = Identity::Address(Address::zero()), } + #[storage(read)] fn get_owner() -> Identity { storage.owner.read() } + +- Asset operations: + use std::{context::msg_amount, call_frames::msg_asset_id}; + assert(msg_amount() > 0); + +- "No method unwrap_or(StorageKey…, numeric)" + - Insert .try_read() before unwrap_or. + +- "add / subtract / ge … for type {unknown}" + - Ensure the variable is a u64 by calling .try_read().unwrap_or(0). + +- "msg_sender not found" + - use std::auth::msg_sender; and drop the .unwrap(). + +- "assert expects 1 argument" + - Change to require(cond, "reason") **or** use the 1-arg + assert(cond) form. + +- "function in ABI is pure but impl is not" + - Copy the #[storage(...)] attribute to the ABI signature. + +RESPONSE FORMAT: +1. Identify the specific error type +2. Apply the correct Sway syntax fix using proven patterns +3. Return complete working code in \`\`\`sway block + +CRITICAL: Only change what's broken. Use exact syntax from proven patterns above.`; + + const userPrompt = `Fix this Sway compilation error by applying ONLY the necessary changes: + +ERROR: ${request.errorMessage} + +CURRENT CODE: +\`\`\`sway +${request.sourceCode} +\`\`\` + +INSTRUCTIONS: +1. Search documentation for this specific error +2. Identify the exact issue causing the error +3. Apply MINIMAL fixes - change only what's broken +4. Keep all working code unchanged +5. Return the complete corrected contract + +CRITICAL: Return the entire corrected Sway contract in a \`\`\`sway code block. Fix ONLY the error, don't refactor working code.`; + + try { + const result = await this.model.generateContent({ + contents: [{ + role: "user", + parts: [{ text: `${systemPrompt}\n\n${userPrompt}` }] + }] + }); + + const response = result.response; + const functionCalls = response.functionCalls(); + + if (functionCalls && functionCalls.length > 0) { + const hasDocumentationSearch = functionCalls.some((call: any) => + call.name === 'searchDocumentation' || call.name === 'getRelevantDocumentation' + ); + + if (!hasDocumentationSearch && this.documentationSearchRequired) { + console.warn('Error analysis proceeded without mandatory documentation search'); + } + + const functionResponses = await Promise.all( + functionCalls.map(async (call: any) => { + return await this.handleFunctionCall(call); + }) + ); + + const followUpResult = await this.model.generateContent({ + contents: [ + { role: "user", parts: [{ text: `${systemPrompt}\n\n${userPrompt}` }] }, + { role: "model", parts: response.candidates[0].content.parts }, + { + role: "function", + parts: functionResponses.map((resp, index) => ({ + functionResponse: { + name: functionCalls[index].name, + response: resp + } + })) + } + ] + }); + + return this.parseErrorAnalysisResponse(followUpResult.response.text()); + } else { + console.warn('No function calls made for error analysis - documentation search was not attempted'); + const response_text = response.text(); + const parsed = this.parseErrorAnalysisResponse(response_text); + + parsed.analysis += '\n\n⚠️ Note: Documentation search was not available. For more accurate error diagnosis, please check docs.fuel.network/docs/sway/reference/ for compiler messages and syntax reference.'; + + return parsed; + } + } catch (error) { + console.error('AI error analysis error:', error); + throw new Error('Failed to analyze error. Please try again.'); + } + } + + + private async handleFunctionCall(call: any): Promise { + try { + switch (call.name) { + case 'searchDocumentation': + if (this.mcpService && this.mcpService.isAvailable()) { + const result = await this.mcpService.searchDocs(call.args); + if (typeof result === 'string' && result.length > 2000) { + return result.substring(0, 2000) + '\n... (truncated for brevity)'; + } + if (result && typeof result === 'object' && result.results) { + const limitedResults = result.results.slice(0, 3).map((r: any) => ({ + ...r, + content: r.content?.substring(0, 500) + (r.content?.length > 500 ? '...' : '') + })); + return { ...result, results: limitedResults }; + } + return result; + } + return { + error: 'MCP not available', + fallback: `Check docs.fuel.network for "${call.args.query}"` + }; + + case 'getRelevantDocumentation': + if (this.mcpService && this.mcpService.isAvailable()) { + const result = await this.mcpService.getRelevantDocs(call.args.topic); + if (typeof result === 'string' && result.length > 1500) { + return { context: result.substring(0, 1500) + '... (truncated)' }; + } + return { context: result }; + } + return { + error: 'MCP not available', + fallback: `Check docs.fuel.network for "${call.args.topic}"` + }; + + default: + return { error: `Unknown function: ${call.name}` }; + } + } catch (error) { + console.error(`Function call error (${call.name}):`, error); + return { + error: `Failed to execute ${call.name}`, + fallback: `Check docs.fuel.network manually.` + }; + } + } + + private parseCodeGenerationResponse(response: string): SwayCodeGenerationResponse { + const codeMatch = response.match(/```(?:sway|rust)?\n([\s\S]*?)```/); + const code = codeMatch ? codeMatch[1].trim() : response; + + const explanation = response.replace(/```(?:sway|rust)?\n[\s\S]*?```/g, '').trim(); + + if (!code || code.length === 0) { + console.warn('⚠️ EMPTY CODE DETECTED'); + } + + const result = { + code, + explanation: explanation || "Generated Sway smart contract", + suggestions: [ + "Review the generated code for your specific requirements", + "Test the contract thoroughly before deployment", + "Consider gas optimization for complex operations" + ] + }; + + return result; + } + + private parseErrorAnalysisResponse(response: string): ErrorAnalysisResponse { + const codeMatch = response.match(/```(?:sway|rust)?\n([\s\S]*?)```/); + let fixedCode = codeMatch ? codeMatch[1].trim() : undefined; + + if (!fixedCode && response.length > 0) { + const partialCodeMatch = response.match(/```(?:sway|rust)?\n([\s\S]*?)$/); + if (partialCodeMatch) { + fixedCode = partialCodeMatch[1].trim(); + } + } + + let analysis = response; + if (!fixedCode) { + console.warn('No fixed code found in AI response'); + analysis += '\n\n**Incomplete Response**: The AI response was truncated and does not contain the complete fixed code. Please try again or manually apply the suggested fixes.'; + } + + return { + analysis, + suggestions: [ + "Verify the fix addresses the root cause", + "Check for similar patterns in your code", + "Consider adding tests to prevent regression" + ], + fixedCode + }; + } +} \ No newline at end of file diff --git a/ai-backend/src/services/mcpService.ts b/ai-backend/src/services/mcpService.ts new file mode 100644 index 0000000..fbf50ba --- /dev/null +++ b/ai-backend/src/services/mcpService.ts @@ -0,0 +1,163 @@ +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; +import { SearchDocsRequest, SearchDocsResponse } from '../types'; +import * as dotenv from 'dotenv'; +dotenv.config(); + +export class MCPService { + private client: Client | null = null; + private transport: StdioClientTransport | null = null; + private isInitialized = false; + private initializationPromise: Promise | null = null; + + constructor( + private mcpPath: string, + private vectraIndexPath: string + ) {} + + async initialize(): Promise { + if (this.isInitialized || !this.mcpPath) { + return; + } + + try { + + // Create transport with server configuration + this.transport = new StdioClientTransport({ + command: 'bun', + args: ['run', this.mcpPath], + env: { + ...process.env, + VECTRA_INDEX_PATH: this.vectraIndexPath + } + }); + + // Create MCP client + this.client = new Client({ + name: "sway-playground-backend", + version: "1.0.0" + }); + + // Connect with timeout + const connectPromise = this.client.connect(this.transport); + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => reject(new Error('MCP connection timeout')), 10000); + }); + + await Promise.race([connectPromise, timeoutPromise]); + + this.isInitialized = true; + } catch (error) { + this.cleanup(); + throw error; + } + } + + + async searchDocs(request: SearchDocsRequest): Promise { + try { + // Ensure service is initialized + if (this.initializationPromise) { + await this.initializationPromise; + } else if (!this.isInitialized) { + this.initializationPromise = this.initialize(); + await this.initializationPromise; + } + + if (!this.isAvailable()) { + throw new Error('MCP service not available'); + } + const result = await this.client!.callTool({ + name: 'searchFuelDocs', + arguments: { + query: request.query, + } + }); + + + return { + results: Array.isArray(result.content) ? result.content : [] + }; + } catch (error) { + throw new Error('Failed to search documentation'); + } + } + + async getRelevantDocs(swayQuery: string): Promise { + try { + const searchResult = await this.searchDocs({ + query: swayQuery, + maxResults: 3 + }); + + if (searchResult.results.length === 0) { + return ''; + } + + // Combine relevant documentation into context string + const context = searchResult.results + .map(result => `## ${result.title}\n${result.content}`) + .join('\n\n'); + + return context; + } catch (error) { + return ''; + } + } + + async getStdContext(): Promise { + try { + // Ensure service is initialized + if (this.initializationPromise) { + await this.initializationPromise; + } else if (!this.isInitialized) { + this.initializationPromise = this.initialize(); + await this.initializationPromise; + } + + if (!this.isAvailable()) { + throw new Error('MCP service not available'); + } + + const result = await this.client!.callTool({ + name: 'provideStdContext', + arguments: {} + }); + + // Parse the MCP response content + let contextContent = ''; + if (result.content && Array.isArray(result.content)) { + contextContent = result.content + .map((item: any) => item.text || item.content || '') + .join('\n\n'); + } + + return contextContent; + } catch (error) { + return ''; + } + } + + isAvailable(): boolean { + return this.isInitialized && + this.client !== null && + this.transport !== null; + } + + private cleanup(): void { + this.isInitialized = false; + this.initializationPromise = null; + + // Close transport if it exists + if (this.transport) { + this.transport.close().catch(console.error); + } + + this.client = null; + this.transport = null; + } + + destroy(): void { + this.cleanup(); + } +} \ No newline at end of file diff --git a/ai-backend/src/types.ts b/ai-backend/src/types.ts new file mode 100644 index 0000000..f1aed3d --- /dev/null +++ b/ai-backend/src/types.ts @@ -0,0 +1,50 @@ + +export interface MCPRequest { + method: string; + params?: any; +} + +export interface MCPResponse { + result?: any; + error?: { + code: number; + message: string; + }; +} + +export interface SearchDocsRequest { + query: string; + maxResults?: number; +} + +export interface SearchDocsResponse { + results: Array<{ + title: string; + content: string; + url?: string; + relevance?: number; + }>; +} + +export interface SwayCodeGenerationRequest { + prompt: string; +} + +export interface SwayCodeGenerationResponse { + code: string; + explanation: string; + suggestions: string[]; +} + +export interface ErrorAnalysisRequest { + errorMessage: string; + sourceCode: string; + lineNumber?: number; +} + +export interface ErrorAnalysisResponse { + analysis: string; + suggestions: string[]; + fixedCode?: string; +} + diff --git a/ai-backend/src/utils/errorHandler.ts b/ai-backend/src/utils/errorHandler.ts new file mode 100644 index 0000000..dee1371 --- /dev/null +++ b/ai-backend/src/utils/errorHandler.ts @@ -0,0 +1,18 @@ +import { Response } from 'express'; + +export function handleRouteError(res: Response, error: unknown, context: string): void { + console.error(`${context} error:`, error); + res.status(500).json({ + error: error instanceof Error ? error.message : `Failed to ${context.toLowerCase()}` + }); +} + +export function handleValidationError(res: Response, message: string): boolean { + res.status(400).json({ error: message }); + return false; +} + +export function handleServiceUnavailable(res: Response, service: string): boolean { + res.status(503).json({ error: `${service} service not available` }); + return false; +} \ No newline at end of file diff --git a/ai-backend/tsconfig.json b/ai-backend/tsconfig.json new file mode 100644 index 0000000..50e813d --- /dev/null +++ b/ai-backend/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "commonjs", + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file From 6b59cdab1f85deecc191b27f59fa9d11f49f5a89 Mon Sep 17 00:00:00 2001 From: PraneshASP Date: Thu, 3 Jul 2025 21:19:10 +0530 Subject: [PATCH 02/25] feat: add frontend conponents for AI Assist --- app/.env.example | 11 + app/.gitignore | 2 + app/package-lock.json | 2584 ++++++++++++++++- app/package.json | 5 + app/src/App.tsx | 58 +- app/src/constants.ts | 4 + .../ai/components/AIGenerationDialog.tsx | 255 ++ .../ai/components/FixWithAIButton.tsx | 277 ++ .../ai/components/MarkdownRenderer.tsx | 110 + app/src/features/ai/hooks/useAIGeneration.tsx | 34 + app/src/features/ai/hooks/useAIService.ts | 90 + .../features/ai/hooks/useErrorAnalysis.tsx | 55 + app/src/features/ai/hooks/useFuelDocs.tsx | 92 + app/src/features/editor/hooks/useCompile.tsx | 20 +- .../toolbar/components/ActionToolbar.tsx | 15 +- app/src/hooks/useCopyToClipboard.ts | 31 + app/src/services/aiService.ts | 35 + app/src/services/apiService.ts | 120 + app/src/services/mcpService.ts | 44 + app/src/utils/aiHelpers.ts | 16 + 20 files changed, 3813 insertions(+), 45 deletions(-) create mode 100644 app/.env.example create mode 100644 app/src/features/ai/components/AIGenerationDialog.tsx create mode 100644 app/src/features/ai/components/FixWithAIButton.tsx create mode 100644 app/src/features/ai/components/MarkdownRenderer.tsx create mode 100644 app/src/features/ai/hooks/useAIGeneration.tsx create mode 100644 app/src/features/ai/hooks/useAIService.ts create mode 100644 app/src/features/ai/hooks/useErrorAnalysis.tsx create mode 100644 app/src/features/ai/hooks/useFuelDocs.tsx create mode 100644 app/src/hooks/useCopyToClipboard.ts create mode 100644 app/src/services/aiService.ts create mode 100644 app/src/services/apiService.ts create mode 100644 app/src/services/mcpService.ts create mode 100644 app/src/utils/aiHelpers.ts diff --git a/app/.env.example b/app/.env.example new file mode 100644 index 0000000..ab1d47a --- /dev/null +++ b/app/.env.example @@ -0,0 +1,11 @@ +# Sway Playground Environment Configuration + +# Server Configuration +REACT_APP_SERVER_API=https://api.sway-playground.fuel.network +REACT_APP_LOCAL_SERVER=false + +REACT_APP_AI_FEATURES_ENABLED=false +REACT_APP_AI_BACKEND_URL=http://localhost:3002 + +# Analytics +REACT_APP_VERCEL_ANALYTICS=false \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore index 4d29575..8b182cf 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -21,3 +21,5 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + +.env diff --git a/app/package-lock.json b/app/package-lock.json index f1a02b1..b7bdec4 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -14,6 +14,7 @@ "@fuel-ui/react": "^0.23.3", "@fuels/connectors": "0.5.0", "@fuels/react": "0.36.0", + "@google/genai": "^1.5.1", "@mui/base": "^5.0.0-beta.2", "@mui/icons-material": "^5.11.16", "@mui/lab": "^5.0.0-alpha.46", @@ -28,8 +29,11 @@ "react": "^18.2.0", "react-ace": "^10.1.0", "react-dom": "^18.2.0", + "react-markdown": "^10.1.0", "react-router-dom": "^6.23.0", "react-scripts": "^5.0.1", + "react-syntax-highlighter": "^15.6.1", + "remark": "^15.0.1", "typescript": "^5.4.5", "web-vitals": "^2.1.4" }, @@ -43,6 +47,7 @@ "@types/node": "^16.18.32", "@types/react": "^18.2.6", "@types/react-dom": "^18.2.4", + "@types/react-syntax-highlighter": "^15.5.13", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", "prettier": "^3.3.1", @@ -4409,6 +4414,64 @@ "resolved": "https://registry.npmjs.org/@fuels/vm-asm/-/vm-asm-0.58.0.tgz", "integrity": "sha512-tfarairW3IAtyoAIL3I5EJiUQzKAsY4J+eLgZg58B7+itDxqF+CUEpKanmiUnt1mBgry5GwtZsPIrUJ7OgTcDA==" }, + "node_modules/@google/genai": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.5.1.tgz", + "integrity": "sha512-9SKpNo5iqvB622lN3tSCbeuiLGTcStRd+3muOrI9pZMpzfLDc/xC7dWIJd5kK+4AZuY28nsvQmCZe0fPj3JUew==", + "dependencies": { + "google-auth-library": "^9.14.2", + "ws": "^8.18.0", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.4" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.11.0" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + } + } + }, + "node_modules/@google/genai/node_modules/ws": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@google/genai/node_modules/zod": { + "version": "3.25.64", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.64.tgz", + "integrity": "sha512-hbP9FpSZf7pkS7hRVUrOjhwKJNyampPgtXKc3AN6DsWtoHsg2Sb4SQaS4Tcay380zSwd2VPo9G9180emBACp5g==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@google/genai/node_modules/zod-to-json-schema": { + "version": "3.24.5", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", + "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "peerDependencies": { + "zod": "^3.24.1" + } + }, "node_modules/@graphql-typed-document-node/core": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", @@ -6609,7 +6672,8 @@ "node_modules/@parcel/watcher-wasm/node_modules/napi-wasm": { "version": "1.1.0", "inBundle": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@parcel/watcher-win32-arm64": { "version": "2.4.1", @@ -13135,7 +13199,6 @@ "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", - "peer": true, "dependencies": { "@types/ms": "*" } @@ -13169,6 +13232,14 @@ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "dependencies": { + "@types/estree": "*" + } + }, "node_modules/@types/express": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", @@ -13199,6 +13270,14 @@ "@types/node": "*" } }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -13258,6 +13337,14 @@ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -13266,8 +13353,7 @@ "node_modules/@types/ms": { "version": "0.7.34", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", - "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", - "peer": true + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" }, "node_modules/@types/node": { "version": "16.18.95", @@ -13329,6 +13415,15 @@ "@types/react": "*" } }, + "node_modules/@types/react-syntax-highlighter": { + "version": "15.5.13", + "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz", + "integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-transition-group": { "version": "4.4.10", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", @@ -13418,6 +13513,11 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==" }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" + }, "node_modules/@types/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", @@ -15681,6 +15781,15 @@ "babel-plugin-transform-react-remove-prop-types": "^0.4.24" } }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -15981,6 +16090,11 @@ "ieee754": "^1.2.1" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -16190,6 +16304,15 @@ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", "peer": true }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -16219,6 +16342,42 @@ "node": ">=10" } }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/check-types": { "version": "11.2.3", "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.3.tgz", @@ -16730,6 +16889,15 @@ "node": ">= 0.8" } }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/command-exists": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", @@ -17636,6 +17804,18 @@ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" }, + "node_modules/decode-named-character-reference": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", + "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/decode-uri-component": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", @@ -17881,6 +18061,18 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -18079,6 +18271,14 @@ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/eciesjs": { "version": "0.3.20", "resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.3.20.tgz", @@ -19330,6 +19530,15 @@ "node": ">=4.0" } }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/estree-walker": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", @@ -19621,6 +19830,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, "node_modules/extension-port-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/extension-port-stream/-/extension-port-stream-3.0.0.tgz", @@ -19726,6 +19940,18 @@ "reusify": "^1.0.4" } }, + "node_modules/fault": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", + "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/faye-websocket": { "version": "0.11.4", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", @@ -19981,6 +20207,14 @@ "node": ">= 6" } }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -20272,6 +20506,66 @@ "node": ">=8" } }, + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gaxios/node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/gaxios/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gaxios/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/gcp-metadata": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", + "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "dependencies": { + "gaxios": "^6.1.1", + "google-logging-utils": "^0.0.2", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -20481,6 +20775,30 @@ "csstype": "^3.0.10" } }, + "node_modules/google-auth-library": { + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", + "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-logging-utils": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", + "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", + "engines": { + "node": ">=14" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -20546,6 +20864,18 @@ "graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" } }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/gzip-size": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", @@ -20700,6 +21030,112 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-parse-selector": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", + "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz", + "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^1.0.0", + "hast-util-parse-selector": "^2.0.0", + "property-information": "^5.0.0", + "space-separated-tokens": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript/node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/hastscript/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/hastscript/node_modules/comma-separated-tokens": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", + "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hastscript/node_modules/property-information": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", + "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", + "dependencies": { + "xtend": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hastscript/node_modules/space-separated-tokens": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", + "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -20729,6 +21165,19 @@ "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==", "peer": true }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "engines": { + "node": "*" + } + }, + "node_modules/highlightjs-vue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz", + "integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==" + }, "node_modules/hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -20863,6 +21312,15 @@ "node": ">= 12" } }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/html-webpack-plugin": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz", @@ -21212,6 +21670,11 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, + "node_modules/inline-style-parser": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==" + }, "node_modules/internal-slot": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", @@ -21261,6 +21724,28 @@ "url": "https://github.com/sponsors/brc-dd" } }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -21397,6 +21882,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-directory": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", @@ -21480,6 +21974,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-inside-container": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", @@ -24321,6 +24824,14 @@ "node": ">=4" } }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -24443,6 +24954,25 @@ "node": ">=4.0" } }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keccak": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.4.tgz", @@ -25035,6 +25565,15 @@ "node": ">=6" } }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -25054,6 +25593,19 @@ "tslib": "^2.0.3" } }, + "node_modules/lowlight": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", + "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==", + "dependencies": { + "fault": "^1.0.0", + "highlight.js": "~10.7.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -25106,6 +25658,151 @@ "integrity": "sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==", "peer": true }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdn-data": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", @@ -26158,6 +26855,427 @@ "integrity": "sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg==", "peer": true }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, "node_modules/micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", @@ -27156,6 +28274,29 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -28802,6 +29943,14 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "engines": { + "node": ">=6" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -28853,6 +30002,15 @@ "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==" }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -29704,6 +30862,32 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, "node_modules/react-native": { "version": "0.75.4", "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.75.4.tgz", @@ -30563,6 +31747,22 @@ } } }, + "node_modules/react-syntax-highlighter": { + "version": "15.6.1", + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.6.1.tgz", + "integrity": "sha512-OqJ2/vL7lEeV5zTJyG7kmARppUjiB9h9udl4qHQjjgEos66z00Ia0OckwYfRxCSFrW8RJIBnsBwQsHZbVPspqg==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "highlight.js": "^10.4.1", + "highlightjs-vue": "^1.0.0", + "lowlight": "^1.17.0", + "prismjs": "^1.27.0", + "refractor": "^3.6.0" + }, + "peerDependencies": { + "react": ">= 0.14.0" + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -30713,6 +31913,112 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/refractor": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz", + "integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==", + "dependencies": { + "hastscript": "^6.0.0", + "parse-entities": "^2.0.0", + "prismjs": "~1.27.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/prismjs": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", + "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==", + "engines": { + "node": ">=6" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -30807,6 +32113,66 @@ "node": ">= 0.10" } }, + "node_modules/remark": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/remark/-/remark-15.0.1.tgz", + "integrity": "sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==", + "dependencies": { + "@types/mdast": "^4.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/renderkid": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", @@ -32085,6 +33451,15 @@ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", "deprecated": "Please use @jridgewell/sourcemap-codec instead" }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/spdy": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", @@ -32473,6 +33848,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/stringify-object": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", @@ -32591,6 +33979,22 @@ "webpack": "^5.0.0" } }, + "node_modules/style-to-js": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.16.tgz", + "integrity": "sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==", + "dependencies": { + "style-to-object": "1.0.8" + } + }, + "node_modules/style-to-object": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", + "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, "node_modules/stylehacks": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", @@ -33348,6 +34752,24 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/tryer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", @@ -33636,6 +35058,35 @@ "node": ">=4" } }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unified/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/unique-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", @@ -33647,6 +35098,69 @@ "node": ">=8" } }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -34009,6 +35523,32 @@ "node": ">= 0.8" } }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/viem": { "version": "2.10.2", "resolved": "https://registry.npmjs.org/viem/-/viem-2.10.2.tgz", @@ -35176,7 +36716,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "peer": true, "engines": { "node": ">=0.4" } @@ -35323,6 +36862,15 @@ "optional": true } } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } }, "dependencies": { @@ -38159,6 +39707,36 @@ "resolved": "https://registry.npmjs.org/@fuels/vm-asm/-/vm-asm-0.58.0.tgz", "integrity": "sha512-tfarairW3IAtyoAIL3I5EJiUQzKAsY4J+eLgZg58B7+itDxqF+CUEpKanmiUnt1mBgry5GwtZsPIrUJ7OgTcDA==" }, + "@google/genai": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.5.1.tgz", + "integrity": "sha512-9SKpNo5iqvB622lN3tSCbeuiLGTcStRd+3muOrI9pZMpzfLDc/xC7dWIJd5kK+4AZuY28nsvQmCZe0fPj3JUew==", + "requires": { + "google-auth-library": "^9.14.2", + "ws": "^8.18.0", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.4" + }, + "dependencies": { + "ws": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "requires": {} + }, + "zod": { + "version": "3.25.64", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.64.tgz", + "integrity": "sha512-hbP9FpSZf7pkS7hRVUrOjhwKJNyampPgtXKc3AN6DsWtoHsg2Sb4SQaS4Tcay380zSwd2VPo9G9180emBACp5g==" + }, + "zod-to-json-schema": { + "version": "3.24.5", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", + "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "requires": {} + } + } + }, "@graphql-typed-document-node/core": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", @@ -39659,7 +41237,8 @@ "dependencies": { "napi-wasm": { "version": "1.1.0", - "bundled": true + "bundled": true, + "peer": true } } }, @@ -44323,7 +45902,6 @@ "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", - "peer": true, "requires": { "@types/ms": "*" } @@ -44357,6 +45935,14 @@ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" }, + "@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "requires": { + "@types/estree": "*" + } + }, "@types/express": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", @@ -44387,6 +45973,14 @@ "@types/node": "*" } }, + "@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "requires": { + "@types/unist": "*" + } + }, "@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -44446,6 +46040,14 @@ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" }, + "@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "requires": { + "@types/unist": "*" + } + }, "@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -44454,8 +46056,7 @@ "@types/ms": { "version": "0.7.34", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", - "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", - "peer": true + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" }, "@types/node": { "version": "16.18.95", @@ -44517,6 +46118,15 @@ "@types/react": "*" } }, + "@types/react-syntax-highlighter": { + "version": "15.5.13", + "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz", + "integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/react-transition-group": { "version": "4.4.10", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", @@ -44606,6 +46216,11 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==" }, + "@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" + }, "@types/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", @@ -46387,6 +48002,11 @@ "babel-plugin-transform-react-remove-prop-types": "^0.4.24" } }, + "bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==" + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -46595,6 +48215,11 @@ "ieee754": "^1.2.1" } }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -46741,6 +48366,11 @@ } } }, + "ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==" + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -46763,6 +48393,26 @@ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==" }, + "character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==" + }, + "character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==" + }, + "character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==" + }, + "character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==" + }, "check-types": { "version": "11.2.3", "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.3.tgz", @@ -47126,6 +48776,11 @@ "delayed-stream": "~1.0.0" } }, + "comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==" + }, "command-exists": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", @@ -47774,6 +49429,14 @@ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" }, + "decode-named-character-reference": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", + "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", + "requires": { + "character-entities": "^2.0.0" + } + }, "decode-uri-component": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", @@ -47956,6 +49619,14 @@ } } }, + "devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "requires": { + "dequal": "^2.0.0" + } + }, "didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -48116,6 +49787,14 @@ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "eciesjs": { "version": "0.3.20", "resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.3.20.tgz", @@ -49037,6 +50716,11 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" }, + "estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==" + }, "estree-walker": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", @@ -49278,6 +50962,11 @@ } } }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, "extension-port-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/extension-port-stream/-/extension-port-stream-3.0.0.tgz", @@ -49355,6 +51044,14 @@ "reusify": "^1.0.4" } }, + "fault": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", + "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", + "requires": { + "format": "^0.2.0" + } + }, "faye-websocket": { "version": "0.11.4", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", @@ -49542,6 +51239,11 @@ "mime-types": "^2.1.12" } }, + "format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==" + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -49744,6 +51446,49 @@ "integrity": "sha512-SewY5KdMpaoCeh7jachEWFsh1nNlaDjNHZXWqL5IGwtpEYHTgkr2+AMCgNwKWkcc0wpSYrZfR7he4WdmHFtDxQ==", "peer": true }, + "gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "requires": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "dependencies": { + "agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==" + }, + "https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "requires": { + "agent-base": "^7.1.2", + "debug": "4" + } + }, + "uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" + } + } + }, + "gcp-metadata": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", + "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "requires": { + "gaxios": "^6.1.1", + "google-logging-utils": "^0.0.2", + "json-bigint": "^1.0.0" + } + }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -49887,6 +51632,24 @@ "integrity": "sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==", "requires": {} }, + "google-auth-library": { + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", + "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "requires": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + } + }, + "google-logging-utils": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", + "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==" + }, "gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -49939,6 +51702,15 @@ "tslib": "^2.1.0" } }, + "gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "requires": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + } + }, "gzip-size": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", @@ -50048,6 +51820,86 @@ "function-bind": "^1.1.2" } }, + "hast-util-parse-selector": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", + "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==" + }, + "hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "requires": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + } + }, + "hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "requires": { + "@types/hast": "^3.0.0" + } + }, + "hastscript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz", + "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==", + "requires": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^1.0.0", + "hast-util-parse-selector": "^2.0.0", + "property-information": "^5.0.0", + "space-separated-tokens": "^1.0.0" + }, + "dependencies": { + "@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "requires": { + "@types/unist": "^2" + } + }, + "@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "comma-separated-tokens": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", + "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==" + }, + "property-information": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", + "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", + "requires": { + "xtend": "^4.0.0" + } + }, + "space-separated-tokens": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", + "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==" + } + } + }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -50074,6 +51926,16 @@ "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==", "peer": true }, + "highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==" + }, + "highlightjs-vue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz", + "integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==" + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -50189,6 +52051,11 @@ } } }, + "html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==" + }, "html-webpack-plugin": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz", @@ -50409,6 +52276,11 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, + "inline-style-parser": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==" + }, "internal-slot": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", @@ -50449,6 +52321,20 @@ "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", "peer": true }, + "is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==" + }, + "is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "requires": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + } + }, "is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -50534,6 +52420,11 @@ "has-tostringtag": "^1.0.0" } }, + "is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==" + }, "is-directory": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", @@ -50584,6 +52475,11 @@ "is-extglob": "^2.1.1" } }, + "is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==" + }, "is-inside-container": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", @@ -52634,6 +54530,14 @@ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" }, + "json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "requires": { + "bignumber.js": "^9.0.0" + } + }, "json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -52736,6 +54640,25 @@ "object.values": "^1.1.6" } }, + "jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "requires": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "keccak": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.4.tgz", @@ -53217,6 +55140,11 @@ } } }, + "longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==" + }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -53233,6 +55161,15 @@ "tslib": "^2.0.3" } }, + "lowlight": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", + "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==", + "requires": { + "fault": "^1.0.0", + "highlight.js": "~10.7.0" + } + }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -53276,6 +55213,119 @@ "integrity": "sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==", "peer": true }, + "mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "requires": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + } + }, + "mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "requires": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + } + }, + "mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "requires": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + } + }, + "mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "requires": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + } + }, + "mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "requires": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + } + }, + "mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "requires": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + } + }, + "mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "requires": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + } + }, + "mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "requires": { + "@types/mdast": "^4.0.0" + } + }, "mdn-data": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", @@ -54120,6 +56170,217 @@ "integrity": "sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg==", "peer": true }, + "micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "requires": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "requires": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "requires": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "requires": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "requires": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "requires": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "requires": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "requires": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "requires": { + "micromark-util-symbol": "^2.0.0" + } + }, + "micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "requires": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "requires": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "requires": { + "micromark-util-symbol": "^2.0.0" + } + }, + "micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "requires": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==" + }, + "micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==" + }, + "micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "requires": { + "micromark-util-symbol": "^2.0.0" + } + }, + "micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "requires": { + "micromark-util-types": "^2.0.0" + } + }, + "micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "requires": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "requires": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==" + }, + "micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==" + }, "micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", @@ -54844,6 +57105,27 @@ "callsites": "^3.0.0" } }, + "parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "requires": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "dependencies": { + "@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + } + } + }, "parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -55862,6 +58144,11 @@ } } }, + "prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==" + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -55912,6 +58199,11 @@ "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==" }, + "property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==" + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -56553,6 +58845,24 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, + "react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "requires": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + } + }, "react-native": { "version": "0.75.4", "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.75.4.tgz", @@ -57199,6 +59509,19 @@ "tslib": "^2.0.0" } }, + "react-syntax-highlighter": { + "version": "15.6.1", + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.6.1.tgz", + "integrity": "sha512-OqJ2/vL7lEeV5zTJyG7kmARppUjiB9h9udl4qHQjjgEos66z00Ia0OckwYfRxCSFrW8RJIBnsBwQsHZbVPspqg==", + "requires": { + "@babel/runtime": "^7.3.1", + "highlight.js": "^10.4.1", + "highlightjs-vue": "^1.0.0", + "lowlight": "^1.17.0", + "prismjs": "^1.27.0", + "refractor": "^3.6.0" + } + }, "react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -57319,6 +59642,75 @@ "which-builtin-type": "^1.1.3" } }, + "refractor": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz", + "integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==", + "requires": { + "hastscript": "^6.0.0", + "parse-entities": "^2.0.0", + "prismjs": "~1.27.0" + }, + "dependencies": { + "character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==" + }, + "character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==" + }, + "character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==" + }, + "is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==" + }, + "is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "requires": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + } + }, + "is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==" + }, + "is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==" + }, + "parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "requires": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + } + }, + "prismjs": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", + "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==" + } + } + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -57394,6 +59786,50 @@ "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==" }, + "remark": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/remark/-/remark-15.0.1.tgz", + "integrity": "sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==", + "requires": { + "@types/mdast": "^4.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + } + }, + "remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "requires": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + } + }, + "remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "requires": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + } + }, + "remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "requires": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + } + }, "renderkid": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", @@ -58331,6 +60767,11 @@ "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" }, + "space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==" + }, "spdy": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", @@ -58631,6 +61072,15 @@ "es-object-atoms": "^1.0.0" } }, + "stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "requires": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + } + }, "stringify-object": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", @@ -58705,6 +61155,22 @@ "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==", "requires": {} }, + "style-to-js": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.16.tgz", + "integrity": "sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==", + "requires": { + "style-to-object": "1.0.8" + } + }, + "style-to-object": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", + "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", + "requires": { + "inline-style-parser": "0.2.4" + } + }, "stylehacks": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", @@ -59273,6 +61739,16 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==" + }, + "trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==" + }, "tryer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", @@ -59489,6 +61965,27 @@ "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==" }, + "unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "requires": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "dependencies": { + "is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==" + } + } + }, "unique-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", @@ -59497,6 +61994,49 @@ "crypto-random-string": "^2.0.0" } }, + "unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "requires": { + "@types/unist": "^3.0.0" + } + }, + "unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "requires": { + "@types/unist": "^3.0.0" + } + }, + "unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "requires": { + "@types/unist": "^3.0.0" + } + }, + "unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "requires": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + } + }, + "unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "requires": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + } + }, "universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -59704,6 +62244,24 @@ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" }, + "vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "requires": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + } + }, + "vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "requires": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + } + }, "viem": { "version": "2.10.2", "resolved": "https://registry.npmjs.org/viem/-/viem-2.10.2.tgz", @@ -60599,8 +63157,7 @@ "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "peer": true + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" }, "y18n": { "version": "5.0.8", @@ -60696,6 +63253,11 @@ "requires": { "use-sync-external-store": "1.2.0" } + }, + "zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==" } } } diff --git a/app/package.json b/app/package.json index 731d77d..eb33b66 100644 --- a/app/package.json +++ b/app/package.json @@ -9,6 +9,7 @@ "@fuel-ui/react": "^0.23.3", "@fuels/connectors": "0.5.0", "@fuels/react": "0.36.0", + "@google/genai": "^1.5.1", "@mui/base": "^5.0.0-beta.2", "@mui/icons-material": "^5.11.16", "@mui/lab": "^5.0.0-alpha.46", @@ -23,8 +24,11 @@ "react": "^18.2.0", "react-ace": "^10.1.0", "react-dom": "^18.2.0", + "react-markdown": "^10.1.0", "react-router-dom": "^6.23.0", "react-scripts": "^5.0.1", + "react-syntax-highlighter": "^15.6.1", + "remark": "^15.0.1", "typescript": "^5.4.5", "web-vitals": "^2.1.4" }, @@ -38,6 +42,7 @@ "@types/node": "^16.18.32", "@types/react": "^18.2.6", "@types/react-dom": "^18.2.4", + "@types/react-syntax-highlighter": "^15.5.13", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", "prettier": "^3.3.1", diff --git a/app/src/App.tsx b/app/src/App.tsx index 61f39dc..c7f5d7f 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -22,61 +22,37 @@ import { useGist } from "./features/editor/hooks/useGist"; import { useSearchParams } from "react-router-dom"; import Copyable from "./components/Copyable"; import useTheme from "./context/theme"; +import { AIGenerationDialog } from "./features/ai/components/AIGenerationDialog"; +import { AI_FEATURES_ENABLED } from "./constants"; const DRAWER_WIDTH = "40vw"; function App() { - // The current sway code in the editor. const [swayCode, setSwayCode] = useState(loadSwayCode()); - - // The current solidity code in the editor. const [solidityCode, setSolidityCode] = useState(loadSolidityCode()); - - // An error message to display to the user. const [showSolidity, setShowSolidity] = useState(false); - - // The most recent code that the user has requested to compile. const [codeToCompile, setCodeToCompile] = useState( undefined, ); - - // The most recent code that the user has requested to transpile. const [codeToTranspile, setCodeToTranspile] = useState( undefined, ); - - // Whether or not the current code in the editor has been compiled. const [isCompiled, setIsCompiled] = useState(false); - - // The toolchain to use for compilation. const [toolchain, setToolchain] = useState("testnet"); - - // The deployment state const [deployState, setDeployState] = useState(DeployState.NOT_DEPLOYED); - - // Functions for reading and writing to the log output. const [log, updateLog] = useLog(); - - // The contract ID of the deployed contract. const [contractId, setContractId] = useState(""); - - // An error message to display to the user. const [drawerOpen, setDrawerOpen] = useState(false); - - // The query parameters for the current URL. const [searchParams] = useSearchParams(); - - // The theme color for the app. const { themeColor } = useTheme(); + const [aiDialogOpen, setAiDialogOpen] = useState(false); - // If showSolidity is toggled on, reset the compiled state. useEffect(() => { if (showSolidity) { setIsCompiled(false); } }, [showSolidity]); - // Load the query parameters from the URL and set the state accordingly. Gists are loaded in useGist. useEffect(() => { if (searchParams.get("transpile") === "true") { setShowSolidity(true); @@ -106,7 +82,6 @@ function App() { [setSolidityCode], ); - // Loading shared code by query parameter and get a function for creating sharable permalinks. const { newGist } = useGist(onSwayCodeChange, onSolidityCodeChange); const setError = useCallback( @@ -144,7 +119,6 @@ function App() { const onCompileClick = useCallback(() => { track("Compile Click", { toolchain }); if (showSolidity) { - // Transpile the Solidity code before compiling. track("Transpile"); setCodeToTranspile(solidityCode); } else { @@ -159,6 +133,22 @@ function App() { toolchain, ]); + const onAIAssistClick = useCallback(() => { + track("AI Assist Click"); + setAiDialogOpen(true); + }, []); + + const onAICodeGenerated = useCallback((code: string) => { + track("AI Code Generated"); + onSwayCodeChange(code); + setAiDialogOpen(false); + }, [onSwayCodeChange]); + + const onAICodeFixed = useCallback((fixedCode: string) => { + track("AI Code Fixed"); + onSwayCodeChange(fixedCode); + }, [onSwayCodeChange]); + useTranspile( codeToTranspile, setCodeToCompile, @@ -166,7 +156,7 @@ function App() { setError, updateLog, ); - useCompile(codeToCompile, setError, setIsCompiled, updateLog, toolchain); + useCompile(codeToCompile, setError, setIsCompiled, updateLog, toolchain, onAICodeFixed); return (
+ {AI_FEATURES_ENABLED && ( + setAiDialogOpen(false)} + onCodeGenerated={onAICodeGenerated} + /> + )}
); diff --git a/app/src/constants.ts b/app/src/constants.ts index db7d65a..87d237f 100644 --- a/app/src/constants.ts +++ b/app/src/constants.ts @@ -7,3 +7,7 @@ export const LOCAL_SERVER_URI = "http://0.0.0.0:8080"; export const SERVER_URI = process.env.REACT_APP_LOCAL_SERVER ? LOCAL_SERVER_URI : SERVER_API; + +// AI Configuration +export const AI_BACKEND_URL = process.env.REACT_APP_AI_BACKEND_URL || 'http://localhost:3001'; +export const AI_FEATURES_ENABLED = process.env.REACT_APP_AI_FEATURES_ENABLED === 'true'; diff --git a/app/src/features/ai/components/AIGenerationDialog.tsx b/app/src/features/ai/components/AIGenerationDialog.tsx new file mode 100644 index 0000000..d445062 --- /dev/null +++ b/app/src/features/ai/components/AIGenerationDialog.tsx @@ -0,0 +1,255 @@ +import { useState } from 'react'; +import { useCopyToClipboard } from '../../../hooks/useCopyToClipboard'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + TextField, + Button, + Box, + Typography, + CircularProgress, + Alert, + Divider, + Paper, +} from '@mui/material'; +import { styled } from '@mui/material/styles'; +import AutoAwesome from '@mui/icons-material/AutoAwesome'; +import ContentCopy from '@mui/icons-material/ContentCopy'; +import CheckCircle from '@mui/icons-material/CheckCircle'; +import { useAIGeneration } from '../hooks/useAIGeneration'; +import { SwayCodeGenerationRequest } from '../../../services/aiService'; +import { MarkdownRenderer } from './MarkdownRenderer'; +import { removeCodeBlocks } from '../../../utils/aiHelpers'; + +const StyledDialog = styled(Dialog)(() => ({ + '& .MuiPaper-root': { + borderRadius: '12px', + minWidth: '600px', + maxWidth: '800px', + }, +})); + +const CodePreview = styled(Paper)(() => ({ + backgroundColor: '#1e1e1e', + color: '#d4d4d4', + padding: '16px', + fontFamily: 'Monaco, Menlo, "Ubuntu Mono", monospace', + fontSize: '14px', + maxHeight: '400px', + overflow: 'auto', + border: '1px solid #333', + borderRadius: '8px', +})); + + +const GenerateButton = styled(Button)(() => ({ + background: 'linear-gradient(45deg, #00f58c, #00d4aa)', + color: '#000', + fontWeight: 600, + '&:hover': { + background: 'linear-gradient(45deg, #00d4aa, #00b894)', + }, + '&:disabled': { + background: '#333', + color: '#666', + }, +})); + +export interface AIGenerationDialogProps { + open: boolean; + onClose: () => void; + onCodeGenerated: (code: string) => void; +} + +export function AIGenerationDialog({ + open, + onClose, + onCodeGenerated, +}: AIGenerationDialogProps) { + const { state, generateCode, clearResult, isAvailable } = useAIGeneration(); + const [prompt, setPrompt] = useState(''); + const { copied, copyToClipboard, resetCopied } = useCopyToClipboard(); + + const handleGenerate = async () => { + if (!prompt.trim()) return; + + const request: SwayCodeGenerationRequest = { + prompt: prompt.trim(), + }; + + await generateCode(request); + }; + + const handleCopyCode = async () => { + if (state.result?.code) { + await copyToClipboard(state.result.code); + } + }; + + const handleUseCode = () => { + if (state.result?.code) { + onCodeGenerated(state.result.code); + handleClose(); + } + }; + + const handleClose = () => { + setPrompt(''); + resetCopied(); + clearResult(); + onClose(); + }; + + const isGenerating = state.isGenerating; + const hasResult = Boolean(state.result); + const hasError = Boolean(state.error); + + if (!isAvailable) { + return ( + + + + + AI Assistant + + + + + AI features are not available. Please configure your Gemini API key in the environment variables. + + + + + + + ); + } + + return ( + + + + + AI Code Generation + + + + + + {/* Input Form */} + + setPrompt(e.target.value)} + disabled={isGenerating} + variant="outlined" + /> + + + {/* Error Display */} + {hasError && ( + + {state.error} + + )} + + {/* Loading State */} + {isGenerating && ( + + + + Generating Sway contract... This may take a few moments. + + + )} + + {/* Generated Code */} + {hasResult && state.result && ( + + + + + Generated Contract + + + + + + + +
{state.result.code}
+
+ + {state.result.explanation && ( + + + Explanation: + + + + )} + + {state.result.suggestions && state.result.suggestions.length > 0 && ( + + + Suggestions: + + + {state.result.suggestions.map((suggestion, index) => ( + + {suggestion} + + ))} + + + )} +
+ )} +
+
+ + + + + + + {!hasResult && ( + : } + variant="contained" + > + Generate Contract + + )} + + {hasResult && ( + } + > + Use This Code + + )} + +
+ ); +} \ No newline at end of file diff --git a/app/src/features/ai/components/FixWithAIButton.tsx b/app/src/features/ai/components/FixWithAIButton.tsx new file mode 100644 index 0000000..2df300a --- /dev/null +++ b/app/src/features/ai/components/FixWithAIButton.tsx @@ -0,0 +1,277 @@ +import { useState } from 'react'; +import { useCopyToClipboard } from '../../../hooks/useCopyToClipboard'; +import { + Button, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Box, + Typography, + CircularProgress, + Alert, + Paper, + Divider, + Chip, +} from '@mui/material'; +import AutoFixHigh from '@mui/icons-material/AutoFixHigh'; +import ContentCopy from '@mui/icons-material/ContentCopy'; +import CheckCircle from '@mui/icons-material/CheckCircle'; +import Close from '@mui/icons-material/Close'; +import { useErrorAnalysis } from '../hooks/useErrorAnalysis'; +import { ErrorAnalysisRequest } from '../../../services/aiService'; +import { MarkdownRenderer } from './MarkdownRenderer'; +import { removeCodeBlocks } from '../../../utils/aiHelpers'; + + + +export interface FixWithAIButtonProps { + errorMessage: string; + sourceCode: string; + onCodeFixed: (fixedCode: string) => void; + disabled?: boolean; +} + +export function FixWithAIButton({ + errorMessage, + sourceCode, + onCodeFixed, + disabled = false, +}: FixWithAIButtonProps) { + const [dialogOpen, setDialogOpen] = useState(false); + const { copied, copyToClipboard, resetCopied } = useCopyToClipboard(); + + const { state, analyzeError, applyFix, clearResult, isAvailable } = useErrorAnalysis( + (fixedCode: string) => { + onCodeFixed(fixedCode); + setDialogOpen(false); + } + ); + + const handleFixClick = async () => { + if (!isAvailable) { + return; + } + + // Clear any previous results before starting new analysis + clearResult(); + resetCopied(); + + // Only open dialog if it's not already open (for initial click) + if (!dialogOpen) { + setDialogOpen(true); + } + + const request: ErrorAnalysisRequest = { + errorMessage, + sourceCode + }; + + await analyzeError(request); + }; + + const handleCopyFixed = async () => { + if (state.result?.fixedCode) { + await copyToClipboard(state.result.fixedCode); + } + }; + + const handleApplyFix = () => { + if (state.result?.fixedCode) { + applyFix(state.result.fixedCode); + } + }; + + const handleClose = () => { + setDialogOpen(false); + clearResult(); + resetCopied(); + }; + + if (!isAvailable) { + return null; + } + + return ( + <> + + + + + + + + AI Error Analysis & Fix + + + + + + + + {/* Error Display */} + + + Compilation Error: + + +
{errorMessage}
+
+
+ + {/* Loading State */} + {state.isAnalyzing && ( + + + + Analyzing error and generating fix... This may take a few moments. + + + )} + + {/* Error Display */} + {state.error && ( + + {state.error} + + )} + + {/* Analysis Results */} + {state.result && ( + + + AI Analysis & Solution + + + + + + + {/* Suggestions */} + {state.result.suggestions && state.result.suggestions.length > 0 && ( + + + Recommendations: + + + {state.result.suggestions.map((suggestion, index) => ( + + ))} + + + )} + + {/* Fixed Code */} + {state.result.fixedCode && ( + + + + Suggested Fix: + + + + + +
{state.result.fixedCode}
+
+
+ )} + + {/* Retry button if no fixed code found */} + {state.result && !state.result.fixedCode && !state.isAnalyzing && ( + + + The AI response didn't include fixed code. This might be due to response truncation. + + + + )} +
+ )} +
+
+ + + + + + + {state.result?.fixedCode && ( + + )} + + {state.result && !state.result.fixedCode && ( + + )} + +
+ + ); +} \ No newline at end of file diff --git a/app/src/features/ai/components/MarkdownRenderer.tsx b/app/src/features/ai/components/MarkdownRenderer.tsx new file mode 100644 index 0000000..79bedb1 --- /dev/null +++ b/app/src/features/ai/components/MarkdownRenderer.tsx @@ -0,0 +1,110 @@ +import React from 'react'; +import ReactMarkdown from 'react-markdown'; +import { Box, Typography, Paper } from '@mui/material'; +import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; +import { vs } from 'react-syntax-highlighter/dist/esm/styles/prism'; + +interface MarkdownComponentProps { + children?: React.ReactNode; + [key: string]: any; +} + +interface MarkdownRendererProps { + content: string; + borderColor?: string; +} + +const markdownComponents = { + code: ({ inline, className, children, ...props }: MarkdownComponentProps) => { + const match = /language-(\w+)/.exec(className || ''); + return !inline && match ? ( + + {String(children).replace(/\n$/, '')} + + ) : ( + + {children} + + ); + }, + p: ({ children }: MarkdownComponentProps) => ( + + {children} + + ), + h1: ({ children }: MarkdownComponentProps) => ( + + {children} + + ), + h2: ({ children }: MarkdownComponentProps) => ( + + {children} + + ), + h3: ({ children }: MarkdownComponentProps) => ( + + {children} + + ), + ul: ({ children }: MarkdownComponentProps) => ( + + {children} + + ), + ol: ({ children }: MarkdownComponentProps) => ( + + {children} + + ), + li: ({ children }: MarkdownComponentProps) => ( + + {children} + + ), +}; + +export function MarkdownRenderer({ content, borderColor = '#00f58c' }: MarkdownRendererProps) { + return ( + + + {content} + + + ); +} \ No newline at end of file diff --git a/app/src/features/ai/hooks/useAIGeneration.tsx b/app/src/features/ai/hooks/useAIGeneration.tsx new file mode 100644 index 0000000..22878ba --- /dev/null +++ b/app/src/features/ai/hooks/useAIGeneration.tsx @@ -0,0 +1,34 @@ +import { aiService, SwayCodeGenerationRequest, SwayCodeGenerationResponse } from '../../../services/aiService'; +import { useAIService } from './useAIService'; + +export interface AIGenerationState { + isGenerating: boolean; + result: SwayCodeGenerationResponse | null; + error: string | null; +} + +export interface UseAIGenerationReturn { + state: AIGenerationState; + generateCode: (request: SwayCodeGenerationRequest) => Promise; + clearResult: () => void; + isAvailable: boolean; +} + +export function useAIGeneration(): UseAIGenerationReturn { + const { state, execute, clearResult, isAvailable } = useAIService( + aiService.generateSwayCode.bind(aiService) + ); + + const transformedState: AIGenerationState = { + isGenerating: state.isLoading, + result: state.result, + error: state.error + }; + + return { + state: transformedState, + generateCode: execute, + clearResult, + isAvailable + }; +} \ No newline at end of file diff --git a/app/src/features/ai/hooks/useAIService.ts b/app/src/features/ai/hooks/useAIService.ts new file mode 100644 index 0000000..7ec8183 --- /dev/null +++ b/app/src/features/ai/hooks/useAIService.ts @@ -0,0 +1,90 @@ +import { useState, useCallback } from 'react'; +import { AI_FEATURES_ENABLED } from '../../../constants'; + +export interface AIServiceState { + isLoading: boolean; + result: TResult | null; + error: string | null; +} + +export interface UseAIServiceOptions { + onApply?: (result: TResult) => void; +} + +export interface UseAIServiceReturn { + state: AIServiceState; + execute: (request: TRequest) => Promise; + apply?: (result: TResult) => void; + clearResult: () => void; + isAvailable: boolean; +} + +export function useAIService( + serviceFunction: (request: TRequest) => Promise, + options: UseAIServiceOptions = {} +): UseAIServiceReturn { + const [state, setState] = useState>({ + isLoading: false, + result: null, + error: null + }); + + const isAvailable = AI_FEATURES_ENABLED; + + const execute = useCallback(async (request: TRequest) => { + if (!isAvailable) { + setState(prev => ({ + ...prev, + error: 'AI features are not enabled. Please configure your API key.' + })); + return; + } + + setState(prev => ({ + ...prev, + isLoading: true, + error: null, + result: null + })); + + try { + const result = await serviceFunction(request); + + setState(prev => ({ + ...prev, + isLoading: false, + result + })); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Operation failed'; + setState(prev => ({ + ...prev, + isLoading: false, + error: errorMessage + })); + } + }, [serviceFunction, isAvailable]); + + const apply = useCallback((result: TResult) => { + if (options.onApply) { + options.onApply(result); + } + clearResult(); + }, [options.onApply]); + + const clearResult = useCallback(() => { + setState({ + isLoading: false, + result: null, + error: null + }); + }, []); + + return { + state, + execute, + apply: options.onApply ? apply : undefined, + clearResult, + isAvailable + }; +} \ No newline at end of file diff --git a/app/src/features/ai/hooks/useErrorAnalysis.tsx b/app/src/features/ai/hooks/useErrorAnalysis.tsx new file mode 100644 index 0000000..8557b5e --- /dev/null +++ b/app/src/features/ai/hooks/useErrorAnalysis.tsx @@ -0,0 +1,55 @@ +import { useCallback } from 'react'; +import { aiService, ErrorAnalysisRequest, ErrorAnalysisResponse } from '../../../services/aiService'; +import { useAIService } from './useAIService'; + +export interface ErrorAnalysisState { + isAnalyzing: boolean; + result: ErrorAnalysisResponse | null; + error: string | null; +} + +export interface UseErrorAnalysisReturn { + state: ErrorAnalysisState; + analyzeError: (request: ErrorAnalysisRequest) => Promise; + applyFix: (fixedCode: string) => void; + clearResult: () => void; + isAvailable: boolean; +} + +export function useErrorAnalysis( + onCodeFixed?: (code: string) => void +): UseErrorAnalysisReturn { + const { state, execute, apply, clearResult, isAvailable } = useAIService( + aiService.analyzeError.bind(aiService), + { + onApply: (result: ErrorAnalysisResponse) => { + if (result.fixedCode && onCodeFixed) { + onCodeFixed(result.fixedCode); + } + } + } + ); + + // Transform the generic state to match the expected interface + const transformedState: ErrorAnalysisState = { + isAnalyzing: state.isLoading, + result: state.result, + error: state.error + }; + + const applyFix = useCallback((fixedCode: string) => { + if (onCodeFixed) { + onCodeFixed(fixedCode); + } + clearResult(); + }, [onCodeFixed, clearResult]); + + return { + state: transformedState, + analyzeError: execute, + applyFix, + clearResult, + isAvailable + }; +} + diff --git a/app/src/features/ai/hooks/useFuelDocs.tsx b/app/src/features/ai/hooks/useFuelDocs.tsx new file mode 100644 index 0000000..89b0148 --- /dev/null +++ b/app/src/features/ai/hooks/useFuelDocs.tsx @@ -0,0 +1,92 @@ +import { useState, useCallback, useEffect } from 'react'; +import { mcpService, SearchDocsResponse } from '../../../services/mcpService'; + +export interface FuelDocsState { + isSearching: boolean; + results: SearchDocsResponse | null; + error: string | null; +} + +export interface UseFuelDocsReturn { + state: FuelDocsState; + searchDocs: (query: string, maxResults?: number) => Promise; + getContextForPrompt: (prompt: string) => Promise; + clearResults: () => void; + isAvailable: boolean; +} + +export function useFuelDocs(): UseFuelDocsReturn { + const [state, setState] = useState({ + isSearching: false, + results: null, + error: null + }); + + const [isAvailable, setIsAvailable] = useState(false); + + useEffect(() => { + mcpService.isAvailable() + .then(setIsAvailable) + .catch(() => setIsAvailable(false)); + }, []); + + const searchDocs = useCallback(async (query: string, maxResults = 5) => { + if (!isAvailable) { + setState(prev => ({ ...prev, error: 'Fuel docs service not available' })); + return; + } + + setState(prev => ({ ...prev, isSearching: true, error: null })); + + try { + const results = await mcpService.searchDocs({ query, maxResults }); + setState(prev => ({ ...prev, isSearching: false, results })); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Failed to search documentation'; + setState(prev => ({ ...prev, isSearching: false, error: errorMessage })); + } + }, []); + + const getContextForPrompt = useCallback(async (prompt: string): Promise => { + if (!isAvailable) { + return ''; + } + + try { + const keywords = extractSwayKeywords(prompt); + const query = keywords.length > 0 ? keywords.join(' ') + ' sway' : prompt + ' sway'; + return await mcpService.getRelevantDocs(query); + } catch (error) { + console.warn('Failed to get context for prompt:', error); + return ''; + } + }, [isAvailable]); + + const clearResults = useCallback(() => { + setState({ + isSearching: false, + results: null, + error: null + }); + }, []); + + return { + state, + searchDocs, + getContextForPrompt, + clearResults, + isAvailable, + }; +} + +function extractSwayKeywords(prompt: string): string[] { + const keywords = [ + 'contract', 'storage', 'impl', 'abi', 'fn', 'struct', 'enum', 'trait', + 'deposit', 'mint', 'burn', 'transfer', 'balance', 'token', 'asset', + 'identity', 'address', 'b256', 'u64', 'u256', 'bool', 'str', + 'require', 'revert', 'assert', 'log', 'msg_sender', 'msg_amount' + ]; + + return prompt.toLowerCase().split(/\s+/) + .filter(word => keywords.some(keyword => word.includes(keyword))); +} \ No newline at end of file diff --git a/app/src/features/editor/hooks/useCompile.tsx b/app/src/features/editor/hooks/useCompile.tsx index 84f864e..9a2843c 100644 --- a/app/src/features/editor/hooks/useCompile.tsx +++ b/app/src/features/editor/hooks/useCompile.tsx @@ -8,8 +8,9 @@ import { } from "../../../utils/localStorage"; import { CopyableHex } from "../../../components/shared"; import { Toolchain } from "../components/ToolchainDropdown"; -import { SERVER_URI } from "../../../constants"; +import { SERVER_URI, AI_FEATURES_ENABLED } from "../../../constants"; import { track } from "@vercel/analytics/react"; +import { FixWithAIButton } from "../../ai/components/FixWithAIButton"; function toResults( prefixedBytecode: string, @@ -36,6 +37,7 @@ export function useCompile( setIsCompiled: (isCompiled: boolean) => void, setResults: (entry: React.ReactElement[]) => void, toolchain: Toolchain, + onCodeFixed?: (fixedCode: string) => void, ) { const [serverError, setServerError] = useState(false); const [version, setVersion] = useState(); @@ -74,7 +76,6 @@ export function useCompile( .then((response) => { const { error, forcVersion } = response; if (error) { - // Preserve the ANSI color codes from the compiler output. const parsedAnsi = ansicolor.parse(error); const results = parsedAnsi.spans.map((span, i) => { const { text, css } = span; @@ -83,7 +84,20 @@ export function useCompile( `; return {text}; }); - setResults(results); + const finalResults = [...results]; + if (AI_FEATURES_ENABLED && onCodeFixed && code) { + finalResults.push( +
+ +
+ ); + } + + setResults(finalResults); setVersion(forcVersion); saveAbi(""); saveBytecode(""); diff --git a/app/src/features/toolbar/components/ActionToolbar.tsx b/app/src/features/toolbar/components/ActionToolbar.tsx index 729b6c0..5cc630d 100644 --- a/app/src/features/toolbar/components/ActionToolbar.tsx +++ b/app/src/features/toolbar/components/ActionToolbar.tsx @@ -1,6 +1,7 @@ -import React, { useCallback } from "react"; +import React, { useCallback, useState } from "react"; import PlayArrow from "@mui/icons-material/PlayArrow"; import OpenInNew from "@mui/icons-material/OpenInNew"; +import AutoAwesome from "@mui/icons-material/AutoAwesome"; import { DeployState } from "../../../utils/types"; import { DeploymentButton } from "./DeploymentButton"; import CompileButton from "./CompileButton"; @@ -15,6 +16,7 @@ import SwitchThemeButton from "./SwitchThemeButton"; import { useConnectIfNotAlready } from "../hooks/useConnectIfNotAlready"; import { useDisconnect } from "@fuels/react"; import { useNavigate } from "react-router-dom"; +import { AI_FEATURES_ENABLED } from "../../../constants"; export interface ActionToolbarProps { deployState: DeployState; @@ -28,6 +30,7 @@ export interface ActionToolbarProps { showSolidity: boolean; setShowSolidity: (open: boolean) => void; updateLog: (entry: string) => void; + onAIAssistClick?: () => void; } function ActionToolbar({ @@ -42,6 +45,7 @@ function ActionToolbar({ showSolidity, setShowSolidity, updateLog, + onAIAssistClick, }: ActionToolbarProps) { const isMobile = useIsMobile(); const { isConnected } = useConnectIfNotAlready(); @@ -107,6 +111,15 @@ function ActionToolbar({ text="ABI" tooltip="Query an already-deployed contract using the ABI" /> + {AI_FEATURES_ENABLED && onAIAssistClick && !isMobile && ( + } + /> + )} Promise; + resetCopied: () => void; +} + +export function useCopyToClipboard(timeout: number = 2000): UseCopyToClipboardReturn { + const [copied, setCopied] = useState(false); + + const copyToClipboard = useCallback(async (text: string) => { + try { + await navigator.clipboard.writeText(text); + setCopied(true); + setTimeout(() => setCopied(false), timeout); + } catch (error) { + console.error('Failed to copy to clipboard:', error); + } + }, [timeout]); + + const resetCopied = useCallback(() => { + setCopied(false); + }, []); + + return { + copied, + copyToClipboard, + resetCopied, + }; +} \ No newline at end of file diff --git a/app/src/services/aiService.ts b/app/src/services/aiService.ts new file mode 100644 index 0000000..25cd0d4 --- /dev/null +++ b/app/src/services/aiService.ts @@ -0,0 +1,35 @@ +import { apiService } from './apiService'; + +export interface SwayCodeGenerationRequest { + prompt: string; +} + +export interface SwayCodeGenerationResponse { + code: string; + explanation: string; + suggestions: string[]; +} + +export interface ErrorAnalysisRequest { + errorMessage: string; + sourceCode: string; + lineNumber?: number; +} + +export interface ErrorAnalysisResponse { + analysis: string; + suggestions: string[]; + fixedCode?: string; +} + +class AIService { + async generateSwayCode(request: SwayCodeGenerationRequest): Promise { + return apiService.generateSwayCode(request); + } + + async analyzeError(request: ErrorAnalysisRequest): Promise { + return apiService.analyzeError(request); + } +} + +export const aiService = new AIService(); \ No newline at end of file diff --git a/app/src/services/apiService.ts b/app/src/services/apiService.ts new file mode 100644 index 0000000..352a684 --- /dev/null +++ b/app/src/services/apiService.ts @@ -0,0 +1,120 @@ +import { AI_BACKEND_URL } from '../constants'; + +export interface ApiRequestOptions { + method?: 'GET' | 'POST' | 'PUT' | 'DELETE'; + headers?: Record; + timeout?: number; +} + +class ApiService { + private baseURL: string; + + constructor() { + this.baseURL = AI_BACKEND_URL; + } + + private async makeRequest( + endpoint: string, + data?: any, + options: ApiRequestOptions = {} + ): Promise { + const { + method = data ? 'POST' : 'GET', + headers = {}, + timeout = 30000 + } = options; + + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), timeout); + + try { + const response = await fetch(`${this.baseURL}/api${endpoint}`, { + method, + headers: { + 'Content-Type': 'application/json', + ...headers, + }, + body: data ? JSON.stringify(data) : undefined, + signal: controller.signal, + }); + + clearTimeout(timeoutId); + + if (!response.ok) { + const error = await response.json().catch(() => ({ error: 'Request failed' })); + throw new Error(error.error || `HTTP ${response.status}: ${response.statusText}`); + } + + return response.json(); + } catch (error) { + clearTimeout(timeoutId); + if (error instanceof Error) { + if (error.name === 'AbortError') { + throw new Error('Request timeout'); + } + throw error; + } + throw new Error('Unknown error occurred'); + } + } + + isAvailable(): boolean { + return Boolean(this.baseURL); + } + + // AI Service methods + async generateSwayCode(request: any): Promise { + if (!this.isAvailable()) { + throw new Error('AI service not available. Please check backend configuration.'); + } + + try { + return await this.makeRequest('/ai/generate', request); + } catch (error) { + throw new Error('Failed to generate Sway code. Please try again.'); + } + } + + async analyzeError(request: any): Promise { + if (!this.isAvailable()) { + throw new Error('AI service not available. Please check backend configuration.'); + } + + try { + return await this.makeRequest('/ai/analyze-error', request); + } catch (error) { + throw new Error('Failed to analyze error. Please try again.'); + } + } + + // MCP Service methods + async searchDocs(request: any): Promise { + try { + return await this.makeRequest('/docs/search', request); + } catch (error) { + console.error('MCP searchDocs error:', error); + return { results: [] }; + } + } + + async getRelevantDocs(swayQuery: string): Promise { + try { + const result = await this.makeRequest('/docs/relevant', { query: swayQuery }); + return result.context || ''; + } catch (error) { + console.error('Error getting relevant docs:', error); + return ''; + } + } + + async isDocsAvailable(): Promise { + try { + const result = await this.makeRequest('/docs/health'); + return result.available; + } catch (error) { + return false; + } + } +} + +export const apiService = new ApiService(); \ No newline at end of file diff --git a/app/src/services/mcpService.ts b/app/src/services/mcpService.ts new file mode 100644 index 0000000..41370a5 --- /dev/null +++ b/app/src/services/mcpService.ts @@ -0,0 +1,44 @@ +import { apiService } from './apiService'; + +export interface MCPRequest { + method: string; + params?: any; +} + +export interface MCPResponse { + result?: any; + error?: { + code: number; + message: string; + }; +} + +export interface SearchDocsRequest { + query: string; + maxResults?: number; +} + +export interface SearchDocsResponse { + results: Array<{ + title: string; + content: string; + url?: string; + relevance?: number; + }>; +} + +class MCPService { + async searchDocs(request: SearchDocsRequest): Promise { + return apiService.searchDocs(request); + } + + async getRelevantDocs(swayQuery: string): Promise { + return apiService.getRelevantDocs(swayQuery); + } + + async isAvailable(): Promise { + return apiService.isDocsAvailable(); + } +} + +export const mcpService = new MCPService(); \ No newline at end of file diff --git a/app/src/utils/aiHelpers.ts b/app/src/utils/aiHelpers.ts new file mode 100644 index 0000000..dc39703 --- /dev/null +++ b/app/src/utils/aiHelpers.ts @@ -0,0 +1,16 @@ +/** + * Removes code blocks and common prefixes from AI-generated content + */ +export function removeCodeBlocks(content: string): string { + return content + .replace(/```[\s\S]*?```/g, '') + .replace(/Here's the corrected code:?/gi, '') + .replace(/Here's the fixed code:?/gi, '') + .replace(/Here's the code:?/gi, '') + .replace(/Here's the contract:?/gi, '') + .replace(/Fixed code:?/gi, '') + .replace(/Corrected code:?/gi, '') + .replace(/Generated code:?/gi, '') + .replace(/Contract code:?/gi, '') + .trim(); +} \ No newline at end of file From 9785762ff2bfa28a2d58d6f3efc2b39973a7c684 Mon Sep 17 00:00:00 2001 From: PraneshASP Date: Tue, 15 Jul 2025 20:00:00 +0530 Subject: [PATCH 03/25] feat: add gemini integration --- Cargo.lock | 1026 +++++++++++++++++++++++++++++++++++++++++++------- Cargo.toml | 3 + src/ai.rs | 630 +++++++++++++++++++++++++++++++ src/error.rs | 3 + src/main.rs | 35 +- src/types.rs | 34 ++ 6 files changed, 1584 insertions(+), 147 deletions(-) create mode 100644 src/ai.rs diff --git a/Cargo.lock b/Cargo.lock index 7823b77..bc1b697 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -123,6 +123,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "atty" version = "0.2.14" @@ -214,9 +220,9 @@ checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "bytes" -version = "1.3.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" @@ -360,6 +366,23 @@ dependencies = [ "subtle", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + [[package]] name = "either" version = "1.8.0" @@ -375,6 +398,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "fastrand" version = "1.8.0" @@ -404,11 +433,26 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] @@ -436,9 +480,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.25" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -446,9 +490,9 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" @@ -463,38 +507,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.25" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.25" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.104", ] [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -508,6 +552,23 @@ dependencies = [ "slab", ] +[[package]] +name = "gemini-rust" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d1cac4e92c4bc2111b9d5058538f3a41f1f053a60dc7578ba8a055cee234deb" +dependencies = [ + "async-trait", + "futures", + "futures-util", + "reqwest 0.12.22", + "serde", + "serde_json", + "thiserror 2.0.12", + "tokio", + "url", +] + [[package]] name = "generator" version = "0.7.2" @@ -578,7 +639,26 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.8", - "indexmap", + "indexmap 1.9.2", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.1.0", + "indexmap 2.10.0", "slab", "tokio", "tokio-util", @@ -591,6 +671,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" + [[package]] name = "heck" version = "0.4.1" @@ -688,9 +774,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.8.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" @@ -708,7 +794,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", + "h2 0.3.15", "http 0.2.8", "http-body 0.4.5", "httparse", @@ -724,13 +810,14 @@ dependencies = [ [[package]] name = "hyper" -version = "1.3.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", "futures-util", + "h2 0.4.11", "http 1.1.0", "http-body 1.0.0", "httparse", @@ -749,7 +836,7 @@ checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" dependencies = [ "futures-util", "http 1.1.0", - "hyper 1.3.1", + "hyper 1.6.0", "hyper-util", "log", "rustls 0.22.4", @@ -760,37 +847,88 @@ dependencies = [ "tower-service", ] +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http 1.1.0", + "hyper 1.6.0", + "hyper-util", + "rustls 0.23.29", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.2", + "tower-service", +] + [[package]] name = "hyper-timeout" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3203a961e5c83b6f5498933e78b6b263e208c197b63e9c6c53cc82ffd3f63793" dependencies = [ - "hyper 1.3.1", + "hyper 1.6.0", "hyper-util", "pin-project-lite", "tokio", "tower-service", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper 0.14.23", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.6.0", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" -version = "0.1.3" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" dependencies = [ + "base64 0.22.1", "bytes", "futures-channel", + "futures-core", "futures-util", "http 1.1.0", "http-body 1.0.0", - "hyper 1.3.1", + "hyper 1.6.0", + "ipnet", + "libc", + "percent-encoding", "pin-project-lite", - "socket2 0.5.7", + "socket2 0.5.10", + "system-configuration 0.6.1", "tokio", - "tower", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -816,14 +954,111 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + [[package]] name = "idna" -version = "0.3.0" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "icu_normalizer", + "icu_properties", ] [[package]] @@ -833,10 +1068,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", "serde", ] +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown 0.15.4", +] + [[package]] name = "inlinable_string" version = "0.1.15" @@ -861,6 +1106,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + [[package]] name = "iri-string" version = "0.7.2" @@ -879,10 +1130,11 @@ checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -909,9 +1161,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.154" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "litemap" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" @@ -1017,6 +1275,23 @@ dependencies = [ "rand", ] +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1092,8 +1367,8 @@ dependencies = [ "http 1.1.0", "http-body 1.0.0", "http-body-util", - "hyper 1.3.1", - "hyper-rustls", + "hyper 1.6.0", + "hyper-rustls 0.26.0", "hyper-timeout", "hyper-util", "jsonwebtoken", @@ -1107,8 +1382,8 @@ dependencies = [ "serde_urlencoded", "snafu", "tokio", - "tower", - "tower-http", + "tower 0.4.13", + "tower-http 0.5.2", "tracing", "url", ] @@ -1125,12 +1400,50 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openssl" +version = "0.10.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +dependencies = [ + "bitflags 2.5.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-sys" +version = "0.9.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "overload" version = "0.1.1" @@ -1195,9 +1508,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project" @@ -1216,7 +1529,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.104", ] [[package]] @@ -1231,6 +1544,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + [[package]] name = "polyval" version = "0.6.0" @@ -1243,6 +1562,15 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1251,9 +1579,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.82" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] @@ -1375,40 +1703,123 @@ dependencies = [ ] [[package]] -name = "ring" -version = "0.16.20" +name = "reqwest" +version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ - "cc", - "libc", + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.3.15", + "http 0.2.8", + "http-body 0.4.5", + "hyper 0.14.23", + "hyper-tls 0.5.0", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", "once_cell", - "spin 0.5.2", - "untrusted 0.7.1", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile 1.0.1", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 0.1.2", + "system-configuration 0.5.1", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", "web-sys", - "winapi", + "winreg", ] [[package]] -name = "ring" -version = "0.17.8" +name = "reqwest" +version = "0.12.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" dependencies = [ - "cc", - "cfg-if", - "getrandom", - "libc", - "spin 0.9.8", - "untrusted 0.9.0", - "windows-sys 0.52.0", -] - -[[package]] -name = "rocket" -version = "0.5.0-rc.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ead083fce4a405feb349cf09abdf64471c6077f14e0ce59364aa90d4b99317" + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.4.11", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.6.0", + "hyper-rustls 0.27.7", + "hyper-tls 0.6.0", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.2", + "tokio", + "tokio-native-tls", + "tokio-util", + "tower 0.5.2", + "tower-http 0.6.6", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted 0.7.1", + "web-sys", + "winapi", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "rocket" +version = "0.5.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98ead083fce4a405feb349cf09abdf64471c6077f14e0ce59364aa90d4b99317" dependencies = [ "async-stream", "async-trait", @@ -1419,7 +1830,7 @@ dependencies = [ "either", "figment", "futures", - "indexmap", + "indexmap 1.9.2", "log", "memchr", "multer", @@ -1451,7 +1862,7 @@ checksum = "d6aeb6bb9c61e9cd2c00d70ea267bf36f76a4cc615e5908b349c2f9d93999b47" dependencies = [ "devise", "glob", - "indexmap", + "indexmap 1.9.2", "proc-macro2", "quote", "rocket_http", @@ -1470,7 +1881,7 @@ dependencies = [ "futures", "http 0.2.8", "hyper 0.14.23", - "indexmap", + "indexmap 1.9.2", "log", "memchr", "pear", @@ -1516,7 +1927,20 @@ dependencies = [ "log", "ring 0.17.8", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.102.3", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls" +version = "0.23.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki 0.103.4", "subtle", "zeroize", ] @@ -1555,9 +1979,12 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.7.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] [[package]] name = "rustls-webpki" @@ -1570,6 +1997,17 @@ dependencies = [ "untrusted 0.9.0", ] +[[package]] +name = "rustls-webpki" +version = "0.103.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +dependencies = [ + "ring 0.17.8", + "rustls-pki-types", + "untrusted 0.9.0", +] + [[package]] name = "rustversion" version = "1.0.11" @@ -1662,7 +2100,7 @@ checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.104", ] [[package]] @@ -1735,7 +2173,7 @@ checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" dependencies = [ "num-bigint", "num-traits", - "thiserror", + "thiserror 1.0.60", "time", ] @@ -1772,7 +2210,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.104", ] [[package]] @@ -1787,9 +2225,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", @@ -1816,6 +2254,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "state" version = "0.5.3" @@ -1835,15 +2279,18 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" name = "sway-playground" version = "0.1.0" dependencies = [ + "dotenv", "fs_extra", + "gemini-rust", "hex", "nanoid", "octocrab", "regex", + "reqwest 0.11.27", "rocket", "serde", "serde_json", - "thiserror", + "thiserror 1.0.60", "tokio", ] @@ -1860,15 +2307,83 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.61" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys 0.5.0", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.5.0", + "core-foundation", + "system-configuration-sys 0.6.0", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tempfile" version = "3.3.0" @@ -1889,7 +2404,16 @@ version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.60", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", ] [[package]] @@ -1900,7 +2424,18 @@ checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.104", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", ] [[package]] @@ -1940,20 +2475,15 @@ dependencies = [ ] [[package]] -name = "tinyvec" -version = "1.6.0" +name = "tinystr" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ - "tinyvec_macros", + "displaydoc", + "zerovec", ] -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "tokio" version = "1.37.0" @@ -1968,7 +2498,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.7", + "socket2 0.5.10", "tokio-macros", "windows-sys 0.48.0", ] @@ -1981,7 +2511,17 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.104", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", ] [[package]] @@ -2006,6 +2546,16 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls 0.23.29", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.11" @@ -2019,16 +2569,15 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.4" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] @@ -2057,6 +2606,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper 1.0.2", + "tokio", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-http" version = "0.5.2" @@ -2071,23 +2635,41 @@ dependencies = [ "http-body-util", "iri-string", "pin-project-lite", - "tower", + "tower 0.4.13", "tower-layer", "tower-service", "tracing", ] +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags 2.5.0", + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "iri-string", + "pin-project-lite", + "tower 0.5.2", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" @@ -2183,27 +2765,12 @@ dependencies = [ "version_check", ] -[[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" - [[package]] name = "unicode-ident" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" -[[package]] -name = "unicode-normalization" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" -dependencies = [ - "tinyvec", -] - [[package]] name = "unicode-xid" version = "0.2.4" @@ -2234,9 +2801,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.3.1" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", @@ -2244,12 +2811,24 @@ dependencies = [ "serde", ] +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "valuable" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -2274,34 +2853,48 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", + "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.104", "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2309,28 +2902,44 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.104", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] [[package]] name = "web-sys" -version = "0.3.60" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", @@ -2390,6 +2999,41 @@ dependencies = [ "windows-targets 0.52.5", ] +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-registry" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.42.0" @@ -2616,14 +3260,108 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + [[package]] name = "yansi" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "synstructure", +] + [[package]] name = "zeroize" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] diff --git a/Cargo.toml b/Cargo.toml index 6a09690..ef3b9af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,6 @@ rocket = { version = "0.5.0-rc.2", features = ["tls", "json"] } serde = { version = "1.0", features = ["derive"] } octocrab = "0.38.0" thiserror = "1.0.60" +gemini-rust = "0.1.0" +reqwest = { version = "0.11", features = ["json"] } +dotenv = "0.15" diff --git a/src/ai.rs b/src/ai.rs new file mode 100644 index 0000000..55bb99d --- /dev/null +++ b/src/ai.rs @@ -0,0 +1,630 @@ +use crate::error::ApiError; +use crate::types::{ + ErrorAnalysisRequest, ErrorAnalysisResponse, SwayCodeGenerationRequest, + SwayCodeGenerationResponse, +}; +use gemini_rust::{Content, FunctionDeclaration, FunctionParameters, FunctionCallingMode, Gemini, PropertyDetails, Role, GenerationConfig}; +use serde_json::{json, Value}; +use std::env; + +pub struct AIService { + client: Option, + mcp_server_url: Option, + http_client: reqwest::Client, +} + +#[derive(serde::Deserialize)] +struct MCPResponse { + result: Option, + error: Option, +} + +#[derive(serde::Deserialize)] +struct MCPError { + code: i32, + message: String, +} + +#[derive(serde::Deserialize)] +struct MCPToolResponse { + content: Vec, +} + +#[derive(serde::Deserialize)] +struct MCPContent { + text: Option, + content: Option, +} + +impl AIService { + pub fn new() -> Result { + let api_key = env::var("GEMINI_API_KEY").ok(); + let mcp_server_url = env::var("MCP_SERVER_URL").ok(); + + let client = api_key.map(|key| Gemini::with_model(key, "models/gemini-2.5-flash".to_string())); + let http_client = reqwest::Client::new(); + + Ok(AIService { + client, + mcp_server_url, + http_client, + }) + } + + pub fn is_ai_available(&self) -> bool { + self.client.is_some() + } + + pub fn is_mcp_available(&self) -> bool { + self.mcp_server_url.is_some() + } + + pub async fn generate_sway_code( + &self, + request: SwayCodeGenerationRequest, + ) -> Result { + if !self.is_ai_available() { + return Err(ApiError::Ai( + "AI service not available. Please configure GEMINI_API_KEY.".to_string(), + )); + } + + let system_prompt = self.get_code_generation_prompt(); + let user_prompt = format!( + "Generate a Sway smart contract for: {}\n\nSTEPS:\n1. Call 'searchDocumentation' to find relevant examples\n2. Generate complete, working Sway contract code\n3. Provide brief explanation\n\nRequired features for common patterns:\n- Tokens: SRC20 standard\n- NFTs: SRC3 standard\n- Access control: SRC5 standard\n- Basic: contract structure, storage, functions", + request.prompt + ); + + let client = self.client.as_ref().unwrap(); + + if self.is_mcp_available() { + let functions = self.create_function_declarations(); + let mut request_builder = client.generate_content(); + request_builder = request_builder.with_user_message(&format!("{}\n\n{}", system_prompt, user_prompt)); + + for function in functions.iter() { + request_builder = request_builder.with_function(function.clone()); + } + + let response = request_builder + .execute() + .await + .map_err(|e| ApiError::Ai(format!("Gemini API error: {}", e)))?; + + let function_calls = response.function_calls(); + if !function_calls.is_empty() { + let mut function_responses = Vec::new(); + for function_call in function_calls.iter() { + let function_response = self.handle_function_call_response(function_call).await?; + function_responses.push((function_call, function_response)); + } + + let mut final_request = client + .generate_content() + .with_user_message(&format!("{}\n\n{}", system_prompt, user_prompt)); + + final_request.contents.push(response.candidates[0].content.clone()); + + let mut function_content = Content::default(); + function_content.role = Some(Role::Function); + + for (function_call, function_response) in function_responses { + let response_content = Content::function_response_json(function_call.name.clone(), function_response); + function_content.parts.extend(response_content.parts); + } + + final_request.contents.push(function_content); + + let final_response = final_request + .execute() + .await + .map_err(|e| ApiError::Ai(format!("Gemini API error: {}", e)))?; + + self.parse_code_generation_response(&final_response.text()) + } else { + self.parse_code_generation_response(&response.text()) + } + } else { + let response = client + .generate_content() + .with_user_message(&format!("{}\n\n{}", system_prompt, user_prompt)) + .execute() + .await + .map_err(|e| ApiError::Ai(format!("Gemini API error: {}", e)))?; + + self.parse_code_generation_response(&response.text()) + } + } + + pub async fn analyze_error( + &self, + request: ErrorAnalysisRequest, + ) -> Result { + if !self.is_ai_available() { + return Err(ApiError::Ai( + "AI service not available. Please configure GEMINI_API_KEY.".to_string(), + )); + } + + let system_prompt = self.get_error_analysis_prompt(); + let user_prompt = format!( + "Fix this Sway compilation error by applying ONLY the necessary changes:\n\nERROR: {}\n\nCURRENT CODE:\n```sway\n{}\n```\n\nINSTRUCTIONS:\n1. If there are multiple errors, call 'searchDocumentation' for EACH DISTINCT error type\n2. Search documentation for each specific error pattern\n3. Identify the exact issue causing each error\n4. Apply MINIMAL fixes - change only what's broken\n5. Keep all working code unchanged\n6. Return the complete corrected contract\n\nCRITICAL: Return the entire corrected Sway contract in a ```sway code block. Fix ONLY the errors, don't refactor working code.", + request.error_message.to_string(), request.source_code + ); + + let client = self.client.as_ref().unwrap(); + + if self.is_mcp_available() { + let functions = self.create_function_declarations(); + let mut request_builder = client + .generate_content() + .with_user_message(&format!("{}\n\n{}", system_prompt, user_prompt)) + .with_function_calling_mode(FunctionCallingMode::Any) + .with_generation_config(GenerationConfig { + temperature: Some(0.7), + top_p: Some(0.95), + top_k: Some(40), + max_output_tokens: Some(8192), candidate_count: Some(1), + stop_sequences: Some(vec!["END".to_string()]), + response_mime_type: None, + response_schema: None,} + ); + + for function in &functions { + request_builder = request_builder.with_function(function.clone()); + } + + let response = request_builder + .execute() + .await + .map_err(|e| ApiError::Ai(format!("Gemini API error: {}", e)))?; + + let function_calls = response.function_calls(); + + if !function_calls.is_empty() { + let mut function_responses = Vec::new(); + for function_call in function_calls.iter() { + let function_response = self.handle_function_call_response(function_call).await?; + function_responses.push((function_call, function_response)); + } + + let mut final_request = client + .generate_content() + .with_user_message(&format!("{}\n\n{}", system_prompt, user_prompt)); + + final_request.contents.push(response.candidates[0].content.clone()); + + let mut function_content = Content::default(); + function_content.role = Some(Role::Function); + + for (function_call, function_response) in function_responses.into_iter() { + let response_content = Content::function_response_json(function_call.name.clone(), function_response); + function_content.parts.extend(response_content.parts); + } + + final_request.contents.push(function_content); + + let final_response = final_request + .execute() + .await + .map_err(|e| ApiError::Ai(format!("Gemini API error: {}", e)))?; + + self.parse_error_analysis_response(&final_response.text()) + } else { + self.parse_error_analysis_response(&response.text()) + } + } else { + let response = client + .generate_content() + .with_user_message(&format!("{}\n\n{}", system_prompt, user_prompt)) + .execute() + .await + .map_err(|e| ApiError::Ai(format!("Gemini API error: {}", e)))?; + + self.parse_error_analysis_response(&response.text()) + } + } + + fn create_function_declarations(&self) -> Vec { + vec![ + FunctionDeclaration::new( + "searchDocumentation", + "Search Fuel/Sway documentation for relevant information", + FunctionParameters::object() + .with_property( + "query", + PropertyDetails::string("Search query for documentation"), + true, + ) + .with_property( + "maxResults", + PropertyDetails::number("Maximum number of results to return"), + false, + ), + ), + FunctionDeclaration::new( + "getRelevantDocumentation", + "Get relevant documentation context for a specific topic or code", + FunctionParameters::object().with_property( + "topic", + PropertyDetails::string("The topic or code to get relevant documentation for"), + true, + ), + ), + ] + } + + async fn handle_function_call_response( + &self, + function_call: &gemini_rust::FunctionCall, + ) -> Result { + match function_call.name.as_str() { + "searchDocumentation" => self.search_mcp_docs(function_call).await, + "getRelevantDocumentation" => self.get_relevant_docs(function_call).await, + _ => Ok(json!({ + "error": format!("Unknown function: {}", function_call.name) + })), + } + } + + async fn search_mcp_docs( + &self, + function_call: &gemini_rust::FunctionCall, + ) -> Result { + let mcp_url = match &self.mcp_server_url { + Some(url) => url, + None => { + return Ok(json!({ + "error": "MCP server not configured", + "fallback": "Check docs.fuel.network/docs/sway/ for documentation" + })) + } + }; + + let query: String = function_call + .get("query") + .unwrap_or_else(|_| "sway".to_string()); + + let max_results: u64 = function_call.get("maxResults").unwrap_or_else(|_| 5); + + self.search_mcp_docs_internal(query, max_results).await + } + + async fn get_relevant_docs( + &self, + function_call: &gemini_rust::FunctionCall, + ) -> Result { + let topic: String = function_call + .get("topic") + .unwrap_or_else(|_| "sway".to_string()); + + // Create a direct search for the topic + let search_result = self.search_mcp_docs_by_query(topic, 3).await?; + + if let Some(results) = search_result.get("results").and_then(|r| r.as_array()) { + let context = results + .iter() + .filter_map(|r| r.get("content").and_then(|c| c.as_str())) + .collect::>() + .join("\n\n"); + + Ok(json!({ "context": context })) + } else { + Ok(json!({ + "error": "No relevant documentation found", + "fallback": "Check docs.fuel.network/docs/sway/ for documentation" + })) + } + } + + async fn search_mcp_docs_by_query( + &self, + query: String, + max_results: u64, + ) -> Result { + // Just delegate to the main search function which handles SSE properly + self.search_mcp_docs_internal(query, max_results).await + } + + async fn search_mcp_docs_internal( + &self, + query: String, + max_results: u64, + ) -> Result { + let mcp_url = match &self.mcp_server_url { + Some(url) => url, + None => { + return Ok(json!({ + "error": "MCP server not configured", + "fallback": "Check docs.fuel.network/docs/sway/ for documentation" + })) + } + }; + + let request_body = json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "tools/call", + "params": { + "name": "searchFuelDocs", + "arguments": { + "query": query, + "maxResults": max_results + } + } + }); + + let response_result = self + .http_client + .post(mcp_url) + .header("Content-Type", "application/json") + .header("Accept", "application/json, text/event-stream") + .json(&request_body) + .send() + .await; + + match response_result { + Ok(response) => { + if response.status().is_success() { + let response_text = response.text().await.map_err(|e| { + ApiError::Ai(format!("Failed to get response text: {}", e)) + })?; + + let json_data = if response_text.starts_with("event:") { + response_text + .lines() + .find(|line| line.starts_with("data: ")) + .and_then(|line| line.strip_prefix("data: ")) + .unwrap_or(&response_text) + } else { + &response_text + }; + + let mcp_response: MCPResponse = serde_json::from_str(json_data).map_err(|e| { + ApiError::Ai(format!("Failed to parse MCP response: {}", e)) + })?; + + if let Some(error) = mcp_response.error { + Ok(json!({ + "error": error.message, + "fallback": "Check docs.fuel.network/docs/sway/ for documentation" + })) + } else if let Some(result) = mcp_response.result { + if let Ok(tool_response) = serde_json::from_value::(result.clone()) + { + let results: Vec = tool_response + .content + .into_iter() + .take(3) + .map(|content| { + let text = content + .text + .clone() + .or(content.content.clone()) + .unwrap_or_else(|| "No content".to_string()); + let truncated = if text.len() > 500 { + text.chars().take(500).collect::() + "..." + } else { + text + }; + json!({ + "title": "Documentation", + "content": truncated + }) + }) + .collect(); + + Ok(json!({ "results": results })) + } else { + Ok(json!({ + "error": "Invalid MCP response format", + "fallback": "Check docs.fuel.network/docs/sway/ for documentation" + })) + } + } else { + Ok(json!({ + "error": "Empty MCP response", + "fallback": "Check docs.fuel.network/docs/sway/ for documentation" + })) + } + } else { + Ok(json!({ + "error": format!("MCP server error: {}", response.status()), + "fallback": "Check docs.fuel.network/docs/sway/ for documentation" + })) + } + } + Err(e) => { + Ok(json!({ + "error": format!("Failed to connect to MCP server: {}", e), + "fallback": "Check docs.fuel.network/docs/sway/ for documentation" + })) + } + } + } + + fn parse_code_generation_response( + &self, + response: &str, + ) -> Result { + let code_regex = regex::Regex::new(r"```(?:sway|rust)?\n([\s\S]*?)```").unwrap(); + let code = code_regex + .captures(response) + .and_then(|caps| caps.get(1)) + .map(|m| m.as_str().trim().to_string()) + .unwrap_or_else(|| response.to_string()); + + let explanation = code_regex.replace_all(response, "").trim().to_string(); + let explanation = if explanation.is_empty() { + "Generated Sway smart contract".to_string() + } else { + explanation + }; + + Ok(SwayCodeGenerationResponse { + code, + explanation, + suggestions: vec![ + "Review the generated code for your specific requirements".to_string(), + "Test the contract thoroughly before deployment".to_string(), + "Consider gas optimization for complex operations".to_string(), + ], + }) + } + + fn parse_error_analysis_response( + &self, + response: &str, + ) -> Result { + let code_regex = regex::Regex::new(r"```(?:sway|rust)?\n([\s\S]*?)```").unwrap(); + let fixed_code = code_regex + .captures(response) + .and_then(|caps| caps.get(1)) + .map(|m| m.as_str().trim().to_string()); + + Ok(ErrorAnalysisResponse { + analysis: response.to_string(), + suggestions: vec![ + "Verify the fix addresses the root cause".to_string(), + "Check for similar patterns in your code".to_string(), + "Consider adding tests to prevent regression".to_string(), + ], + fixed_code, + }) + } + + fn get_code_generation_prompt(&self) -> String { + r#"You are an expert Sway smart contract developer. Generate secure, efficient Sway contracts. + +MANDATORY: ALWAYS call 'searchDocumentation' BEFORE generating code. + +SWAY SYNTAX ESSENTIALS: +- Contract: 'contract;' +- ABI: 'abi ContractName { ... }' +- Storage: 'storage { field: Type = default_value, }' (trailing comma required) +- Implementation: 'impl AbiName for Contract { ... }' +- Storage access: '#[storage(read)]' or '#[storage(read, write)]' on both ABI and implementation +- Payable: '#[payable]' on both ABI and implementation +- StorageMap: storage.map.get(key).try_read().unwrap_or(0) +- Validation: assert(condition) or require(condition, "message") +- Identity: Identity::Address(addr) +- No need to import AssetId - Included in prelude. + + +IMPORTS: +- use std::{asset::{mint_to, transfer}, call_frames::msg_asset_id, context::msg_amount, auth::msg_sender, block::timestamp, asset::transfer}; +- use standards::{src3::SRC3, src5::SRC5, src20::SRC20}; + +FALLBACK: If documentation search fails, direct users to docs.fuel.network/docs/sway/"#.to_string() + } + + fn get_error_analysis_prompt(&self) -> String { + r#"You are an expert Sway compiler error analyst. Fix Sway compilation errors with accurate, working code. + +MANDATORY: Always call 'searchDocumentation' before analyzing errors. Go one by one and fix errors. + +CRITICAL SWAY SYNTAX RULES: +1. Context imports: use std::{context::msg_amount, auth::msg_sender, call_frames::msg_asset_id}; +2. Storage syntax: storage { field: Type = default_value, } (trailing comma required) +3. Validation: Use assert() not require() +4. Identity type: Identity::Address(addr) for addresses +5. ABI functions: Must match impl exactly +6. Storage attributes: #[storage(read)] or #[storage(read, write)] + +IMPORTANT CORRECTIONS: +- Identity::zero() is NOT a method. Use Identity::Address(Address::zero()). +- Option pattern-match limitation: +- GOOD: + if storage.highest_bidder.read().is_some() { … } +- BAD (will not compile): + if let Option::Some(x) = storage.highest_bidder.read() { … } +- assert has ONE parameter; use require for message strings. +- Never import or call transfer_inner; only transfer() is public. +- Always unwrap msg_sender() once: + let sender = msg_sender().expect("unauthenticated"); +- Built-ins for time & value: + msg_amount() // std::context + block_timestamp() // std::context + Never import them from anywhere else. +- There is NO transfer_to_contract. + To move tokens into the contract, call: + transfer(this_contract_id(), asset_id, amount); +- Do NOT import StorageMap. + Just use it inside the storage { … } block, e.g. + sales: StorageMap = StorageMap {}, + and access via storage.sales. +- Replace unwrap_or_revert("msg") with expect("msg") (same semantics). +- self is a *type parameter* in Sway ABIs, not a variable. + Call sibling fns directly: + let price = get_current_auction_price(id); + +COMMON ERROR FIXES: +- "No storage has been declared" + insert a storage { … } block and ensure every .read() / .write() target is declared there. +- "symbol transfer_inner / msg_amount / block_height not found" + remove the bad import; use the std::context versions shown above. +- "Identity::zero() not found" replace with Identity::Address(Address::zero()). +- "Option::Some cannot be matched" read into a variable and use .is_some() / .unwrap() instead of pattern matching. +- "assert expects 1 argument" change to require(cond,"msg"). +- "No method .write / .read" make sure the field is declared as a StorageValue (or StorageMap) and the type matches exactly. +- "Could not find symbol transfer_to_contract / msg_amount / block_timestamp" + Use the import list shown above and call transfer(this_contract_id(), …). +- "Mismatched types – expected Identity, found u64" + Your parameter order in transfer is wrong. + Correct: (to: Identity, asset_id: AssetId, amount: u64) +- "Function assert expects 1 argument" + change to require(condition, "explanation") +- "Option::Some cannot be matched" + use .is_some() / .unwrap() instead of pattern matching. +- "unwrap_or_revert not found" + use .expect("msg") (same effect). +- "Field access requires a struct" + The storage field or local struct wasn't declared; verify your + Auction struct and storage map types. +- "cannot find msg_sender": Add use std::auth::msg_sender; +- "cannot find assert": Use assert() instead of require() +- "type mismatch Identity": Use Identity::Address(addr) +- "storage field not found": Check storage block syntax +- "ABI mismatch": Ensure impl matches abi exactly +- StorageMap operations: + insert: storage.my_map.insert(key, value); + read : storage.my_map.get(key).try_read().unwrap_or(default); +- Nested map read/write: + storage.nested.get(k1).insert(k2, v); // write + let v = storage.nested.get(k1).get(k2).try_read(); // read + +PROVEN SWAY PATTERNS: +- Basic contract structure: + contract; + use std::context::msg_sender; + abi MyContract { fn my_function(); } + impl MyContract for Contract { fn my_function() { } } + +- Storage with validation: + storage { owner: Identity = Identity::Address(Address::zero()), } + #[storage(read)] fn get_owner() -> Identity { storage.owner.read() } + +- Asset operations: + use std::{context::msg_amount, call_frames::msg_asset_id}; + assert(msg_amount() > 0); + +ADDITIONAL ERROR FIXES: +- "No method unwrap_or(StorageKey…, numeric)" + Insert .try_read() before unwrap_or. +- "add / subtract / ge … for type {unknown}" + Ensure the variable is a u64 by calling .try_read().unwrap_or(0). +- "msg_sender not found" + use std::auth::msg_sender; and drop the .unwrap(). +- "assert expects 1 argument" + Change to require(cond, "reason") **or** use the 1-arg assert(cond) form. +- "function in ABI is pure but impl is not" + Copy the #[storage(...)] attribute to the ABI signature. + +RESPONSE FORMAT: +1. Identify the specific error type +2. Apply the correct Sway syntax fix using proven patterns +3. Return complete working code in \`\`\`sway block + +CRITICAL: Only change what's broken. Use exact syntax from proven patterns above."#.to_string() + } +} diff --git a/src/error.rs b/src/error.rs index 15ab376..4733a5d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -21,6 +21,8 @@ pub enum ApiError { Charcoal(String), #[error("GitHub error: {0}")] Github(String), + #[error("AI service error: {0}")] + Ai(String), } impl<'r, 'o: 'r> Responder<'r, 'o> for ApiError { @@ -29,6 +31,7 @@ impl<'r, 'o: 'r> Responder<'r, 'o> for ApiError { ApiError::Filesystem(_) => Err(Status::InternalServerError), ApiError::Charcoal(_) => Err(Status::InternalServerError), ApiError::Github(_) => Err(Status::InternalServerError), + ApiError::Ai(_) => Err(Status::InternalServerError), } } } diff --git a/src/main.rs b/src/main.rs index 1c0a12f..6417f1a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ #[macro_use] extern crate rocket; +mod ai; mod compilation; mod cors; mod error; @@ -11,17 +12,19 @@ mod transpilation; mod types; mod util; +use crate::ai::AIService; use crate::compilation::build_and_destroy_project; use crate::cors::Cors; use crate::error::ApiResult; use crate::gist::GistClient; use crate::types::{ - CompileRequest, CompileResponse, GistResponse, Language, NewGistRequest, NewGistResponse, + CompileRequest, CompileResponse, ErrorAnalysisRequest, ErrorAnalysisResponse, GistResponse, + Language, NewGistRequest, NewGistResponse, SwayCodeGenerationRequest, SwayCodeGenerationResponse, TranspileRequest, }; use crate::{transpilation::solidity_to_sway, types::TranspileResponse}; use rocket::serde::json::Json; -use rocket::State; +use rocket::{State, Request, catch}; /// The endpoint to compile a Sway contract. #[post("/compile", data = "")] @@ -57,6 +60,26 @@ async fn get_gist(id: String, gist: &State) -> ApiResult, + ai_service: &State, +) -> ApiResult { + let response = ai_service.generate_sway_code(request.into_inner()).await?; + Ok(Json(response)) +} + +/// The endpoint to analyze compilation errors using AI. +#[post("/ai/analyze-error", data = "")] +async fn analyze_error( + request: Json, + ai_service: &State, +) -> ApiResult { + let response = ai_service.analyze_error(request.into_inner()).await?; + Ok(Json(response)) +} + /// Catches all OPTION requests in order to get the CORS related Fairing triggered. #[options("/<_..>")] fn all_options() { @@ -72,11 +95,17 @@ fn health() -> String { // Launch the rocket server. #[launch] fn rocket() -> _ { + // Load environment variables from .env file + dotenv::dotenv().ok(); + + let ai_service = AIService::new().expect("Failed to initialize AI service"); + rocket::build() .manage(GistClient::default()) + .manage(ai_service) .attach(Cors) .mount( "/", - routes![compile, transpile, new_gist, get_gist, all_options, health], + routes![compile, transpile, new_gist, get_gist, generate_sway_code, analyze_error, all_options, health], ) } diff --git a/src/types.rs b/src/types.rs index a898d6f..848892d 100644 --- a/src/types.rs +++ b/src/types.rs @@ -104,3 +104,37 @@ pub struct GistResponse { #[serde(skip_serializing_if = "Option::is_none")] pub error: Option, } + +/// The AI Sway code generation request. +#[derive(Deserialize)] +pub struct SwayCodeGenerationRequest { + pub prompt: String, +} + +/// The response to an AI Sway code generation request. +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SwayCodeGenerationResponse { + pub code: String, + pub explanation: String, + pub suggestions: Vec, +} + +/// The AI error analysis request. +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ErrorAnalysisRequest { + pub error_message: String, + pub source_code: String, + pub line_number: Option, +} + +/// The response to an AI error analysis request. +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ErrorAnalysisResponse { + pub analysis: String, + pub suggestions: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub fixed_code: Option, +} From 0cab49f00d9d3a083ab29ca279832a1a4c38031e Mon Sep 17 00:00:00 2001 From: PraneshASP Date: Tue, 15 Jul 2025 20:00:32 +0530 Subject: [PATCH 04/25] refactor: update frontend to use rust-backend for ai service --- app/package-lock.json | 14 +++ app/package.json | 1 - app/src/App.tsx | 7 +- app/src/constants.ts | 4 - app/src/features/ai/hooks/useAIService.ts | 4 +- .../features/ai/hooks/useErrorAnalysis.tsx | 1 - app/src/features/ai/hooks/useFuelDocs.tsx | 92 ------------------- app/src/features/editor/hooks/useCompile.tsx | 5 +- .../toolbar/components/ActionToolbar.tsx | 4 +- app/src/services/aiService.ts | 27 +++++- app/src/services/apiService.ts | 60 +----------- app/src/services/mcpService.ts | 44 --------- 12 files changed, 53 insertions(+), 210 deletions(-) delete mode 100644 app/src/features/ai/hooks/useFuelDocs.tsx delete mode 100644 app/src/services/mcpService.ts diff --git a/app/package-lock.json b/app/package-lock.json index b7bdec4..3ca245f 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -15,6 +15,7 @@ "@fuels/connectors": "0.5.0", "@fuels/react": "0.36.0", "@google/genai": "^1.5.1", + "@google/generative-ai": "^0.24.1", "@mui/base": "^5.0.0-beta.2", "@mui/icons-material": "^5.11.16", "@mui/lab": "^5.0.0-alpha.46", @@ -4472,6 +4473,14 @@ "zod": "^3.24.1" } }, + "node_modules/@google/generative-ai": { + "version": "0.24.1", + "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.24.1.tgz", + "integrity": "sha512-MqO+MLfM6kjxcKoy0p1wRzG3b4ZZXtPI+z2IE26UogS2Cm/XHO+7gGRBh6gcJsOiIVoH93UwKvW4HdgiOZCy9Q==", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@graphql-typed-document-node/core": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", @@ -39737,6 +39746,11 @@ } } }, + "@google/generative-ai": { + "version": "0.24.1", + "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.24.1.tgz", + "integrity": "sha512-MqO+MLfM6kjxcKoy0p1wRzG3b4ZZXtPI+z2IE26UogS2Cm/XHO+7gGRBh6gcJsOiIVoH93UwKvW4HdgiOZCy9Q==" + }, "@graphql-typed-document-node/core": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", diff --git a/app/package.json b/app/package.json index eb33b66..29d19a3 100644 --- a/app/package.json +++ b/app/package.json @@ -9,7 +9,6 @@ "@fuel-ui/react": "^0.23.3", "@fuels/connectors": "0.5.0", "@fuels/react": "0.36.0", - "@google/genai": "^1.5.1", "@mui/base": "^5.0.0-beta.2", "@mui/icons-material": "^5.11.16", "@mui/lab": "^5.0.0-alpha.46", diff --git a/app/src/App.tsx b/app/src/App.tsx index c7f5d7f..a43ee14 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -23,7 +23,7 @@ import { useSearchParams } from "react-router-dom"; import Copyable from "./components/Copyable"; import useTheme from "./context/theme"; import { AIGenerationDialog } from "./features/ai/components/AIGenerationDialog"; -import { AI_FEATURES_ENABLED } from "./constants"; +import { aiService } from "./services/aiService"; const DRAWER_WIDTH = "40vw"; @@ -69,6 +69,7 @@ function App() { saveSwayCode(code); setSwayCode(code); setIsCompiled(false); + setCodeToCompile(undefined); // Clear previous compilation state }, [setSwayCode], ); @@ -178,7 +179,7 @@ function App() { showSolidity={showSolidity} setShowSolidity={setShowSolidity} updateLog={updateLog} - onAIAssistClick={AI_FEATURES_ENABLED ? onAIAssistClick : undefined} + onAIAssistClick={aiService.isAvailable() ? onAIAssistClick : undefined} />
- {AI_FEATURES_ENABLED && ( + {aiService.isAvailable() && ( setAiDialogOpen(false)} diff --git a/app/src/constants.ts b/app/src/constants.ts index 87d237f..db7d65a 100644 --- a/app/src/constants.ts +++ b/app/src/constants.ts @@ -7,7 +7,3 @@ export const LOCAL_SERVER_URI = "http://0.0.0.0:8080"; export const SERVER_URI = process.env.REACT_APP_LOCAL_SERVER ? LOCAL_SERVER_URI : SERVER_API; - -// AI Configuration -export const AI_BACKEND_URL = process.env.REACT_APP_AI_BACKEND_URL || 'http://localhost:3001'; -export const AI_FEATURES_ENABLED = process.env.REACT_APP_AI_FEATURES_ENABLED === 'true'; diff --git a/app/src/features/ai/hooks/useAIService.ts b/app/src/features/ai/hooks/useAIService.ts index 7ec8183..8b7961d 100644 --- a/app/src/features/ai/hooks/useAIService.ts +++ b/app/src/features/ai/hooks/useAIService.ts @@ -1,5 +1,5 @@ import { useState, useCallback } from 'react'; -import { AI_FEATURES_ENABLED } from '../../../constants'; +import { aiService } from '../../../services/aiService'; export interface AIServiceState { isLoading: boolean; @@ -29,7 +29,7 @@ export function useAIService( error: null }); - const isAvailable = AI_FEATURES_ENABLED; + const isAvailable = aiService.isAvailable(); const execute = useCallback(async (request: TRequest) => { if (!isAvailable) { diff --git a/app/src/features/ai/hooks/useErrorAnalysis.tsx b/app/src/features/ai/hooks/useErrorAnalysis.tsx index 8557b5e..512ce4c 100644 --- a/app/src/features/ai/hooks/useErrorAnalysis.tsx +++ b/app/src/features/ai/hooks/useErrorAnalysis.tsx @@ -52,4 +52,3 @@ export function useErrorAnalysis( isAvailable }; } - diff --git a/app/src/features/ai/hooks/useFuelDocs.tsx b/app/src/features/ai/hooks/useFuelDocs.tsx deleted file mode 100644 index 89b0148..0000000 --- a/app/src/features/ai/hooks/useFuelDocs.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { useState, useCallback, useEffect } from 'react'; -import { mcpService, SearchDocsResponse } from '../../../services/mcpService'; - -export interface FuelDocsState { - isSearching: boolean; - results: SearchDocsResponse | null; - error: string | null; -} - -export interface UseFuelDocsReturn { - state: FuelDocsState; - searchDocs: (query: string, maxResults?: number) => Promise; - getContextForPrompt: (prompt: string) => Promise; - clearResults: () => void; - isAvailable: boolean; -} - -export function useFuelDocs(): UseFuelDocsReturn { - const [state, setState] = useState({ - isSearching: false, - results: null, - error: null - }); - - const [isAvailable, setIsAvailable] = useState(false); - - useEffect(() => { - mcpService.isAvailable() - .then(setIsAvailable) - .catch(() => setIsAvailable(false)); - }, []); - - const searchDocs = useCallback(async (query: string, maxResults = 5) => { - if (!isAvailable) { - setState(prev => ({ ...prev, error: 'Fuel docs service not available' })); - return; - } - - setState(prev => ({ ...prev, isSearching: true, error: null })); - - try { - const results = await mcpService.searchDocs({ query, maxResults }); - setState(prev => ({ ...prev, isSearching: false, results })); - } catch (error) { - const errorMessage = error instanceof Error ? error.message : 'Failed to search documentation'; - setState(prev => ({ ...prev, isSearching: false, error: errorMessage })); - } - }, []); - - const getContextForPrompt = useCallback(async (prompt: string): Promise => { - if (!isAvailable) { - return ''; - } - - try { - const keywords = extractSwayKeywords(prompt); - const query = keywords.length > 0 ? keywords.join(' ') + ' sway' : prompt + ' sway'; - return await mcpService.getRelevantDocs(query); - } catch (error) { - console.warn('Failed to get context for prompt:', error); - return ''; - } - }, [isAvailable]); - - const clearResults = useCallback(() => { - setState({ - isSearching: false, - results: null, - error: null - }); - }, []); - - return { - state, - searchDocs, - getContextForPrompt, - clearResults, - isAvailable, - }; -} - -function extractSwayKeywords(prompt: string): string[] { - const keywords = [ - 'contract', 'storage', 'impl', 'abi', 'fn', 'struct', 'enum', 'trait', - 'deposit', 'mint', 'burn', 'transfer', 'balance', 'token', 'asset', - 'identity', 'address', 'b256', 'u64', 'u256', 'bool', 'str', - 'require', 'revert', 'assert', 'log', 'msg_sender', 'msg_amount' - ]; - - return prompt.toLowerCase().split(/\s+/) - .filter(word => keywords.some(keyword => word.includes(keyword))); -} \ No newline at end of file diff --git a/app/src/features/editor/hooks/useCompile.tsx b/app/src/features/editor/hooks/useCompile.tsx index 9a2843c..bb95a46 100644 --- a/app/src/features/editor/hooks/useCompile.tsx +++ b/app/src/features/editor/hooks/useCompile.tsx @@ -8,9 +8,10 @@ import { } from "../../../utils/localStorage"; import { CopyableHex } from "../../../components/shared"; import { Toolchain } from "../components/ToolchainDropdown"; -import { SERVER_URI, AI_FEATURES_ENABLED } from "../../../constants"; +import { SERVER_URI } from "../../../constants"; import { track } from "@vercel/analytics/react"; import { FixWithAIButton } from "../../ai/components/FixWithAIButton"; +import { aiService } from "../../../services/aiService"; function toResults( prefixedBytecode: string, @@ -85,7 +86,7 @@ export function useCompile( return {text}; }); const finalResults = [...results]; - if (AI_FEATURES_ENABLED && onCodeFixed && code) { + if (aiService.isAvailable() && onCodeFixed && code) { finalResults.push(
- {AI_FEATURES_ENABLED && onAIAssistClick && !isMobile && ( + {aiService.isAvailable() && onAIAssistClick && !isMobile && ( (endpoint: string, data: any): Promise { + const response = await fetch(`${SERVER_URI}${endpoint}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({ error: 'Unknown error' })); + throw new Error(errorData.error || `HTTP ${response.status}: ${response.statusText}`); + } + + return response.json(); + } + async generateSwayCode(request: SwayCodeGenerationRequest): Promise { - return apiService.generateSwayCode(request); + return this.makeRequest('/ai/generate', request); } async analyzeError(request: ErrorAnalysisRequest): Promise { - return apiService.analyzeError(request); + return this.makeRequest('/ai/analyze-error', request); + } + + isAvailable(): boolean { + return true; // Backend handles availability checks } } diff --git a/app/src/services/apiService.ts b/app/src/services/apiService.ts index 352a684..d25cb35 100644 --- a/app/src/services/apiService.ts +++ b/app/src/services/apiService.ts @@ -1,4 +1,4 @@ -import { AI_BACKEND_URL } from '../constants'; +import { SERVER_URI } from '../constants'; export interface ApiRequestOptions { method?: 'GET' | 'POST' | 'PUT' | 'DELETE'; @@ -10,7 +10,7 @@ class ApiService { private baseURL: string; constructor() { - this.baseURL = AI_BACKEND_URL; + this.baseURL = SERVER_URI; } private async makeRequest( @@ -28,7 +28,7 @@ class ApiService { const timeoutId = setTimeout(() => controller.abort(), timeout); try { - const response = await fetch(`${this.baseURL}/api${endpoint}`, { + const response = await fetch(`${this.baseURL}${endpoint}`, { method, headers: { 'Content-Type': 'application/json', @@ -62,59 +62,7 @@ class ApiService { return Boolean(this.baseURL); } - // AI Service methods - async generateSwayCode(request: any): Promise { - if (!this.isAvailable()) { - throw new Error('AI service not available. Please check backend configuration.'); - } - - try { - return await this.makeRequest('/ai/generate', request); - } catch (error) { - throw new Error('Failed to generate Sway code. Please try again.'); - } - } - - async analyzeError(request: any): Promise { - if (!this.isAvailable()) { - throw new Error('AI service not available. Please check backend configuration.'); - } - - try { - return await this.makeRequest('/ai/analyze-error', request); - } catch (error) { - throw new Error('Failed to analyze error. Please try again.'); - } - } - - // MCP Service methods - async searchDocs(request: any): Promise { - try { - return await this.makeRequest('/docs/search', request); - } catch (error) { - console.error('MCP searchDocs error:', error); - return { results: [] }; - } - } - - async getRelevantDocs(swayQuery: string): Promise { - try { - const result = await this.makeRequest('/docs/relevant', { query: swayQuery }); - return result.context || ''; - } catch (error) { - console.error('Error getting relevant docs:', error); - return ''; - } - } - - async isDocsAvailable(): Promise { - try { - const result = await this.makeRequest('/docs/health'); - return result.available; - } catch (error) { - return false; - } - } + // Add other non-AI/MCP API methods here as needed } export const apiService = new ApiService(); \ No newline at end of file diff --git a/app/src/services/mcpService.ts b/app/src/services/mcpService.ts deleted file mode 100644 index 41370a5..0000000 --- a/app/src/services/mcpService.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { apiService } from './apiService'; - -export interface MCPRequest { - method: string; - params?: any; -} - -export interface MCPResponse { - result?: any; - error?: { - code: number; - message: string; - }; -} - -export interface SearchDocsRequest { - query: string; - maxResults?: number; -} - -export interface SearchDocsResponse { - results: Array<{ - title: string; - content: string; - url?: string; - relevance?: number; - }>; -} - -class MCPService { - async searchDocs(request: SearchDocsRequest): Promise { - return apiService.searchDocs(request); - } - - async getRelevantDocs(swayQuery: string): Promise { - return apiService.getRelevantDocs(swayQuery); - } - - async isAvailable(): Promise { - return apiService.isDocsAvailable(); - } -} - -export const mcpService = new MCPService(); \ No newline at end of file From 0f06196d4ae12a31944a3e5ffd0eff837ad049d1 Mon Sep 17 00:00:00 2001 From: PraneshASP Date: Tue, 15 Jul 2025 20:01:25 +0530 Subject: [PATCH 05/25] chore: remove ts-backend for ai service --- ai-backend/.env.example | 9 - ai-backend/README.md | 123 --- ai-backend/package-lock.json | 1434 ------------------------- ai-backend/package.json | 28 - ai-backend/pnpm-lock.yaml | 1025 ------------------ ai-backend/src/routes/ai.ts | 41 - ai-backend/src/routes/docs.ts | 67 -- ai-backend/src/server.ts | 99 -- ai-backend/src/services/aiService.ts | 468 -------- ai-backend/src/services/mcpService.ts | 163 --- ai-backend/src/types.ts | 50 - ai-backend/src/utils/errorHandler.ts | 18 - ai-backend/tsconfig.json | 23 - 13 files changed, 3548 deletions(-) delete mode 100644 ai-backend/.env.example delete mode 100644 ai-backend/README.md delete mode 100644 ai-backend/package-lock.json delete mode 100644 ai-backend/package.json delete mode 100644 ai-backend/pnpm-lock.yaml delete mode 100644 ai-backend/src/routes/ai.ts delete mode 100644 ai-backend/src/routes/docs.ts delete mode 100644 ai-backend/src/server.ts delete mode 100644 ai-backend/src/services/aiService.ts delete mode 100644 ai-backend/src/services/mcpService.ts delete mode 100644 ai-backend/src/types.ts delete mode 100644 ai-backend/src/utils/errorHandler.ts delete mode 100644 ai-backend/tsconfig.json diff --git a/ai-backend/.env.example b/ai-backend/.env.example deleted file mode 100644 index 913e922..0000000 --- a/ai-backend/.env.example +++ /dev/null @@ -1,9 +0,0 @@ -PORT=3001 -NODE_ENV=development - -GEMINI_API_KEY=your_gemini_api_key_here - -FUEL_DOCS_MCP_PATH= -FUEL_DOCS_VECTRA_INDEX_PATH= - -CORS_ORIGIN=http://localhost:3000 \ No newline at end of file diff --git a/ai-backend/README.md b/ai-backend/README.md deleted file mode 100644 index 920b89b..0000000 --- a/ai-backend/README.md +++ /dev/null @@ -1,123 +0,0 @@ -# Sway Playground AI Backend - -A lightweight Node.js backend service that provides AI-powered code generation and documentation search for the Sway Playground. This service bridges the browser frontend with the fuel-docs MCP server and Gemini AI. - -## Features - -- **AI Code Generation**: Generate Sway smart contracts from user prompts -- **Documentation Search**: Search and retrieve relevant Fuel/Sway documentation -- **Error Analysis**: Analyze compilation errors and provide fix suggestions -- **MCP Integration**: Connects to fuel-docs MCP server for documentation context - -## Quick Start - -### 1. Install Dependencies -```bash -pnpm install -``` - -### 2. Setup Environment -```bash -cp .env.example .env -# Edit .env with your configuration -``` - -Required environment variables: -```env -GEMINI_API_KEY=your_gemini_api_key_here -FUEL_DOCS_MCP_PATH=/path/to/fuel-mcp-server/src/mcp-server.ts -FUEL_DOCS_VECTRA_INDEX_PATH=/path/to/fuel-mcp-server/vectra_index -``` - -### 3. Start Development Server -```bash -pnpm dev -``` - -The server will start on `http://localhost:3001` - -## API Endpoints - -### AI Endpoints - -#### Generate Sway Code -```http -POST /api/ai/generate -Content-Type: application/json - -{ - "prompt": "Create a token contract with minting functionality" -} -``` - -#### Analyze Compilation Error -```http -POST /api/ai/analyze-error -Content-Type: application/json - -{ - "errorMessage": "cannot find function `transfer` in scope", - "sourceCode": "contract MyToken { ... }" -} -``` - -### Documentation Endpoints - -#### Search Documentation -```http -POST /api/docs/search -Content-Type: application/json - -{ - "query": "storage read write", - "maxResults": 5 -} -``` - -#### Get Relevant Documentation -```http -POST /api/docs/relevant -Content-Type: application/json - -{ - "query": "token contract implementation" -} -``` - -#### Health Check -```http -GET /api/docs/health -``` - -### Services - -- **AIService**: Handles Gemini AI integration for code generation and analysis -- **MCPService**: Manages fuel-docs MCP server communication via child process -- **Routes**: Express.js API endpoints for AI and documentation operations - -### MCP Integration - -The backend spawns the fuel-docs MCP server as a child process and communicates via JSON-RPC over stdio: - -```typescript -// MCP server is spawned with: -bun run /path/to/fuel-mcp-server/src/mcp-server.ts - -// Environment passed: -VECTRA_INDEX_PATH=/path/to/vectra_index -``` - -> TODO: Convert docs server into a remote MCP server - - -## Environment Variables - -| Variable | Description | Required | -|----------|-------------|----------| -| `PORT` | Server port (default: 3001) | No | -| `GEMINI_API_KEY` | Google Gemini AI API key | Yes | -| `FUEL_DOCS_MCP_PATH` | Path to MCP server TypeScript file | No* | -| `FUEL_DOCS_VECTRA_INDEX_PATH` | Path to Vectra index directory | No* | -| `NODE_ENV` | Environment (development/production) | No | - -*Required for documentation feature \ No newline at end of file diff --git a/ai-backend/package-lock.json b/ai-backend/package-lock.json deleted file mode 100644 index cec43a8..0000000 --- a/ai-backend/package-lock.json +++ /dev/null @@ -1,1434 +0,0 @@ -{ - "name": "sway-playground-ai-backend", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "sway-playground-ai-backend", - "version": "1.0.0", - "dependencies": { - "@google/generative-ai": "^0.24.1", - "cors": "^2.8.5", - "dotenv": "^16.5.0", - "express": "^4.18.2" - }, - "devDependencies": { - "@types/cors": "^2.8.17", - "@types/express": "^4.17.21", - "@types/node": "^24.0.1", - "tsx": "^4.6.2", - "typescript": "^5.3.3" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", - "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", - "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", - "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", - "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", - "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", - "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", - "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", - "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", - "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", - "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", - "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", - "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", - "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", - "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", - "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", - "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", - "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", - "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", - "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", - "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", - "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", - "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", - "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", - "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", - "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@google/generative-ai": { - "version": "0.24.1", - "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.24.1.tgz", - "integrity": "sha512-MqO+MLfM6kjxcKoy0p1wRzG3b4ZZXtPI+z2IE26UogS2Cm/XHO+7gGRBh6gcJsOiIVoH93UwKvW4HdgiOZCy9Q==", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@types/body-parser": { - "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", - "dev": true, - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/cors": { - "version": "2.8.19", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", - "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/express": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", - "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", - "dev": true, - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.19.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", - "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", - "dev": true, - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/http-errors": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "dev": true - }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true - }, - "node_modules/@types/node": { - "version": "24.0.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.1.tgz", - "integrity": "sha512-MX4Zioh39chHlDJbKmEgydJDS3tspMP/lnQC67G3SWsTnb9NeYVWOjkxpOSy4oMfPs4StcWHwBrvUb4ybfnuaw==", - "dev": true, - "dependencies": { - "undici-types": "~7.8.0" - } - }, - "node_modules/@types/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", - "dev": true - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true - }, - "node_modules/@types/send": { - "version": "0.17.5", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", - "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", - "dev": true, - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", - "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", - "dev": true, - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "*" - } - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/dotenv": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", - "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/esbuild": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", - "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.5", - "@esbuild/android-arm": "0.25.5", - "@esbuild/android-arm64": "0.25.5", - "@esbuild/android-x64": "0.25.5", - "@esbuild/darwin-arm64": "0.25.5", - "@esbuild/darwin-x64": "0.25.5", - "@esbuild/freebsd-arm64": "0.25.5", - "@esbuild/freebsd-x64": "0.25.5", - "@esbuild/linux-arm": "0.25.5", - "@esbuild/linux-arm64": "0.25.5", - "@esbuild/linux-ia32": "0.25.5", - "@esbuild/linux-loong64": "0.25.5", - "@esbuild/linux-mips64el": "0.25.5", - "@esbuild/linux-ppc64": "0.25.5", - "@esbuild/linux-riscv64": "0.25.5", - "@esbuild/linux-s390x": "0.25.5", - "@esbuild/linux-x64": "0.25.5", - "@esbuild/netbsd-arm64": "0.25.5", - "@esbuild/netbsd-x64": "0.25.5", - "@esbuild/openbsd-arm64": "0.25.5", - "@esbuild/openbsd-x64": "0.25.5", - "@esbuild/sunos-x64": "0.25.5", - "@esbuild/win32-arm64": "0.25.5", - "@esbuild/win32-ia32": "0.25.5", - "@esbuild/win32-x64": "0.25.5" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-tsconfig": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", - "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", - "dev": true, - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tsx": { - "version": "4.20.3", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.3.tgz", - "integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==", - "dev": true, - "dependencies": { - "esbuild": "~0.25.0", - "get-tsconfig": "^4.7.5" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", - "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", - "dev": true - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "engines": { - "node": ">= 0.8" - } - } - } -} diff --git a/ai-backend/package.json b/ai-backend/package.json deleted file mode 100644 index 4dd7a8e..0000000 --- a/ai-backend/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "sway-playground-ai-backend", - "version": "1.0.0", - "description": "Tiny Node.js backend for AI and MCP integration with Sway Playground", - "main": "dist/server.js", - "scripts": { - "dev": "tsx watch src/server.ts", - "build": "tsc", - "start": "node dist/server.js", - "type-check": "tsc --noEmit" - }, - "dependencies": { - "@google/generative-ai": "^0.24.1", - "cors": "^2.8.5", - "dotenv": "^16.5.0", - "express": "^4.18.2" - }, - "devDependencies": { - "@types/cors": "^2.8.17", - "@types/express": "^4.17.21", - "@types/node": "^24.0.1", - "tsx": "^4.6.2", - "typescript": "^5.3.3" - }, - "engines": { - "node": ">=18.0.0" - } -} diff --git a/ai-backend/pnpm-lock.yaml b/ai-backend/pnpm-lock.yaml deleted file mode 100644 index 799a7e3..0000000 --- a/ai-backend/pnpm-lock.yaml +++ /dev/null @@ -1,1025 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - '@google/generative-ai': - specifier: ^0.24.1 - version: 0.24.1 - cors: - specifier: ^2.8.5 - version: 2.8.5 - dotenv: - specifier: ^16.3.1 - version: 16.5.0 - express: - specifier: ^4.18.2 - version: 4.21.2 - devDependencies: - '@types/cors': - specifier: ^2.8.17 - version: 2.8.19 - '@types/express': - specifier: ^4.17.21 - version: 4.17.23 - '@types/node': - specifier: ^24.0.1 - version: 24.0.1 - tsx: - specifier: ^4.6.2 - version: 4.20.3 - typescript: - specifier: ^5.3.3 - version: 5.8.3 - -packages: - - '@esbuild/aix-ppc64@0.25.5': - resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.25.5': - resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.25.5': - resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.25.5': - resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.25.5': - resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.25.5': - resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.25.5': - resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.25.5': - resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.25.5': - resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.25.5': - resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.25.5': - resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.25.5': - resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.25.5': - resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.25.5': - resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.25.5': - resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.25.5': - resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.25.5': - resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.25.5': - resolution: {integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.25.5': - resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.25.5': - resolution: {integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.25.5': - resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/sunos-x64@0.25.5': - resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.25.5': - resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.25.5': - resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.25.5': - resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==} - 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'} - - '@types/body-parser@1.19.6': - resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} - - '@types/connect@3.4.38': - resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} - - '@types/cors@2.8.19': - resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} - - '@types/express-serve-static-core@4.19.6': - resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==} - - '@types/express@4.17.23': - resolution: {integrity: sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==} - - '@types/http-errors@2.0.5': - resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} - - '@types/mime@1.3.5': - resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} - - '@types/node@24.0.1': - resolution: {integrity: sha512-MX4Zioh39chHlDJbKmEgydJDS3tspMP/lnQC67G3SWsTnb9NeYVWOjkxpOSy4oMfPs4StcWHwBrvUb4ybfnuaw==} - - '@types/qs@6.14.0': - resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} - - '@types/range-parser@1.2.7': - resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} - - '@types/send@0.17.5': - resolution: {integrity: sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==} - - '@types/serve-static@1.15.8': - resolution: {integrity: sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==} - - accepts@1.3.8: - resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} - engines: {node: '>= 0.6'} - - array-flatten@1.1.1: - resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} - - body-parser@1.20.3: - resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - - bytes@3.1.2: - resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} - engines: {node: '>= 0.8'} - - call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} - - call-bound@1.0.4: - resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} - engines: {node: '>= 0.4'} - - content-disposition@0.5.4: - resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} - engines: {node: '>= 0.6'} - - content-type@1.0.5: - resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} - engines: {node: '>= 0.6'} - - cookie-signature@1.0.6: - resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} - - cookie@0.7.1: - resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} - engines: {node: '>= 0.6'} - - cors@2.8.5: - resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} - engines: {node: '>= 0.10'} - - debug@2.6.9: - resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - depd@2.0.0: - resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} - engines: {node: '>= 0.8'} - - destroy@1.2.0: - resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - - dotenv@16.5.0: - resolution: {integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==} - engines: {node: '>=12'} - - dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} - - ee-first@1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - - encodeurl@1.0.2: - resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} - engines: {node: '>= 0.8'} - - encodeurl@2.0.0: - resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} - engines: {node: '>= 0.8'} - - es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} - - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} - - es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} - engines: {node: '>= 0.4'} - - esbuild@0.25.5: - resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==} - engines: {node: '>=18'} - hasBin: true - - escape-html@1.0.3: - resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - - etag@1.8.1: - resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} - engines: {node: '>= 0.6'} - - express@4.21.2: - resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} - engines: {node: '>= 0.10.0'} - - finalhandler@1.3.1: - resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} - engines: {node: '>= 0.8'} - - forwarded@0.2.0: - resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} - engines: {node: '>= 0.6'} - - fresh@0.5.2: - resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} - engines: {node: '>= 0.6'} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} - - get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} - - get-tsconfig@4.10.1: - resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} - - gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} - - has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} - - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} - - http-errors@2.0.0: - resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} - engines: {node: '>= 0.8'} - - iconv-lite@0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} - engines: {node: '>=0.10.0'} - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - ipaddr.js@1.9.1: - resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} - engines: {node: '>= 0.10'} - - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} - - media-typer@0.3.0: - resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} - engines: {node: '>= 0.6'} - - merge-descriptors@1.0.3: - resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} - - methods@1.1.2: - resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} - engines: {node: '>= 0.6'} - - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - - mime@1.6.0: - resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} - engines: {node: '>=4'} - hasBin: true - - ms@2.0.0: - resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - negotiator@0.6.3: - resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} - engines: {node: '>= 0.6'} - - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - object-inspect@1.13.4: - resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} - engines: {node: '>= 0.4'} - - on-finished@2.4.1: - resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} - engines: {node: '>= 0.8'} - - parseurl@1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} - - path-to-regexp@0.1.12: - resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} - - proxy-addr@2.0.7: - resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} - engines: {node: '>= 0.10'} - - qs@6.13.0: - resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} - engines: {node: '>=0.6'} - - range-parser@1.2.1: - resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} - engines: {node: '>= 0.6'} - - raw-body@2.5.2: - resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} - engines: {node: '>= 0.8'} - - resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - - send@0.19.0: - resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} - engines: {node: '>= 0.8.0'} - - serve-static@1.16.2: - resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} - engines: {node: '>= 0.8.0'} - - setprototypeof@1.2.0: - resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - - side-channel-list@1.0.0: - resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} - engines: {node: '>= 0.4'} - - side-channel-map@1.0.1: - resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} - engines: {node: '>= 0.4'} - - side-channel-weakmap@1.0.2: - resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} - engines: {node: '>= 0.4'} - - side-channel@1.1.0: - resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} - engines: {node: '>= 0.4'} - - statuses@2.0.1: - resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} - engines: {node: '>= 0.8'} - - toidentifier@1.0.1: - resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} - engines: {node: '>=0.6'} - - tsx@4.20.3: - resolution: {integrity: sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==} - engines: {node: '>=18.0.0'} - hasBin: true - - type-is@1.6.18: - resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} - engines: {node: '>= 0.6'} - - typescript@5.8.3: - resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} - engines: {node: '>=14.17'} - hasBin: true - - undici-types@7.8.0: - resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} - - unpipe@1.0.0: - resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} - engines: {node: '>= 0.8'} - - utils-merge@1.0.1: - resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} - engines: {node: '>= 0.4.0'} - - vary@1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} - engines: {node: '>= 0.8'} - -snapshots: - - '@esbuild/aix-ppc64@0.25.5': - optional: true - - '@esbuild/android-arm64@0.25.5': - optional: true - - '@esbuild/android-arm@0.25.5': - optional: true - - '@esbuild/android-x64@0.25.5': - optional: true - - '@esbuild/darwin-arm64@0.25.5': - optional: true - - '@esbuild/darwin-x64@0.25.5': - optional: true - - '@esbuild/freebsd-arm64@0.25.5': - optional: true - - '@esbuild/freebsd-x64@0.25.5': - optional: true - - '@esbuild/linux-arm64@0.25.5': - optional: true - - '@esbuild/linux-arm@0.25.5': - optional: true - - '@esbuild/linux-ia32@0.25.5': - optional: true - - '@esbuild/linux-loong64@0.25.5': - optional: true - - '@esbuild/linux-mips64el@0.25.5': - optional: true - - '@esbuild/linux-ppc64@0.25.5': - optional: true - - '@esbuild/linux-riscv64@0.25.5': - optional: true - - '@esbuild/linux-s390x@0.25.5': - optional: true - - '@esbuild/linux-x64@0.25.5': - optional: true - - '@esbuild/netbsd-arm64@0.25.5': - optional: true - - '@esbuild/netbsd-x64@0.25.5': - optional: true - - '@esbuild/openbsd-arm64@0.25.5': - optional: true - - '@esbuild/openbsd-x64@0.25.5': - optional: true - - '@esbuild/sunos-x64@0.25.5': - optional: true - - '@esbuild/win32-arm64@0.25.5': - optional: true - - '@esbuild/win32-ia32@0.25.5': - optional: true - - '@esbuild/win32-x64@0.25.5': - optional: true - - '@google/generative-ai@0.24.1': {} - - '@types/body-parser@1.19.6': - dependencies: - '@types/connect': 3.4.38 - '@types/node': 24.0.1 - - '@types/connect@3.4.38': - dependencies: - '@types/node': 24.0.1 - - '@types/cors@2.8.19': - dependencies: - '@types/node': 24.0.1 - - '@types/express-serve-static-core@4.19.6': - dependencies: - '@types/node': 24.0.1 - '@types/qs': 6.14.0 - '@types/range-parser': 1.2.7 - '@types/send': 0.17.5 - - '@types/express@4.17.23': - dependencies: - '@types/body-parser': 1.19.6 - '@types/express-serve-static-core': 4.19.6 - '@types/qs': 6.14.0 - '@types/serve-static': 1.15.8 - - '@types/http-errors@2.0.5': {} - - '@types/mime@1.3.5': {} - - '@types/node@24.0.1': - dependencies: - undici-types: 7.8.0 - - '@types/qs@6.14.0': {} - - '@types/range-parser@1.2.7': {} - - '@types/send@0.17.5': - dependencies: - '@types/mime': 1.3.5 - '@types/node': 24.0.1 - - '@types/serve-static@1.15.8': - dependencies: - '@types/http-errors': 2.0.5 - '@types/node': 24.0.1 - '@types/send': 0.17.5 - - accepts@1.3.8: - dependencies: - mime-types: 2.1.35 - negotiator: 0.6.3 - - array-flatten@1.1.1: {} - - body-parser@1.20.3: - dependencies: - bytes: 3.1.2 - content-type: 1.0.5 - debug: 2.6.9 - depd: 2.0.0 - destroy: 1.2.0 - http-errors: 2.0.0 - iconv-lite: 0.4.24 - on-finished: 2.4.1 - qs: 6.13.0 - raw-body: 2.5.2 - type-is: 1.6.18 - unpipe: 1.0.0 - transitivePeerDependencies: - - supports-color - - bytes@3.1.2: {} - - call-bind-apply-helpers@1.0.2: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - - call-bound@1.0.4: - dependencies: - call-bind-apply-helpers: 1.0.2 - get-intrinsic: 1.3.0 - - content-disposition@0.5.4: - dependencies: - safe-buffer: 5.2.1 - - content-type@1.0.5: {} - - cookie-signature@1.0.6: {} - - cookie@0.7.1: {} - - cors@2.8.5: - dependencies: - object-assign: 4.1.1 - vary: 1.1.2 - - debug@2.6.9: - dependencies: - ms: 2.0.0 - - depd@2.0.0: {} - - destroy@1.2.0: {} - - dotenv@16.5.0: {} - - dunder-proto@1.0.1: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-errors: 1.3.0 - gopd: 1.2.0 - - ee-first@1.1.1: {} - - encodeurl@1.0.2: {} - - encodeurl@2.0.0: {} - - es-define-property@1.0.1: {} - - es-errors@1.3.0: {} - - es-object-atoms@1.1.1: - dependencies: - es-errors: 1.3.0 - - esbuild@0.25.5: - optionalDependencies: - '@esbuild/aix-ppc64': 0.25.5 - '@esbuild/android-arm': 0.25.5 - '@esbuild/android-arm64': 0.25.5 - '@esbuild/android-x64': 0.25.5 - '@esbuild/darwin-arm64': 0.25.5 - '@esbuild/darwin-x64': 0.25.5 - '@esbuild/freebsd-arm64': 0.25.5 - '@esbuild/freebsd-x64': 0.25.5 - '@esbuild/linux-arm': 0.25.5 - '@esbuild/linux-arm64': 0.25.5 - '@esbuild/linux-ia32': 0.25.5 - '@esbuild/linux-loong64': 0.25.5 - '@esbuild/linux-mips64el': 0.25.5 - '@esbuild/linux-ppc64': 0.25.5 - '@esbuild/linux-riscv64': 0.25.5 - '@esbuild/linux-s390x': 0.25.5 - '@esbuild/linux-x64': 0.25.5 - '@esbuild/netbsd-arm64': 0.25.5 - '@esbuild/netbsd-x64': 0.25.5 - '@esbuild/openbsd-arm64': 0.25.5 - '@esbuild/openbsd-x64': 0.25.5 - '@esbuild/sunos-x64': 0.25.5 - '@esbuild/win32-arm64': 0.25.5 - '@esbuild/win32-ia32': 0.25.5 - '@esbuild/win32-x64': 0.25.5 - - escape-html@1.0.3: {} - - etag@1.8.1: {} - - express@4.21.2: - dependencies: - accepts: 1.3.8 - array-flatten: 1.1.1 - body-parser: 1.20.3 - content-disposition: 0.5.4 - content-type: 1.0.5 - cookie: 0.7.1 - cookie-signature: 1.0.6 - debug: 2.6.9 - depd: 2.0.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 1.3.1 - fresh: 0.5.2 - http-errors: 2.0.0 - merge-descriptors: 1.0.3 - methods: 1.1.2 - on-finished: 2.4.1 - parseurl: 1.3.3 - path-to-regexp: 0.1.12 - proxy-addr: 2.0.7 - qs: 6.13.0 - range-parser: 1.2.1 - safe-buffer: 5.2.1 - send: 0.19.0 - serve-static: 1.16.2 - setprototypeof: 1.2.0 - statuses: 2.0.1 - type-is: 1.6.18 - utils-merge: 1.0.1 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - - finalhandler@1.3.1: - dependencies: - debug: 2.6.9 - encodeurl: 2.0.0 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.1 - unpipe: 1.0.0 - transitivePeerDependencies: - - supports-color - - forwarded@0.2.0: {} - - fresh@0.5.2: {} - - fsevents@2.3.3: - optional: true - - function-bind@1.1.2: {} - - get-intrinsic@1.3.0: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - function-bind: 1.1.2 - get-proto: 1.0.1 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.2 - math-intrinsics: 1.1.0 - - get-proto@1.0.1: - dependencies: - dunder-proto: 1.0.1 - es-object-atoms: 1.1.1 - - get-tsconfig@4.10.1: - dependencies: - resolve-pkg-maps: 1.0.0 - - gopd@1.2.0: {} - - has-symbols@1.1.0: {} - - hasown@2.0.2: - dependencies: - function-bind: 1.1.2 - - http-errors@2.0.0: - dependencies: - depd: 2.0.0 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 2.0.1 - toidentifier: 1.0.1 - - iconv-lite@0.4.24: - dependencies: - safer-buffer: 2.1.2 - - inherits@2.0.4: {} - - ipaddr.js@1.9.1: {} - - math-intrinsics@1.1.0: {} - - media-typer@0.3.0: {} - - merge-descriptors@1.0.3: {} - - methods@1.1.2: {} - - mime-db@1.52.0: {} - - mime-types@2.1.35: - dependencies: - mime-db: 1.52.0 - - mime@1.6.0: {} - - ms@2.0.0: {} - - ms@2.1.3: {} - - negotiator@0.6.3: {} - - object-assign@4.1.1: {} - - object-inspect@1.13.4: {} - - on-finished@2.4.1: - dependencies: - ee-first: 1.1.1 - - parseurl@1.3.3: {} - - path-to-regexp@0.1.12: {} - - proxy-addr@2.0.7: - dependencies: - forwarded: 0.2.0 - ipaddr.js: 1.9.1 - - qs@6.13.0: - dependencies: - side-channel: 1.1.0 - - range-parser@1.2.1: {} - - raw-body@2.5.2: - dependencies: - bytes: 3.1.2 - http-errors: 2.0.0 - iconv-lite: 0.4.24 - unpipe: 1.0.0 - - resolve-pkg-maps@1.0.0: {} - - safe-buffer@5.2.1: {} - - safer-buffer@2.1.2: {} - - send@0.19.0: - dependencies: - debug: 2.6.9 - depd: 2.0.0 - destroy: 1.2.0 - encodeurl: 1.0.2 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 0.5.2 - http-errors: 2.0.0 - mime: 1.6.0 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.2.1 - statuses: 2.0.1 - transitivePeerDependencies: - - supports-color - - serve-static@1.16.2: - dependencies: - encodeurl: 2.0.0 - escape-html: 1.0.3 - parseurl: 1.3.3 - send: 0.19.0 - transitivePeerDependencies: - - supports-color - - setprototypeof@1.2.0: {} - - side-channel-list@1.0.0: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - - side-channel-map@1.0.1: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - - side-channel-weakmap@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - side-channel-map: 1.0.1 - - side-channel@1.1.0: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - side-channel-list: 1.0.0 - side-channel-map: 1.0.1 - side-channel-weakmap: 1.0.2 - - statuses@2.0.1: {} - - toidentifier@1.0.1: {} - - tsx@4.20.3: - dependencies: - esbuild: 0.25.5 - get-tsconfig: 4.10.1 - optionalDependencies: - fsevents: 2.3.3 - - type-is@1.6.18: - dependencies: - media-typer: 0.3.0 - mime-types: 2.1.35 - - typescript@5.8.3: {} - - undici-types@7.8.0: {} - - unpipe@1.0.0: {} - - utils-merge@1.0.1: {} - - vary@1.1.2: {} diff --git a/ai-backend/src/routes/ai.ts b/ai-backend/src/routes/ai.ts deleted file mode 100644 index 5e4245c..0000000 --- a/ai-backend/src/routes/ai.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Router, Request, Response } from 'express'; -import { AIService } from '../services/aiService'; -import { handleRouteError, handleValidationError } from '../utils/errorHandler'; - -export function createAIRouter(aiService: AIService): Router { - const router = Router(); - - router.post('/generate', async (req: Request, res: Response) => { - const { prompt } = req.body; - - if (!prompt || typeof prompt !== 'string') { - return handleValidationError(res, 'Missing or invalid prompt'); - } - - try { - const result = await aiService.generateSwayCode({ prompt }); - res.json(result); - } catch (error) { - handleRouteError(res, error, 'Code generation'); - } - }); - - router.post('/analyze-error', async (req: Request, res: Response) => { - const { errorMessage, sourceCode, lineNumber } = req.body; - - if (!errorMessage || !sourceCode) { - return handleValidationError(res, 'Missing errorMessage or sourceCode'); - } - - try { - const result = await aiService.analyzeError({ errorMessage, sourceCode, lineNumber }); - res.json(result); - } catch (error) { - handleRouteError(res, error, 'Error analysis'); - } - }); - - - return router; -} - diff --git a/ai-backend/src/routes/docs.ts b/ai-backend/src/routes/docs.ts deleted file mode 100644 index c26cd21..0000000 --- a/ai-backend/src/routes/docs.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { Router, Request, Response } from 'express'; -import { MCPService } from '../services/mcpService'; -import { handleRouteError, handleValidationError, handleServiceUnavailable } from '../utils/errorHandler'; - -export function createDocsRouter(mcpService: MCPService): Router { - const router = Router(); - - router.post('/search', async (req: Request, res: Response) => { - const { query } = req.body; - - if (!query || typeof query !== 'string') { - return handleValidationError(res, 'Missing or invalid query'); - } - - if (!mcpService.isAvailable()) { - return handleServiceUnavailable(res, 'Documentation'); - } - - try { - const result = await mcpService.searchDocs({ query, maxResults: req.body.maxResults }); - res.json(result); - } catch (error) { - handleRouteError(res, error, 'Documentation search'); - } - }); - - router.post('/relevant', async (req: Request, res: Response) => { - const { query } = req.body; - - if (!query || typeof query !== 'string') { - return handleValidationError(res, 'Missing or invalid query'); - } - - if (!mcpService.isAvailable()) { - return handleServiceUnavailable(res, 'Documentation'); - } - - try { - const result = await mcpService.getRelevantDocs(query); - res.json(result); - } catch (error) { - handleRouteError(res, error, 'Relevant documentation'); - } - }); - - router.get('/std-context', async (_req: Request, res: Response) => { - if (!mcpService.isAvailable()) { - return handleServiceUnavailable(res, 'Documentation'); - } - - try { - const context = await mcpService.getStdContext(); - res.json({ context }); - } catch (error) { - handleRouteError(res, error, 'Standard library context'); - } - }); - - router.get('/health', (_req: Request, res: Response) => { - res.json({ - available: mcpService.isAvailable(), - status: mcpService.isAvailable() ? 'connected' : 'disconnected' - }); - }); - - return router; -} \ No newline at end of file diff --git a/ai-backend/src/server.ts b/ai-backend/src/server.ts deleted file mode 100644 index 437917c..0000000 --- a/ai-backend/src/server.ts +++ /dev/null @@ -1,99 +0,0 @@ -import express from 'express'; -import cors from 'cors'; -import * as dotenv from 'dotenv'; -import { MCPService } from './services/mcpService'; -import { AIService } from './services/aiService'; -import { createAIRouter } from './routes/ai'; -import { createDocsRouter } from './routes/docs'; - -dotenv.config(); - -const app = express(); -const PORT = process.env.PORT || 3001; - -const mcpService = new MCPService( - process.env.FUEL_DOCS_MCP_PATH || '', - process.env.FUEL_DOCS_VECTRA_INDEX_PATH || '' -); - -const aiService = new AIService( - process.env.GEMINI_API_KEY || '', - mcpService -); - -app.use(express.json({ limit: '10mb' })); -app.use(cors({ - origin: process.env.CORS_ORIGIN || 'http://localhost:3000', - credentials: true -})); - -app.get('/health', (req, res) => { - res.json({ - status: 'healthy', - timestamp: new Date().toISOString(), - version: '1.0.0' - }); -}); - -async function startServer() { - try { - console.log('Starting AI backend server...'); - - if (process.env.FUEL_DOCS_MCP_PATH && process.env.FUEL_DOCS_VECTRA_INDEX_PATH) { - try { - console.log('Initializing MCP service...'); - await mcpService.initialize(); - console.log('MCP service initialized successfully'); - } catch (error) { - console.warn('Failed to initialize MCP service:', error); - console.warn('MCP-dependent features will be unavailable'); - } - } else { - console.warn('MCP configuration missing, documentation search will be unavailable'); - } - - app.use('/api/ai', createAIRouter(aiService)); - app.use('/api/docs', createDocsRouter(mcpService)); - - app.use((error: Error, _req: express.Request, res: express.Response, _next: express.NextFunction) => { - console.error('Unhandled error:', error); - res.status(500).json({ - error: 'Internal server error', - message: process.env.NODE_ENV === 'development' ? error.message : 'Something went wrong' - }); - }); - - app.all('*', (req, res) => { - res.status(404).json({ error: 'Endpoint not found' }); - }); - - app.listen(PORT, () => { - console.log(`🚀 Sway Playground AI Backend running on port ${PORT}`); - console.log(`📚 API endpoints:`); - console.log(` POST /api/ai/generate - Generate Sway code`); - console.log(` POST /api/ai/analyze-error - Analyze compilation errors`); - console.log(` POST /api/docs/search - Search documentation`); - console.log(` POST /api/docs/relevant - Get relevant docs`); - console.log(` GET /api/docs/health - MCP service health`); - console.log(` GET /health - Server health check`); - }); - - process.on('SIGTERM', () => { - console.log('SIGTERM received, shutting down gracefully...'); - mcpService.destroy(); - process.exit(0); - }); - - process.on('SIGINT', () => { - console.log('SIGINT received, shutting down gracefully...'); - mcpService.destroy(); - process.exit(0); - }); - - } catch (error) { - console.error('Failed to start server:', error); - process.exit(1); - } -} - -startServer(); \ No newline at end of file diff --git a/ai-backend/src/services/aiService.ts b/ai-backend/src/services/aiService.ts deleted file mode 100644 index 6d778da..0000000 --- a/ai-backend/src/services/aiService.ts +++ /dev/null @@ -1,468 +0,0 @@ -import { GoogleGenerativeAI } from '@google/generative-ai'; -import { - SwayCodeGenerationRequest, - SwayCodeGenerationResponse, - ErrorAnalysisRequest, - ErrorAnalysisResponse -} from '../types'; - -export class AIService { - private genai: GoogleGenerativeAI | null = null; - private model: any = null; - private functionDeclarations: any[] = []; - private documentationSearchRequired = true; - - constructor(apiKey: string, private mcpService?: any) { - if (apiKey) { - this.genai = new GoogleGenerativeAI(apiKey); - - this.functionDeclarations = [ - { - name: "searchDocumentation", - description: "Search Fuel/Sway documentation for relevant information", - parameters: { - type: "object", - properties: { - query: { - type: "string", - description: "Search query for documentation" - }, - maxResults: { - type: "number", - description: "Maximum number of results to return", - default: 5 - } - }, - required: ["query"] - } - }, - { - name: "getRelevantDocumentation", - description: "Get relevant documentation context for a specific topic or code", - parameters: { - type: "object", - properties: { - topic: { - type: "string", - description: "The topic or code to get relevant documentation for" - } - }, - required: ["topic"] - } - } - ]; - - this.model = this.genai.getGenerativeModel({ - model: "gemini-2.5-flash-preview-05-20", - tools: [{ functionDeclarations: this.functionDeclarations }], - toolConfig: { - functionCallingConfig: { - mode: "auto" as any - } - }, - generationConfig: { - temperature: 0.7, - topK: 40, - topP: 0.95, - maxOutputTokens: 8192, - } - }); - } - } - - private isAvailable(): boolean { - return this.genai !== null && this.model !== null; - } - - public setMCPService(mcpService: any): void { - this.mcpService = mcpService; - } - - - async generateSwayCode(request: SwayCodeGenerationRequest): Promise { - if (!this.isAvailable()) { - throw new Error('AI service not available. Please check your API key configuration.'); - } - - const systemPrompt = `You are an expert Sway smart contract developer. Generate secure, efficient Sway contracts. - -MANDATORY: ALWAYS call 'searchDocumentation' BEFORE generating code. - -SWAY SYNTAX ESSENTIALS: -- Contract: 'contract;' -- ABI: 'abi ContractName { ... }' -- Storage: 'storage { field: Type = default_value, }' (trailing comma required) -- Implementation: 'impl AbiName for Contract { ... }' -- Storage access: '#[storage(read)]' or '#[storage(read, write)]' on both ABI and implementation -- Payable: '#[payable]' on both ABI and implementation -- StorageMap: storage.map.get(key).try_read().unwrap_or(0) -- Validation: assert(condition) or require(condition, "message") -- Identity: Identity::Address(addr) -- No need to import AssetId - Included in prelude. - - -IMPORTS: -- use std::{asset::{mint_to, transfer}, call_frames::msg_asset_id, context::msg_amount, auth::msg_sender, block::timestamp, asset::transfer}; -- use standards::{src3::SRC3, src5::SRC5, src20::SRC20}; - -FALLBACK: If documentation search fails, direct users to docs.fuel.network/docs/sway/`; - - const userPrompt = `Generate a Sway smart contract for: ${request.prompt} - -STEPS: -1. Call 'searchDocumentation' with relevant keywords -2. Generate complete, working Sway contract code -3. Provide brief explanation - -SEARCH KEYWORDS: -- Tokens: "SRC20", "token", "mint", "transfer" -- NFTs: "SRC3", "NFT" -- Access control: "SRC5", "ownership" -- DeFi: "asset management", "swap" -- Basic: "contract", "storage", "functions"` - try { - const result = await this.model.generateContent({ - contents: [{ - role: "user", - parts: [{ text: `${systemPrompt}\n\n${userPrompt}` }] - }] - }); - - const response = result.response; - const functionCalls = response.functionCalls(); - - if (functionCalls && functionCalls.length > 0) { - - const functionResponses = await Promise.all( - functionCalls.map(async (call: any) => { - return await this.handleFunctionCall(call); - }) - ); - - const followUpContents = [ - { role: "user", parts: [{ text: `${systemPrompt}\n\n${userPrompt}` }] }, - { role: "model", parts: response.candidates[0].content.parts }, - { - role: "function", - parts: functionResponses.map((resp, index) => ({ - functionResponse: { - name: functionCalls[index].name, - response: resp - } - })) - } - ]; - - const followUpResult = await this.model.generateContent({ - contents: followUpContents - }); - - const followUpText = followUpResult.response.text(); - if (!followUpText?.trim()) { - throw new Error('AI response was empty. Please try again with a more specific prompt.'); - } - - return this.parseCodeGenerationResponse(followUpText); - } else { - const response_text = response.text(); - return this.parseCodeGenerationResponse(response_text); - } - } catch (error) { - console.error('AI code generation error:', error); - throw new Error('Failed to generate Sway code. Please try again.'); - } - } - - - async analyzeError(request: ErrorAnalysisRequest): Promise { - if (!this.isAvailable()) { - throw new Error('AI service not available. Please check your API key configuration.'); - } - - const systemPrompt = `You are an expert Sway compiler error analyst. Fix Sway compilation errors with accurate, working code. - -MANDATORY: Always call 'searchDocumentation' before analyzing errors. Go one by one and fix errors. - -CRITICAL SWAY SYNTAX RULES: -1. Context imports: use std::{context::{msg_sender, msg_amount}, call_frames::msg_asset_id}; -2. Storage syntax: storage { field: Type = default_value, } (trailing comma required) -3. Validation: Use assert() not require() -4. Identity type: Identity::Address(addr) for addresses -5. ABI functions: Must match impl exactly -6. Storage attributes: #[storage(read)] or #[storage(read, write)] - -IMPORTANT CORRECTIONS: -- Identity::zero() is NOT a method. Use Identity::Address(Address::zero()). -- Option pattern-match limitation: - // GOOD - if storage.highest_bidder.read().is_some() { … } - // BAD (will not compile) - if let Option::Some(x) = storage.highest_bidder.read() { … } -- assert has ONE parameter; use require for message strings. -- Never import or call transfer_inner; only transfer() is public. -- Always unwrap msg_sender() once: - let sender = msg_sender().expect("unauthenticated"); -- Built-ins for time & value: - msg_amount() // std::context - block_timestamp() // std::context - Never import them from anywhere else. -- There is NO transfer_to_contract. - To move tokens into the contract, call - transfer(this_contract_id(), asset_id, amount); -- Do NOT import StorageMap. - Just use it inside the storage { … } block, e.g. - sales: StorageMap = StorageMap {}, - and access via storage.sales. -- Replace unwrap_or_revert("msg") ➜ expect("msg") (same semantics). -- self is a *type parameter* in Sway ABIs, not a variable. - Call sibling fns directly: - let price = get_current_auction_price(id); - -COMMON ERROR FIXES: -- "No storage has been declared" - - insert a storage { … } block and ensure every .read() / .write() target is declared there. -- "symbol transfer_inner / msg_amount / block_height not found" - - remove the bad import; use the std::context versions shown above. -- "Identity::zero() not found" - replace with Identity::Address(Address::zero()). -- "Option::Some cannot be matched" - read into a variable and use .is_some() / .unwrap() instead of pattern matching. -- "assert expects 1 argument" - change to require(cond,"msg"). -- "No method .write / .read" - make sure the field is declared as a StorageValue (or StorageMap) and the type matches exactly. -- "Could not find symbol transfer_to_contract / msg_amount / block_timestamp" - - Use the import list shown above and call transfer(this_contract_id(), …). -- "Mismatched types – expected Identity, found u64" - - Your parameter order in transfer is wrong. - Correct: (to: Identity, asset_id: AssetId, amount: u64) -- "Function assert expects 1 argument" - - change to require(condition, "explanation") -- "Option::Some cannot be matched" - - use .is_some() / .unwrap() instead of pattern matching. -- "unwrap_or_revert not found" - - use .expect("msg") (same effect). -- "Field access requires a struct" - - The storage field or local struct wasn't declared; verify your - Auction struct and storage map types. -- "cannot find msg_sender": Add use std::auth::msg_sender; -- "cannot find assert": Use assert() instead of require() -- "type mismatch Identity": Use Identity::Address(addr) -- "storage field not found": Check storage block syntax -- "ABI mismatch": Ensure impl matches abi exactly - - insert: storage.my_map.insert(key, value); - - read : storage.my_map.get(key).try_read().unwrap_or(default); -- Nested map read/write: - storage.nested.get(k1).insert(k2, v); // write - let v = storage.nested.get(k1).get(k2).try_read(); // read - -PROVEN SWAY PATTERNS: -- Basic contract structure: - contract; - use std::context::msg_sender; - abi MyContract { fn my_function(); } - impl MyContract for Contract { fn my_function() { } } - -- Storage with validation: - storage { owner: Identity = Identity::Address(Address::zero()), } - #[storage(read)] fn get_owner() -> Identity { storage.owner.read() } - -- Asset operations: - use std::{context::msg_amount, call_frames::msg_asset_id}; - assert(msg_amount() > 0); - -- "No method unwrap_or(StorageKey…, numeric)" - - Insert .try_read() before unwrap_or. - -- "add / subtract / ge … for type {unknown}" - - Ensure the variable is a u64 by calling .try_read().unwrap_or(0). - -- "msg_sender not found" - - use std::auth::msg_sender; and drop the .unwrap(). - -- "assert expects 1 argument" - - Change to require(cond, "reason") **or** use the 1-arg - assert(cond) form. - -- "function in ABI is pure but impl is not" - - Copy the #[storage(...)] attribute to the ABI signature. - -RESPONSE FORMAT: -1. Identify the specific error type -2. Apply the correct Sway syntax fix using proven patterns -3. Return complete working code in \`\`\`sway block - -CRITICAL: Only change what's broken. Use exact syntax from proven patterns above.`; - - const userPrompt = `Fix this Sway compilation error by applying ONLY the necessary changes: - -ERROR: ${request.errorMessage} - -CURRENT CODE: -\`\`\`sway -${request.sourceCode} -\`\`\` - -INSTRUCTIONS: -1. Search documentation for this specific error -2. Identify the exact issue causing the error -3. Apply MINIMAL fixes - change only what's broken -4. Keep all working code unchanged -5. Return the complete corrected contract - -CRITICAL: Return the entire corrected Sway contract in a \`\`\`sway code block. Fix ONLY the error, don't refactor working code.`; - - try { - const result = await this.model.generateContent({ - contents: [{ - role: "user", - parts: [{ text: `${systemPrompt}\n\n${userPrompt}` }] - }] - }); - - const response = result.response; - const functionCalls = response.functionCalls(); - - if (functionCalls && functionCalls.length > 0) { - const hasDocumentationSearch = functionCalls.some((call: any) => - call.name === 'searchDocumentation' || call.name === 'getRelevantDocumentation' - ); - - if (!hasDocumentationSearch && this.documentationSearchRequired) { - console.warn('Error analysis proceeded without mandatory documentation search'); - } - - const functionResponses = await Promise.all( - functionCalls.map(async (call: any) => { - return await this.handleFunctionCall(call); - }) - ); - - const followUpResult = await this.model.generateContent({ - contents: [ - { role: "user", parts: [{ text: `${systemPrompt}\n\n${userPrompt}` }] }, - { role: "model", parts: response.candidates[0].content.parts }, - { - role: "function", - parts: functionResponses.map((resp, index) => ({ - functionResponse: { - name: functionCalls[index].name, - response: resp - } - })) - } - ] - }); - - return this.parseErrorAnalysisResponse(followUpResult.response.text()); - } else { - console.warn('No function calls made for error analysis - documentation search was not attempted'); - const response_text = response.text(); - const parsed = this.parseErrorAnalysisResponse(response_text); - - parsed.analysis += '\n\n⚠️ Note: Documentation search was not available. For more accurate error diagnosis, please check docs.fuel.network/docs/sway/reference/ for compiler messages and syntax reference.'; - - return parsed; - } - } catch (error) { - console.error('AI error analysis error:', error); - throw new Error('Failed to analyze error. Please try again.'); - } - } - - - private async handleFunctionCall(call: any): Promise { - try { - switch (call.name) { - case 'searchDocumentation': - if (this.mcpService && this.mcpService.isAvailable()) { - const result = await this.mcpService.searchDocs(call.args); - if (typeof result === 'string' && result.length > 2000) { - return result.substring(0, 2000) + '\n... (truncated for brevity)'; - } - if (result && typeof result === 'object' && result.results) { - const limitedResults = result.results.slice(0, 3).map((r: any) => ({ - ...r, - content: r.content?.substring(0, 500) + (r.content?.length > 500 ? '...' : '') - })); - return { ...result, results: limitedResults }; - } - return result; - } - return { - error: 'MCP not available', - fallback: `Check docs.fuel.network for "${call.args.query}"` - }; - - case 'getRelevantDocumentation': - if (this.mcpService && this.mcpService.isAvailable()) { - const result = await this.mcpService.getRelevantDocs(call.args.topic); - if (typeof result === 'string' && result.length > 1500) { - return { context: result.substring(0, 1500) + '... (truncated)' }; - } - return { context: result }; - } - return { - error: 'MCP not available', - fallback: `Check docs.fuel.network for "${call.args.topic}"` - }; - - default: - return { error: `Unknown function: ${call.name}` }; - } - } catch (error) { - console.error(`Function call error (${call.name}):`, error); - return { - error: `Failed to execute ${call.name}`, - fallback: `Check docs.fuel.network manually.` - }; - } - } - - private parseCodeGenerationResponse(response: string): SwayCodeGenerationResponse { - const codeMatch = response.match(/```(?:sway|rust)?\n([\s\S]*?)```/); - const code = codeMatch ? codeMatch[1].trim() : response; - - const explanation = response.replace(/```(?:sway|rust)?\n[\s\S]*?```/g, '').trim(); - - if (!code || code.length === 0) { - console.warn('⚠️ EMPTY CODE DETECTED'); - } - - const result = { - code, - explanation: explanation || "Generated Sway smart contract", - suggestions: [ - "Review the generated code for your specific requirements", - "Test the contract thoroughly before deployment", - "Consider gas optimization for complex operations" - ] - }; - - return result; - } - - private parseErrorAnalysisResponse(response: string): ErrorAnalysisResponse { - const codeMatch = response.match(/```(?:sway|rust)?\n([\s\S]*?)```/); - let fixedCode = codeMatch ? codeMatch[1].trim() : undefined; - - if (!fixedCode && response.length > 0) { - const partialCodeMatch = response.match(/```(?:sway|rust)?\n([\s\S]*?)$/); - if (partialCodeMatch) { - fixedCode = partialCodeMatch[1].trim(); - } - } - - let analysis = response; - if (!fixedCode) { - console.warn('No fixed code found in AI response'); - analysis += '\n\n**Incomplete Response**: The AI response was truncated and does not contain the complete fixed code. Please try again or manually apply the suggested fixes.'; - } - - return { - analysis, - suggestions: [ - "Verify the fix addresses the root cause", - "Check for similar patterns in your code", - "Consider adding tests to prevent regression" - ], - fixedCode - }; - } -} \ No newline at end of file diff --git a/ai-backend/src/services/mcpService.ts b/ai-backend/src/services/mcpService.ts deleted file mode 100644 index fbf50ba..0000000 --- a/ai-backend/src/services/mcpService.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { Client } from '@modelcontextprotocol/sdk/client/index.js'; -import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; -import { SearchDocsRequest, SearchDocsResponse } from '../types'; -import * as dotenv from 'dotenv'; -dotenv.config(); - -export class MCPService { - private client: Client | null = null; - private transport: StdioClientTransport | null = null; - private isInitialized = false; - private initializationPromise: Promise | null = null; - - constructor( - private mcpPath: string, - private vectraIndexPath: string - ) {} - - async initialize(): Promise { - if (this.isInitialized || !this.mcpPath) { - return; - } - - try { - - // Create transport with server configuration - this.transport = new StdioClientTransport({ - command: 'bun', - args: ['run', this.mcpPath], - env: { - ...process.env, - VECTRA_INDEX_PATH: this.vectraIndexPath - } - }); - - // Create MCP client - this.client = new Client({ - name: "sway-playground-backend", - version: "1.0.0" - }); - - // Connect with timeout - const connectPromise = this.client.connect(this.transport); - const timeoutPromise = new Promise((_, reject) => { - setTimeout(() => reject(new Error('MCP connection timeout')), 10000); - }); - - await Promise.race([connectPromise, timeoutPromise]); - - this.isInitialized = true; - } catch (error) { - this.cleanup(); - throw error; - } - } - - - async searchDocs(request: SearchDocsRequest): Promise { - try { - // Ensure service is initialized - if (this.initializationPromise) { - await this.initializationPromise; - } else if (!this.isInitialized) { - this.initializationPromise = this.initialize(); - await this.initializationPromise; - } - - if (!this.isAvailable()) { - throw new Error('MCP service not available'); - } - const result = await this.client!.callTool({ - name: 'searchFuelDocs', - arguments: { - query: request.query, - } - }); - - - return { - results: Array.isArray(result.content) ? result.content : [] - }; - } catch (error) { - throw new Error('Failed to search documentation'); - } - } - - async getRelevantDocs(swayQuery: string): Promise { - try { - const searchResult = await this.searchDocs({ - query: swayQuery, - maxResults: 3 - }); - - if (searchResult.results.length === 0) { - return ''; - } - - // Combine relevant documentation into context string - const context = searchResult.results - .map(result => `## ${result.title}\n${result.content}`) - .join('\n\n'); - - return context; - } catch (error) { - return ''; - } - } - - async getStdContext(): Promise { - try { - // Ensure service is initialized - if (this.initializationPromise) { - await this.initializationPromise; - } else if (!this.isInitialized) { - this.initializationPromise = this.initialize(); - await this.initializationPromise; - } - - if (!this.isAvailable()) { - throw new Error('MCP service not available'); - } - - const result = await this.client!.callTool({ - name: 'provideStdContext', - arguments: {} - }); - - // Parse the MCP response content - let contextContent = ''; - if (result.content && Array.isArray(result.content)) { - contextContent = result.content - .map((item: any) => item.text || item.content || '') - .join('\n\n'); - } - - return contextContent; - } catch (error) { - return ''; - } - } - - isAvailable(): boolean { - return this.isInitialized && - this.client !== null && - this.transport !== null; - } - - private cleanup(): void { - this.isInitialized = false; - this.initializationPromise = null; - - // Close transport if it exists - if (this.transport) { - this.transport.close().catch(console.error); - } - - this.client = null; - this.transport = null; - } - - destroy(): void { - this.cleanup(); - } -} \ No newline at end of file diff --git a/ai-backend/src/types.ts b/ai-backend/src/types.ts deleted file mode 100644 index f1aed3d..0000000 --- a/ai-backend/src/types.ts +++ /dev/null @@ -1,50 +0,0 @@ - -export interface MCPRequest { - method: string; - params?: any; -} - -export interface MCPResponse { - result?: any; - error?: { - code: number; - message: string; - }; -} - -export interface SearchDocsRequest { - query: string; - maxResults?: number; -} - -export interface SearchDocsResponse { - results: Array<{ - title: string; - content: string; - url?: string; - relevance?: number; - }>; -} - -export interface SwayCodeGenerationRequest { - prompt: string; -} - -export interface SwayCodeGenerationResponse { - code: string; - explanation: string; - suggestions: string[]; -} - -export interface ErrorAnalysisRequest { - errorMessage: string; - sourceCode: string; - lineNumber?: number; -} - -export interface ErrorAnalysisResponse { - analysis: string; - suggestions: string[]; - fixedCode?: string; -} - diff --git a/ai-backend/src/utils/errorHandler.ts b/ai-backend/src/utils/errorHandler.ts deleted file mode 100644 index dee1371..0000000 --- a/ai-backend/src/utils/errorHandler.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Response } from 'express'; - -export function handleRouteError(res: Response, error: unknown, context: string): void { - console.error(`${context} error:`, error); - res.status(500).json({ - error: error instanceof Error ? error.message : `Failed to ${context.toLowerCase()}` - }); -} - -export function handleValidationError(res: Response, message: string): boolean { - res.status(400).json({ error: message }); - return false; -} - -export function handleServiceUnavailable(res: Response, service: string): boolean { - res.status(503).json({ error: `${service} service not available` }); - return false; -} \ No newline at end of file diff --git a/ai-backend/tsconfig.json b/ai-backend/tsconfig.json deleted file mode 100644 index 50e813d..0000000 --- a/ai-backend/tsconfig.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "commonjs", - "outDir": "./dist", - "rootDir": "./src", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "declaration": true, - "declarationMap": true, - "sourceMap": true - }, - "include": [ - "src/**/*" - ], - "exclude": [ - "node_modules", - "dist" - ] -} \ No newline at end of file From 245aacc08996239700b61e01990646c77b1200d5 Mon Sep 17 00:00:00 2001 From: PraneshASP Date: Tue, 15 Jul 2025 20:02:30 +0530 Subject: [PATCH 06/25] chore: fmt --- src/ai.rs | 97 ++++++++++++++++++++++++++++++++--------------------- src/main.rs | 23 +++++++++---- 2 files changed, 74 insertions(+), 46 deletions(-) diff --git a/src/ai.rs b/src/ai.rs index 55bb99d..ff73595 100644 --- a/src/ai.rs +++ b/src/ai.rs @@ -3,7 +3,10 @@ use crate::types::{ ErrorAnalysisRequest, ErrorAnalysisResponse, SwayCodeGenerationRequest, SwayCodeGenerationResponse, }; -use gemini_rust::{Content, FunctionDeclaration, FunctionParameters, FunctionCallingMode, Gemini, PropertyDetails, Role, GenerationConfig}; +use gemini_rust::{ + Content, FunctionCallingMode, FunctionDeclaration, FunctionParameters, Gemini, + GenerationConfig, PropertyDetails, Role, +}; use serde_json::{json, Value}; use std::env; @@ -41,7 +44,8 @@ impl AIService { let api_key = env::var("GEMINI_API_KEY").ok(); let mcp_server_url = env::var("MCP_SERVER_URL").ok(); - let client = api_key.map(|key| Gemini::with_model(key, "models/gemini-2.5-flash".to_string())); + let client = + api_key.map(|key| Gemini::with_model(key, "models/gemini-2.5-flash".to_string())); let http_client = reqwest::Client::new(); Ok(AIService { @@ -80,7 +84,8 @@ impl AIService { if self.is_mcp_available() { let functions = self.create_function_declarations(); let mut request_builder = client.generate_content(); - request_builder = request_builder.with_user_message(&format!("{}\n\n{}", system_prompt, user_prompt)); + request_builder = + request_builder.with_user_message(&format!("{}\n\n{}", system_prompt, user_prompt)); for function in functions.iter() { request_builder = request_builder.with_function(function.clone()); @@ -95,7 +100,8 @@ impl AIService { if !function_calls.is_empty() { let mut function_responses = Vec::new(); for function_call in function_calls.iter() { - let function_response = self.handle_function_call_response(function_call).await?; + let function_response = + self.handle_function_call_response(function_call).await?; function_responses.push((function_call, function_response)); } @@ -103,16 +109,21 @@ impl AIService { .generate_content() .with_user_message(&format!("{}\n\n{}", system_prompt, user_prompt)); - final_request.contents.push(response.candidates[0].content.clone()); + final_request + .contents + .push(response.candidates[0].content.clone()); let mut function_content = Content::default(); function_content.role = Some(Role::Function); - + for (function_call, function_response) in function_responses { - let response_content = Content::function_response_json(function_call.name.clone(), function_response); + let response_content = Content::function_response_json( + function_call.name.clone(), + function_response, + ); function_content.parts.extend(response_content.parts); } - + final_request.contents.push(function_content); let final_response = final_request @@ -160,15 +171,16 @@ impl AIService { .generate_content() .with_user_message(&format!("{}\n\n{}", system_prompt, user_prompt)) .with_function_calling_mode(FunctionCallingMode::Any) - .with_generation_config(GenerationConfig { + .with_generation_config(GenerationConfig { temperature: Some(0.7), top_p: Some(0.95), top_k: Some(40), - max_output_tokens: Some(8192), candidate_count: Some(1), - stop_sequences: Some(vec!["END".to_string()]), - response_mime_type: None, - response_schema: None,} - ); + max_output_tokens: Some(8192), + candidate_count: Some(1), + stop_sequences: Some(vec!["END".to_string()]), + response_mime_type: None, + response_schema: None, + }); for function in &functions { request_builder = request_builder.with_function(function.clone()); @@ -180,11 +192,12 @@ impl AIService { .map_err(|e| ApiError::Ai(format!("Gemini API error: {}", e)))?; let function_calls = response.function_calls(); - + if !function_calls.is_empty() { let mut function_responses = Vec::new(); for function_call in function_calls.iter() { - let function_response = self.handle_function_call_response(function_call).await?; + let function_response = + self.handle_function_call_response(function_call).await?; function_responses.push((function_call, function_response)); } @@ -192,16 +205,21 @@ impl AIService { .generate_content() .with_user_message(&format!("{}\n\n{}", system_prompt, user_prompt)); - final_request.contents.push(response.candidates[0].content.clone()); + final_request + .contents + .push(response.candidates[0].content.clone()); let mut function_content = Content::default(); function_content.role = Some(Role::Function); - + for (function_call, function_response) in function_responses.into_iter() { - let response_content = Content::function_response_json(function_call.name.clone(), function_response); + let response_content = Content::function_response_json( + function_call.name.clone(), + function_response, + ); function_content.parts.extend(response_content.parts); } - + final_request.contents.push(function_content); let final_response = final_request @@ -284,9 +302,9 @@ impl AIService { let query: String = function_call .get("query") .unwrap_or_else(|_| "sway".to_string()); - + let max_results: u64 = function_call.get("maxResults").unwrap_or_else(|_| 5); - + self.search_mcp_docs_internal(query, max_results).await } @@ -325,7 +343,7 @@ impl AIService { // Just delegate to the main search function which handles SSE properly self.search_mcp_docs_internal(query, max_results).await } - + async fn search_mcp_docs_internal( &self, query: String, @@ -362,14 +380,15 @@ impl AIService { .json(&request_body) .send() .await; - + match response_result { Ok(response) => { if response.status().is_success() { - let response_text = response.text().await.map_err(|e| { - ApiError::Ai(format!("Failed to get response text: {}", e)) - })?; - + let response_text = response + .text() + .await + .map_err(|e| ApiError::Ai(format!("Failed to get response text: {}", e)))?; + let json_data = if response_text.starts_with("event:") { response_text .lines() @@ -379,10 +398,11 @@ impl AIService { } else { &response_text }; - - let mcp_response: MCPResponse = serde_json::from_str(json_data).map_err(|e| { - ApiError::Ai(format!("Failed to parse MCP response: {}", e)) - })?; + + let mcp_response: MCPResponse = + serde_json::from_str(json_data).map_err(|e| { + ApiError::Ai(format!("Failed to parse MCP response: {}", e)) + })?; if let Some(error) = mcp_response.error { Ok(json!({ @@ -390,7 +410,8 @@ impl AIService { "fallback": "Check docs.fuel.network/docs/sway/ for documentation" })) } else if let Some(result) = mcp_response.result { - if let Ok(tool_response) = serde_json::from_value::(result.clone()) + if let Ok(tool_response) = + serde_json::from_value::(result.clone()) { let results: Vec = tool_response .content @@ -434,12 +455,10 @@ impl AIService { })) } } - Err(e) => { - Ok(json!({ - "error": format!("Failed to connect to MCP server: {}", e), - "fallback": "Check docs.fuel.network/docs/sway/ for documentation" - })) - } + Err(e) => Ok(json!({ + "error": format!("Failed to connect to MCP server: {}", e), + "fallback": "Check docs.fuel.network/docs/sway/ for documentation" + })), } } diff --git a/src/main.rs b/src/main.rs index 6417f1a..e15e270 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,13 +18,13 @@ use crate::cors::Cors; use crate::error::ApiResult; use crate::gist::GistClient; use crate::types::{ - CompileRequest, CompileResponse, ErrorAnalysisRequest, ErrorAnalysisResponse, GistResponse, - Language, NewGistRequest, NewGistResponse, SwayCodeGenerationRequest, SwayCodeGenerationResponse, - TranspileRequest, + CompileRequest, CompileResponse, ErrorAnalysisRequest, ErrorAnalysisResponse, GistResponse, + Language, NewGistRequest, NewGistResponse, SwayCodeGenerationRequest, + SwayCodeGenerationResponse, TranspileRequest, }; use crate::{transpilation::solidity_to_sway, types::TranspileResponse}; use rocket::serde::json::Json; -use rocket::{State, Request, catch}; +use rocket::{catch, Request, State}; /// The endpoint to compile a Sway contract. #[post("/compile", data = "")] @@ -97,15 +97,24 @@ fn health() -> String { fn rocket() -> _ { // Load environment variables from .env file dotenv::dotenv().ok(); - + let ai_service = AIService::new().expect("Failed to initialize AI service"); - + rocket::build() .manage(GistClient::default()) .manage(ai_service) .attach(Cors) .mount( "/", - routes![compile, transpile, new_gist, get_gist, generate_sway_code, analyze_error, all_options, health], + routes![ + compile, + transpile, + new_gist, + get_gist, + generate_sway_code, + analyze_error, + all_options, + health + ], ) } From 0c2a6b5df67bb6db1fdf2cd00279e10ed258e5c2 Mon Sep 17 00:00:00 2001 From: PraneshASP Date: Tue, 15 Jul 2025 20:40:29 +0530 Subject: [PATCH 07/25] chore: fix linter issues --- app/src/App.tsx | 33 ++- .../ai/components/AIGenerationDialog.tsx | 132 ++++++------ .../ai/components/FixWithAIButton.tsx | 201 ++++++++++-------- .../ai/components/MarkdownRenderer.tsx | 65 +++--- app/src/features/ai/hooks/useAIGeneration.tsx | 16 +- app/src/features/ai/hooks/useAIService.ts | 93 ++++---- .../features/ai/hooks/useErrorAnalysis.tsx | 35 +-- app/src/features/editor/hooks/useCompile.tsx | 13 +- app/src/hooks/useCopyToClipboard.ts | 27 +-- app/src/services/aiService.ts | 34 ++- app/src/services/apiService.ts | 34 +-- app/src/utils/aiHelpers.ts | 20 +- src/ai.rs | 25 +-- src/main.rs | 2 +- src/types.rs | 1 - 15 files changed, 413 insertions(+), 318 deletions(-) diff --git a/app/src/App.tsx b/app/src/App.tsx index a43ee14..fe8c83e 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -139,16 +139,22 @@ function App() { setAiDialogOpen(true); }, []); - const onAICodeGenerated = useCallback((code: string) => { - track("AI Code Generated"); - onSwayCodeChange(code); - setAiDialogOpen(false); - }, [onSwayCodeChange]); + const onAICodeGenerated = useCallback( + (code: string) => { + track("AI Code Generated"); + onSwayCodeChange(code); + setAiDialogOpen(false); + }, + [onSwayCodeChange], + ); - const onAICodeFixed = useCallback((fixedCode: string) => { - track("AI Code Fixed"); - onSwayCodeChange(fixedCode); - }, [onSwayCodeChange]); + const onAICodeFixed = useCallback( + (fixedCode: string) => { + track("AI Code Fixed"); + onSwayCodeChange(fixedCode); + }, + [onSwayCodeChange], + ); useTranspile( codeToTranspile, @@ -157,7 +163,14 @@ function App() { setError, updateLog, ); - useCompile(codeToCompile, setError, setIsCompiled, updateLog, toolchain, onAICodeFixed); + useCompile( + codeToCompile, + setError, + setIsCompiled, + updateLog, + toolchain, + onAICodeFixed, + ); return (
({ - '& .MuiPaper-root': { - borderRadius: '12px', - minWidth: '600px', - maxWidth: '800px', + "& .MuiPaper-root": { + borderRadius: "12px", + minWidth: "600px", + maxWidth: "800px", }, })); const CodePreview = styled(Paper)(() => ({ - backgroundColor: '#1e1e1e', - color: '#d4d4d4', - padding: '16px', + backgroundColor: "#1e1e1e", + color: "#d4d4d4", + padding: "16px", fontFamily: 'Monaco, Menlo, "Ubuntu Mono", monospace', - fontSize: '14px', - maxHeight: '400px', - overflow: 'auto', - border: '1px solid #333', - borderRadius: '8px', + fontSize: "14px", + maxHeight: "400px", + overflow: "auto", + border: "1px solid #333", + borderRadius: "8px", })); - const GenerateButton = styled(Button)(() => ({ - background: 'linear-gradient(45deg, #00f58c, #00d4aa)', - color: '#000', + background: "linear-gradient(45deg, #00f58c, #00d4aa)", + color: "#000", fontWeight: 600, - '&:hover': { - background: 'linear-gradient(45deg, #00d4aa, #00b894)', + "&:hover": { + background: "linear-gradient(45deg, #00d4aa, #00b894)", }, - '&:disabled': { - background: '#333', - color: '#666', + "&:disabled": { + background: "#333", + color: "#666", }, })); @@ -69,7 +68,7 @@ export function AIGenerationDialog({ onCodeGenerated, }: AIGenerationDialogProps) { const { state, generateCode, clearResult, isAvailable } = useAIGeneration(); - const [prompt, setPrompt] = useState(''); + const [prompt, setPrompt] = useState(""); const { copied, copyToClipboard, resetCopied } = useCopyToClipboard(); const handleGenerate = async () => { @@ -96,7 +95,7 @@ export function AIGenerationDialog({ }; const handleClose = () => { - setPrompt(''); + setPrompt(""); resetCopied(); clearResult(); onClose(); @@ -117,7 +116,8 @@ export function AIGenerationDialog({ - AI features are not available. Please configure your Gemini API key in the environment variables. + AI features are not available. Please configure your Gemini API key + in the environment variables. @@ -135,7 +135,7 @@ export function AIGenerationDialog({ AI Code Generation - + {/* Input Form */} @@ -154,11 +154,7 @@ export function AIGenerationDialog({ {/* Error Display */} - {hasError && ( - - {state.error} - - )} + {hasError && {state.error}} {/* Loading State */} {isGenerating && ( @@ -173,7 +169,12 @@ export function AIGenerationDialog({ {/* Generated Code */} {hasResult && state.result && ( - + Generated Contract @@ -185,11 +186,10 @@ export function AIGenerationDialog({ onClick={handleCopyCode} color={copied ? "success" : "primary"} > - {copied ? 'Copied!' : 'Copy Code'} + {copied ? "Copied!" : "Copy Code"} -
{state.result.code}
@@ -199,24 +199,32 @@ export function AIGenerationDialog({ Explanation: - +
)} - {state.result.suggestions && state.result.suggestions.length > 0 && ( - - - Suggestions: - - - {state.result.suggestions.map((suggestion, index) => ( - - {suggestion} - - ))} + {state.result.suggestions && + state.result.suggestions.length > 0 && ( + + + Suggestions: + + + {state.result.suggestions.map((suggestion, index) => ( + + {suggestion} + + ))} + - - )} + )} )}
@@ -228,18 +236,20 @@ export function AIGenerationDialog({ - + {!hasResult && ( : } + startIcon={ + isGenerating ? : + } variant="contained" > Generate Contract )} - + {hasResult && ( ); -} \ No newline at end of file +} diff --git a/app/src/features/ai/components/FixWithAIButton.tsx b/app/src/features/ai/components/FixWithAIButton.tsx index 2df300a..efd4640 100644 --- a/app/src/features/ai/components/FixWithAIButton.tsx +++ b/app/src/features/ai/components/FixWithAIButton.tsx @@ -1,5 +1,5 @@ -import { useState } from 'react'; -import { useCopyToClipboard } from '../../../hooks/useCopyToClipboard'; +import { useState } from "react"; +import { useCopyToClipboard } from "../../../hooks/useCopyToClipboard"; import { Button, Dialog, @@ -13,17 +13,15 @@ import { Paper, Divider, Chip, -} from '@mui/material'; -import AutoFixHigh from '@mui/icons-material/AutoFixHigh'; -import ContentCopy from '@mui/icons-material/ContentCopy'; -import CheckCircle from '@mui/icons-material/CheckCircle'; -import Close from '@mui/icons-material/Close'; -import { useErrorAnalysis } from '../hooks/useErrorAnalysis'; -import { ErrorAnalysisRequest } from '../../../services/aiService'; -import { MarkdownRenderer } from './MarkdownRenderer'; -import { removeCodeBlocks } from '../../../utils/aiHelpers'; - - +} from "@mui/material"; +import AutoFixHigh from "@mui/icons-material/AutoFixHigh"; +import ContentCopy from "@mui/icons-material/ContentCopy"; +import CheckCircle from "@mui/icons-material/CheckCircle"; +import Close from "@mui/icons-material/Close"; +import { useErrorAnalysis } from "../hooks/useErrorAnalysis"; +import { ErrorAnalysisRequest } from "../../../services/aiService"; +import { MarkdownRenderer } from "./MarkdownRenderer"; +import { removeCodeBlocks } from "../../../utils/aiHelpers"; export interface FixWithAIButtonProps { errorMessage: string; @@ -40,13 +38,12 @@ export function FixWithAIButton({ }: FixWithAIButtonProps) { const [dialogOpen, setDialogOpen] = useState(false); const { copied, copyToClipboard, resetCopied } = useCopyToClipboard(); - - const { state, analyzeError, applyFix, clearResult, isAvailable } = useErrorAnalysis( - (fixedCode: string) => { + + const { state, analyzeError, applyFix, clearResult, isAvailable } = + useErrorAnalysis((fixedCode: string) => { onCodeFixed(fixedCode); setDialogOpen(false); - } - ); + }); const handleFixClick = async () => { if (!isAvailable) { @@ -56,15 +53,15 @@ export function FixWithAIButton({ // Clear any previous results before starting new analysis clearResult(); resetCopied(); - + // Only open dialog if it's not already open (for initial click) if (!dialogOpen) { setDialogOpen(true); } - + const request: ErrorAnalysisRequest = { errorMessage, - sourceCode + sourceCode, }; await analyzeError(request); @@ -106,15 +103,19 @@ export function FixWithAIButton({ Fix with AI - - + AI Error Analysis & Fix @@ -124,7 +125,7 @@ export function FixWithAIButton({ - + {/* Error Display */} @@ -132,13 +133,13 @@ export function FixWithAIButton({ Compilation Error: -
{errorMessage}
@@ -150,17 +151,14 @@ export function FixWithAIButton({ - Analyzing error and generating fix... This may take a few moments. + Analyzing error and generating fix... This may take a few + moments. )} {/* Error Display */} - {state.error && ( - - {state.error} - - )} + {state.error && {state.error}} {/* Analysis Results */} {state.result && ( @@ -168,34 +166,43 @@ export function FixWithAIButton({ AI Analysis & Solution - + - + {/* Suggestions */} - {state.result.suggestions && state.result.suggestions.length > 0 && ( - - - Recommendations: - - - {state.result.suggestions.map((suggestion, index) => ( - - ))} + {state.result.suggestions && + state.result.suggestions.length > 0 && ( + + + Recommendations: + + + {state.result.suggestions.map((suggestion, index) => ( + + ))} + - - )} + )} {/* Fixed Code */} {state.result.fixedCode && ( - + Suggested Fix: @@ -205,39 +212,53 @@ export function FixWithAIButton({ onClick={handleCopyFixed} color={copied ? "success" : "primary"} > - {copied ? 'Copied!' : 'Copy'} + {copied ? "Copied!" : "Copy"} - - + +
{state.result.fixedCode}
)} {/* Retry button if no fixed code found */} - {state.result && !state.result.fixedCode && !state.isAnalyzing && ( - - - The AI response didn't include fixed code. This might be due to response truncation. - - - - )} + {state.result && + !state.result.fixedCode && + !state.isAnalyzing && ( + + + The AI response didn't include fixed code. This might be + due to response truncation. + + + + )}
)}
@@ -249,7 +270,7 @@ export function FixWithAIButton({ - + {state.result?.fixedCode && ( )} @@ -274,4 +291,4 @@ export function FixWithAIButton({
); -} \ No newline at end of file +} diff --git a/app/src/features/ai/components/MarkdownRenderer.tsx b/app/src/features/ai/components/MarkdownRenderer.tsx index 79bedb1..bbc51fa 100644 --- a/app/src/features/ai/components/MarkdownRenderer.tsx +++ b/app/src/features/ai/components/MarkdownRenderer.tsx @@ -1,8 +1,8 @@ -import React from 'react'; -import ReactMarkdown from 'react-markdown'; -import { Box, Typography, Paper } from '@mui/material'; -import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; -import { vs } from 'react-syntax-highlighter/dist/esm/styles/prism'; +import React from "react"; +import ReactMarkdown from "react-markdown"; +import { Box, Typography, Paper } from "@mui/material"; +import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; +import { vs } from "react-syntax-highlighter/dist/esm/styles/prism"; interface MarkdownComponentProps { children?: React.ReactNode; @@ -16,29 +16,29 @@ interface MarkdownRendererProps { const markdownComponents = { code: ({ inline, className, children, ...props }: MarkdownComponentProps) => { - const match = /language-(\w+)/.exec(className || ''); + const match = /language-(\w+)/.exec(className || ""); return !inline && match ? ( - {String(children).replace(/\n$/, '')} + {String(children).replace(/\n$/, "")} ) : ( @@ -52,17 +52,29 @@ const markdownComponents = { ), h1: ({ children }: MarkdownComponentProps) => ( - + {children} ), h2: ({ children }: MarkdownComponentProps) => ( - + {children} ), h3: ({ children }: MarkdownComponentProps) => ( - + {children} ), @@ -83,28 +95,29 @@ const markdownComponents = { ), }; -export function MarkdownRenderer({ content, borderColor = '#00f58c' }: MarkdownRendererProps) { +export function MarkdownRenderer({ + content, + borderColor = "#00f58c", +}: MarkdownRendererProps) { return ( - - {content} - + {content} ); -} \ No newline at end of file +} diff --git a/app/src/features/ai/hooks/useAIGeneration.tsx b/app/src/features/ai/hooks/useAIGeneration.tsx index 22878ba..b9e59f1 100644 --- a/app/src/features/ai/hooks/useAIGeneration.tsx +++ b/app/src/features/ai/hooks/useAIGeneration.tsx @@ -1,5 +1,9 @@ -import { aiService, SwayCodeGenerationRequest, SwayCodeGenerationResponse } from '../../../services/aiService'; -import { useAIService } from './useAIService'; +import { + aiService, + SwayCodeGenerationRequest, + SwayCodeGenerationResponse, +} from "../../../services/aiService"; +import { useAIService } from "./useAIService"; export interface AIGenerationState { isGenerating: boolean; @@ -16,19 +20,19 @@ export interface UseAIGenerationReturn { export function useAIGeneration(): UseAIGenerationReturn { const { state, execute, clearResult, isAvailable } = useAIService( - aiService.generateSwayCode.bind(aiService) + aiService.generateSwayCode.bind(aiService), ); const transformedState: AIGenerationState = { isGenerating: state.isLoading, result: state.result, - error: state.error + error: state.error, }; return { state: transformedState, generateCode: execute, clearResult, - isAvailable + isAvailable, }; -} \ No newline at end of file +} diff --git a/app/src/features/ai/hooks/useAIService.ts b/app/src/features/ai/hooks/useAIService.ts index 8b7961d..03ee952 100644 --- a/app/src/features/ai/hooks/useAIService.ts +++ b/app/src/features/ai/hooks/useAIService.ts @@ -1,5 +1,5 @@ -import { useState, useCallback } from 'react'; -import { aiService } from '../../../services/aiService'; +import { useState, useCallback } from "react"; +import { aiService } from "../../../services/aiService"; export interface AIServiceState { isLoading: boolean; @@ -21,62 +21,69 @@ export interface UseAIServiceReturn { export function useAIService( serviceFunction: (request: TRequest) => Promise, - options: UseAIServiceOptions = {} + options: UseAIServiceOptions = {}, ): UseAIServiceReturn { const [state, setState] = useState>({ isLoading: false, result: null, - error: null + error: null, }); const isAvailable = aiService.isAvailable(); - const execute = useCallback(async (request: TRequest) => { - if (!isAvailable) { - setState(prev => ({ - ...prev, - error: 'AI features are not enabled. Please configure your API key.' + const execute = useCallback( + async (request: TRequest) => { + if (!isAvailable) { + setState((prev) => ({ + ...prev, + error: "AI features are not enabled. Please configure your API key.", + })); + return; + } + + setState((prev) => ({ + ...prev, + isLoading: true, + error: null, + result: null, })); - return; - } - setState(prev => ({ - ...prev, - isLoading: true, - error: null, - result: null - })); + try { + const result = await serviceFunction(request); - try { - const result = await serviceFunction(request); + setState((prev) => ({ + ...prev, + isLoading: false, + result, + })); + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : "Operation failed"; + setState((prev) => ({ + ...prev, + isLoading: false, + error: errorMessage, + })); + } + }, + [serviceFunction, isAvailable], + ); - setState(prev => ({ - ...prev, - isLoading: false, - result - })); - } catch (error) { - const errorMessage = error instanceof Error ? error.message : 'Operation failed'; - setState(prev => ({ - ...prev, - isLoading: false, - error: errorMessage - })); - } - }, [serviceFunction, isAvailable]); - - const apply = useCallback((result: TResult) => { - if (options.onApply) { - options.onApply(result); - } - clearResult(); - }, [options.onApply]); + const apply = useCallback( + (result: TResult) => { + if (options.onApply) { + options.onApply(result); + } + clearResult(); + }, + [options.onApply], + ); const clearResult = useCallback(() => { setState({ isLoading: false, result: null, - error: null + error: null, }); }, []); @@ -85,6 +92,6 @@ export function useAIService( execute, apply: options.onApply ? apply : undefined, clearResult, - isAvailable + isAvailable, }; -} \ No newline at end of file +} diff --git a/app/src/features/ai/hooks/useErrorAnalysis.tsx b/app/src/features/ai/hooks/useErrorAnalysis.tsx index 512ce4c..c5d738e 100644 --- a/app/src/features/ai/hooks/useErrorAnalysis.tsx +++ b/app/src/features/ai/hooks/useErrorAnalysis.tsx @@ -1,6 +1,10 @@ -import { useCallback } from 'react'; -import { aiService, ErrorAnalysisRequest, ErrorAnalysisResponse } from '../../../services/aiService'; -import { useAIService } from './useAIService'; +import { useCallback } from "react"; +import { + aiService, + ErrorAnalysisRequest, + ErrorAnalysisResponse, +} from "../../../services/aiService"; +import { useAIService } from "./useAIService"; export interface ErrorAnalysisState { isAnalyzing: boolean; @@ -17,7 +21,7 @@ export interface UseErrorAnalysisReturn { } export function useErrorAnalysis( - onCodeFixed?: (code: string) => void + onCodeFixed?: (code: string) => void, ): UseErrorAnalysisReturn { const { state, execute, apply, clearResult, isAvailable } = useAIService( aiService.analyzeError.bind(aiService), @@ -26,29 +30,32 @@ export function useErrorAnalysis( if (result.fixedCode && onCodeFixed) { onCodeFixed(result.fixedCode); } - } - } + }, + }, ); // Transform the generic state to match the expected interface const transformedState: ErrorAnalysisState = { isAnalyzing: state.isLoading, result: state.result, - error: state.error + error: state.error, }; - const applyFix = useCallback((fixedCode: string) => { - if (onCodeFixed) { - onCodeFixed(fixedCode); - } - clearResult(); - }, [onCodeFixed, clearResult]); + const applyFix = useCallback( + (fixedCode: string) => { + if (onCodeFixed) { + onCodeFixed(fixedCode); + } + clearResult(); + }, + [onCodeFixed, clearResult], + ); return { state: transformedState, analyzeError: execute, applyFix, clearResult, - isAvailable + isAvailable, }; } diff --git a/app/src/features/editor/hooks/useCompile.tsx b/app/src/features/editor/hooks/useCompile.tsx index bb95a46..4301e3e 100644 --- a/app/src/features/editor/hooks/useCompile.tsx +++ b/app/src/features/editor/hooks/useCompile.tsx @@ -88,16 +88,23 @@ export function useCompile( const finalResults = [...results]; if (aiService.isAvailable() && onCodeFixed && code) { finalResults.push( -
+
-
+
, ); } - + setResults(finalResults); setVersion(forcVersion); saveAbi(""); diff --git a/app/src/hooks/useCopyToClipboard.ts b/app/src/hooks/useCopyToClipboard.ts index c101efc..b1434b9 100644 --- a/app/src/hooks/useCopyToClipboard.ts +++ b/app/src/hooks/useCopyToClipboard.ts @@ -1,4 +1,4 @@ -import { useState, useCallback } from 'react'; +import { useState, useCallback } from "react"; export interface UseCopyToClipboardReturn { copied: boolean; @@ -6,18 +6,21 @@ export interface UseCopyToClipboardReturn { resetCopied: () => void; } -export function useCopyToClipboard(timeout: number = 2000): UseCopyToClipboardReturn { +export function useCopyToClipboard(timeout = 2000): UseCopyToClipboardReturn { const [copied, setCopied] = useState(false); - const copyToClipboard = useCallback(async (text: string) => { - try { - await navigator.clipboard.writeText(text); - setCopied(true); - setTimeout(() => setCopied(false), timeout); - } catch (error) { - console.error('Failed to copy to clipboard:', error); - } - }, [timeout]); + const copyToClipboard = useCallback( + async (text: string) => { + try { + await navigator.clipboard.writeText(text); + setCopied(true); + setTimeout(() => setCopied(false), timeout); + } catch (error) { + console.error("Failed to copy to clipboard:", error); + } + }, + [timeout], + ); const resetCopied = useCallback(() => { setCopied(false); @@ -28,4 +31,4 @@ export function useCopyToClipboard(timeout: number = 2000): UseCopyToClipboardRe copyToClipboard, resetCopied, }; -} \ No newline at end of file +} diff --git a/app/src/services/aiService.ts b/app/src/services/aiService.ts index 2360857..49d37ff 100644 --- a/app/src/services/aiService.ts +++ b/app/src/services/aiService.ts @@ -1,4 +1,4 @@ -import { SERVER_URI } from '../constants'; +import { SERVER_URI } from "../constants"; export interface SwayCodeGenerationRequest { prompt: string; @@ -25,27 +25,41 @@ export interface ErrorAnalysisResponse { class AIService { private async makeRequest(endpoint: string, data: any): Promise { const response = await fetch(`${SERVER_URI}${endpoint}`, { - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json', + "Content-Type": "application/json", }, body: JSON.stringify(data), }); if (!response.ok) { - const errorData = await response.json().catch(() => ({ error: 'Unknown error' })); - throw new Error(errorData.error || `HTTP ${response.status}: ${response.statusText}`); + const errorData = await response + .json() + .catch(() => ({ error: "Unknown error" })); + throw new Error( + errorData.error || `HTTP ${response.status}: ${response.statusText}`, + ); } return response.json(); } - async generateSwayCode(request: SwayCodeGenerationRequest): Promise { - return this.makeRequest('/ai/generate', request); + async generateSwayCode( + request: SwayCodeGenerationRequest, + ): Promise { + return this.makeRequest( + "/ai/generate", + request, + ); } - async analyzeError(request: ErrorAnalysisRequest): Promise { - return this.makeRequest('/ai/analyze-error', request); + async analyzeError( + request: ErrorAnalysisRequest, + ): Promise { + return this.makeRequest( + "/ai/analyze-error", + request, + ); } isAvailable(): boolean { @@ -53,4 +67,4 @@ class AIService { } } -export const aiService = new AIService(); \ No newline at end of file +export const aiService = new AIService(); diff --git a/app/src/services/apiService.ts b/app/src/services/apiService.ts index d25cb35..25d96c2 100644 --- a/app/src/services/apiService.ts +++ b/app/src/services/apiService.ts @@ -1,7 +1,7 @@ -import { SERVER_URI } from '../constants'; +import { SERVER_URI } from "../constants"; export interface ApiRequestOptions { - method?: 'GET' | 'POST' | 'PUT' | 'DELETE'; + method?: "GET" | "POST" | "PUT" | "DELETE"; headers?: Record; timeout?: number; } @@ -14,14 +14,14 @@ class ApiService { } private async makeRequest( - endpoint: string, - data?: any, - options: ApiRequestOptions = {} + endpoint: string, + data?: any, + options: ApiRequestOptions = {}, ): Promise { - const { - method = data ? 'POST' : 'GET', + const { + method = data ? "POST" : "GET", headers = {}, - timeout = 30000 + timeout = 30000, } = options; const controller = new AbortController(); @@ -31,7 +31,7 @@ class ApiService { const response = await fetch(`${this.baseURL}${endpoint}`, { method, headers: { - 'Content-Type': 'application/json', + "Content-Type": "application/json", ...headers, }, body: data ? JSON.stringify(data) : undefined, @@ -41,20 +41,24 @@ class ApiService { clearTimeout(timeoutId); if (!response.ok) { - const error = await response.json().catch(() => ({ error: 'Request failed' })); - throw new Error(error.error || `HTTP ${response.status}: ${response.statusText}`); + const error = await response + .json() + .catch(() => ({ error: "Request failed" })); + throw new Error( + error.error || `HTTP ${response.status}: ${response.statusText}`, + ); } return response.json(); } catch (error) { clearTimeout(timeoutId); if (error instanceof Error) { - if (error.name === 'AbortError') { - throw new Error('Request timeout'); + if (error.name === "AbortError") { + throw new Error("Request timeout"); } throw error; } - throw new Error('Unknown error occurred'); + throw new Error("Unknown error occurred"); } } @@ -65,4 +69,4 @@ class ApiService { // Add other non-AI/MCP API methods here as needed } -export const apiService = new ApiService(); \ No newline at end of file +export const apiService = new ApiService(); diff --git a/app/src/utils/aiHelpers.ts b/app/src/utils/aiHelpers.ts index dc39703..acd92ce 100644 --- a/app/src/utils/aiHelpers.ts +++ b/app/src/utils/aiHelpers.ts @@ -3,14 +3,14 @@ */ export function removeCodeBlocks(content: string): string { return content - .replace(/```[\s\S]*?```/g, '') - .replace(/Here's the corrected code:?/gi, '') - .replace(/Here's the fixed code:?/gi, '') - .replace(/Here's the code:?/gi, '') - .replace(/Here's the contract:?/gi, '') - .replace(/Fixed code:?/gi, '') - .replace(/Corrected code:?/gi, '') - .replace(/Generated code:?/gi, '') - .replace(/Contract code:?/gi, '') + .replace(/```[\s\S]*?```/g, "") + .replace(/Here's the corrected code:?/gi, "") + .replace(/Here's the fixed code:?/gi, "") + .replace(/Here's the code:?/gi, "") + .replace(/Here's the contract:?/gi, "") + .replace(/Fixed code:?/gi, "") + .replace(/Corrected code:?/gi, "") + .replace(/Generated code:?/gi, "") + .replace(/Contract code:?/gi, "") .trim(); -} \ No newline at end of file +} diff --git a/src/ai.rs b/src/ai.rs index ff73595..7e8e831 100644 --- a/src/ai.rs +++ b/src/ai.rs @@ -24,7 +24,6 @@ struct MCPResponse { #[derive(serde::Deserialize)] struct MCPError { - code: i32, message: String, } @@ -85,7 +84,7 @@ impl AIService { let functions = self.create_function_declarations(); let mut request_builder = client.generate_content(); request_builder = - request_builder.with_user_message(&format!("{}\n\n{}", system_prompt, user_prompt)); + request_builder.with_user_message(format!("{}\n\n{}", system_prompt, user_prompt)); for function in functions.iter() { request_builder = request_builder.with_function(function.clone()); @@ -107,14 +106,13 @@ impl AIService { let mut final_request = client .generate_content() - .with_user_message(&format!("{}\n\n{}", system_prompt, user_prompt)); + .with_user_message(format!("{}\n\n{}", system_prompt, user_prompt)); final_request .contents .push(response.candidates[0].content.clone()); - let mut function_content = Content::default(); - function_content.role = Some(Role::Function); + let mut function_content = Content { role: Some(Role::Function), ..Default::default() }; for (function_call, function_response) in function_responses { let response_content = Content::function_response_json( @@ -138,7 +136,7 @@ impl AIService { } else { let response = client .generate_content() - .with_user_message(&format!("{}\n\n{}", system_prompt, user_prompt)) + .with_user_message(format!("{}\n\n{}", system_prompt, user_prompt)) .execute() .await .map_err(|e| ApiError::Ai(format!("Gemini API error: {}", e)))?; @@ -160,7 +158,7 @@ impl AIService { let system_prompt = self.get_error_analysis_prompt(); let user_prompt = format!( "Fix this Sway compilation error by applying ONLY the necessary changes:\n\nERROR: {}\n\nCURRENT CODE:\n```sway\n{}\n```\n\nINSTRUCTIONS:\n1. If there are multiple errors, call 'searchDocumentation' for EACH DISTINCT error type\n2. Search documentation for each specific error pattern\n3. Identify the exact issue causing each error\n4. Apply MINIMAL fixes - change only what's broken\n5. Keep all working code unchanged\n6. Return the complete corrected contract\n\nCRITICAL: Return the entire corrected Sway contract in a ```sway code block. Fix ONLY the errors, don't refactor working code.", - request.error_message.to_string(), request.source_code + request.error_message, request.source_code ); let client = self.client.as_ref().unwrap(); @@ -169,7 +167,7 @@ impl AIService { let functions = self.create_function_declarations(); let mut request_builder = client .generate_content() - .with_user_message(&format!("{}\n\n{}", system_prompt, user_prompt)) + .with_user_message(format!("{}\n\n{}", system_prompt, user_prompt)) .with_function_calling_mode(FunctionCallingMode::Any) .with_generation_config(GenerationConfig { temperature: Some(0.7), @@ -203,14 +201,13 @@ impl AIService { let mut final_request = client .generate_content() - .with_user_message(&format!("{}\n\n{}", system_prompt, user_prompt)); + .with_user_message(format!("{}\n\n{}", system_prompt, user_prompt)); final_request .contents .push(response.candidates[0].content.clone()); - let mut function_content = Content::default(); - function_content.role = Some(Role::Function); + let mut function_content = Content { role: Some(Role::Function), ..Default::default() }; for (function_call, function_response) in function_responses.into_iter() { let response_content = Content::function_response_json( @@ -234,7 +231,7 @@ impl AIService { } else { let response = client .generate_content() - .with_user_message(&format!("{}\n\n{}", system_prompt, user_prompt)) + .with_user_message(format!("{}\n\n{}", system_prompt, user_prompt)) .execute() .await .map_err(|e| ApiError::Ai(format!("Gemini API error: {}", e)))?; @@ -289,7 +286,7 @@ impl AIService { &self, function_call: &gemini_rust::FunctionCall, ) -> Result { - let mcp_url = match &self.mcp_server_url { + let _mcp_url = match &self.mcp_server_url { Some(url) => url, None => { return Ok(json!({ @@ -303,7 +300,7 @@ impl AIService { .get("query") .unwrap_or_else(|_| "sway".to_string()); - let max_results: u64 = function_call.get("maxResults").unwrap_or_else(|_| 5); + let max_results: u64 = function_call.get("maxResults").unwrap_or(5); self.search_mcp_docs_internal(query, max_results).await } diff --git a/src/main.rs b/src/main.rs index e15e270..9f5ebd0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,7 +24,7 @@ use crate::types::{ }; use crate::{transpilation::solidity_to_sway, types::TranspileResponse}; use rocket::serde::json::Json; -use rocket::{catch, Request, State}; +use rocket::State; /// The endpoint to compile a Sway contract. #[post("/compile", data = "")] diff --git a/src/types.rs b/src/types.rs index 848892d..9bc9963 100644 --- a/src/types.rs +++ b/src/types.rs @@ -126,7 +126,6 @@ pub struct SwayCodeGenerationResponse { pub struct ErrorAnalysisRequest { pub error_message: String, pub source_code: String, - pub line_number: Option, } /// The response to an AI error analysis request. From 0088e997ab3f69eb478ed699255146b4b1283c4b Mon Sep 17 00:00:00 2001 From: PraneshASP Date: Tue, 15 Jul 2025 21:35:50 +0530 Subject: [PATCH 08/25] chore: fix linter issues --- src/ai.rs | 38 ++++++++++++++++++++++---------------- src/compilation/mod.rs | 8 +++----- src/compilation/tooling.rs | 2 +- src/types.rs | 2 +- 4 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/ai.rs b/src/ai.rs index 7e8e831..a22ad36 100644 --- a/src/ai.rs +++ b/src/ai.rs @@ -84,7 +84,7 @@ impl AIService { let functions = self.create_function_declarations(); let mut request_builder = client.generate_content(); request_builder = - request_builder.with_user_message(format!("{}\n\n{}", system_prompt, user_prompt)); + request_builder.with_user_message(format!("{system_prompt}\n\n{user_prompt}")); for function in functions.iter() { request_builder = request_builder.with_function(function.clone()); @@ -93,7 +93,7 @@ impl AIService { let response = request_builder .execute() .await - .map_err(|e| ApiError::Ai(format!("Gemini API error: {}", e)))?; + .map_err(|e| ApiError::Ai(format!("Gemini API error: {e}")))?; let function_calls = response.function_calls(); if !function_calls.is_empty() { @@ -106,13 +106,16 @@ impl AIService { let mut final_request = client .generate_content() - .with_user_message(format!("{}\n\n{}", system_prompt, user_prompt)); + .with_user_message(format!("{system_prompt}\n\n{user_prompt}")); final_request .contents .push(response.candidates[0].content.clone()); - let mut function_content = Content { role: Some(Role::Function), ..Default::default() }; + let mut function_content = Content { + role: Some(Role::Function), + ..Default::default() + }; for (function_call, function_response) in function_responses { let response_content = Content::function_response_json( @@ -127,7 +130,7 @@ impl AIService { let final_response = final_request .execute() .await - .map_err(|e| ApiError::Ai(format!("Gemini API error: {}", e)))?; + .map_err(|e| ApiError::Ai(format!("Gemini API error: {e}")))?; self.parse_code_generation_response(&final_response.text()) } else { @@ -136,10 +139,10 @@ impl AIService { } else { let response = client .generate_content() - .with_user_message(format!("{}\n\n{}", system_prompt, user_prompt)) + .with_user_message(format!("{system_prompt}\n\n{user_prompt}")) .execute() .await - .map_err(|e| ApiError::Ai(format!("Gemini API error: {}", e)))?; + .map_err(|e| ApiError::Ai(format!("Gemini API error: {e}")))?; self.parse_code_generation_response(&response.text()) } @@ -167,7 +170,7 @@ impl AIService { let functions = self.create_function_declarations(); let mut request_builder = client .generate_content() - .with_user_message(format!("{}\n\n{}", system_prompt, user_prompt)) + .with_user_message(format!("{system_prompt}\n\n{user_prompt}")) .with_function_calling_mode(FunctionCallingMode::Any) .with_generation_config(GenerationConfig { temperature: Some(0.7), @@ -187,7 +190,7 @@ impl AIService { let response = request_builder .execute() .await - .map_err(|e| ApiError::Ai(format!("Gemini API error: {}", e)))?; + .map_err(|e| ApiError::Ai(format!("Gemini API error: {e}")))?; let function_calls = response.function_calls(); @@ -201,13 +204,16 @@ impl AIService { let mut final_request = client .generate_content() - .with_user_message(format!("{}\n\n{}", system_prompt, user_prompt)); + .with_user_message(format!("{system_prompt}\n\n{user_prompt}")); final_request .contents .push(response.candidates[0].content.clone()); - let mut function_content = Content { role: Some(Role::Function), ..Default::default() }; + let mut function_content = Content { + role: Some(Role::Function), + ..Default::default() + }; for (function_call, function_response) in function_responses.into_iter() { let response_content = Content::function_response_json( @@ -222,7 +228,7 @@ impl AIService { let final_response = final_request .execute() .await - .map_err(|e| ApiError::Ai(format!("Gemini API error: {}", e)))?; + .map_err(|e| ApiError::Ai(format!("Gemini API error: {e}")))?; self.parse_error_analysis_response(&final_response.text()) } else { @@ -231,10 +237,10 @@ impl AIService { } else { let response = client .generate_content() - .with_user_message(format!("{}\n\n{}", system_prompt, user_prompt)) + .with_user_message(format!("{system_prompt}\n\n{user_prompt}")) .execute() .await - .map_err(|e| ApiError::Ai(format!("Gemini API error: {}", e)))?; + .map_err(|e| ApiError::Ai(format!("Gemini API error: {e}")))?; self.parse_error_analysis_response(&response.text()) } @@ -384,7 +390,7 @@ impl AIService { let response_text = response .text() .await - .map_err(|e| ApiError::Ai(format!("Failed to get response text: {}", e)))?; + .map_err(|e| ApiError::Ai(format!("Failed to get response text: {e}")))?; let json_data = if response_text.starts_with("event:") { response_text @@ -398,7 +404,7 @@ impl AIService { let mcp_response: MCPResponse = serde_json::from_str(json_data).map_err(|e| { - ApiError::Ai(format!("Failed to parse MCP response: {}", e)) + ApiError::Ai(format!("Failed to parse MCP response: {e}")) })?; if let Some(error) = mcp_response.error { diff --git a/src/compilation/mod.rs b/src/compilation/mod.rs index 9ed09b7..d8965c1 100644 --- a/src/compilation/mod.rs +++ b/src/compilation/mod.rs @@ -47,14 +47,12 @@ pub fn build_and_destroy_project( // If the project compiled successfully, read the ABI and BIN files. if output.status.success() { let abi = read_to_string(format!( - "projects/{}/out/debug/swaypad-abi.json", - project_name + "projects/{project_name}/out/debug/swaypad-abi.json" )) .expect("Should have been able to read the file"); - let bin = read_file_contents(format!("projects/{}/out/debug/swaypad.bin", project_name)); + let bin = read_file_contents(format!("projects/{project_name}/out/debug/swaypad.bin")); let storage_slots = read_file_contents(format!( - "projects/{}/out/debug/swaypad-storage_slots.json", - project_name + "projects/{project_name}/out/debug/swaypad-storage_slots.json" )); // Remove the project directory and contents. diff --git a/src/compilation/tooling.rs b/src/compilation/tooling.rs index 2cc7574..44eb226 100644 --- a/src/compilation/tooling.rs +++ b/src/compilation/tooling.rs @@ -23,6 +23,6 @@ pub fn build_project(project_name: String) -> Output { Command::new(FORC) .arg("build") .arg("--path") - .arg(format!("projects/{}", project_name)), + .arg(format!("projects/{project_name}")), ) } diff --git a/src/types.rs b/src/types.rs index 9bc9963..f7e46a2 100644 --- a/src/types.rs +++ b/src/types.rs @@ -25,7 +25,7 @@ impl fmt::Display for Toolchain { Toolchain::Mainnet => "mainnet", }; - write!(formatter, "{}", s) + write!(formatter, "{s}") } } From a85505f6a5a1eaa581f68570903108b51dd1a1de Mon Sep 17 00:00:00 2001 From: PraneshASP Date: Tue, 15 Jul 2025 23:40:03 +0530 Subject: [PATCH 09/25] chore: fmt --- src/ai.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/ai.rs b/src/ai.rs index a22ad36..2a3c992 100644 --- a/src/ai.rs +++ b/src/ai.rs @@ -402,10 +402,8 @@ impl AIService { &response_text }; - let mcp_response: MCPResponse = - serde_json::from_str(json_data).map_err(|e| { - ApiError::Ai(format!("Failed to parse MCP response: {e}")) - })?; + let mcp_response: MCPResponse = serde_json::from_str(json_data) + .map_err(|e| ApiError::Ai(format!("Failed to parse MCP response: {e}")))?; if let Some(error) = mcp_response.error { Ok(json!({ From d30e2a700724d40deb14884f06032e2529a1d086 Mon Sep 17 00:00:00 2001 From: PraneshASP Date: Wed, 16 Jul 2025 11:23:27 +0530 Subject: [PATCH 10/25] feat: add rate limiter --- Cargo.lock | 107 +++++++----------- Cargo.toml | 2 + src/error.rs | 19 ++++ src/main.rs | 19 +++- src/rate_limiter.rs | 257 ++++++++++++++++++++++++++++++++++++++++++++ src/types.rs | 20 ++++ 6 files changed, 357 insertions(+), 67 deletions(-) create mode 100644 src/rate_limiter.rs diff --git a/Cargo.lock b/Cargo.lock index bc1b697..28e999b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -244,8 +244,10 @@ checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", "windows-targets 0.52.5", ] @@ -322,6 +324,19 @@ dependencies = [ "cipher", ] +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "devise" version = "0.3.1" @@ -671,6 +686,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.4" @@ -1173,9 +1194,9 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -1390,9 +1411,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.16.0" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "opaque-debug" @@ -1462,15 +1483,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.5" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.13", "smallvec", - "windows-sys 0.42.0", + "windows-targets 0.52.5", ] [[package]] @@ -1647,6 +1668,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +dependencies = [ + "bitflags 2.5.0", +] + [[package]] name = "ref-cast" version = "1.0.14" @@ -2279,6 +2309,8 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" name = "sway-playground" version = "0.1.0" dependencies = [ + "chrono", + "dashmap", "dotenv", "fs_extra", "gemini-rust", @@ -2393,7 +2425,7 @@ dependencies = [ "cfg-if", "fastrand", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "remove_dir_all", "winapi", ] @@ -3034,21 +3066,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.0", - "windows_aarch64_msvc 0.42.0", - "windows_i686_gnu 0.42.0", - "windows_i686_msvc 0.42.0", - "windows_x86_64_gnu 0.42.0", - "windows_x86_64_gnullvm 0.42.0", - "windows_x86_64_msvc 0.42.0", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -3098,12 +3115,6 @@ dependencies = [ "windows_x86_64_msvc 0.52.5", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -3122,12 +3133,6 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -3146,12 +3151,6 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b" -[[package]] -name = "windows_i686_gnu" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -3176,12 +3175,6 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106" -[[package]] -name = "windows_i686_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -3200,12 +3193,6 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -3218,12 +3205,6 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -3242,12 +3223,6 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" diff --git a/Cargo.toml b/Cargo.toml index ef3b9af..f7db978 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,5 @@ thiserror = "1.0.60" gemini-rust = "0.1.0" reqwest = { version = "0.11", features = ["json"] } dotenv = "0.15" +dashmap = "5.5" +chrono = { version = "0.4", features = ["serde"] } diff --git a/src/error.rs b/src/error.rs index 4733a5d..5f2a025 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,6 +5,8 @@ use rocket::{ Request, }; use thiserror::Error; +use crate::rate_limiter::RateLimitError; +use crate::types::RateLimitErrorResponse; /// A wrapper for API responses that can return errors. pub type ApiResult = Result, ApiError>; @@ -23,6 +25,8 @@ pub enum ApiError { Github(String), #[error("AI service error: {0}")] Ai(String), + #[error("Rate limit error: {0}")] + RateLimit(#[from] RateLimitError), } impl<'r, 'o: 'r> Responder<'r, 'o> for ApiError { @@ -32,6 +36,21 @@ impl<'r, 'o: 'r> Responder<'r, 'o> for ApiError { ApiError::Charcoal(_) => Err(Status::InternalServerError), ApiError::Github(_) => Err(Status::InternalServerError), ApiError::Ai(_) => Err(Status::InternalServerError), + ApiError::RateLimit(rate_limit_error) => { + let RateLimitError::LimitExceeded { limit, reset_time } = rate_limit_error; + let retry_after = (reset_time - chrono::Utc::now()).num_seconds() as u64; + let error_response = RateLimitErrorResponse { + error: "Rate limit exceeded".to_string(), + requests_limit: limit, + reset_time, + retry_after_seconds: retry_after, + }; + + let json_response = Json(error_response); + let mut response = json_response.respond_to(_request)?; + response.set_status(Status::TooManyRequests); + Ok(response) + } } } } diff --git a/src/main.rs b/src/main.rs index 9f5ebd0..89382c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ mod compilation; mod cors; mod error; mod gist; +mod rate_limiter; mod transpilation; mod types; mod util; @@ -17,10 +18,11 @@ use crate::compilation::build_and_destroy_project; use crate::cors::Cors; use crate::error::ApiResult; use crate::gist::GistClient; +use crate::rate_limiter::{RateLimiter, RateLimitConfig, RateLimitGuard, ClientIp}; use crate::types::{ CompileRequest, CompileResponse, ErrorAnalysisRequest, ErrorAnalysisResponse, GistResponse, Language, NewGistRequest, NewGistResponse, SwayCodeGenerationRequest, - SwayCodeGenerationResponse, TranspileRequest, + SwayCodeGenerationResponse, TranspileRequest, RateLimitStatus, }; use crate::{transpilation::solidity_to_sway, types::TranspileResponse}; use rocket::serde::json::Json; @@ -65,6 +67,7 @@ async fn get_gist(id: String, gist: &State) -> ApiResult, ai_service: &State, + _rate_limit: RateLimitGuard, ) -> ApiResult { let response = ai_service.generate_sway_code(request.into_inner()).await?; Ok(Json(response)) @@ -75,11 +78,22 @@ async fn generate_sway_code( async fn analyze_error( request: Json, ai_service: &State, + _rate_limit: RateLimitGuard, ) -> ApiResult { let response = ai_service.analyze_error(request.into_inner()).await?; Ok(Json(response)) } +/// The endpoint to get rate limit status. +#[get("/ai/rate-limit-status")] +fn get_rate_limit_status( + rate_limiter: &State, + client_ip: ClientIp, +) -> ApiResult { + let status = rate_limiter.get_rate_limit_status(client_ip.0); + Ok(Json(status)) +} + /// Catches all OPTION requests in order to get the CORS related Fairing triggered. #[options("/<_..>")] fn all_options() { @@ -99,10 +113,12 @@ fn rocket() -> _ { dotenv::dotenv().ok(); let ai_service = AIService::new().expect("Failed to initialize AI service"); + let rate_limiter = RateLimiter::new(RateLimitConfig::from_env()); rocket::build() .manage(GistClient::default()) .manage(ai_service) + .manage(rate_limiter) .attach(Cors) .mount( "/", @@ -113,6 +129,7 @@ fn rocket() -> _ { get_gist, generate_sway_code, analyze_error, + get_rate_limit_status, all_options, health ], diff --git a/src/rate_limiter.rs b/src/rate_limiter.rs new file mode 100644 index 0000000..830c048 --- /dev/null +++ b/src/rate_limiter.rs @@ -0,0 +1,257 @@ +use chrono::{DateTime, Utc}; +use dashmap::DashMap; +use rocket::request::{FromRequest, Outcome}; +use rocket::{http::Status, Request, State}; +use std::env; +use std::net::IpAddr; +use std::str::FromStr; +use std::sync::Arc; +use std::time::Duration; +use tokio::time::interval; +use crate::types::RateLimitStatus; + +const DAY_SECONDS: u64 = 86400; + +#[derive(Debug, Clone)] +pub struct RateLimitConfig { + pub requests_per_day: u32, + pub cleanup_interval_minutes: u64, +} + +impl Default for RateLimitConfig { + fn default() -> Self { + Self { + requests_per_day: 20, + cleanup_interval_minutes: 60, + } + } +} + +impl RateLimitConfig { + pub fn from_env() -> Self { + let requests_per_day = env::var("RATE_LIMIT_REQUESTS_PER_DAY") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(20); + + let cleanup_interval_minutes = env::var("RATE_LIMIT_CLEANUP_INTERVAL_MINUTES") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(10); + + Self { + requests_per_day, + cleanup_interval_minutes, + } + } +} + +#[derive(Debug, Clone)] +struct RequestRecord { + count: u32, + window_start: DateTime, +} + +impl RequestRecord { + fn new() -> Self { + Self { + count: 0, + window_start: Utc::now(), + } + } + + fn reset_if_expired(&mut self) -> bool { + let now = Utc::now(); + let elapsed = now.signed_duration_since(self.window_start); + + if elapsed.num_seconds() > DAY_SECONDS as i64 { + self.count = 0; + self.window_start = now; + true + } else { + false + } + } +} + +pub struct RateLimiter { + storage: Arc>, + config: RateLimitConfig, +} + +impl RateLimiter { + pub fn new(config: RateLimitConfig) -> Self { + let storage = Arc::new(DashMap::new()); + + let limiter = Self { + storage: storage.clone(), + config: config.clone(), + }; + + limiter.start_cleanup_task(); + limiter + } + + pub fn check_rate_limit(&self, ip: IpAddr) -> Result<(), RateLimitError> { + let mut entry = self.storage.entry(ip).or_insert_with(RequestRecord::new); + + entry.reset_if_expired(); + + if entry.count < self.config.requests_per_day { + entry.count += 1; + Ok(()) + } else { + let reset_time = entry.window_start + chrono::Duration::seconds(DAY_SECONDS as i64); + Err(RateLimitError::LimitExceeded { + limit: self.config.requests_per_day, + reset_time, + }) + } + } + + pub fn get_rate_limit_status(&self, ip: IpAddr) -> RateLimitStatus { + if let Some(entry) = self.storage.get(&ip) { + let now = Utc::now(); + let elapsed = now.signed_duration_since(entry.window_start); + + if elapsed.num_seconds() > DAY_SECONDS as i64 { + RateLimitStatus { + requests_remaining: self.config.requests_per_day, + requests_limit: self.config.requests_per_day, + reset_time: None, + window_duration_seconds: DAY_SECONDS, + } + } else { + let remaining = if entry.count >= self.config.requests_per_day { + 0 + } else { + self.config.requests_per_day - entry.count + }; + + let reset_time = entry.window_start + chrono::Duration::seconds(DAY_SECONDS as i64); + + RateLimitStatus { + requests_remaining: remaining, + requests_limit: self.config.requests_per_day, + reset_time: Some(reset_time), + window_duration_seconds: DAY_SECONDS, + } + } + } else { + RateLimitStatus { + requests_remaining: self.config.requests_per_day, + requests_limit: self.config.requests_per_day, + reset_time: None, + window_duration_seconds: DAY_SECONDS, + } + } + } + + fn start_cleanup_task(&self) { + let storage = self.storage.clone(); + let cleanup_interval = Duration::from_secs(self.config.cleanup_interval_minutes * 60); + + tokio::spawn(async move { + let mut interval = interval(cleanup_interval); + + loop { + interval.tick().await; + + let now = Utc::now(); + let mut to_remove = Vec::new(); + + for entry in storage.iter() { + let elapsed = now.signed_duration_since(entry.window_start); + if elapsed.num_seconds() > DAY_SECONDS as i64 { + to_remove.push(*entry.key()); + } + } + + for ip in to_remove { + storage.remove(&ip); + } + } + }); + } +} + +#[derive(Debug)] +pub enum RateLimitError { + LimitExceeded { + limit: u32, + reset_time: DateTime, + }, +} + +impl std::fmt::Display for RateLimitError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + RateLimitError::LimitExceeded { limit, reset_time } => { + write!(f, "Rate limit exceeded. Limit: {} requests per day. Reset at: {}", limit, reset_time) + } + } + } +} + +impl std::error::Error for RateLimitError {} + +pub struct RateLimitGuard { + pub ip: IpAddr, +} + +pub struct ClientIp(pub IpAddr); + +#[rocket::async_trait] +impl<'r> FromRequest<'r> for ClientIp { + type Error = (); + + async fn from_request(request: &'r Request<'_>) -> Outcome { + let ip = extract_client_ip(request).unwrap_or_else(|| "127.0.0.1".parse().unwrap()); + Outcome::Success(ClientIp(ip)) + } +} + +#[rocket::async_trait] +impl<'r> FromRequest<'r> for RateLimitGuard { + type Error = RateLimitError; + + async fn from_request(request: &'r Request<'_>) -> Outcome { + let rate_limiter = match request.guard::<&State>().await { + Outcome::Success(limiter) => limiter, + Outcome::Failure((status, _)) => return Outcome::Failure((status, RateLimitError::LimitExceeded { + limit: 0, + reset_time: chrono::Utc::now() + })), + Outcome::Forward(f) => return Outcome::Forward(f), + }; + + let ip = extract_client_ip(request).unwrap_or_else(|| "127.0.0.1".parse().unwrap()); + + match rate_limiter.check_rate_limit(ip) { + Ok(()) => Outcome::Success(RateLimitGuard { ip }), + Err(e) => Outcome::Failure((Status::TooManyRequests, e)), + } + } +} + +pub fn extract_client_ip(request: &Request) -> Option { + // Check X-Forwarded-For header (proxy/load balancer) + if let Some(forwarded) = request.headers().get_one("X-Forwarded-For") { + if let Some(ip_str) = forwarded.split(',').next() { + if let Ok(ip) = IpAddr::from_str(ip_str.trim()) { + return Some(ip); + } + } + } + + // Check X-Real-IP header (Nginx proxy) + if let Some(real_ip) = request.headers().get_one("X-Real-IP") { + if let Ok(ip) = IpAddr::from_str(real_ip.trim()) { + return Some(ip); + } + } + + // Fall back to remote address + request.remote() + .map(|addr| addr.ip()) +} \ No newline at end of file diff --git a/src/types.rs b/src/types.rs index f7e46a2..7ddc1fb 100644 --- a/src/types.rs +++ b/src/types.rs @@ -137,3 +137,23 @@ pub struct ErrorAnalysisResponse { #[serde(skip_serializing_if = "Option::is_none")] pub fixed_code: Option, } + +/// Rate limit status information. +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RateLimitStatus { + pub requests_remaining: u32, + pub requests_limit: u32, + pub reset_time: Option>, + pub window_duration_seconds: u64, +} + +/// Enhanced error response with rate limit information. +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RateLimitErrorResponse { + pub error: String, + pub requests_limit: u32, + pub reset_time: chrono::DateTime, + pub retry_after_seconds: u64, +} From bb7b924fbb90dff3691c6ba40d23360d96f63655 Mon Sep 17 00:00:00 2001 From: PraneshASP Date: Wed, 16 Jul 2025 11:23:49 +0530 Subject: [PATCH 11/25] feat: add rate limit checks --- .../ai/components/AIGenerationDialog.tsx | 31 +++++++-- .../ai/components/FixWithAIButton.tsx | 43 +++++++++---- .../ai/components/RateLimitDisplay.tsx | 64 +++++++++++++++++++ app/src/features/ai/hooks/useAIGeneration.tsx | 10 ++- app/src/features/ai/hooks/useAIService.ts | 34 +++++++--- .../features/ai/hooks/useErrorAnalysis.tsx | 7 ++ .../features/ai/hooks/useRateLimitStatus.ts | 48 ++++++++++++++ app/src/services/aiService.ts | 60 +++++++++++++++++ 8 files changed, 271 insertions(+), 26 deletions(-) create mode 100644 app/src/features/ai/components/RateLimitDisplay.tsx create mode 100644 app/src/features/ai/hooks/useRateLimitStatus.ts diff --git a/app/src/features/ai/components/AIGenerationDialog.tsx b/app/src/features/ai/components/AIGenerationDialog.tsx index 2c8daa5..4dcc3c9 100644 --- a/app/src/features/ai/components/AIGenerationDialog.tsx +++ b/app/src/features/ai/components/AIGenerationDialog.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import { useCopyToClipboard } from "../../../hooks/useCopyToClipboard"; import { Dialog, @@ -22,6 +22,8 @@ import { useAIGeneration } from "../hooks/useAIGeneration"; import { SwayCodeGenerationRequest } from "../../../services/aiService"; import { MarkdownRenderer } from "./MarkdownRenderer"; import { removeCodeBlocks } from "../../../utils/aiHelpers"; +import { RateLimitDisplay } from "./RateLimitDisplay"; +import { useRateLimitStatus } from "../hooks/useRateLimitStatus"; const StyledDialog = styled(Dialog)(() => ({ "& .MuiPaper-root": { @@ -67,10 +69,22 @@ export function AIGenerationDialog({ onClose, onCodeGenerated, }: AIGenerationDialogProps) { - const { state, generateCode, clearResult, isAvailable } = useAIGeneration(); + const { status: rateLimitStatus, fetchStatus: fetchRateLimitStatus, updateStatusAfterError } = useRateLimitStatus(); + const { state, generateCode, clearResult, isAvailable } = useAIGeneration({ + onRateLimitError: (error) => { + updateStatusAfterError(error); + } + }); const [prompt, setPrompt] = useState(""); const { copied, copyToClipboard, resetCopied } = useCopyToClipboard(); + // Fetch rate limit status when modal opens + useEffect(() => { + if (open) { + fetchRateLimitStatus(); + } + }, [open, fetchRateLimitStatus]); + const handleGenerate = async () => { if (!prompt.trim()) return; @@ -79,6 +93,8 @@ export function AIGenerationDialog({ }; await generateCode(request); + // Refresh rate limit status after making a request + await fetchRateLimitStatus(); }; const handleCopyCode = async () => { @@ -139,7 +155,10 @@ export function AIGenerationDialog({ {/* Input Form */} - + + + + setPrompt(e.target.value)} - disabled={isGenerating} + disabled={isGenerating || (rateLimitStatus?.requestsRemaining === 0)} variant="outlined" /> @@ -240,13 +259,13 @@ export function AIGenerationDialog({ {!hasResult && ( : } variant="contained" > - Generate Contract + {rateLimitStatus?.requestsRemaining === 0 ? 'Limit Reached' : 'Generate Contract'} )} diff --git a/app/src/features/ai/components/FixWithAIButton.tsx b/app/src/features/ai/components/FixWithAIButton.tsx index efd4640..fe393c4 100644 --- a/app/src/features/ai/components/FixWithAIButton.tsx +++ b/app/src/features/ai/components/FixWithAIButton.tsx @@ -22,6 +22,7 @@ import { useErrorAnalysis } from "../hooks/useErrorAnalysis"; import { ErrorAnalysisRequest } from "../../../services/aiService"; import { MarkdownRenderer } from "./MarkdownRenderer"; import { removeCodeBlocks } from "../../../utils/aiHelpers"; +import { useRateLimitStatus } from "../hooks/useRateLimitStatus"; export interface FixWithAIButtonProps { errorMessage: string; @@ -38,11 +39,16 @@ export function FixWithAIButton({ }: FixWithAIButtonProps) { const [dialogOpen, setDialogOpen] = useState(false); const { copied, copyToClipboard, resetCopied } = useCopyToClipboard(); + const { status: rateLimitStatus, fetchStatus: fetchRateLimitStatus, updateStatusAfterError } = useRateLimitStatus(); const { state, analyzeError, applyFix, clearResult, isAvailable } = useErrorAnalysis((fixedCode: string) => { onCodeFixed(fixedCode); setDialogOpen(false); + }, { + onRateLimitError: (error) => { + updateStatusAfterError(error); + } }); const handleFixClick = async () => { @@ -91,17 +97,32 @@ export function FixWithAIButton({ return ( <> - + + + {rateLimitStatus && ( + + {rateLimitStatus.requestsRemaining === 0 && rateLimitStatus.resetTime + ? `(resets ${new Date(rateLimitStatus.resetTime).toLocaleString(undefined, { + hour: 'numeric', + minute: '2-digit', + hour12: true, + month: 'short', + day: 'numeric' + })})` + : `(${rateLimitStatus.requestsRemaining} calls remaining today)` + } + + )} + + Loading... +
+ ); + } + + if (!status) { + return null; + } + + const isAtLimit = status.requestsRemaining === 0; + const isNearLimit = status.requestsRemaining <= 5; + + const getTextColor = () => { + if (isAtLimit) return 'error.main'; + if (isNearLimit) return 'warning.main'; + return 'text.secondary'; + }; + + const formatResetTime = (resetTime: string) => { + const resetDate = new Date(resetTime); + return resetDate.toLocaleString(undefined, { + hour: 'numeric', + minute: '2-digit', + hour12: true, + month: 'short', + day: 'numeric' + }); + }; + + if (isAtLimit && status.resetTime) { + return ( + + Limit reached - resets at {formatResetTime(status.resetTime)} + + ); + } + + return ( + + {status.requestsRemaining}/{status.requestsLimit} remaining today + + ); +} \ No newline at end of file diff --git a/app/src/features/ai/hooks/useAIGeneration.tsx b/app/src/features/ai/hooks/useAIGeneration.tsx index b9e59f1..6f3cea0 100644 --- a/app/src/features/ai/hooks/useAIGeneration.tsx +++ b/app/src/features/ai/hooks/useAIGeneration.tsx @@ -2,6 +2,7 @@ import { aiService, SwayCodeGenerationRequest, SwayCodeGenerationResponse, + RateLimitError, } from "../../../services/aiService"; import { useAIService } from "./useAIService"; @@ -18,9 +19,16 @@ export interface UseAIGenerationReturn { isAvailable: boolean; } -export function useAIGeneration(): UseAIGenerationReturn { +export interface UseAIGenerationOptions { + onRateLimitError?: (error: RateLimitError) => void; +} + +export function useAIGeneration(options: UseAIGenerationOptions = {}): UseAIGenerationReturn { const { state, execute, clearResult, isAvailable } = useAIService( aiService.generateSwayCode.bind(aiService), + { + onRateLimitError: options.onRateLimitError, + }, ); const transformedState: AIGenerationState = { diff --git a/app/src/features/ai/hooks/useAIService.ts b/app/src/features/ai/hooks/useAIService.ts index 03ee952..1159617 100644 --- a/app/src/features/ai/hooks/useAIService.ts +++ b/app/src/features/ai/hooks/useAIService.ts @@ -1,14 +1,16 @@ import { useState, useCallback } from "react"; -import { aiService } from "../../../services/aiService"; +import { aiService, RateLimitError } from "../../../services/aiService"; export interface AIServiceState { isLoading: boolean; result: TResult | null; error: string | null; + rateLimitError?: RateLimitError; } export interface UseAIServiceOptions { onApply?: (result: TResult) => void; + onRateLimitError?: (error: RateLimitError) => void; } export interface UseAIServiceReturn { @@ -27,6 +29,7 @@ export function useAIService( isLoading: false, result: null, error: null, + rateLimitError: undefined, }); const isAvailable = aiService.isAvailable(); @@ -46,6 +49,7 @@ export function useAIService( isLoading: true, error: null, result: null, + rateLimitError: undefined, })); try { @@ -57,13 +61,26 @@ export function useAIService( result, })); } catch (error) { - const errorMessage = - error instanceof Error ? error.message : "Operation failed"; - setState((prev) => ({ - ...prev, - isLoading: false, - error: errorMessage, - })); + if (error instanceof RateLimitError) { + setState((prev) => ({ + ...prev, + isLoading: false, + error: error.message, + rateLimitError: error, + })); + + if (options.onRateLimitError) { + options.onRateLimitError(error); + } + } else { + const errorMessage = + error instanceof Error ? error.message : "Operation failed"; + setState((prev) => ({ + ...prev, + isLoading: false, + error: errorMessage, + })); + } } }, [serviceFunction, isAvailable], @@ -84,6 +101,7 @@ export function useAIService( isLoading: false, result: null, error: null, + rateLimitError: undefined, }); }, []); diff --git a/app/src/features/ai/hooks/useErrorAnalysis.tsx b/app/src/features/ai/hooks/useErrorAnalysis.tsx index c5d738e..f42309f 100644 --- a/app/src/features/ai/hooks/useErrorAnalysis.tsx +++ b/app/src/features/ai/hooks/useErrorAnalysis.tsx @@ -3,6 +3,7 @@ import { aiService, ErrorAnalysisRequest, ErrorAnalysisResponse, + RateLimitError, } from "../../../services/aiService"; import { useAIService } from "./useAIService"; @@ -20,8 +21,13 @@ export interface UseErrorAnalysisReturn { isAvailable: boolean; } +export interface UseErrorAnalysisOptions { + onRateLimitError?: (error: RateLimitError) => void; +} + export function useErrorAnalysis( onCodeFixed?: (code: string) => void, + options: UseErrorAnalysisOptions = {}, ): UseErrorAnalysisReturn { const { state, execute, apply, clearResult, isAvailable } = useAIService( aiService.analyzeError.bind(aiService), @@ -31,6 +37,7 @@ export function useErrorAnalysis( onCodeFixed(result.fixedCode); } }, + onRateLimitError: options.onRateLimitError, }, ); diff --git a/app/src/features/ai/hooks/useRateLimitStatus.ts b/app/src/features/ai/hooks/useRateLimitStatus.ts new file mode 100644 index 0000000..894eec3 --- /dev/null +++ b/app/src/features/ai/hooks/useRateLimitStatus.ts @@ -0,0 +1,48 @@ +import { useState, useEffect, useCallback } from 'react'; +import { aiService, RateLimitStatus, RateLimitError } from '../../../services/aiService'; + +export function useRateLimitStatus() { + const [status, setStatus] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const fetchStatus = useCallback(async () => { + try { + setIsLoading(true); + setError(null); + const rateLimitStatus = await aiService.getRateLimitStatus(); + setStatus(rateLimitStatus); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to fetch rate limit status'); + } finally { + setIsLoading(false); + } + }, []); + + const updateStatusAfterError = useCallback((rateLimitError: RateLimitError) => { + setStatus({ + requestsRemaining: 0, + requestsLimit: rateLimitError.requestsLimit, + resetTime: rateLimitError.resetTime, + windowDurationSeconds: rateLimitError.retryAfterSeconds, + }); + }, []); + + const resetStatus = useCallback(() => { + setStatus(null); + setError(null); + }, []); + + useEffect(() => { + fetchStatus(); + }, [fetchStatus]); + + return { + status, + isLoading, + error, + fetchStatus, + updateStatusAfterError, + resetStatus, + }; +} \ No newline at end of file diff --git a/app/src/services/aiService.ts b/app/src/services/aiService.ts index 49d37ff..e02ed70 100644 --- a/app/src/services/aiService.ts +++ b/app/src/services/aiService.ts @@ -22,6 +22,32 @@ export interface ErrorAnalysisResponse { fixedCode?: string; } +export interface RateLimitStatus { + requestsRemaining: number; + requestsLimit: number; + resetTime?: string; + windowDurationSeconds: number; +} + +export interface RateLimitErrorResponse { + error: string; + requestsLimit: number; + resetTime: string; + retryAfterSeconds: number; +} + +export class RateLimitError extends Error { + constructor( + message: string, + public readonly requestsLimit: number, + public readonly resetTime: string, + public readonly retryAfterSeconds: number + ) { + super(message); + this.name = 'RateLimitError'; + } +} + class AIService { private async makeRequest(endpoint: string, data: any): Promise { const response = await fetch(`${SERVER_URI}${endpoint}`, { @@ -33,6 +59,20 @@ class AIService { }); if (!response.ok) { + if (response.status === 429) { + // Rate limit error - parse the enhanced error response + const errorData = await response + .json() + .catch(() => ({ error: "Rate limit exceeded" })) as RateLimitErrorResponse; + + throw new RateLimitError( + errorData.error || "Rate limit exceeded", + errorData.requestsLimit, + errorData.resetTime, + errorData.retryAfterSeconds + ); + } + const errorData = await response .json() .catch(() => ({ error: "Unknown error" })); @@ -62,6 +102,26 @@ class AIService { ); } + async getRateLimitStatus(): Promise { + const response = await fetch(`${SERVER_URI}/ai/rate-limit-status`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + const errorData = await response + .json() + .catch(() => ({ error: "Unknown error" })); + throw new Error( + errorData.error || `HTTP ${response.status}: ${response.statusText}`, + ); + } + + return response.json(); + } + isAvailable(): boolean { return true; // Backend handles availability checks } From 8ce8610088fa5df922778cf560bda1d5595c5ccf Mon Sep 17 00:00:00 2001 From: PraneshASP Date: Wed, 16 Jul 2025 11:33:48 +0530 Subject: [PATCH 12/25] chore: fmt and linting --- src/error.rs | 6 +++--- src/main.rs | 6 +++--- src/rate_limiter.rs | 52 +++++++++++++++++++++++++-------------------- 3 files changed, 35 insertions(+), 29 deletions(-) diff --git a/src/error.rs b/src/error.rs index 5f2a025..3c1d124 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,3 +1,5 @@ +use crate::rate_limiter::RateLimitError; +use crate::types::RateLimitErrorResponse; use rocket::{ http::Status, response::Responder, @@ -5,8 +7,6 @@ use rocket::{ Request, }; use thiserror::Error; -use crate::rate_limiter::RateLimitError; -use crate::types::RateLimitErrorResponse; /// A wrapper for API responses that can return errors. pub type ApiResult = Result, ApiError>; @@ -45,7 +45,7 @@ impl<'r, 'o: 'r> Responder<'r, 'o> for ApiError { reset_time, retry_after_seconds: retry_after, }; - + let json_response = Json(error_response); let mut response = json_response.respond_to(_request)?; response.set_status(Status::TooManyRequests); diff --git a/src/main.rs b/src/main.rs index 89382c4..8e55b4b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,11 +18,11 @@ use crate::compilation::build_and_destroy_project; use crate::cors::Cors; use crate::error::ApiResult; use crate::gist::GistClient; -use crate::rate_limiter::{RateLimiter, RateLimitConfig, RateLimitGuard, ClientIp}; +use crate::rate_limiter::{ClientIp, RateLimitConfig, RateLimitGuard, RateLimiter}; use crate::types::{ CompileRequest, CompileResponse, ErrorAnalysisRequest, ErrorAnalysisResponse, GistResponse, - Language, NewGistRequest, NewGistResponse, SwayCodeGenerationRequest, - SwayCodeGenerationResponse, TranspileRequest, RateLimitStatus, + Language, NewGistRequest, NewGistResponse, RateLimitStatus, SwayCodeGenerationRequest, + SwayCodeGenerationResponse, TranspileRequest, }; use crate::{transpilation::solidity_to_sway, types::TranspileResponse}; use rocket::serde::json::Json; diff --git a/src/rate_limiter.rs b/src/rate_limiter.rs index 830c048..62041c4 100644 --- a/src/rate_limiter.rs +++ b/src/rate_limiter.rs @@ -1,3 +1,4 @@ +use crate::types::RateLimitStatus; use chrono::{DateTime, Utc}; use dashmap::DashMap; use rocket::request::{FromRequest, Outcome}; @@ -8,7 +9,6 @@ use std::str::FromStr; use std::sync::Arc; use std::time::Duration; use tokio::time::interval; -use crate::types::RateLimitStatus; const DAY_SECONDS: u64 = 86400; @@ -63,7 +63,7 @@ impl RequestRecord { fn reset_if_expired(&mut self) -> bool { let now = Utc::now(); let elapsed = now.signed_duration_since(self.window_start); - + if elapsed.num_seconds() > DAY_SECONDS as i64 { self.count = 0; self.window_start = now; @@ -82,7 +82,7 @@ pub struct RateLimiter { impl RateLimiter { pub fn new(config: RateLimitConfig) -> Self { let storage = Arc::new(DashMap::new()); - + let limiter = Self { storage: storage.clone(), config: config.clone(), @@ -94,9 +94,9 @@ impl RateLimiter { pub fn check_rate_limit(&self, ip: IpAddr) -> Result<(), RateLimitError> { let mut entry = self.storage.entry(ip).or_insert_with(RequestRecord::new); - + entry.reset_if_expired(); - + if entry.count < self.config.requests_per_day { entry.count += 1; Ok(()) @@ -113,7 +113,7 @@ impl RateLimiter { if let Some(entry) = self.storage.get(&ip) { let now = Utc::now(); let elapsed = now.signed_duration_since(entry.window_start); - + if elapsed.num_seconds() > DAY_SECONDS as i64 { RateLimitStatus { requests_remaining: self.config.requests_per_day, @@ -150,23 +150,23 @@ impl RateLimiter { fn start_cleanup_task(&self) { let storage = self.storage.clone(); let cleanup_interval = Duration::from_secs(self.config.cleanup_interval_minutes * 60); - + tokio::spawn(async move { let mut interval = interval(cleanup_interval); - + loop { interval.tick().await; - + let now = Utc::now(); let mut to_remove = Vec::new(); - + for entry in storage.iter() { let elapsed = now.signed_duration_since(entry.window_start); if elapsed.num_seconds() > DAY_SECONDS as i64 { to_remove.push(*entry.key()); } } - + for ip in to_remove { storage.remove(&ip); } @@ -187,7 +187,11 @@ impl std::fmt::Display for RateLimitError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { RateLimitError::LimitExceeded { limit, reset_time } => { - write!(f, "Rate limit exceeded. Limit: {} requests per day. Reset at: {}", limit, reset_time) + write!( + f, + "Rate limit exceeded. Limit: {} requests per day. Reset at: {}", + limit, reset_time + ) } } } @@ -195,9 +199,7 @@ impl std::fmt::Display for RateLimitError { impl std::error::Error for RateLimitError {} -pub struct RateLimitGuard { - pub ip: IpAddr, -} +pub struct RateLimitGuard; pub struct ClientIp(pub IpAddr); @@ -218,17 +220,22 @@ impl<'r> FromRequest<'r> for RateLimitGuard { async fn from_request(request: &'r Request<'_>) -> Outcome { let rate_limiter = match request.guard::<&State>().await { Outcome::Success(limiter) => limiter, - Outcome::Failure((status, _)) => return Outcome::Failure((status, RateLimitError::LimitExceeded { - limit: 0, - reset_time: chrono::Utc::now() - })), + Outcome::Failure((status, _)) => { + return Outcome::Failure(( + status, + RateLimitError::LimitExceeded { + limit: 0, + reset_time: chrono::Utc::now(), + }, + )) + } Outcome::Forward(f) => return Outcome::Forward(f), }; let ip = extract_client_ip(request).unwrap_or_else(|| "127.0.0.1".parse().unwrap()); match rate_limiter.check_rate_limit(ip) { - Ok(()) => Outcome::Success(RateLimitGuard { ip }), + Ok(()) => Outcome::Success(RateLimitGuard), Err(e) => Outcome::Failure((Status::TooManyRequests, e)), } } @@ -252,6 +259,5 @@ pub fn extract_client_ip(request: &Request) -> Option { } // Fall back to remote address - request.remote() - .map(|addr| addr.ip()) -} \ No newline at end of file + request.remote().map(|addr| addr.ip()) +} From 895beb8ddc83738bf07bfdeccab1e69f831787f0 Mon Sep 17 00:00:00 2001 From: PraneshASP Date: Wed, 16 Jul 2025 11:50:36 +0530 Subject: [PATCH 13/25] chore: fmt and linting --- .../ai/components/AIGenerationDialog.tsx | 26 +++++--- .../ai/components/FixWithAIButton.tsx | 63 ++++++++++++------- .../ai/components/MarkdownRenderer.tsx | 4 +- .../ai/components/RateLimitDisplay.tsx | 40 ++++++------ app/src/features/ai/hooks/useAIGeneration.tsx | 4 +- app/src/features/ai/hooks/useAIService.ts | 24 +++---- .../features/ai/hooks/useErrorAnalysis.tsx | 2 +- .../features/ai/hooks/useRateLimitStatus.ts | 35 +++++++---- app/src/features/editor/hooks/useCompile.tsx | 2 +- .../toolbar/components/ActionToolbar.tsx | 2 +- app/src/services/aiService.ts | 18 +++--- app/src/services/apiService.ts | 4 +- src/rate_limiter.rs | 9 +-- 13 files changed, 138 insertions(+), 95 deletions(-) diff --git a/app/src/features/ai/components/AIGenerationDialog.tsx b/app/src/features/ai/components/AIGenerationDialog.tsx index 4dcc3c9..0248634 100644 --- a/app/src/features/ai/components/AIGenerationDialog.tsx +++ b/app/src/features/ai/components/AIGenerationDialog.tsx @@ -69,11 +69,15 @@ export function AIGenerationDialog({ onClose, onCodeGenerated, }: AIGenerationDialogProps) { - const { status: rateLimitStatus, fetchStatus: fetchRateLimitStatus, updateStatusAfterError } = useRateLimitStatus(); + const { + status: rateLimitStatus, + fetchStatus: fetchRateLimitStatus, + updateStatusAfterError, + } = useRateLimitStatus(); const { state, generateCode, clearResult, isAvailable } = useAIGeneration({ onRateLimitError: (error) => { updateStatusAfterError(error); - } + }, }); const [prompt, setPrompt] = useState(""); const { copied, copyToClipboard, resetCopied } = useCopyToClipboard(); @@ -155,8 +159,8 @@ export function AIGenerationDialog({ {/* Input Form */} - - + + setPrompt(e.target.value)} - disabled={isGenerating || (rateLimitStatus?.requestsRemaining === 0)} + disabled={ + isGenerating || rateLimitStatus?.requestsRemaining === 0 + } variant="outlined" /> @@ -259,13 +265,19 @@ export function AIGenerationDialog({ {!hasResult && ( : } variant="contained" > - {rateLimitStatus?.requestsRemaining === 0 ? 'Limit Reached' : 'Generate Contract'} + {rateLimitStatus?.requestsRemaining === 0 + ? "Limit Reached" + : "Generate Contract"} )} diff --git a/app/src/features/ai/components/FixWithAIButton.tsx b/app/src/features/ai/components/FixWithAIButton.tsx index fe393c4..01ee29c 100644 --- a/app/src/features/ai/components/FixWithAIButton.tsx +++ b/app/src/features/ai/components/FixWithAIButton.tsx @@ -39,17 +39,21 @@ export function FixWithAIButton({ }: FixWithAIButtonProps) { const [dialogOpen, setDialogOpen] = useState(false); const { copied, copyToClipboard, resetCopied } = useCopyToClipboard(); - const { status: rateLimitStatus, fetchStatus: fetchRateLimitStatus, updateStatusAfterError } = useRateLimitStatus(); + const { status: rateLimitStatus, updateStatusAfterError } = + useRateLimitStatus(); const { state, analyzeError, applyFix, clearResult, isAvailable } = - useErrorAnalysis((fixedCode: string) => { - onCodeFixed(fixedCode); - setDialogOpen(false); - }, { - onRateLimitError: (error) => { - updateStatusAfterError(error); - } - }); + useErrorAnalysis( + (fixedCode: string) => { + onCodeFixed(fixedCode); + setDialogOpen(false); + }, + { + onRateLimitError: (error) => { + updateStatusAfterError(error); + }, + }, + ); const handleFixClick = async () => { if (!isAvailable) { @@ -97,29 +101,42 @@ export function FixWithAIButton({ return ( <> - + {rateLimitStatus && ( - - {rateLimitStatus.requestsRemaining === 0 && rateLimitStatus.resetTime - ? `(resets ${new Date(rateLimitStatus.resetTime).toLocaleString(undefined, { - hour: 'numeric', - minute: '2-digit', - hour12: true, - month: 'short', - day: 'numeric' - })})` - : `(${rateLimitStatus.requestsRemaining} calls remaining today)` - } + + {rateLimitStatus.requestsRemaining === 0 && + rateLimitStatus.resetTime + ? `(resets ${new Date(rateLimitStatus.resetTime).toLocaleString( + undefined, + { + hour: "numeric", + minute: "2-digit", + hour12: true, + month: "short", + day: "numeric", + }, + )})` + : `(${rateLimitStatus.requestsRemaining} calls remaining today)`} )} diff --git a/app/src/features/ai/components/MarkdownRenderer.tsx b/app/src/features/ai/components/MarkdownRenderer.tsx index bbc51fa..bab0dfd 100644 --- a/app/src/features/ai/components/MarkdownRenderer.tsx +++ b/app/src/features/ai/components/MarkdownRenderer.tsx @@ -6,7 +6,9 @@ import { vs } from "react-syntax-highlighter/dist/esm/styles/prism"; interface MarkdownComponentProps { children?: React.ReactNode; - [key: string]: any; + inline?: boolean; + className?: string; + [key: string]: unknown; } interface MarkdownRendererProps { diff --git a/app/src/features/ai/components/RateLimitDisplay.tsx b/app/src/features/ai/components/RateLimitDisplay.tsx index 79cd7a5..cbee73d 100644 --- a/app/src/features/ai/components/RateLimitDisplay.tsx +++ b/app/src/features/ai/components/RateLimitDisplay.tsx @@ -1,6 +1,6 @@ -import React from 'react'; -import { Box, Typography, Chip } from '@mui/material'; -import { RateLimitStatus } from '../../../services/aiService'; +import React from "react"; +import { Typography } from "@mui/material"; +import { RateLimitStatus } from "../../../services/aiService"; interface RateLimitDisplayProps { status: RateLimitStatus | null; @@ -10,7 +10,11 @@ interface RateLimitDisplayProps { export function RateLimitDisplay({ status, isLoading }: RateLimitDisplayProps) { if (isLoading) { return ( - + Loading... ); @@ -24,28 +28,28 @@ export function RateLimitDisplay({ status, isLoading }: RateLimitDisplayProps) { const isNearLimit = status.requestsRemaining <= 5; const getTextColor = () => { - if (isAtLimit) return 'error.main'; - if (isNearLimit) return 'warning.main'; - return 'text.secondary'; + if (isAtLimit) return "error.main"; + if (isNearLimit) return "warning.main"; + return "text.secondary"; }; const formatResetTime = (resetTime: string) => { const resetDate = new Date(resetTime); return resetDate.toLocaleString(undefined, { - hour: 'numeric', - minute: '2-digit', + hour: "numeric", + minute: "2-digit", hour12: true, - month: 'short', - day: 'numeric' + month: "short", + day: "numeric", }); }; if (isAtLimit && status.resetTime) { return ( - Limit reached - resets at {formatResetTime(status.resetTime)} @@ -53,12 +57,12 @@ export function RateLimitDisplay({ status, isLoading }: RateLimitDisplayProps) { } return ( - {status.requestsRemaining}/{status.requestsLimit} remaining today ); -} \ No newline at end of file +} diff --git a/app/src/features/ai/hooks/useAIGeneration.tsx b/app/src/features/ai/hooks/useAIGeneration.tsx index 6f3cea0..e3c564d 100644 --- a/app/src/features/ai/hooks/useAIGeneration.tsx +++ b/app/src/features/ai/hooks/useAIGeneration.tsx @@ -23,7 +23,9 @@ export interface UseAIGenerationOptions { onRateLimitError?: (error: RateLimitError) => void; } -export function useAIGeneration(options: UseAIGenerationOptions = {}): UseAIGenerationReturn { +export function useAIGeneration( + options: UseAIGenerationOptions = {}, +): UseAIGenerationReturn { const { state, execute, clearResult, isAvailable } = useAIService( aiService.generateSwayCode.bind(aiService), { diff --git a/app/src/features/ai/hooks/useAIService.ts b/app/src/features/ai/hooks/useAIService.ts index 1159617..a44c4c6 100644 --- a/app/src/features/ai/hooks/useAIService.ts +++ b/app/src/features/ai/hooks/useAIService.ts @@ -34,6 +34,15 @@ export function useAIService( const isAvailable = aiService.isAvailable(); + const clearResult = useCallback(() => { + setState({ + isLoading: false, + result: null, + error: null, + rateLimitError: undefined, + }); + }, []); + const execute = useCallback( async (request: TRequest) => { if (!isAvailable) { @@ -68,7 +77,7 @@ export function useAIService( error: error.message, rateLimitError: error, })); - + if (options.onRateLimitError) { options.onRateLimitError(error); } @@ -83,7 +92,7 @@ export function useAIService( } } }, - [serviceFunction, isAvailable], + [serviceFunction, isAvailable, options], ); const apply = useCallback( @@ -93,18 +102,9 @@ export function useAIService( } clearResult(); }, - [options.onApply], + [options, clearResult], ); - const clearResult = useCallback(() => { - setState({ - isLoading: false, - result: null, - error: null, - rateLimitError: undefined, - }); - }, []); - return { state, execute, diff --git a/app/src/features/ai/hooks/useErrorAnalysis.tsx b/app/src/features/ai/hooks/useErrorAnalysis.tsx index f42309f..f49f58b 100644 --- a/app/src/features/ai/hooks/useErrorAnalysis.tsx +++ b/app/src/features/ai/hooks/useErrorAnalysis.tsx @@ -29,7 +29,7 @@ export function useErrorAnalysis( onCodeFixed?: (code: string) => void, options: UseErrorAnalysisOptions = {}, ): UseErrorAnalysisReturn { - const { state, execute, apply, clearResult, isAvailable } = useAIService( + const { state, execute, clearResult, isAvailable } = useAIService( aiService.analyzeError.bind(aiService), { onApply: (result: ErrorAnalysisResponse) => { diff --git a/app/src/features/ai/hooks/useRateLimitStatus.ts b/app/src/features/ai/hooks/useRateLimitStatus.ts index 894eec3..0c62aa0 100644 --- a/app/src/features/ai/hooks/useRateLimitStatus.ts +++ b/app/src/features/ai/hooks/useRateLimitStatus.ts @@ -1,5 +1,9 @@ -import { useState, useEffect, useCallback } from 'react'; -import { aiService, RateLimitStatus, RateLimitError } from '../../../services/aiService'; +import { useState, useEffect, useCallback } from "react"; +import { + aiService, + RateLimitStatus, + RateLimitError, +} from "../../../services/aiService"; export function useRateLimitStatus() { const [status, setStatus] = useState(null); @@ -13,20 +17,27 @@ export function useRateLimitStatus() { const rateLimitStatus = await aiService.getRateLimitStatus(); setStatus(rateLimitStatus); } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to fetch rate limit status'); + setError( + err instanceof Error + ? err.message + : "Failed to fetch rate limit status", + ); } finally { setIsLoading(false); } }, []); - const updateStatusAfterError = useCallback((rateLimitError: RateLimitError) => { - setStatus({ - requestsRemaining: 0, - requestsLimit: rateLimitError.requestsLimit, - resetTime: rateLimitError.resetTime, - windowDurationSeconds: rateLimitError.retryAfterSeconds, - }); - }, []); + const updateStatusAfterError = useCallback( + (rateLimitError: RateLimitError) => { + setStatus({ + requestsRemaining: 0, + requestsLimit: rateLimitError.requestsLimit, + resetTime: rateLimitError.resetTime, + windowDurationSeconds: rateLimitError.retryAfterSeconds, + }); + }, + [], + ); const resetStatus = useCallback(() => { setStatus(null); @@ -45,4 +56,4 @@ export function useRateLimitStatus() { updateStatusAfterError, resetStatus, }; -} \ No newline at end of file +} diff --git a/app/src/features/editor/hooks/useCompile.tsx b/app/src/features/editor/hooks/useCompile.tsx index 4301e3e..4de55fe 100644 --- a/app/src/features/editor/hooks/useCompile.tsx +++ b/app/src/features/editor/hooks/useCompile.tsx @@ -125,7 +125,7 @@ export function useCompile( setServerError(true); }); setIsCompiled(true); - }, [code, setIsCompiled, setResults, toolchain]); + }, [code, setIsCompiled, setResults, toolchain, onCodeFixed]); useEffect(() => { if (serverError) { diff --git a/app/src/features/toolbar/components/ActionToolbar.tsx b/app/src/features/toolbar/components/ActionToolbar.tsx index 3a3376a..dfab00c 100644 --- a/app/src/features/toolbar/components/ActionToolbar.tsx +++ b/app/src/features/toolbar/components/ActionToolbar.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState } from "react"; +import React, { useCallback } from "react"; import PlayArrow from "@mui/icons-material/PlayArrow"; import OpenInNew from "@mui/icons-material/OpenInNew"; import AutoAwesome from "@mui/icons-material/AutoAwesome"; diff --git a/app/src/services/aiService.ts b/app/src/services/aiService.ts index e02ed70..381cbfb 100644 --- a/app/src/services/aiService.ts +++ b/app/src/services/aiService.ts @@ -41,15 +41,15 @@ export class RateLimitError extends Error { message: string, public readonly requestsLimit: number, public readonly resetTime: string, - public readonly retryAfterSeconds: number + public readonly retryAfterSeconds: number, ) { super(message); - this.name = 'RateLimitError'; + this.name = "RateLimitError"; } } class AIService { - private async makeRequest(endpoint: string, data: any): Promise { + private async makeRequest(endpoint: string, data: unknown): Promise { const response = await fetch(`${SERVER_URI}${endpoint}`, { method: "POST", headers: { @@ -61,18 +61,18 @@ class AIService { if (!response.ok) { if (response.status === 429) { // Rate limit error - parse the enhanced error response - const errorData = await response - .json() - .catch(() => ({ error: "Rate limit exceeded" })) as RateLimitErrorResponse; - + const errorData = (await response.json().catch(() => ({ + error: "Rate limit exceeded", + }))) as RateLimitErrorResponse; + throw new RateLimitError( errorData.error || "Rate limit exceeded", errorData.requestsLimit, errorData.resetTime, - errorData.retryAfterSeconds + errorData.retryAfterSeconds, ); } - + const errorData = await response .json() .catch(() => ({ error: "Unknown error" })); diff --git a/app/src/services/apiService.ts b/app/src/services/apiService.ts index 25d96c2..9c62d27 100644 --- a/app/src/services/apiService.ts +++ b/app/src/services/apiService.ts @@ -15,9 +15,9 @@ class ApiService { private async makeRequest( endpoint: string, - data?: any, + data?: unknown, options: ApiRequestOptions = {}, - ): Promise { + ): Promise { const { method = data ? "POST" : "GET", headers = {}, diff --git a/src/rate_limiter.rs b/src/rate_limiter.rs index 62041c4..e3c6bba 100644 --- a/src/rate_limiter.rs +++ b/src/rate_limiter.rs @@ -122,11 +122,7 @@ impl RateLimiter { window_duration_seconds: DAY_SECONDS, } } else { - let remaining = if entry.count >= self.config.requests_per_day { - 0 - } else { - self.config.requests_per_day - entry.count - }; + let remaining = self.config.requests_per_day.saturating_sub(entry.count); let reset_time = entry.window_start + chrono::Duration::seconds(DAY_SECONDS as i64); @@ -189,8 +185,7 @@ impl std::fmt::Display for RateLimitError { RateLimitError::LimitExceeded { limit, reset_time } => { write!( f, - "Rate limit exceeded. Limit: {} requests per day. Reset at: {}", - limit, reset_time + "Rate limit exceeded. Limit: {limit} requests per day. Reset at: {reset_time}" ) } } From d607711aef41d1445fc6740bc3f92bf4a456d278 Mon Sep 17 00:00:00 2001 From: PraneshASP Date: Wed, 16 Jul 2025 13:32:55 +0530 Subject: [PATCH 14/25] feat: add e2e tests --- app/package.json | 4 + .../__tests__/e2e/ai/ai-generation.test.ts | 79 +++++++++++++++++++ app/src/test-utils/setup/global-setup.ts | 17 ++++ app/src/test-utils/test-helpers.ts | 28 +++++++ 4 files changed, 128 insertions(+) create mode 100644 app/src/__tests__/e2e/ai/ai-generation.test.ts create mode 100644 app/src/test-utils/setup/global-setup.ts create mode 100644 app/src/test-utils/test-helpers.ts diff --git a/app/package.json b/app/package.json index 29d19a3..4e0a930 100644 --- a/app/package.json +++ b/app/package.json @@ -58,6 +58,7 @@ "start": "DISABLE_ESLINT_PLUGIN=true react-scripts start", "build": "react-scripts build", "test": "react-scripts test", + "test:ai": "react-scripts test --testPathPattern=__tests__/e2e/ai --watchAll=false", "eject": "react-scripts eject", "lint": "eslint --fix 'src/**/*.{ts,tsx}'", "lint-check": "eslint 'src/**/*.{ts,tsx}'", @@ -88,5 +89,8 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "jest": { + "globalSetup": "/src/test-utils/setup/global-setup.ts" } } diff --git a/app/src/__tests__/e2e/ai/ai-generation.test.ts b/app/src/__tests__/e2e/ai/ai-generation.test.ts new file mode 100644 index 0000000..8bdfb8d --- /dev/null +++ b/app/src/__tests__/e2e/ai/ai-generation.test.ts @@ -0,0 +1,79 @@ +import { SERVER_URI } from "../../../constants"; +import { + SwayCodeGenerationRequest, + SwayCodeGenerationResponse, + ErrorAnalysisRequest, + ErrorAnalysisResponse, +} from "../../../services/aiService"; +import { ensureBackendReady } from "../../../test-utils/test-helpers"; + +describe("AI Features E2E", () => { + const generateUri = `${SERVER_URI}/ai/generate`; + const analyzeUri = `${SERVER_URI}/ai/analyze-error`; + + beforeEach(async () => { + await ensureBackendReady(); + }); + + describe("AI Code Generation", () => { + it("should generate Sway code from a valid prompt", async () => { + const request: SwayCodeGenerationRequest = { + prompt: "Create a simple contract with a test function that returns 42", + }; + + const response = await fetch(generateUri, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(request), + }); + + const result: SwayCodeGenerationResponse = await response.json(); + + expect(response.ok).toBe(true); + expect(result.code).toBeDefined(); + expect(result.code).toContain("contract;"); + expect(result.code).toContain("42"); + expect(result.explanation).toBeDefined(); + }, 30000); + }); + + describe("AI Error Analysis and Fix", () => { + it("should provide fix for invalid Sway code", async () => { + const invalidCode = `contract; + +impl MyContract for Contract { + fn test_function() -> u64 { + let x = 42 + x + } +}`; + + const errorMessage = "Expected ';' after expression"; + + const request: ErrorAnalysisRequest = { + errorMessage, + sourceCode: invalidCode, + }; + + const response = await fetch(analyzeUri, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(request), + }); + + const result: ErrorAnalysisResponse = await response.json(); + + expect(response.ok).toBe(true); + expect(result.analysis).toBeDefined(); + expect(result.analysis).toContain("semicolon"); + expect(result.suggestions).toBeDefined(); + expect(Array.isArray(result.suggestions)).toBe(true); + expect(result.fixedCode).toBeDefined(); + expect(result.fixedCode).toContain("let x = 42;"); + }, 30000); + }); +}); diff --git a/app/src/test-utils/setup/global-setup.ts b/app/src/test-utils/setup/global-setup.ts new file mode 100644 index 0000000..59bb5bd --- /dev/null +++ b/app/src/test-utils/setup/global-setup.ts @@ -0,0 +1,17 @@ +import { waitForBackend } from "../test-helpers"; + +// Simple global setup - just wait for backend +const globalSetup = async () => { + console.log("Waiting for backend..."); + + const isReady = await waitForBackend(45000); + if (!isReady) { + throw new Error( + "Backend not available after 45 seconds. Make sure to start backend with: cargo run", + ); + } + + console.log("Backend ready"); +}; + +export default globalSetup; diff --git a/app/src/test-utils/test-helpers.ts b/app/src/test-utils/test-helpers.ts new file mode 100644 index 0000000..13ec871 --- /dev/null +++ b/app/src/test-utils/test-helpers.ts @@ -0,0 +1,28 @@ +// Test helper - check backend before each test +export const ensureBackendReady = async () => { + try { + const response = await fetch("http://127.0.0.1:8080/health"); + if (!response.ok) { + throw new Error("Backend not ready"); + } + } catch (error) { + throw new Error("Backend not available. Start with: cargo run"); + } +}; + +// wait for backend to be ready with retries +export const waitForBackend = async (timeout = 30000): Promise => { + const start = Date.now(); + + while (Date.now() - start < timeout) { + try { + const response = await fetch("http://127.0.0.1:8080/health"); + if (response.ok) return true; + } catch { + // Backend not ready yet + } + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + + return false; +}; From 4b4afc34cb4b21022dcaf3aacd0318b37c1ce121 Mon Sep 17 00:00:00 2001 From: PraneshASP Date: Wed, 16 Jul 2025 13:37:55 +0530 Subject: [PATCH 15/25] ci: enable e2e tests --- .github/workflows/ci.yml | 56 ++++++++++++++++++++++++++++++++++++++++ app/src/constants.ts | 2 +- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index baf29ba..db4d2ed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -196,6 +196,62 @@ jobs: npm run build npm run test + ai-e2e-tests: + if: github.ref == 'refs/heads/master' || github.event_name == 'pull_request' + needs: cancel-previous-runs + runs-on: ubuntu-latest + # Local docker image registry + services: + registry: + image: registry:2 + ports: + - 5000:5000 + env: + LOCAL_REGISTRY: localhost:5000 + LOCAL_TAG: sway-playground:local + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + with: + driver-opts: network=host + + - name: Log in to the local registry + uses: docker/login-action@v1 + with: + registry: ${{ env.LOCAL_REGISTRY }} + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build image and push to local registry + uses: docker/build-push-action@v2 + with: + context: . + file: deployment/Dockerfile + push: true + tags: ${{ env.LOCAL_REGISTRY }}/${{ env.LOCAL_TAG }} + + - name: Run the service in docker + run: | + docker run -d -p 8080:8080 \ + -e GEMINI_API_KEY=${{ secrets.GEMINI_API_KEY }} \ + -e RATE_LIMIT_REQUESTS_PER_DAY=10 \ + ${{ env.LOCAL_REGISTRY }}/${{ env.LOCAL_TAG }} + + - name: Wait for service to be ready + run: | + timeout 60 bash -c 'until curl -f http://localhost:8080/health; do sleep 2; done' + + - name: Run E2E Tests + env: + REACT_APP_LOCAL_SERVER: true + run: | + cd app + npm ci + npm run test:ai + deploy: if: github.ref == 'refs/heads/master' needs: diff --git a/app/src/constants.ts b/app/src/constants.ts index db7d65a..8fa0207 100644 --- a/app/src/constants.ts +++ b/app/src/constants.ts @@ -3,7 +3,7 @@ const SERVER_API = "https://api.sway-playground.fuel.network"; export const FUEL_GREEN = "#00f58c"; -export const LOCAL_SERVER_URI = "http://0.0.0.0:8080"; +export const LOCAL_SERVER_URI = "http://127.0.0.1:8080"; export const SERVER_URI = process.env.REACT_APP_LOCAL_SERVER ? LOCAL_SERVER_URI : SERVER_API; From 9e4d48cc71ab01a31a61120c64b9ae3b7bcdea9e Mon Sep 17 00:00:00 2001 From: PraneshASP Date: Wed, 16 Jul 2025 13:42:33 +0530 Subject: [PATCH 16/25] ci: update rust version --- deployment/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/Dockerfile b/deployment/Dockerfile index 1af56e3..4265648 100644 --- a/deployment/Dockerfile +++ b/deployment/Dockerfile @@ -1,5 +1,5 @@ # Stage 1: Build -FROM lukemathwalker/cargo-chef:latest-rust-1.81 as chef +FROM lukemathwalker/cargo-chef:latest-rust-1.82 as chef WORKDIR /build/ # hadolint ignore=DL3008 From 1e0eed71b299f2d14f58ce57a6e9774e94910a70 Mon Sep 17 00:00:00 2001 From: PraneshASP Date: Wed, 16 Jul 2025 13:59:53 +0530 Subject: [PATCH 17/25] fix: update markdown renderer component --- .../ai/components/MarkdownRenderer.tsx | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/app/src/features/ai/components/MarkdownRenderer.tsx b/app/src/features/ai/components/MarkdownRenderer.tsx index bab0dfd..9223592 100644 --- a/app/src/features/ai/components/MarkdownRenderer.tsx +++ b/app/src/features/ai/components/MarkdownRenderer.tsx @@ -3,12 +3,12 @@ import ReactMarkdown from "react-markdown"; import { Box, Typography, Paper } from "@mui/material"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { vs } from "react-syntax-highlighter/dist/esm/styles/prism"; +import type { Components } from "react-markdown"; -interface MarkdownComponentProps { - children?: React.ReactNode; +interface CodeProps { inline?: boolean; className?: string; - [key: string]: unknown; + children?: React.ReactNode; } interface MarkdownRendererProps { @@ -16,8 +16,8 @@ interface MarkdownRendererProps { borderColor?: string; } -const markdownComponents = { - code: ({ inline, className, children, ...props }: MarkdownComponentProps) => { +const markdownComponents: Components = { + code: ({ inline, className, children }: CodeProps) => { const match = /language-(\w+)/.exec(className || ""); return !inline && match ? ( {String(children).replace(/\n$/, "")} @@ -42,18 +41,17 @@ const markdownComponents = { fontWeight: 600, display: "inline", }} - {...props} > {children} ); }, - p: ({ children }: MarkdownComponentProps) => ( + p: ({ children }) => ( {children} ), - h1: ({ children }: MarkdownComponentProps) => ( + h1: ({ children }) => ( ), - h2: ({ children }: MarkdownComponentProps) => ( + h2: ({ children }) => ( ), - h3: ({ children }: MarkdownComponentProps) => ( + h3: ({ children }) => ( ), - ul: ({ children }: MarkdownComponentProps) => ( + ul: ({ children }) => ( {children} ), - ol: ({ children }: MarkdownComponentProps) => ( + ol: ({ children }) => ( {children} ), - li: ({ children }: MarkdownComponentProps) => ( + li: ({ children }) => ( {children} From deb4b749c7108e1e89a1fca6ad998247ca19f2a7 Mon Sep 17 00:00:00 2001 From: PraneshASP Date: Wed, 16 Jul 2025 14:00:12 +0530 Subject: [PATCH 18/25] chore: remove unused deps from swaypad --- projects/swaypad/Forc.lock | 22 +++------------------- projects/swaypad/Forc.toml | 2 -- 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/projects/swaypad/Forc.lock b/projects/swaypad/Forc.lock index 12230d4..d8bbe7e 100644 --- a/projects/swaypad/Forc.lock +++ b/projects/swaypad/Forc.lock @@ -1,25 +1,9 @@ -[[package]] -name = "standards" -source = "git+https://github.com/FuelLabs/sway-standards?tag=v0.7.0#7d35df95e0b96dc8ad188ab169fbbeeac896aae8" -dependencies = ["std"] - [[package]] name = "std" -source = "git+https://github.com/fuellabs/sway?tag=v0.67.0#d821dcb0c7edb1d6e2a772f5a1ccefe38902eaec" - -[[package]] -name = "sway_libs" -source = "git+https://github.com/FuelLabs/sway-libs?tag=v0.25.1#00569f811eae256a522c0e592522ea638815b362" -dependencies = [ - "standards", - "std", -] +version = "0.68.9" +source = "registry+std?0.68.9#QmUaBxMs2JvY1bXgRCdeCsG3o6TN82ftRgv4Tq7ytqUGUT!" [[package]] name = "swaypad" source = "member" -dependencies = [ - "standards", - "std", - "sway_libs", -] +dependencies = ["std"] diff --git a/projects/swaypad/Forc.toml b/projects/swaypad/Forc.toml index 91094c4..bf41688 100644 --- a/projects/swaypad/Forc.toml +++ b/projects/swaypad/Forc.toml @@ -5,5 +5,3 @@ license = "Apache-2.0" name = "swaypad" [dependencies] -standards = { git = "https://github.com/FuelLabs/sway-standards", tag = "v0.7.0" } -sway_libs = { git = "https://github.com/FuelLabs/sway-libs", tag = "v0.25.2" } From fd9f1ed37253daf40fef55672f347a36267ed727 Mon Sep 17 00:00:00 2001 From: PraneshASP Date: Wed, 16 Jul 2025 14:14:30 +0530 Subject: [PATCH 19/25] fix: update examples and config to use latest versions --- .../editor/examples/sway/multiasset.ts | 6 ++--- .../editor/examples/sway/singleasset.ts | 6 ++--- projects/swaypad/Forc.lock | 25 ++++++++++++++++++- projects/swaypad/Forc.toml | 3 +++ 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/app/src/features/editor/examples/sway/multiasset.ts b/app/src/features/editor/examples/sway/multiasset.ts index de4a864..ea9b7ad 100644 --- a/app/src/features/editor/examples/sway/multiasset.ts +++ b/app/src/features/editor/examples/sway/multiasset.ts @@ -1,9 +1,9 @@ export const EXAMPLE_SWAY_CONTRACT_MULTIASSET = `// ERC1155 equivalent in Sway. contract; -use standards::src5::{AccessError, SRC5, State}; -use standards::src20::{SetDecimalsEvent, SetNameEvent, SetSymbolEvent, SRC20, TotalSupplyEvent}; -use standards::src3::SRC3; +use src5::{AccessError, SRC5, State}; +use src20::{SetDecimalsEvent, SetNameEvent, SetSymbolEvent, SRC20, TotalSupplyEvent}; +use src3::SRC3; use std::{ asset::{ burn, diff --git a/app/src/features/editor/examples/sway/singleasset.ts b/app/src/features/editor/examples/sway/singleasset.ts index 35883fb..8cb62be 100644 --- a/app/src/features/editor/examples/sway/singleasset.ts +++ b/app/src/features/editor/examples/sway/singleasset.ts @@ -1,9 +1,9 @@ export const EXAMPLE_SWAY_CONTRACT_SINGLEASSET = `// ERC20 equivalent in Sway. contract; -use standards::src3::SRC3; -use standards::src5::{AccessError, SRC5, State}; -use standards::src20::{SetDecimalsEvent, SetNameEvent, SetSymbolEvent, SRC20, TotalSupplyEvent}; +use src3::SRC3; +use src5::{AccessError, SRC5, State}; +use src20::{SetDecimalsEvent, SetNameEvent, SetSymbolEvent, SRC20, TotalSupplyEvent}; use std::{ asset::{ burn, diff --git a/projects/swaypad/Forc.lock b/projects/swaypad/Forc.lock index d8bbe7e..b70dab1 100644 --- a/projects/swaypad/Forc.lock +++ b/projects/swaypad/Forc.lock @@ -1,3 +1,21 @@ +[[package]] +name = "src20" +version = "0.8.0" +source = "registry+src20?0.8.0#QmSwYjybtdvSF3Ey9RncVUUaFaWsizSBNgAtY9JoyaAHvh!" +dependencies = ["std"] + +[[package]] +name = "src3" +version = "0.8.0" +source = "registry+src3?0.8.0#QmWZt1NRQHid4p5cmFMG11n1tnRfNSgWGXUWDgojt9hjzs!" +dependencies = ["std"] + +[[package]] +name = "src5" +version = "0.8.0" +source = "registry+src5?0.8.0#QmNRPZrPHFBiEAyWPU8gesdPsD2zb3cMKwEgxJwV1ZEjyD!" +dependencies = ["std"] + [[package]] name = "std" version = "0.68.9" @@ -6,4 +24,9 @@ source = "registry+std?0.68.9#QmUaBxMs2JvY1bXgRCdeCsG3o6TN82ftRgv4Tq7ytqUGUT!" [[package]] name = "swaypad" source = "member" -dependencies = ["std"] +dependencies = [ + "src20", + "src3", + "src5", + "std", +] diff --git a/projects/swaypad/Forc.toml b/projects/swaypad/Forc.toml index bf41688..0056943 100644 --- a/projects/swaypad/Forc.toml +++ b/projects/swaypad/Forc.toml @@ -5,3 +5,6 @@ license = "Apache-2.0" name = "swaypad" [dependencies] +src20 = "0.8.0" +src3 = "0.8.0" +src5 = "0.8.0" From c8acf324c19a16f609b307ba0150e6d91472229b Mon Sep 17 00:00:00 2001 From: PraneshASP Date: Wed, 16 Jul 2025 14:34:06 +0530 Subject: [PATCH 20/25] ci: remove rendundant tests --- .github/workflows/ci.yml | 60 ++++++---------------------------------- 1 file changed, 9 insertions(+), 51 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index db4d2ed..4edab25 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -147,7 +147,6 @@ jobs: npm run format-check frontend-build-and-test: - if: github.ref != 'refs/heads/master' needs: cancel-previous-runs runs-on: ubuntu-latest # Local docker image registry @@ -185,7 +184,14 @@ jobs: - name: Run the service in docker run: | - docker run -d -p 8080:8080 ${{ env.LOCAL_REGISTRY }}/${{ env.LOCAL_TAG }} + docker run -d -p 8080:8080 \ + -e GEMINI_API_KEY=${{ secrets.GEMINI_API_KEY }} \ + -e RATE_LIMIT_REQUESTS_PER_DAY=10 \ + ${{ env.LOCAL_REGISTRY }}/${{ env.LOCAL_TAG }} + + - name: Wait for service to be ready + run: | + timeout 60 bash -c 'until curl -f http://localhost:8080/health; do sleep 2; done' - name: NPM build and test env: @@ -196,60 +202,12 @@ jobs: npm run build npm run test - ai-e2e-tests: - if: github.ref == 'refs/heads/master' || github.event_name == 'pull_request' - needs: cancel-previous-runs - runs-on: ubuntu-latest - # Local docker image registry - services: - registry: - image: registry:2 - ports: - - 5000:5000 - env: - LOCAL_REGISTRY: localhost:5000 - LOCAL_TAG: sway-playground:local - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 - with: - driver-opts: network=host - - - name: Log in to the local registry - uses: docker/login-action@v1 - with: - registry: ${{ env.LOCAL_REGISTRY }} - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build image and push to local registry - uses: docker/build-push-action@v2 - with: - context: . - file: deployment/Dockerfile - push: true - tags: ${{ env.LOCAL_REGISTRY }}/${{ env.LOCAL_TAG }} - - - name: Run the service in docker - run: | - docker run -d -p 8080:8080 \ - -e GEMINI_API_KEY=${{ secrets.GEMINI_API_KEY }} \ - -e RATE_LIMIT_REQUESTS_PER_DAY=10 \ - ${{ env.LOCAL_REGISTRY }}/${{ env.LOCAL_TAG }} - - - name: Wait for service to be ready - run: | - timeout 60 bash -c 'until curl -f http://localhost:8080/health; do sleep 2; done' - - name: Run E2E Tests + if: github.ref == 'refs/heads/master' || github.event_name == 'pull_request' env: REACT_APP_LOCAL_SERVER: true run: | cd app - npm ci npm run test:ai deploy: From 6e9d0f528093c56c7f21ce4dff12dfdad7a38a14 Mon Sep 17 00:00:00 2001 From: PraneshASP Date: Wed, 16 Jul 2025 15:52:35 +0530 Subject: [PATCH 21/25] chore: fix ci --- .github/workflows/ci.yml | 8 ++++---- app/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4edab25..4ffe155 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -189,10 +189,6 @@ jobs: -e RATE_LIMIT_REQUESTS_PER_DAY=10 \ ${{ env.LOCAL_REGISTRY }}/${{ env.LOCAL_TAG }} - - name: Wait for service to be ready - run: | - timeout 60 bash -c 'until curl -f http://localhost:8080/health; do sleep 2; done' - - name: NPM build and test env: CI: true @@ -201,6 +197,10 @@ jobs: npm ci npm run build npm run test + + - name: Wait for service to be ready + run: | + timeout 60 bash -c 'until curl -f http://localhost:8080/health; do sleep 2; done' - name: Run E2E Tests if: github.ref == 'refs/heads/master' || github.event_name == 'pull_request' diff --git a/app/package.json b/app/package.json index 4e0a930..3179263 100644 --- a/app/package.json +++ b/app/package.json @@ -57,7 +57,7 @@ "analyze": "source-map-explorer 'build/static/js/*.js'", "start": "DISABLE_ESLINT_PLUGIN=true react-scripts start", "build": "react-scripts build", - "test": "react-scripts test", + "test": "react-scripts test --testPathIgnorePatterns=__tests__/e2e/ai", "test:ai": "react-scripts test --testPathPattern=__tests__/e2e/ai --watchAll=false", "eject": "react-scripts eject", "lint": "eslint --fix 'src/**/*.{ts,tsx}'", From 291e364759667642b1eeb5ab77d7a5287c50a31e Mon Sep 17 00:00:00 2001 From: PraneshASP Date: Wed, 16 Jul 2025 16:53:41 +0530 Subject: [PATCH 22/25] chore: update prompt --- src/ai.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ai.rs b/src/ai.rs index 2a3c992..1dde9dc 100644 --- a/src/ai.rs +++ b/src/ai.rs @@ -533,7 +533,7 @@ SWAY SYNTAX ESSENTIALS: IMPORTS: - use std::{asset::{mint_to, transfer}, call_frames::msg_asset_id, context::msg_amount, auth::msg_sender, block::timestamp, asset::transfer}; -- use standards::{src3::SRC3, src5::SRC5, src20::SRC20}; +- use src3::SRC3, src5::SRC5, src20::SRC20; and so on., standards::srcX is deprecated. FALLBACK: If documentation search fails, direct users to docs.fuel.network/docs/sway/"#.to_string() } From a25aafd1bd6d06a4c5e7a86b1778f29f1e348b90 Mon Sep 17 00:00:00 2001 From: vignesh-fuel Date: Thu, 31 Jul 2025 15:38:17 +0530 Subject: [PATCH 23/25] feat: add config map (#127) --- helm/sway-playground/templates/deployment.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/helm/sway-playground/templates/deployment.yaml b/helm/sway-playground/templates/deployment.yaml index b88b993..4e1f446 100644 --- a/helm/sway-playground/templates/deployment.yaml +++ b/helm/sway-playground/templates/deployment.yaml @@ -36,6 +36,9 @@ spec: envFrom: - secretRef: name: sway-playground + envFrom: + - configMapRef: + name: app-sway-playground ports: - name: http containerPort: {{ .Values.service.port }} From 7233b765c539cc51ee38632ceca5bccb429e30f1 Mon Sep 17 00:00:00 2001 From: PraneshASP Date: Thu, 7 Aug 2025 14:21:39 +0530 Subject: [PATCH 24/25] ci: bump chart version to 0.1.5 --- helm/sway-playground/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm/sway-playground/Chart.yaml b/helm/sway-playground/Chart.yaml index 7bb42ed..f428064 100644 --- a/helm/sway-playground/Chart.yaml +++ b/helm/sway-playground/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.4 +version: 0.1.5 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to From 3c408cd2865725f94ed9ff7e0bad2ad7e0b18047 Mon Sep 17 00:00:00 2001 From: PraneshASP Date: Thu, 7 Aug 2025 15:30:41 +0530 Subject: [PATCH 25/25] ci: bump chart version to 0.1.6 --- helm/sway-playground/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm/sway-playground/Chart.yaml b/helm/sway-playground/Chart.yaml index f428064..038c447 100644 --- a/helm/sway-playground/Chart.yaml +++ b/helm/sway-playground/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.5 +version: 0.1.6 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to