├── .gitignore ├── README.md ├── architecture.png ├── biome.json ├── bun.lock ├── config └── config.yaml ├── db2llm.sqlite ├── package.json ├── plan.md ├── readme-cn.md ├── src ├── api │ ├── executor.ts │ └── generator.ts ├── db │ ├── connection.ts │ └── metadata.ts ├── index.ts ├── llm │ ├── conversation.ts │ ├── openai.ts │ └── types.ts ├── public │ └── index.html └── utils │ └── config.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .specstory/history 3 | .env 4 | .cursor -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DB2LLM Minimal Prototype 2 | 3 | ## Overview 4 | DB2LLM is a minimal prototype that combines SQLite database metadata with RESTful APIs and large language models (LLMs). It allows users to interact with databases using natural language, without writing SQL queries or understanding database structures. Currently, SQLite is used for demonstration purposes. For actual use, please use your own database and update the relevant configurations. 5 | 6 | ## Features 7 | - Provides a user chat window supporting LLM API address and authorization key configuration 8 | - Supports user-specified SQLite database files 9 | - Automatically analyzes database structure and extracts metadata: 10 | - Table and field information 11 | - Field enumeration value mapping (e.g., gender: male/female, male/female, etc.) 12 | - Primary keys, foreign keys, and index information 13 | - Dynamically generates RESTful APIs for database operations 14 | - Intelligent session management: 15 | - Supports context memory for understanding follow-up queries 16 | - Automatic session cleanup after timeout (30 minutes) 17 | - Result summary generation 18 | - Converts user natural language queries into API requests 19 | - Executes API requests and returns results 20 | - Supports multi-step complex queries 21 | 22 | ## Tech Stack 23 | - **Runtime**: Bun 24 | - **Web Framework**: Hono 25 | - **Database**: SQLite 26 | - **ORM**: TypeORM 27 | - **LLM Integration**: OpenAI API and compatible interfaces 28 | 29 | ## Quick Start 30 | 31 | ### Prerequisites 32 | - Install [Bun](https://bun.sh/) runtime 33 | - Prepare a SQLite database file 34 | - Obtain an OpenAI API key or other compatible LLM API key 35 | 36 | ### Installation 37 | 1. Clone the repository 38 | ```bash 39 | git clone https://github.com/loocor/db2llm.git 40 | cd db2llm 41 | ``` 42 | 43 | 2. Install dependencies 44 | ```bash 45 | bun install 46 | ``` 47 | 48 | 3. Configure LLM 49 | Modify the `config/config.yaml` file: 50 | ```yaml 51 | server: 52 | port: 3000 53 | host: "localhost" 54 | 55 | database: 56 | tempDir: "db2llm" 57 | defaultName: "db2llm.sqlite" 58 | connection: 59 | synchronize: false 60 | logging: ["error", "warn"] 61 | 62 | llm: 63 | provider: "deepseek" 64 | openai: 65 | model: "deepseek-chat" 66 | temperature: 0.3 67 | defaultApiUrl: "https://api.deepseek.com/v1" 68 | apiKey: "sk-4c907ed3eed5468db793b6f431e9a28c" 69 | 70 | ui: 71 | title: "DB2LLM - Database Chat Assistant" 72 | welcomeMessage: "Welcome to DB2LLM Database Chat Assistant! Please connect to the database and configure the LLM API first." 73 | readyMessage: "I'm ready, let's chat!" 74 | ``` 75 | 76 | Supported LLM providers: 77 | - DeepSeek API (default) 78 | - OpenAI API 79 | - Azure OpenAI 80 | - Claude API 81 | - Other OpenAI API compatible services 82 | 83 | ### Run 84 | ```bash 85 | bun run dev 86 | ``` 87 | 88 | The application will start at http://localhost:3000. 89 | 90 | ### Usage 91 | 1. Open your browser and visit http://localhost:3000 92 | 2. Upload a SQLite database file 93 | 3. Enter the LLM API key (and optional API address) 94 | 4. Click the "Connect" button 95 | 5. After successful connection, enter natural language queries in the dialog box 96 | 6. The system will automatically process the query and return results 97 | 98 | ## Example Queries 99 | - "Show all user information" 100 | - "Find all female users" 101 | - "Count the number of male users" 102 | - "Add a new user named Li Si, male, age 30" 103 | - "Update the phone number of user with ID 5 to 13812345678" 104 | - "Delete user with ID 10" 105 | - "Try again" (context-based follow-up query) 106 | 107 | ## Database Support 108 | ### Field Types 109 | - Basic types: INTEGER, TEXT, NUMBER, etc. 110 | - Supports custom enumeration value mapping, such as: 111 | - Gender: ['female', 'female', 'f', '2', '0'] -> Female 112 | - Status: ['active', '1', 'enabled'] -> Enabled 113 | 114 | ### Metadata 115 | - Table structure information 116 | - Field attributes (primary key, not null, etc.) 117 | - Field enumeration value mapping 118 | - Foreign key relationships 119 | - Index information 120 | 121 | ## Notes 122 | - This project is a proof-of-concept prototype and is not recommended for production use 123 | - User authentication and authorization mechanisms are not implemented 124 | - Performance with large databases is not optimized 125 | - API keys are entered directly in the frontend, posing security risks 126 | - Session data is stored in memory and will be lost after server restart 127 | 128 | ## License 129 | MIT 130 | 131 | ## Contribution 132 | Welcome to submit Issues and Pull Requests! -------------------------------------------------------------------------------- /architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loocor/db2llm/79eb8af1049609c8105e09e88f285f4d0e4fb769/architecture.png -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "vcs": { 4 | "clientKind": "git", 5 | "enabled": true, 6 | "useIgnoreFile": true 7 | }, 8 | "files": { 9 | "ignore": [ 10 | "node_modules/**", 11 | "components/ui/**", 12 | "dist/**", 13 | "build/**", 14 | ".next/**", 15 | "coverage/**", 16 | "*.log" 17 | ], 18 | "ignoreUnknown": false 19 | }, 20 | "formatter": { 21 | "attributePosition": "multiline", 22 | "bracketSpacing": true, 23 | "enabled": true, 24 | "formatWithErrors": true, 25 | "indentStyle": "space", 26 | "indentWidth": 2, 27 | "lineWidth": 100 28 | }, 29 | "javascript": { 30 | "formatter": { 31 | "attributePosition": "multiline", 32 | "arrowParentheses": "asNeeded", 33 | "bracketSameLine": false, 34 | "bracketSpacing": true, 35 | "enabled": true, 36 | "jsxQuoteStyle": "single", 37 | "quoteProperties": "asNeeded", 38 | "quoteStyle": "single", 39 | "semicolons": "asNeeded", 40 | "trailingCommas": "all" 41 | } 42 | }, 43 | "linter": { 44 | "enabled": true, 45 | "rules": { 46 | "recommended": true, 47 | "correctness": { 48 | "noUnusedImports": "error", 49 | "noUnusedVariables": "info", 50 | "noUndeclaredVariables": "error" 51 | }, 52 | "nursery": { 53 | "noCommonJs": "error" 54 | }, 55 | "style": { 56 | "noDefaultExport": "off", 57 | "noParameterAssign": "error", 58 | "noShoutyConstants": "error", 59 | "useNodejsImportProtocol": "off", 60 | "noUnusedTemplateLiteral": "error", 61 | "noVar": "error", 62 | "useBlockStatements": "info", 63 | "useCollapsedElseIf": "error", 64 | "useShorthandArrayType": "error", 65 | "useTemplate": "error" 66 | }, 67 | "suspicious": { 68 | "noExplicitAny": "info", 69 | "noArrayIndexKey": "off" 70 | }, 71 | "a11y": { 72 | "useValidAnchor": "off", 73 | "noRedundantAlt": "off", 74 | "noSvgWithoutTitle": "off" 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /bun.lock: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1, 3 | "workspaces": { 4 | "": { 5 | "name": "db2llm", 6 | "dependencies": { 7 | "hono": "^3.12.0", 8 | "openai": "^4.20.0", 9 | "reflect-metadata": "^0.1.13", 10 | "sqlite3": "^5.1.6", 11 | "typeorm": "^0.3.17", 12 | "yaml": "^2.3.4", 13 | }, 14 | "devDependencies": { 15 | "bun-types": "latest", 16 | "typescript": "^5.0.0", 17 | }, 18 | }, 19 | }, 20 | "packages": { 21 | "@gar/promisify": ["@gar/promisify@1.1.3", "https://registry.npmmirror.com/@gar/promisify/-/promisify-1.1.3.tgz", {}, "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw=="], 22 | 23 | "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], 24 | 25 | "@npmcli/fs": ["@npmcli/fs@1.1.1", "https://registry.npmmirror.com/@npmcli/fs/-/fs-1.1.1.tgz", { "dependencies": { "@gar/promisify": "^1.0.1", "semver": "^7.3.5" } }, "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ=="], 26 | 27 | "@npmcli/move-file": ["@npmcli/move-file@1.1.2", "https://registry.npmmirror.com/@npmcli/move-file/-/move-file-1.1.2.tgz", { "dependencies": { "mkdirp": "^1.0.4", "rimraf": "^3.0.2" } }, "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg=="], 28 | 29 | "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "https://registry.npmmirror.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], 30 | 31 | "@sqltools/formatter": ["@sqltools/formatter@1.2.5", "https://registry.npmmirror.com/@sqltools/formatter/-/formatter-1.2.5.tgz", {}, "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw=="], 32 | 33 | "@tootallnate/once": ["@tootallnate/once@1.1.2", "https://registry.npmmirror.com/@tootallnate/once/-/once-1.1.2.tgz", {}, "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw=="], 34 | 35 | "@types/node": ["@types/node@22.13.10", "https://registry.npmmirror.com/@types/node/-/node-22.13.10.tgz", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="], 36 | 37 | "@types/node-fetch": ["@types/node-fetch@2.6.12", "https://registry.npmmirror.com/@types/node-fetch/-/node-fetch-2.6.12.tgz", { "dependencies": { "@types/node": "*", "form-data": "^4.0.0" } }, "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA=="], 38 | 39 | "@types/ws": ["@types/ws@8.5.14", "https://registry.npmmirror.com/@types/ws/-/ws-8.5.14.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="], 40 | 41 | "abbrev": ["abbrev@1.1.1", "https://registry.npmmirror.com/abbrev/-/abbrev-1.1.1.tgz", {}, "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="], 42 | 43 | "abort-controller": ["abort-controller@3.0.0", "https://registry.npmmirror.com/abort-controller/-/abort-controller-3.0.0.tgz", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], 44 | 45 | "agent-base": ["agent-base@6.0.2", "https://registry.npmmirror.com/agent-base/-/agent-base-6.0.2.tgz", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], 46 | 47 | "agentkeepalive": ["agentkeepalive@4.6.0", "https://registry.npmmirror.com/agentkeepalive/-/agentkeepalive-4.6.0.tgz", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="], 48 | 49 | "aggregate-error": ["aggregate-error@3.1.0", "https://registry.npmmirror.com/aggregate-error/-/aggregate-error-3.1.0.tgz", { "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA=="], 50 | 51 | "ansi-regex": ["ansi-regex@5.0.1", "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], 52 | 53 | "ansi-styles": ["ansi-styles@4.3.0", "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], 54 | 55 | "ansis": ["ansis@3.17.0", "https://registry.npmmirror.com/ansis/-/ansis-3.17.0.tgz", {}, "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg=="], 56 | 57 | "app-root-path": ["app-root-path@3.1.0", "https://registry.npmmirror.com/app-root-path/-/app-root-path-3.1.0.tgz", {}, "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA=="], 58 | 59 | "aproba": ["aproba@2.0.0", "https://registry.npmmirror.com/aproba/-/aproba-2.0.0.tgz", {}, "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ=="], 60 | 61 | "are-we-there-yet": ["are-we-there-yet@3.0.1", "https://registry.npmmirror.com/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", { "dependencies": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" } }, "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg=="], 62 | 63 | "asynckit": ["asynckit@0.4.0", "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], 64 | 65 | "balanced-match": ["balanced-match@1.0.2", "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], 66 | 67 | "base64-js": ["base64-js@1.5.1", "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], 68 | 69 | "bindings": ["bindings@1.5.0", "https://registry.npmmirror.com/bindings/-/bindings-1.5.0.tgz", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="], 70 | 71 | "bl": ["bl@4.1.0", "https://registry.npmmirror.com/bl/-/bl-4.1.0.tgz", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], 72 | 73 | "brace-expansion": ["brace-expansion@2.0.1", "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], 74 | 75 | "buffer": ["buffer@6.0.3", "https://registry.npmmirror.com/buffer/-/buffer-6.0.3.tgz", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], 76 | 77 | "bun-types": ["bun-types@1.2.4", "https://registry.npmmirror.com/bun-types/-/bun-types-1.2.4.tgz", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-nDPymR207ZZEoWD4AavvEaa/KZe/qlrbMSchqpQwovPZCKc7pwMoENjEtHgMKaAjJhy+x6vfqSBA1QU3bJgs0Q=="], 78 | 79 | "cacache": ["cacache@15.3.0", "https://registry.npmmirror.com/cacache/-/cacache-15.3.0.tgz", { "dependencies": { "@npmcli/fs": "^1.0.0", "@npmcli/move-file": "^1.0.1", "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "glob": "^7.1.4", "infer-owner": "^1.0.4", "lru-cache": "^6.0.0", "minipass": "^3.1.1", "minipass-collect": "^1.0.2", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.2", "mkdirp": "^1.0.3", "p-map": "^4.0.0", "promise-inflight": "^1.0.1", "rimraf": "^3.0.2", "ssri": "^8.0.1", "tar": "^6.0.2", "unique-filename": "^1.1.1" } }, "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ=="], 80 | 81 | "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], 82 | 83 | "chownr": ["chownr@2.0.0", "https://registry.npmmirror.com/chownr/-/chownr-2.0.0.tgz", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="], 84 | 85 | "clean-stack": ["clean-stack@2.2.0", "https://registry.npmmirror.com/clean-stack/-/clean-stack-2.2.0.tgz", {}, "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="], 86 | 87 | "cliui": ["cliui@8.0.1", "https://registry.npmmirror.com/cliui/-/cliui-8.0.1.tgz", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], 88 | 89 | "color-convert": ["color-convert@2.0.1", "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], 90 | 91 | "color-name": ["color-name@1.1.4", "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], 92 | 93 | "color-support": ["color-support@1.1.3", "https://registry.npmmirror.com/color-support/-/color-support-1.1.3.tgz", { "bin": { "color-support": "bin.js" } }, "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg=="], 94 | 95 | "combined-stream": ["combined-stream@1.0.8", "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], 96 | 97 | "concat-map": ["concat-map@0.0.1", "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], 98 | 99 | "console-control-strings": ["console-control-strings@1.1.0", "https://registry.npmmirror.com/console-control-strings/-/console-control-strings-1.1.0.tgz", {}, "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="], 100 | 101 | "cross-spawn": ["cross-spawn@7.0.6", "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], 102 | 103 | "dayjs": ["dayjs@1.11.13", "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz", {}, "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="], 104 | 105 | "debug": ["debug@4.4.0", "https://registry.npmmirror.com/debug/-/debug-4.4.0.tgz", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], 106 | 107 | "decompress-response": ["decompress-response@6.0.0", "https://registry.npmmirror.com/decompress-response/-/decompress-response-6.0.0.tgz", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], 108 | 109 | "deep-extend": ["deep-extend@0.6.0", "https://registry.npmmirror.com/deep-extend/-/deep-extend-0.6.0.tgz", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], 110 | 111 | "delayed-stream": ["delayed-stream@1.0.0", "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], 112 | 113 | "delegates": ["delegates@1.0.0", "https://registry.npmmirror.com/delegates/-/delegates-1.0.0.tgz", {}, "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="], 114 | 115 | "detect-libc": ["detect-libc@2.0.3", "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.0.3.tgz", {}, "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="], 116 | 117 | "dotenv": ["dotenv@16.4.7", "https://registry.npmmirror.com/dotenv/-/dotenv-16.4.7.tgz", {}, "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ=="], 118 | 119 | "dunder-proto": ["dunder-proto@1.0.1", "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], 120 | 121 | "eastasianwidth": ["eastasianwidth@0.2.0", "https://registry.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], 122 | 123 | "emoji-regex": ["emoji-regex@8.0.0", "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], 124 | 125 | "encoding": ["encoding@0.1.13", "https://registry.npmmirror.com/encoding/-/encoding-0.1.13.tgz", { "dependencies": { "iconv-lite": "^0.6.2" } }, "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A=="], 126 | 127 | "end-of-stream": ["end-of-stream@1.4.4", "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.4.tgz", { "dependencies": { "once": "^1.4.0" } }, "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q=="], 128 | 129 | "env-paths": ["env-paths@2.2.1", "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], 130 | 131 | "err-code": ["err-code@2.0.3", "https://registry.npmmirror.com/err-code/-/err-code-2.0.3.tgz", {}, "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="], 132 | 133 | "es-define-property": ["es-define-property@1.0.1", "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], 134 | 135 | "es-errors": ["es-errors@1.3.0", "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], 136 | 137 | "es-object-atoms": ["es-object-atoms@1.1.1", "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], 138 | 139 | "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], 140 | 141 | "escalade": ["escalade@3.2.0", "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], 142 | 143 | "event-target-shim": ["event-target-shim@5.0.1", "https://registry.npmmirror.com/event-target-shim/-/event-target-shim-5.0.1.tgz", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], 144 | 145 | "expand-template": ["expand-template@2.0.3", "https://registry.npmmirror.com/expand-template/-/expand-template-2.0.3.tgz", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], 146 | 147 | "file-uri-to-path": ["file-uri-to-path@1.0.0", "https://registry.npmmirror.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="], 148 | 149 | "foreground-child": ["foreground-child@3.3.1", "https://registry.npmmirror.com/foreground-child/-/foreground-child-3.3.1.tgz", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], 150 | 151 | "form-data": ["form-data@4.0.2", "https://registry.npmmirror.com/form-data/-/form-data-4.0.2.tgz", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" } }, "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w=="], 152 | 153 | "form-data-encoder": ["form-data-encoder@1.7.2", "https://registry.npmmirror.com/form-data-encoder/-/form-data-encoder-1.7.2.tgz", {}, "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="], 154 | 155 | "formdata-node": ["formdata-node@4.4.1", "https://registry.npmmirror.com/formdata-node/-/formdata-node-4.4.1.tgz", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="], 156 | 157 | "fs-constants": ["fs-constants@1.0.0", "https://registry.npmmirror.com/fs-constants/-/fs-constants-1.0.0.tgz", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], 158 | 159 | "fs-minipass": ["fs-minipass@2.1.0", "https://registry.npmmirror.com/fs-minipass/-/fs-minipass-2.1.0.tgz", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg=="], 160 | 161 | "fs.realpath": ["fs.realpath@1.0.0", "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], 162 | 163 | "function-bind": ["function-bind@1.1.2", "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], 164 | 165 | "gauge": ["gauge@4.0.4", "https://registry.npmmirror.com/gauge/-/gauge-4.0.4.tgz", { "dependencies": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.3", "console-control-strings": "^1.1.0", "has-unicode": "^2.0.1", "signal-exit": "^3.0.7", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "wide-align": "^1.1.5" } }, "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg=="], 166 | 167 | "get-caller-file": ["get-caller-file@2.0.5", "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], 168 | 169 | "get-intrinsic": ["get-intrinsic@1.3.0", "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", { "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" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], 170 | 171 | "get-proto": ["get-proto@1.0.1", "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], 172 | 173 | "github-from-package": ["github-from-package@0.0.0", "https://registry.npmmirror.com/github-from-package/-/github-from-package-0.0.0.tgz", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], 174 | 175 | "glob": ["glob@10.4.5", "https://registry.npmmirror.com/glob/-/glob-10.4.5.tgz", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], 176 | 177 | "gopd": ["gopd@1.2.0", "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], 178 | 179 | "graceful-fs": ["graceful-fs@4.2.11", "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], 180 | 181 | "has-symbols": ["has-symbols@1.1.0", "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], 182 | 183 | "has-tostringtag": ["has-tostringtag@1.0.2", "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], 184 | 185 | "has-unicode": ["has-unicode@2.0.1", "https://registry.npmmirror.com/has-unicode/-/has-unicode-2.0.1.tgz", {}, "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="], 186 | 187 | "hasown": ["hasown@2.0.2", "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], 188 | 189 | "hono": ["hono@3.12.12", "https://registry.npmmirror.com/hono/-/hono-3.12.12.tgz", {}, "sha512-5IAMJOXfpA5nT+K0MNjClchzz0IhBHs2Szl7WFAhrFOsbtQsYmNynFyJRg/a3IPsmCfxcrf8txUGiNShXpK5Rg=="], 190 | 191 | "http-cache-semantics": ["http-cache-semantics@4.1.1", "https://registry.npmmirror.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", {}, "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ=="], 192 | 193 | "http-proxy-agent": ["http-proxy-agent@4.0.1", "https://registry.npmmirror.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", { "dependencies": { "@tootallnate/once": "1", "agent-base": "6", "debug": "4" } }, "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg=="], 194 | 195 | "https-proxy-agent": ["https-proxy-agent@5.0.1", "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], 196 | 197 | "humanize-ms": ["humanize-ms@1.2.1", "https://registry.npmmirror.com/humanize-ms/-/humanize-ms-1.2.1.tgz", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="], 198 | 199 | "iconv-lite": ["iconv-lite@0.6.3", "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], 200 | 201 | "ieee754": ["ieee754@1.2.1", "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], 202 | 203 | "imurmurhash": ["imurmurhash@0.1.4", "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], 204 | 205 | "indent-string": ["indent-string@4.0.0", "https://registry.npmmirror.com/indent-string/-/indent-string-4.0.0.tgz", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="], 206 | 207 | "infer-owner": ["infer-owner@1.0.4", "https://registry.npmmirror.com/infer-owner/-/infer-owner-1.0.4.tgz", {}, "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A=="], 208 | 209 | "inflight": ["inflight@1.0.6", "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], 210 | 211 | "inherits": ["inherits@2.0.4", "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], 212 | 213 | "ini": ["ini@1.3.8", "https://registry.npmmirror.com/ini/-/ini-1.3.8.tgz", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], 214 | 215 | "ip-address": ["ip-address@9.0.5", "https://registry.npmmirror.com/ip-address/-/ip-address-9.0.5.tgz", { "dependencies": { "jsbn": "1.1.0", "sprintf-js": "^1.1.3" } }, "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g=="], 216 | 217 | "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], 218 | 219 | "is-lambda": ["is-lambda@1.0.1", "https://registry.npmmirror.com/is-lambda/-/is-lambda-1.0.1.tgz", {}, "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ=="], 220 | 221 | "isexe": ["isexe@2.0.0", "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], 222 | 223 | "jackspeak": ["jackspeak@3.4.3", "https://registry.npmmirror.com/jackspeak/-/jackspeak-3.4.3.tgz", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], 224 | 225 | "jsbn": ["jsbn@1.1.0", "https://registry.npmmirror.com/jsbn/-/jsbn-1.1.0.tgz", {}, "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A=="], 226 | 227 | "lru-cache": ["lru-cache@6.0.0", "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], 228 | 229 | "make-fetch-happen": ["make-fetch-happen@9.1.0", "https://registry.npmmirror.com/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", { "dependencies": { "agentkeepalive": "^4.1.3", "cacache": "^15.2.0", "http-cache-semantics": "^4.1.0", "http-proxy-agent": "^4.0.1", "https-proxy-agent": "^5.0.0", "is-lambda": "^1.0.1", "lru-cache": "^6.0.0", "minipass": "^3.1.3", "minipass-collect": "^1.0.2", "minipass-fetch": "^1.3.2", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^0.6.2", "promise-retry": "^2.0.1", "socks-proxy-agent": "^6.0.0", "ssri": "^8.0.0" } }, "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg=="], 230 | 231 | "math-intrinsics": ["math-intrinsics@1.1.0", "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], 232 | 233 | "mime-db": ["mime-db@1.52.0", "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], 234 | 235 | "mime-types": ["mime-types@2.1.35", "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], 236 | 237 | "mimic-response": ["mimic-response@3.1.0", "https://registry.npmmirror.com/mimic-response/-/mimic-response-3.1.0.tgz", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], 238 | 239 | "minimatch": ["minimatch@9.0.5", "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], 240 | 241 | "minimist": ["minimist@1.2.8", "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], 242 | 243 | "minipass": ["minipass@5.0.0", "https://registry.npmmirror.com/minipass/-/minipass-5.0.0.tgz", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="], 244 | 245 | "minipass-collect": ["minipass-collect@1.0.2", "https://registry.npmmirror.com/minipass-collect/-/minipass-collect-1.0.2.tgz", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA=="], 246 | 247 | "minipass-fetch": ["minipass-fetch@1.4.1", "https://registry.npmmirror.com/minipass-fetch/-/minipass-fetch-1.4.1.tgz", { "dependencies": { "minipass": "^3.1.0", "minipass-sized": "^1.0.3", "minizlib": "^2.0.0" }, "optionalDependencies": { "encoding": "^0.1.12" } }, "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw=="], 248 | 249 | "minipass-flush": ["minipass-flush@1.0.5", "https://registry.npmmirror.com/minipass-flush/-/minipass-flush-1.0.5.tgz", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw=="], 250 | 251 | "minipass-pipeline": ["minipass-pipeline@1.2.4", "https://registry.npmmirror.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A=="], 252 | 253 | "minipass-sized": ["minipass-sized@1.0.3", "https://registry.npmmirror.com/minipass-sized/-/minipass-sized-1.0.3.tgz", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g=="], 254 | 255 | "minizlib": ["minizlib@2.1.2", "https://registry.npmmirror.com/minizlib/-/minizlib-2.1.2.tgz", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="], 256 | 257 | "mkdirp": ["mkdirp@1.0.4", "https://registry.npmmirror.com/mkdirp/-/mkdirp-1.0.4.tgz", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], 258 | 259 | "mkdirp-classic": ["mkdirp-classic@0.5.3", "https://registry.npmmirror.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], 260 | 261 | "ms": ["ms@2.1.3", "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 262 | 263 | "napi-build-utils": ["napi-build-utils@2.0.0", "https://registry.npmmirror.com/napi-build-utils/-/napi-build-utils-2.0.0.tgz", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], 264 | 265 | "negotiator": ["negotiator@0.6.4", "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.4.tgz", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="], 266 | 267 | "node-abi": ["node-abi@3.74.0", "https://registry.npmmirror.com/node-abi/-/node-abi-3.74.0.tgz", { "dependencies": { "semver": "^7.3.5" } }, "sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w=="], 268 | 269 | "node-addon-api": ["node-addon-api@7.1.1", "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-7.1.1.tgz", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="], 270 | 271 | "node-domexception": ["node-domexception@1.0.0", "https://registry.npmmirror.com/node-domexception/-/node-domexception-1.0.0.tgz", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], 272 | 273 | "node-fetch": ["node-fetch@2.7.0", "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.7.0.tgz", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], 274 | 275 | "node-gyp": ["node-gyp@8.4.1", "https://registry.npmmirror.com/node-gyp/-/node-gyp-8.4.1.tgz", { "dependencies": { "env-paths": "^2.2.0", "glob": "^7.1.4", "graceful-fs": "^4.2.6", "make-fetch-happen": "^9.1.0", "nopt": "^5.0.0", "npmlog": "^6.0.0", "rimraf": "^3.0.2", "semver": "^7.3.5", "tar": "^6.1.2", "which": "^2.0.2" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w=="], 276 | 277 | "nopt": ["nopt@5.0.0", "https://registry.npmmirror.com/nopt/-/nopt-5.0.0.tgz", { "dependencies": { "abbrev": "1" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ=="], 278 | 279 | "npmlog": ["npmlog@6.0.2", "https://registry.npmmirror.com/npmlog/-/npmlog-6.0.2.tgz", { "dependencies": { "are-we-there-yet": "^3.0.0", "console-control-strings": "^1.1.0", "gauge": "^4.0.3", "set-blocking": "^2.0.0" } }, "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg=="], 280 | 281 | "once": ["once@1.4.0", "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], 282 | 283 | "openai": ["openai@4.86.2", "https://registry.npmmirror.com/openai/-/openai-4.86.2.tgz", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" }, "peerDependencies": { "ws": "^8.18.0", "zod": "^3.23.8" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-nvYeFjmjdSu6/msld+22JoUlCICNk/lUFpSMmc6KNhpeNLpqL70TqbD/8Vura/tFmYqHKW0trcjgPwUpKSPwaA=="], 284 | 285 | "p-map": ["p-map@4.0.0", "https://registry.npmmirror.com/p-map/-/p-map-4.0.0.tgz", { "dependencies": { "aggregate-error": "^3.0.0" } }, "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ=="], 286 | 287 | "package-json-from-dist": ["package-json-from-dist@1.0.1", "https://registry.npmmirror.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], 288 | 289 | "path-is-absolute": ["path-is-absolute@1.0.1", "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], 290 | 291 | "path-key": ["path-key@3.1.1", "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], 292 | 293 | "path-scurry": ["path-scurry@1.11.1", "https://registry.npmmirror.com/path-scurry/-/path-scurry-1.11.1.tgz", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], 294 | 295 | "prebuild-install": ["prebuild-install@7.1.3", "https://registry.npmmirror.com/prebuild-install/-/prebuild-install-7.1.3.tgz", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], 296 | 297 | "promise-inflight": ["promise-inflight@1.0.1", "https://registry.npmmirror.com/promise-inflight/-/promise-inflight-1.0.1.tgz", {}, "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g=="], 298 | 299 | "promise-retry": ["promise-retry@2.0.1", "https://registry.npmmirror.com/promise-retry/-/promise-retry-2.0.1.tgz", { "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" } }, "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g=="], 300 | 301 | "pump": ["pump@3.0.2", "https://registry.npmmirror.com/pump/-/pump-3.0.2.tgz", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw=="], 302 | 303 | "rc": ["rc@1.2.8", "https://registry.npmmirror.com/rc/-/rc-1.2.8.tgz", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], 304 | 305 | "readable-stream": ["readable-stream@3.6.2", "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], 306 | 307 | "reflect-metadata": ["reflect-metadata@0.1.14", "https://registry.npmmirror.com/reflect-metadata/-/reflect-metadata-0.1.14.tgz", {}, "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A=="], 308 | 309 | "require-directory": ["require-directory@2.1.1", "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], 310 | 311 | "retry": ["retry@0.12.0", "https://registry.npmmirror.com/retry/-/retry-0.12.0.tgz", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], 312 | 313 | "rimraf": ["rimraf@3.0.2", "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], 314 | 315 | "safe-buffer": ["safe-buffer@5.2.1", "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], 316 | 317 | "safer-buffer": ["safer-buffer@2.1.2", "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], 318 | 319 | "semver": ["semver@7.7.1", "https://registry.npmmirror.com/semver/-/semver-7.7.1.tgz", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], 320 | 321 | "set-blocking": ["set-blocking@2.0.0", "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="], 322 | 323 | "sha.js": ["sha.js@2.4.11", "https://registry.npmmirror.com/sha.js/-/sha.js-2.4.11.tgz", { "dependencies": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" }, "bin": { "sha.js": "./bin.js" } }, "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ=="], 324 | 325 | "shebang-command": ["shebang-command@2.0.0", "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], 326 | 327 | "shebang-regex": ["shebang-regex@3.0.0", "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], 328 | 329 | "signal-exit": ["signal-exit@4.1.0", "https://registry.npmmirror.com/signal-exit/-/signal-exit-4.1.0.tgz", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], 330 | 331 | "simple-concat": ["simple-concat@1.0.1", "https://registry.npmmirror.com/simple-concat/-/simple-concat-1.0.1.tgz", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], 332 | 333 | "simple-get": ["simple-get@4.0.1", "https://registry.npmmirror.com/simple-get/-/simple-get-4.0.1.tgz", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], 334 | 335 | "smart-buffer": ["smart-buffer@4.2.0", "https://registry.npmmirror.com/smart-buffer/-/smart-buffer-4.2.0.tgz", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], 336 | 337 | "socks": ["socks@2.8.4", "https://registry.npmmirror.com/socks/-/socks-2.8.4.tgz", { "dependencies": { "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" } }, "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ=="], 338 | 339 | "socks-proxy-agent": ["socks-proxy-agent@6.2.1", "https://registry.npmmirror.com/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", { "dependencies": { "agent-base": "^6.0.2", "debug": "^4.3.3", "socks": "^2.6.2" } }, "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ=="], 340 | 341 | "sprintf-js": ["sprintf-js@1.1.3", "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.1.3.tgz", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="], 342 | 343 | "sql-highlight": ["sql-highlight@6.0.0", "https://registry.npmmirror.com/sql-highlight/-/sql-highlight-6.0.0.tgz", {}, "sha512-+fLpbAbWkQ+d0JEchJT/NrRRXbYRNbG15gFpANx73EwxQB1PRjj+k/OI0GTU0J63g8ikGkJECQp9z8XEJZvPRw=="], 344 | 345 | "sqlite3": ["sqlite3@5.1.7", "https://registry.npmmirror.com/sqlite3/-/sqlite3-5.1.7.tgz", { "dependencies": { "bindings": "^1.5.0", "node-addon-api": "^7.0.0", "prebuild-install": "^7.1.1", "tar": "^6.1.11" }, "optionalDependencies": { "node-gyp": "8.x" } }, "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog=="], 346 | 347 | "ssri": ["ssri@8.0.1", "https://registry.npmmirror.com/ssri/-/ssri-8.0.1.tgz", { "dependencies": { "minipass": "^3.1.1" } }, "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ=="], 348 | 349 | "string-width": ["string-width@4.2.3", "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], 350 | 351 | "string-width-cjs": ["string-width@4.2.3", "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], 352 | 353 | "string_decoder": ["string_decoder@1.3.0", "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], 354 | 355 | "strip-ansi": ["strip-ansi@6.0.1", "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], 356 | 357 | "strip-ansi-cjs": ["strip-ansi@6.0.1", "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], 358 | 359 | "strip-json-comments": ["strip-json-comments@2.0.1", "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], 360 | 361 | "tar": ["tar@6.2.1", "https://registry.npmmirror.com/tar/-/tar-6.2.1.tgz", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="], 362 | 363 | "tar-fs": ["tar-fs@2.1.2", "https://registry.npmmirror.com/tar-fs/-/tar-fs-2.1.2.tgz", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA=="], 364 | 365 | "tar-stream": ["tar-stream@2.2.0", "https://registry.npmmirror.com/tar-stream/-/tar-stream-2.2.0.tgz", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], 366 | 367 | "tr46": ["tr46@0.0.3", "https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], 368 | 369 | "tslib": ["tslib@2.8.1", "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], 370 | 371 | "tunnel-agent": ["tunnel-agent@0.6.0", "https://registry.npmmirror.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], 372 | 373 | "typeorm": ["typeorm@0.3.21", "https://registry.npmmirror.com/typeorm/-/typeorm-0.3.21.tgz", { "dependencies": { "@sqltools/formatter": "^1.2.5", "ansis": "^3.9.0", "app-root-path": "^3.1.0", "buffer": "^6.0.3", "dayjs": "^1.11.9", "debug": "^4.3.4", "dotenv": "^16.0.3", "glob": "^10.4.5", "sha.js": "^2.4.11", "sql-highlight": "^6.0.0", "tslib": "^2.5.0", "uuid": "^11.0.5", "yargs": "^17.6.2" }, "peerDependencies": { "@google-cloud/spanner": "^5.18.0", "@sap/hana-client": "^2.12.25", "better-sqlite3": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", "hdb-pool": "^0.1.6", "ioredis": "^5.0.4", "mongodb": "^5.8.0", "mssql": "^9.1.1 || ^10.0.1 || ^11.0.1", "mysql2": "^2.2.5 || ^3.0.1", "oracledb": "^6.3.0", "pg": "^8.5.1", "pg-native": "^3.0.0", "pg-query-stream": "^4.0.0", "redis": "^3.1.1 || ^4.0.0", "reflect-metadata": "^0.1.14 || ^0.2.0", "sql.js": "^1.4.0", "sqlite3": "^5.0.3", "ts-node": "^10.7.0", "typeorm-aurora-data-api-driver": "^2.0.0" }, "optionalPeers": ["@google-cloud/spanner", "@sap/hana-client", "better-sqlite3", "hdb-pool", "ioredis", "mongodb", "mssql", "mysql2", "oracledb", "pg", "pg-native", "pg-query-stream", "redis", "sql.js", "sqlite3", "ts-node", "typeorm-aurora-data-api-driver"], "bin": { "typeorm": "cli.js", "typeorm-ts-node-commonjs": "cli-ts-node-commonjs.js", "typeorm-ts-node-esm": "cli-ts-node-esm.js" } }, "sha512-lh4rUWl1liZGjyPTWpwcK8RNI5x4ekln+/JJOox1wCd7xbucYDOXWD+1cSzTN3L0wbTGxxOtloM5JlxbOxEufA=="], 374 | 375 | "typescript": ["typescript@5.8.2", "https://registry.npmmirror.com/typescript/-/typescript-5.8.2.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="], 376 | 377 | "undici-types": ["undici-types@6.20.0", "https://registry.npmmirror.com/undici-types/-/undici-types-6.20.0.tgz", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], 378 | 379 | "unique-filename": ["unique-filename@1.1.1", "https://registry.npmmirror.com/unique-filename/-/unique-filename-1.1.1.tgz", { "dependencies": { "unique-slug": "^2.0.0" } }, "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ=="], 380 | 381 | "unique-slug": ["unique-slug@2.0.2", "https://registry.npmmirror.com/unique-slug/-/unique-slug-2.0.2.tgz", { "dependencies": { "imurmurhash": "^0.1.4" } }, "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w=="], 382 | 383 | "util-deprecate": ["util-deprecate@1.0.2", "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], 384 | 385 | "uuid": ["uuid@11.1.0", "https://registry.npmmirror.com/uuid/-/uuid-11.1.0.tgz", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="], 386 | 387 | "web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "https://registry.npmmirror.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="], 388 | 389 | "webidl-conversions": ["webidl-conversions@3.0.1", "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], 390 | 391 | "whatwg-url": ["whatwg-url@5.0.0", "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-5.0.0.tgz", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], 392 | 393 | "which": ["which@2.0.2", "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], 394 | 395 | "wide-align": ["wide-align@1.1.5", "https://registry.npmmirror.com/wide-align/-/wide-align-1.1.5.tgz", { "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } }, "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg=="], 396 | 397 | "wrap-ansi": ["wrap-ansi@7.0.0", "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], 398 | 399 | "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], 400 | 401 | "wrappy": ["wrappy@1.0.2", "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], 402 | 403 | "y18n": ["y18n@5.0.8", "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], 404 | 405 | "yallist": ["yallist@4.0.0", "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], 406 | 407 | "yaml": ["yaml@2.7.0", "https://registry.npmmirror.com/yaml/-/yaml-2.7.0.tgz", { "bin": { "yaml": "bin.mjs" } }, "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA=="], 408 | 409 | "yargs": ["yargs@17.7.2", "https://registry.npmmirror.com/yargs/-/yargs-17.7.2.tgz", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], 410 | 411 | "yargs-parser": ["yargs-parser@21.1.1", "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-21.1.1.tgz", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], 412 | 413 | "@isaacs/cliui/string-width": ["string-width@5.1.2", "https://registry.npmmirror.com/string-width/-/string-width-5.1.2.tgz", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], 414 | 415 | "@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.0", "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.0.tgz", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], 416 | 417 | "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], 418 | 419 | "bl/buffer": ["buffer@5.7.1", "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], 420 | 421 | "cacache/glob": ["glob@7.2.3", "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], 422 | 423 | "cacache/minipass": ["minipass@3.3.6", "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], 424 | 425 | "fs-minipass/minipass": ["minipass@3.3.6", "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], 426 | 427 | "gauge/signal-exit": ["signal-exit@3.0.7", "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], 428 | 429 | "glob/minipass": ["minipass@7.1.2", "https://registry.npmmirror.com/minipass/-/minipass-7.1.2.tgz", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], 430 | 431 | "make-fetch-happen/minipass": ["minipass@3.3.6", "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], 432 | 433 | "minipass-collect/minipass": ["minipass@3.3.6", "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], 434 | 435 | "minipass-fetch/minipass": ["minipass@3.3.6", "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], 436 | 437 | "minipass-flush/minipass": ["minipass@3.3.6", "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], 438 | 439 | "minipass-pipeline/minipass": ["minipass@3.3.6", "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], 440 | 441 | "minipass-sized/minipass": ["minipass@3.3.6", "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], 442 | 443 | "minizlib/minipass": ["minipass@3.3.6", "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], 444 | 445 | "node-gyp/glob": ["glob@7.2.3", "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], 446 | 447 | "openai/@types/node": ["@types/node@18.19.80", "https://registry.npmmirror.com/@types/node/-/node-18.19.80.tgz", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-kEWeMwMeIvxYkeg1gTc01awpwLbfMRZXdIhwRcakd/KlK53jmRC26LqcbIt7fnAQTu5GzlnWmzA3H6+l1u6xxQ=="], 448 | 449 | "path-scurry/lru-cache": ["lru-cache@10.4.3", "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.4.3.tgz", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], 450 | 451 | "path-scurry/minipass": ["minipass@7.1.2", "https://registry.npmmirror.com/minipass/-/minipass-7.1.2.tgz", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], 452 | 453 | "rimraf/glob": ["glob@7.2.3", "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], 454 | 455 | "ssri/minipass": ["minipass@3.3.6", "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], 456 | 457 | "tar-fs/chownr": ["chownr@1.1.4", "https://registry.npmmirror.com/chownr/-/chownr-1.1.4.tgz", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], 458 | 459 | "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-9.2.2.tgz", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], 460 | 461 | "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.1.0.tgz", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], 462 | 463 | "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.1.tgz", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="], 464 | 465 | "cacache/glob/minimatch": ["minimatch@3.1.2", "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], 466 | 467 | "node-gyp/glob/minimatch": ["minimatch@3.1.2", "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], 468 | 469 | "openai/@types/node/undici-types": ["undici-types@5.26.5", "https://registry.npmmirror.com/undici-types/-/undici-types-5.26.5.tgz", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], 470 | 471 | "rimraf/glob/minimatch": ["minimatch@3.1.2", "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], 472 | 473 | "cacache/glob/minimatch/brace-expansion": ["brace-expansion@1.1.11", "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], 474 | 475 | "node-gyp/glob/minimatch/brace-expansion": ["brace-expansion@1.1.11", "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], 476 | 477 | "rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.11", "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], 478 | } 479 | } 480 | -------------------------------------------------------------------------------- /config/config.yaml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 3000 3 | host: "localhost" 4 | 5 | database: 6 | tempDir: "db2llm" 7 | defaultName: "db2llm.sqlite" 8 | connection: 9 | synchronize: false 10 | logging: ["error", "warn"] 11 | 12 | llm: 13 | provider: "deepseek" 14 | openai: 15 | model: "deepseek-chat" 16 | temperature: 0.3 17 | defaultApiUrl: "https://api.deepseek.com/v1" 18 | apiKey: "sk-4c907ed3eed5468db793b6f431e9a28c" 19 | 20 | ui: 21 | title: "DB2LLM - 数据库对话助手" 22 | welcomeMessage: "欢迎使用 DB2LLM 数据库对话助手!请先连接数据库和配置 LLM API。" 23 | readyMessage: "我已准备好,跟我来聊吧!" 24 | -------------------------------------------------------------------------------- /db2llm.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loocor/db2llm/79eb8af1049609c8105e09e88f285f4d0e4fb769/db2llm.sqlite -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "db2llm", 3 | "version": "0.1.0", 4 | "description": "A minimal prototype combining the database with a RESTful API and LLM", 5 | "main": "src/index.ts", 6 | "type": "module", 7 | "scripts": { 8 | "dev": "bun run --watch src/index.ts", 9 | "start": "bun run src/index.ts", 10 | "build": "bun build src/index.ts --outdir dist" 11 | }, 12 | "dependencies": { 13 | "hono": "^3.12.0", 14 | "typeorm": "^0.3.17", 15 | "sqlite3": "^5.1.6", 16 | "reflect-metadata": "^0.1.13", 17 | "openai": "^4.20.0", 18 | "yaml": "^2.3.4" 19 | }, 20 | "devDependencies": { 21 | "bun-types": "latest", 22 | "typescript": "^5.0.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /plan.md: -------------------------------------------------------------------------------- 1 | # DB2LLM 最小化原型 2 | 3 | ## 概述 4 | 该原型旨在验证将数据库(以 SQLite 为例)的 meta 信息封装为动态 RESTful API 接口后与 LLM 结合的可行性。 5 | 6 | ## 功能 7 | 1. 提供一个用户对话窗口,允许配置 LLM 的 API 地址和授权 key。 8 | 2. 支持用户指定一个规范命名的 SQLite 数据库。 9 | 3. 使用 Bun web 框架启动 Web 服务,自动生成 RESTful API。 10 | 4. 向用户界面输出“我已准备好,跟我来聊吧”的信息开始对话。 11 | 5. 通过对话窗口发出查询请求时,将 meta 信息与请求合并,生成 prompt。 12 | 6. LLM 解析 prompt 并生成 HTTP 请求,调用 API。 13 | 7. API 命中则返回数据库信息,未命中则返回错误信息。 14 | 15 | ## 设计要点 16 | - 注重验证核心概念,暂未考虑认证、安全、性能等生产问题; 17 | - 依赖轻量级框架和工具,如 Honojs 框架 和 TypeORM/Sequelize 等 ORM 工具。 18 | - 通过 meta 信息动态生成 API 接口并作为 prompt 的一部分,确保 LLM 能够正确理解和使用。 19 | 20 | ## 注意事项 21 | - 确保系统健壮性,提供明确的错误反馈。 22 | - 提供清晰的用户指引和界面交互。 -------------------------------------------------------------------------------- /readme-cn.md: -------------------------------------------------------------------------------- 1 | # DB2LLM 最小化原型 2 | 3 | ## 概述 4 | DB2LLM 是一个将 SQLite 数据库的元数据与 RESTful API 和大型语言模型(LLM)结合的最小化原型。它允许用户通过自然语言与数据库进行交互,无需编写 SQL 查询或了解数据库结构。当前为便于演示,使用 SQLite 数据库,实际使用时,请使用自己的数据库并更新相关的配置。 5 | 6 | ## 功能特点 7 | - 提供用户对话窗口,支持配置 LLM 的 API 地址和授权密钥 8 | - 支持用户指定 SQLite 数据库文件 9 | - 自动分析数据库结构,提取元数据信息 10 | - 表结构和字段信息 11 | - 字段枚举值映射(如性别:男/女、male/female 等) 12 | - 主键、外键和索引信息 13 | - 动态生成 RESTful API,用于数据库操作 14 | - 智能会话管理 15 | - 支持上下文记忆,理解后续查询 16 | - 会话超时自动清理(30分钟) 17 | - 结果摘要生成 18 | - 将用户自然语言查询转换为 API 请求 19 | - 执行 API 请求并返回结果 20 | - 支持多步骤复杂查询 21 | 22 | ## 技术栈 23 | - **运行时**: Bun 24 | - **Web 框架**: Hono 25 | - **数据库**: SQLite 26 | - **ORM**: TypeORM 27 | - **LLM 集成**: OpenAI API 及兼容接口 28 | 29 | ## 快速开始 30 | 31 | ### 前提条件 32 | - 安装 [Bun](https://bun.sh/) 运行时 33 | - 准备一个 SQLite 数据库文件 34 | - 获取 OpenAI API 密钥或其他兼容的 LLM API 密钥 35 | 36 | ### 安装 37 | 1. 克隆仓库 38 | ```bash 39 | git clone https://github.com/loocor/db2llm.git 40 | cd db2llm 41 | ``` 42 | 43 | 2. 安装依赖 44 | ```bash 45 | bun install 46 | ``` 47 | 48 | 3. 配置 LLM 49 | 修改 `config/config.yaml` 文件: 50 | ```yaml 51 | server: 52 | port: 3000 53 | host: "localhost" 54 | 55 | database: 56 | tempDir: "db2llm" 57 | defaultName: "db2llm.sqlite" 58 | connection: 59 | synchronize: false 60 | logging: ["error", "warn"] 61 | 62 | llm: 63 | provider: "deepseek" 64 | openai: 65 | model: "deepseek-chat" 66 | temperature: 0.3 67 | defaultApiUrl: "https://api.deepseek.com/v1" 68 | apiKey: "sk-4c907ed3eed5468db793b6f431e9a28c" 69 | 70 | ui: 71 | title: "DB2LLM - 数据库对话助手" 72 | welcomeMessage: "欢迎使用 DB2LLM 数据库对话助手!请先连接数据库和配置 LLM API。" 73 | readyMessage: "我已准备好,跟我来聊吧!" 74 | ``` 75 | 76 | 支持的 LLM 提供商: 77 | - DeepSeek API(默认) 78 | - OpenAI API 79 | - Azure OpenAI 80 | - Claude API 81 | - 其他兼容 OpenAI API 格式的服务 82 | 83 | ### 运行 84 | ```bash 85 | bun run dev 86 | ``` 87 | 88 | 应用将在 http://localhost:3000 启动。 89 | 90 | ### 使用方法 91 | 1. 打开浏览器访问 http://localhost:3000 92 | 2. 上传 SQLite 数据库文件 93 | 3. 输入 LLM API 密钥(和可选的 API 地址) 94 | 4. 点击"连接"按钮 95 | 5. 连接成功后,在对话框中输入自然语言查询 96 | 6. 系统将自动处理查询并返回结果 97 | 98 | ## 示例查询 99 | - "显示所有用户信息" 100 | - "查找所有女性用户" 101 | - "统计男性用户数量" 102 | - "添加一个新用户,姓名为李四,性别男,年龄30岁" 103 | - "更新ID为5的用户的电话号码为13812345678" 104 | - "删除ID为10的用户" 105 | - "再找找看"(基于上下文的后续查询) 106 | 107 | ## 数据库支持 108 | ### 字段类型 109 | - 基本类型:INTEGER, TEXT, NUMBER 等 110 | - 支持自定义枚举值映射,如: 111 | - 性别:['女', 'female', 'f', '2', '0'] -> 女性 112 | - 状态:['active', '1', '启用'] -> 启用 113 | 114 | ### 元数据 115 | - 表结构信息 116 | - 字段属性(主键、非空等) 117 | - 字段枚举值映射 118 | - 外键关系 119 | - 索引信息 120 | 121 | ## 注意事项 122 | - 本项目是一个概念验证原型,不建议在生产环境中使用 123 | - 未实现用户认证和授权机制 124 | - 未优化大型数据库的性能 125 | - API 密钥直接在前端输入,存在安全风险 126 | - 会话数据存储在内存中,服务重启后会丢失 127 | 128 | ## 许可证 129 | MIT 130 | 131 | ## 贡献 132 | 欢迎提交 Issue 和 Pull Request! -------------------------------------------------------------------------------- /src/api/executor.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from 'hono' 2 | import { LLMRequest } from '../llm/types' 3 | 4 | export interface ApiRequest { 5 | method: 'GET' | 'POST' | 'PUT' | 'DELETE' 6 | url: string 7 | body?: any 8 | } 9 | 10 | /** 11 | * 将 LLM 请求转换为 API 请求 12 | * @param request LLM 请求 13 | * @returns API 请求 14 | */ 15 | function convertToApiRequest(request: LLMRequest): ApiRequest { 16 | const method = request.method.toUpperCase() as ApiRequest['method'] 17 | if (!['GET', 'POST', 'PUT', 'DELETE'].includes(method)) { 18 | throw new Error(`不支持的请求方法: ${request.method}`) 19 | } 20 | 21 | return { 22 | method, 23 | url: request.url, 24 | body: request.body, 25 | } 26 | } 27 | 28 | /** 29 | * 执行 API 请求 30 | * @param app Hono 应用实例 31 | * @param request API 请求对象 32 | * @returns API 响应结果 33 | */ 34 | export async function executeApiRequest(app: Hono, request: ApiRequest): Promise { 35 | const { method, url, body } = request 36 | 37 | // 构建请求对象 38 | const requestInit: RequestInit = { 39 | method, 40 | headers: { 41 | 'Content-Type': 'application/json', 42 | }, 43 | } 44 | 45 | // 添加请求体(如果有) 46 | if (body && (method === 'POST' || method === 'PUT')) { 47 | requestInit.body = JSON.stringify(body) 48 | } 49 | 50 | // 创建请求 51 | const req = new Request(`http://localhost${url}`, requestInit) 52 | 53 | try { 54 | // 使用 Hono 的请求处理机制执行请求 55 | const res = await app.fetch(req) 56 | 57 | if (!res.ok) { 58 | console.error(`API 请求失败: ${method} ${url}`, await res.text()) 59 | return { 60 | success: false, 61 | error: `API 请求失败: ${res.status} ${res.statusText}`, 62 | } 63 | } 64 | 65 | // 解析响应 66 | const data = await res.json() 67 | return data 68 | } catch (error) { 69 | console.error(`执行 API 请求时出错: ${method} ${url}`, error) 70 | return { 71 | success: false, 72 | error: `执行 API 请求时出错: ${error instanceof Error ? error.message : String(error)}`, 73 | } 74 | } 75 | } 76 | 77 | /** 78 | * 执行多个 API 请求 79 | * @param app Hono 应用实例 80 | * @param requests API 请求对象数组 81 | * @returns 所有 API 响应结果 82 | */ 83 | export async function executeApiRequests(app: Hono, requests: LLMRequest[]): Promise { 84 | const results = [] 85 | 86 | for (const request of requests) { 87 | const apiRequest = convertToApiRequest(request) 88 | const response = await app.request(apiRequest.url, { 89 | method: apiRequest.method, 90 | body: apiRequest.body ? JSON.stringify(apiRequest.body) : undefined, 91 | }) 92 | 93 | const result = await response.json() 94 | results.push(result) 95 | } 96 | 97 | return results 98 | } 99 | -------------------------------------------------------------------------------- /src/api/generator.ts: -------------------------------------------------------------------------------- 1 | import type { Hono } from 'hono' 2 | import { getDataSource } from '../db/connection' 3 | import type { TableMetadata } from '../db/metadata' 4 | import { getAllTablesMetadata } from '../db/metadata' 5 | 6 | // 存储当前的表元数据 7 | let currentTables: TableMetadata[] | null = null 8 | 9 | /** 10 | * 为数据库中的每个表生成 RESTful API 11 | * @param app Hono 应用实例 12 | */ 13 | export async function generateRestApi(app: Hono): Promise { 14 | // 获取表元数据 15 | currentTables = await getAllTablesMetadata() 16 | console.log(`已为 ${currentTables.length} 个表生成 RESTful API`) 17 | } 18 | 19 | /** 20 | * 创建动态路由中间件 21 | */ 22 | export function createDynamicRouteMiddleware() { 23 | return async (c: any, next: () => Promise) => { 24 | const path = c.req.path 25 | const method = c.req.method 26 | 27 | console.log('\n=== 动态路由请求开始 ===') 28 | console.log('请求路径:', path) 29 | console.log('请求方法:', method) 30 | 31 | // 如果不是 API 请求,继续下一个中间件 32 | if ( 33 | !path.startsWith('/api/') || 34 | path === '/api/config' || 35 | path === '/api/connect' || 36 | path === '/api/query' 37 | ) { 38 | console.log('非动态 API 请求,跳过') 39 | console.log('=== 动态路由请求结束 ===\n') 40 | return next() 41 | } 42 | 43 | // 检查是否已初始化 44 | if (!currentTables) { 45 | console.log('错误: 数据库 API 未初始化') 46 | console.log('=== 动态路由请求结束 ===\n') 47 | return c.json({ success: false, error: '数据库 API 未初始化' }, 503) 48 | } 49 | 50 | const dataSource = getDataSource() 51 | const segments = path.split('/').filter(Boolean) 52 | console.log('路径段:', segments) 53 | 54 | // 处理 /api/metadata 请求 55 | if (segments.length === 2 && segments[1] === 'metadata') { 56 | console.log('请求类型: 获取元数据') 57 | console.log('=== 动态路由请求结束 ===\n') 58 | return c.json({ success: true, data: currentTables }) 59 | } 60 | 61 | // 确保路径格式正确 62 | if (segments.length < 2 || segments.length > 3) { 63 | console.log('错误: 无效的路径格式') 64 | console.log('=== 动态路由请求结束 ===\n') 65 | return next() 66 | } 67 | 68 | const tableName = segments[1] 69 | const id = segments[2] 70 | console.log('目标表:', tableName) 71 | console.log('记录 ID:', id || '无') 72 | 73 | // 检查表是否存在 74 | const table = currentTables.find(t => t.name.toLowerCase() === tableName.toLowerCase()) 75 | if (!table) { 76 | console.log('错误: 表不存在') 77 | console.log('=== 动态路由请求结束 ===\n') 78 | return c.json({ success: false, error: `表 ${tableName} 不存在` }, 404) 79 | } 80 | 81 | // 获取主键 82 | const primaryKey = table.columns.find(col => col.isPrimary)?.name || 'id' 83 | console.log('使用主键:', primaryKey) 84 | 85 | try { 86 | let result: any[] 87 | 88 | switch (method) { 89 | case 'GET': { 90 | if (id) { 91 | console.log('操作: 获取单条记录') 92 | // 获取单个记录 93 | const query = `SELECT * FROM "${tableName}" WHERE ${primaryKey} = ?` 94 | console.log('SQL:', query) 95 | console.log('参数:', [id]) 96 | 97 | result = await dataSource.query(query, [id]) 98 | console.log('查询结果:', result) 99 | 100 | if (result.length === 0) { 101 | console.log('错误: 记录不存在') 102 | console.log('=== 动态路由请求结束 ===\n') 103 | return c.json({ success: false, error: '记录不存在' }, 404) 104 | } 105 | 106 | console.log('=== 动态路由请求结束 ===\n') 107 | return c.json({ success: true, data: result[0] }) 108 | } 109 | 110 | console.log('操作: 获取记录列表') 111 | 112 | // 获取查询参数 113 | const queryParams = c.req.query() 114 | 115 | // 构建 SQL 查询 116 | let sql = `SELECT * FROM "${tableName}"` 117 | const params: any[] = [] 118 | 119 | // 如果有 ID,使用 ID 查询 120 | if (id) { 121 | sql += ` WHERE "${primaryKey}" = ?` 122 | params.push(id) 123 | } 124 | // 否则,处理查询参数 125 | else if (Object.keys(queryParams).length > 0) { 126 | const conditions = [] 127 | for (const [key, value] of Object.entries(queryParams)) { 128 | conditions.push(`"${key}" = ?`) 129 | params.push(value) 130 | } 131 | if (conditions.length > 0) { 132 | sql += ` WHERE ${conditions.join(' AND ')}` 133 | } 134 | } 135 | 136 | console.log('SQL:', sql) 137 | console.log('参数:', params) 138 | 139 | // 执行查询 140 | result = await dataSource.query(sql, params) 141 | console.log('查询结果数量:', result.length) 142 | 143 | console.log('=== 动态路由请求结束 ===\n') 144 | return c.json({ 145 | success: true, 146 | data: result, 147 | }) 148 | } 149 | 150 | case 'POST': { 151 | if (id) { 152 | console.log('错误: 创建记录时不需要指定 ID') 153 | console.log('=== 动态路由请求结束 ===\n') 154 | return c.json({ success: false, error: '创建记录时不需要指定 ID' }, 400) 155 | } 156 | 157 | // 创建记录 158 | const body = await c.req.json() 159 | console.log('操作: 创建记录') 160 | console.log('请求体:', body) 161 | 162 | const columns = Object.keys(body).filter(key => 163 | table.columns.some(col => col.name === key), 164 | ) 165 | 166 | if (columns.length === 0) { 167 | console.log('错误: 没有提供有效的字段') 168 | console.log('=== 动态路由请求结束 ===\n') 169 | return c.json({ success: false, error: '没有提供有效的字段' }, 400) 170 | } 171 | 172 | const placeholders = columns.map(() => '?').join(', ') 173 | const values = columns.map(col => body[col]) 174 | const insertQuery = ` 175 | INSERT INTO "${tableName}" (${columns.join(', ')}) 176 | VALUES (${placeholders}) 177 | ` 178 | console.log('SQL:', insertQuery) 179 | console.log('参数:', values) 180 | 181 | result = await dataSource.query(insertQuery, values) 182 | console.log('插入结果:', result) 183 | console.log('=== 动态路由请求结束 ===\n') 184 | 185 | return c.json( 186 | { 187 | success: true, 188 | message: '记录创建成功', 189 | id: result[0].lastID, 190 | }, 191 | 201, 192 | ) 193 | } 194 | 195 | case 'PUT': { 196 | if (!id) { 197 | console.log('错误: 更新记录时需要指定 ID') 198 | console.log('=== 动态路由请求结束 ===\n') 199 | return c.json({ success: false, error: '更新记录时需要指定 ID' }, 400) 200 | } 201 | 202 | // 更新记录 203 | const updateBody = await c.req.json() 204 | console.log('操作: 更新记录') 205 | console.log('请求体:', updateBody) 206 | 207 | const updateColumns = Object.keys(updateBody).filter(key => 208 | table.columns.some(col => col.name === key), 209 | ) 210 | 211 | if (updateColumns.length === 0) { 212 | console.log('错误: 没有提供有效的字段') 213 | console.log('=== 动态路由请求结束 ===\n') 214 | return c.json({ success: false, error: '没有提供有效的字段' }, 400) 215 | } 216 | 217 | const setClause = updateColumns.map(col => `${col} = ?`).join(', ') 218 | const updateValues = [...updateColumns.map(col => updateBody[col]), id] 219 | const updateQuery = ` 220 | UPDATE "${tableName}" 221 | SET ${setClause} 222 | WHERE ${primaryKey} = ? 223 | ` 224 | console.log('SQL:', updateQuery) 225 | console.log('参数:', updateValues) 226 | 227 | result = await dataSource.query(updateQuery, updateValues) 228 | console.log('更新结果:', result) 229 | 230 | if (result[0].changes === 0) { 231 | console.log('错误: 记录不存在或未更改') 232 | console.log('=== 动态路由请求结束 ===\n') 233 | return c.json({ success: false, error: '记录不存在或未更改' }, 404) 234 | } 235 | 236 | console.log('=== 动态路由请求结束 ===\n') 237 | return c.json({ 238 | success: true, 239 | message: '记录更新成功', 240 | }) 241 | } 242 | 243 | case 'DELETE': { 244 | if (!id) { 245 | console.log('错误: 删除记录时需要指定 ID') 246 | console.log('=== 动态路由请求结束 ===\n') 247 | return c.json({ success: false, error: '删除记录时需要指定 ID' }, 400) 248 | } 249 | 250 | console.log('操作: 删除记录') 251 | // 删除记录 252 | const deleteQuery = `DELETE FROM "${tableName}" WHERE ${primaryKey} = ?` 253 | console.log('SQL:', deleteQuery) 254 | console.log('参数:', [id]) 255 | 256 | result = await dataSource.query(deleteQuery, [id]) 257 | console.log('删除结果:', result) 258 | 259 | if (result[0].changes === 0) { 260 | console.log('错误: 记录不存在') 261 | console.log('=== 动态路由请求结束 ===\n') 262 | return c.json({ success: false, error: '记录不存在' }, 404) 263 | } 264 | 265 | console.log('=== 动态路由请求结束 ===\n') 266 | return c.json({ 267 | success: true, 268 | message: '记录删除成功', 269 | }) 270 | } 271 | 272 | default: 273 | console.log('错误: 不支持的请求方法') 274 | console.log('=== 动态路由请求结束 ===\n') 275 | return next() 276 | } 277 | } catch (error) { 278 | console.error('处理请求时发生错误:', error) 279 | console.log('=== 动态路由请求异常结束 ===\n') 280 | return c.json( 281 | { 282 | success: false, 283 | error: error instanceof Error ? error.message : '未知错误', 284 | }, 285 | 500, 286 | ) 287 | } 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /src/db/connection.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import { DataSource } from 'typeorm' 3 | import { getConfig } from '../utils/config' 4 | 5 | let dataSource: DataSource | null = null 6 | 7 | /** 8 | * 连接到指定的 SQLite 数据库 9 | * @param dbPath SQLite 数据库文件路径 10 | * @returns 数据库连接实例 11 | */ 12 | export async function connectToDatabase(dbPath: string): Promise { 13 | // 检查文件是否存在 14 | if (!fs.existsSync(dbPath)) { 15 | throw new Error(`数据库文件不存在: ${dbPath}`) 16 | } 17 | 18 | // 关闭现有连接 19 | if (dataSource?.isInitialized) { 20 | await dataSource.destroy() 21 | } 22 | 23 | // 获取配置 24 | const config = getConfig() 25 | const dbConfig = config.database.connection 26 | 27 | // 创建新连接 28 | dataSource = new DataSource({ 29 | type: 'sqlite', 30 | database: dbPath, 31 | synchronize: dbConfig.synchronize, 32 | logging: dbConfig.logging, 33 | entities: [], 34 | }) 35 | 36 | // 初始化连接 37 | await dataSource.initialize() 38 | console.log(`已连接到数据库: ${dbPath}`) 39 | 40 | return dataSource 41 | } 42 | 43 | /** 44 | * 获取当前数据库连接 45 | * @returns 当前数据库连接实例 46 | */ 47 | export function getDataSource(): DataSource { 48 | if (!dataSource || !dataSource.isInitialized) { 49 | throw new Error('数据库未连接,请先调用 connectToDatabase') 50 | } 51 | return dataSource 52 | } 53 | 54 | /** 55 | * 关闭数据库连接 56 | */ 57 | export async function closeConnection(): Promise { 58 | if (dataSource?.isInitialized) { 59 | await dataSource.destroy() 60 | dataSource = null 61 | console.log('数据库连接已关闭') 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/db/metadata.ts: -------------------------------------------------------------------------------- 1 | import { getDataSource } from './connection' 2 | 3 | export interface TableMetadata { 4 | name: string 5 | columns: ColumnMetadata[] 6 | foreignKeys: ForeignKeyMetadata[] 7 | indices: IndexMetadata[] 8 | comment?: string 9 | } 10 | 11 | export interface ColumnMetadata { 12 | name: string 13 | type: string 14 | isPrimary: boolean 15 | isNullable: boolean 16 | default?: string 17 | comment?: string 18 | enumValues?: { [key: string]: string[] } 19 | } 20 | 21 | export interface ForeignKeyMetadata { 22 | columnName: string 23 | referencedTableName: string 24 | referencedColumnName: string 25 | } 26 | 27 | export interface IndexMetadata { 28 | name: string 29 | columnNames: string[] 30 | isUnique: boolean 31 | } 32 | 33 | /** 34 | * 获取数据库中所有表的元数据 35 | * @returns 所有表的元数据 36 | */ 37 | export async function getAllTablesMetadata(): Promise { 38 | const dataSource = getDataSource() 39 | const queryRunner = dataSource.createQueryRunner() 40 | 41 | try { 42 | // 获取所有表名 43 | const tables = await queryRunner.query(` 44 | SELECT name FROM sqlite_master 45 | WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE 'typeorm_%' 46 | `) 47 | 48 | const tablesMetadata: TableMetadata[] = [] 49 | 50 | // 获取每个表的详细信息 51 | for (const table of tables) { 52 | const tableName = table.name 53 | 54 | // 使用引号包裹表名以处理 SQL 关键字 55 | const quotedTableName = `"${tableName}"` 56 | 57 | // 获取表的列信息 58 | const columns = await queryRunner.query(`PRAGMA table_info(${quotedTableName})`) 59 | 60 | // 获取外键信息 61 | const foreignKeys = await queryRunner.query(`PRAGMA foreign_key_list(${quotedTableName})`) 62 | 63 | // 获取索引信息 64 | const indices = await queryRunner.query(`PRAGMA index_list(${quotedTableName})`) 65 | 66 | // 获取每个索引的详细信息 67 | const indexDetails = await Promise.all( 68 | indices.map(async (idx: any) => ({ 69 | ...idx, 70 | columns: await queryRunner.query(`PRAGMA index_info("${idx.name}")`), 71 | })), 72 | ) 73 | 74 | const columnsMetadata: ColumnMetadata[] = columns.map((column: any) => ({ 75 | name: column.name, 76 | type: column.type, 77 | isPrimary: column.pk === 1, 78 | isNullable: column.notnull === 0, 79 | default: column.dflt_value, 80 | })) 81 | 82 | const fks: ForeignKeyMetadata[] = foreignKeys.map((fk: any) => ({ 83 | columnName: fk.from, 84 | referencedTableName: fk.table, 85 | referencedColumnName: fk.to, 86 | })) 87 | 88 | const indexMetadata: IndexMetadata[] = indexDetails.map((idx: any) => ({ 89 | name: idx.name, 90 | columnNames: idx.columns.map((col: any) => col.name), 91 | isUnique: idx.unique === 1, 92 | })) 93 | 94 | const tableInfo: TableMetadata = { 95 | name: tableName, 96 | columns: columnsMetadata, 97 | foreignKeys: fks, 98 | indices: indexMetadata, 99 | } 100 | 101 | // 为特定字段添加枚举值信息 102 | if (tableName === 'user' && columnsMetadata.some(col => col.name === 'sex')) { 103 | for (const col of tableInfo.columns) { 104 | if (col.name === 'sex') { 105 | col.enumValues = { 106 | female: ['女', 'female', 'f', '2', '0'], 107 | male: ['男', 'male', 'm', '1', '1'], 108 | } 109 | } 110 | } 111 | } 112 | 113 | tablesMetadata.push(tableInfo) 114 | } 115 | 116 | return tablesMetadata 117 | } finally { 118 | await queryRunner.release() 119 | } 120 | } 121 | 122 | /** 123 | * 获取数据库元数据的文本描述 124 | * @returns 数据库结构的文本描述 125 | */ 126 | export async function getDatabaseDescription(): Promise { 127 | const tables = await getAllTablesMetadata() 128 | 129 | let description = `数据库包含 ${tables.length} 个表:\n\n` 130 | 131 | for (const table of tables) { 132 | description += `表名: ${table.name}\n` 133 | if (table.comment) { 134 | description += `说明: ${table.comment}\n` 135 | } 136 | 137 | description += '列:\n' 138 | for (const column of table.columns) { 139 | let columnDesc = ` - ${column.name} (${column.type})` 140 | if (column.isPrimary) { columnDesc += ' [主键]' } 141 | if (!column.isNullable) { columnDesc += ' [非空]' } 142 | if (column.comment) { columnDesc += ` [${column.comment}]` } 143 | 144 | // 添加枚举值信息 145 | if (column.enumValues) { 146 | const enumDesc = Object.entries(column.enumValues) 147 | .map(([key, values]) => `${key}: ${values.join('/')}`) 148 | .join(', ') 149 | columnDesc += ` [可选值: ${enumDesc}]` 150 | } 151 | 152 | description += `${columnDesc}\n` 153 | } 154 | description += '\n' 155 | } 156 | 157 | return description 158 | } 159 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import os from 'os' 3 | import path from 'path' 4 | import { Hono } from 'hono' 5 | import { serveStatic } from 'hono/bun' 6 | import { cors } from 'hono/cors' 7 | import { executeApiRequests } from './api/executor' 8 | import { generateRestApi, createDynamicRouteMiddleware } from './api/generator' 9 | import { closeConnection, connectToDatabase } from './db/connection' 10 | import { processUserQuery } from './llm/openai' 11 | import { initializeOpenAI } from './llm/openai' 12 | import { getConfig, loadConfig } from './utils/config' 13 | import { updateConversationContext } from './llm/conversation' 14 | 15 | // 加载配置文件 16 | loadConfig() 17 | const config = getConfig() 18 | 19 | // 创建 Hono 应用 20 | const app = new Hono() 21 | 22 | // 启用 CORS 23 | app.use(cors()) 24 | 25 | // 提供静态文件 26 | app.use('/*', serveStatic({ root: './src/public' })) 27 | 28 | // 临时目录,用于存储上传的数据库文件 29 | const tempDir = path.join(os.tmpdir(), config.database.tempDir) 30 | if (!fs.existsSync(tempDir)) { 31 | fs.mkdirSync(tempDir, { recursive: true }) 32 | } 33 | 34 | // 提供配置信息 35 | app.get('/api/config', c => { 36 | return c.json({ 37 | ui: config.ui, 38 | llm: { 39 | provider: config.llm.provider, 40 | openai: { 41 | defaultApiUrl: config.llm.openai.defaultApiUrl, 42 | apiKey: config.llm.openai.apiKey, 43 | model: config.llm.openai.model, 44 | }, 45 | }, 46 | database: { 47 | defaultName: config.database.defaultName, 48 | }, 49 | }) 50 | }) 51 | 52 | // 连接数据库和初始化 API 53 | app.post('/api/connect', async c => { 54 | try { 55 | const formData = await c.req.formData() 56 | 57 | // 获取数据库文件 58 | const dbFile = formData.get('dbFile') as File 59 | if (!dbFile) { 60 | return c.json({ success: false, error: '未提供数据库文件' }, 400) 61 | } 62 | 63 | // 获取 API 密钥(优先使用配置文件中的密钥) 64 | const apiKey = config.llm.openai.apiKey || (formData.get('apiKey') as string) 65 | if (!apiKey) { 66 | return c.json({ success: false, error: '未提供 API 密钥' }, 400) 67 | } 68 | 69 | // 可选的 API URL 70 | const apiUrl = formData.get('apiUrl') as string 71 | 72 | // 保存数据库文件到临时目录,使用默认名称或原始文件名 73 | const fileName = 74 | dbFile.name === config.database.defaultName 75 | ? `${Date.now()}_${config.database.defaultName}` 76 | : config.database.defaultName 77 | 78 | const dbFilePath = path.join(tempDir, fileName) 79 | const buffer = await dbFile.arrayBuffer() 80 | fs.writeFileSync(dbFilePath, Buffer.from(buffer)) 81 | 82 | // 连接到数据库 83 | const dataSource = await connectToDatabase(dbFilePath) 84 | 85 | // 初始化 OpenAI 客户端 86 | initializeOpenAI(apiKey, apiUrl) 87 | 88 | // 生成 RESTful API 89 | await generateRestApi(app) 90 | 91 | return c.json({ success: true }) 92 | } catch (error) { 93 | console.error('连接数据库失败:', error) 94 | return c.json( 95 | { 96 | success: false, 97 | error: error instanceof Error ? error.message : '未知错误', 98 | }, 99 | 500, 100 | ) 101 | } 102 | }) 103 | 104 | // 处理用户查询 105 | app.post('/api/query', async c => { 106 | try { 107 | const { query } = await c.req.json() 108 | 109 | if (!query) { 110 | return c.json({ success: false, error: '未提供查询内容' }, 400) 111 | } 112 | 113 | // 获取或生成会话 ID 114 | let sessionId = c.req.header('X-Session-ID') 115 | if (!sessionId) { 116 | sessionId = Math.random().toString(36).substring(2, 15) 117 | c.header('X-Session-ID', sessionId) 118 | } 119 | 120 | // 处理用户查询,获取 LLM 响应 121 | const llmResponse = await processUserQuery(query, sessionId) 122 | 123 | // 如果是表信息查询(没有具体的 API 请求) 124 | if (llmResponse.tables) { 125 | return c.json({ 126 | success: true, 127 | response: llmResponse, 128 | }) 129 | } 130 | 131 | // 检查是否包含子任务 132 | if (llmResponse.subtasks && Array.isArray(llmResponse.subtasks)) { 133 | const subtaskResults = [] 134 | 135 | // 依次执行每个子任务 136 | for (const subtask of llmResponse.subtasks) { 137 | if (subtask.requests && Array.isArray(subtask.requests)) { 138 | // 执行子任务的 API 请求 139 | const results = await executeApiRequests(app, subtask.requests) 140 | 141 | // 如果需要处理结果,再次调用 LLM 生成摘要 142 | if (subtask.process_results) { 143 | const processResponse = await processUserQuery( 144 | `请根据以下查询结果生成摘要:\n${JSON.stringify(results, null, 2)}\n\n请遵循以下规则: 145 | 1. 对于统计类查询,直接给出具体数字 146 | 2. 对于列表类查询,总结关键信息而不是显示全部原始数据 147 | 3. 对于详细信息查询,以易读的格式展示重要字段 148 | 4. 使用自然语言描述结果 149 | 5. 如果查询结果为空,明确说明"未找到相关数据"`, 150 | sessionId, 151 | ) 152 | 153 | subtaskResults.push({ 154 | thought: subtask.thoughts, 155 | results, 156 | summary: processResponse.result_summary || processResponse.thoughts, 157 | }) 158 | } else { 159 | subtaskResults.push({ 160 | thought: subtask.thoughts, 161 | results, 162 | }) 163 | } 164 | } 165 | } 166 | 167 | // 更新会话上下文中的结果 168 | updateConversationContext(sessionId, { 169 | lastResults: subtaskResults, 170 | }) 171 | 172 | // 如果有最终总结 173 | if (llmResponse.summary) { 174 | return c.json({ 175 | success: true, 176 | response: { 177 | thoughts: llmResponse.summary, 178 | subtasks: subtaskResults, 179 | }, 180 | }) 181 | } 182 | 183 | return c.json({ 184 | success: true, 185 | response: { 186 | thoughts: llmResponse.thoughts, 187 | subtasks: subtaskResults, 188 | }, 189 | }) 190 | } 191 | 192 | // 检查是否包含单个 API 请求 193 | if ( 194 | !llmResponse.requests || 195 | !Array.isArray(llmResponse.requests) || 196 | llmResponse.requests.length === 0 197 | ) { 198 | return c.json({ 199 | success: false, 200 | error: '无法理解查询或生成 API 请求', 201 | response: llmResponse, 202 | }) 203 | } 204 | 205 | // 执行单个任务的 API 请求 206 | const results = await executeApiRequests(app, llmResponse.requests) 207 | 208 | // 更新会话上下文中的结果 209 | updateConversationContext(sessionId, { 210 | lastResults: results, 211 | }) 212 | 213 | // 如果需要处理结果,再次调用 LLM 生成摘要 214 | if (llmResponse.process_results) { 215 | const processResponse = await processUserQuery( 216 | `请根据以下查询结果生成摘要:\n${JSON.stringify(results, null, 2)}\n\n请遵循以下规则: 217 | 1. 对于统计类查询,直接给出具体数字 218 | 2. 对于列表类查询,总结关键信息而不是显示全部原始数据 219 | 3. 对于详细信息查询,以易读的格式展示重要字段 220 | 4. 使用自然语言描述结果 221 | 5. 如果查询结果为空,明确说明"未找到相关数据" 222 | 6. 必须返回以下格式的 JSON: 223 | { 224 | "thoughts": "结果分析", 225 | "result_summary": "根据规则生成的摘要" 226 | }`, 227 | sessionId, 228 | ) 229 | 230 | return c.json({ 231 | success: true, 232 | response: { 233 | thoughts: llmResponse.thoughts, 234 | result_summary: processResponse.result_summary || processResponse.thoughts, 235 | }, 236 | }) 237 | } 238 | 239 | return c.json({ 240 | success: true, 241 | response: llmResponse, 242 | results, 243 | }) 244 | } catch (error) { 245 | console.error('处理查询失败:', error) 246 | return c.json( 247 | { 248 | success: false, 249 | error: error instanceof Error ? error.message : '未知错误', 250 | }, 251 | 500, 252 | ) 253 | } 254 | }) 255 | 256 | // 添加动态路由中间件 257 | app.use('*', createDynamicRouteMiddleware()) 258 | 259 | // 启动服务器 260 | const port = config.server.port || process.env.PORT || 3000 261 | const host = config.server.host || 'localhost' 262 | console.log(`启动服务器,监听 ${host}:${port}...`) 263 | 264 | export default { 265 | port, 266 | fetch: app.fetch, 267 | async close() { 268 | // 关闭数据库连接 269 | await closeConnection() 270 | 271 | // 清理临时文件 272 | try { 273 | if (fs.existsSync(tempDir)) { 274 | const files = fs.readdirSync(tempDir) 275 | for (const file of files) { 276 | fs.unlinkSync(path.join(tempDir, file)) 277 | } 278 | } 279 | } catch (error) { 280 | console.error('清理临时文件失败:', error) 281 | } 282 | }, 283 | } 284 | -------------------------------------------------------------------------------- /src/llm/conversation.ts: -------------------------------------------------------------------------------- 1 | import { LLMRequest } from './types' 2 | 3 | interface ConversationContext { 4 | lastQuery: string // 上一次的用户查询 5 | lastIntent?: string // 上一次的意图 6 | lastRequest?: LLMRequest // 上一次的 API 请求 7 | lastResults?: any[] // 上一次的查询结果 8 | timestamp: number // 最后更新时间 9 | } 10 | 11 | // 使用 Map 存储会话上下文,key 为会话 ID 12 | const conversations = new Map() 13 | 14 | // 会话过期时间(30分钟) 15 | const CONVERSATION_TIMEOUT = 30 * 60 * 1000 16 | 17 | /** 18 | * 获取或创建会话上下文 19 | * @param sessionId 会话 ID 20 | * @returns 会话上下文 21 | */ 22 | export function getConversationContext(sessionId: string): ConversationContext | null { 23 | const context = conversations.get(sessionId) 24 | if (!context) return null 25 | 26 | // 检查是否过期 27 | if (Date.now() - context.timestamp > CONVERSATION_TIMEOUT) { 28 | conversations.delete(sessionId) 29 | return null 30 | } 31 | 32 | return context 33 | } 34 | 35 | /** 36 | * 更新会话上下文 37 | * @param sessionId 会话 ID 38 | * @param context 部分上下文数据 39 | */ 40 | export function updateConversationContext( 41 | sessionId: string, 42 | context: Partial, 43 | ): void { 44 | const currentContext = conversations.get(sessionId) || { 45 | lastQuery: '', 46 | timestamp: Date.now(), 47 | } 48 | 49 | conversations.set(sessionId, { 50 | ...currentContext, 51 | ...context, 52 | timestamp: Date.now(), 53 | }) 54 | } 55 | 56 | /** 57 | * 清理过期的会话 58 | */ 59 | export function cleanupExpiredConversations(): void { 60 | const now = Date.now() 61 | for (const [sessionId, context] of conversations.entries()) { 62 | if (now - context.timestamp > CONVERSATION_TIMEOUT) { 63 | conversations.delete(sessionId) 64 | } 65 | } 66 | } 67 | 68 | // 定期清理过期会话(每5分钟) 69 | setInterval(cleanupExpiredConversations, 5 * 60 * 1000) 70 | -------------------------------------------------------------------------------- /src/llm/openai.ts: -------------------------------------------------------------------------------- 1 | import OpenAI from 'openai' 2 | import { getDatabaseDescription } from '../db/metadata' 3 | import { getConfig } from '../utils/config' 4 | import { getConversationContext, updateConversationContext } from './conversation' 5 | import { LLMResponse } from './types' 6 | 7 | let openaiClient: OpenAI | null = null 8 | 9 | /** 10 | * 初始化 OpenAI 客户端 11 | * @param apiKey OpenAI API 密钥 12 | * @param apiUrl OpenAI API 地址(可选,用于自定义 API 端点) 13 | */ 14 | export function initializeOpenAI(apiKey: string, apiUrl?: string): void { 15 | const config = getConfig() 16 | const openaiConfig = config.llm.openai 17 | 18 | const options: any = { apiKey } 19 | 20 | // 使用提供的 API URL 或配置中的默认值 21 | if (apiUrl) { 22 | options.baseURL = apiUrl 23 | } else if (openaiConfig.defaultApiUrl) { 24 | options.baseURL = openaiConfig.defaultApiUrl 25 | } 26 | 27 | openaiClient = new OpenAI(options) 28 | console.log('OpenAI 客户端已初始化') 29 | } 30 | 31 | /** 32 | * 获取 OpenAI 客户端实例 33 | * @returns OpenAI 客户端实例 34 | */ 35 | export function getOpenAIClient(): OpenAI { 36 | if (!openaiClient) { 37 | throw new Error('OpenAI 客户端未初始化,请先调用 initializeOpenAI') 38 | } 39 | return openaiClient 40 | } 41 | 42 | /** 43 | * 生成系统提示信息 44 | * @returns 包含数据库描述的系统提示 45 | */ 46 | export async function generateSystemPrompt(): Promise { 47 | const dbDescription = await getDatabaseDescription() 48 | console.log('数据库描述:', dbDescription) 49 | 50 | return `你是一个数据库助手,可以帮助用户查询和操作数据库。 51 | 以下是数据库的结构描述: 52 | 53 | ${dbDescription} 54 | 55 | 特别说明: 56 | 1. 对于包含枚举值的字段,请注意其可能的取值: 57 | - 性别(sex)字段:使用"女"表示女性,"男"表示男性 58 | - 其他枚举字段会在字段描述中说明可能的取值 59 | 60 | 当用户询问可查询的信息或打招呼时(比如:"你好,我可以查什么?","有什么数据?"等),请直接返回数据库中的表名,优先使用表的中文注释说明,如果没有注释则翻译表名为中文。 61 | 例如: 62 | { 63 | "thoughts": "让我告诉你数据库中有哪些信息可以查询", 64 | "tables": [ 65 | "用户信息表 (users) - 存储用户基本信息", 66 | "订单记录 (orders) - 用户的订单数据", 67 | "商品目录 (products) - 可购买的商品信息" 68 | ] 69 | } 70 | 71 | 如果是需要多个步骤才能完成的查询,请将查询拆分为多个子任务,并在每个子任务中处理和总结结果。使用以下格式: 72 | { 73 | "thoughts": "这个查询需要分两步完成", 74 | "subtasks": [ 75 | { 76 | "thoughts": "第一步:获取所有客户记录以统计数量", 77 | "requests": [ 78 | { 79 | "method": "GET", 80 | "url": "/api/customer" 81 | } 82 | ], 83 | "process_results": true, 84 | "result_summary": "根据查询结果生成的摘要说明" 85 | }, 86 | { 87 | "thoughts": "第二步:获取特定客户的详细信息", 88 | "requests": [ 89 | { 90 | "method": "GET", 91 | "url": "/api/customer/1" 92 | } 93 | ], 94 | "process_results": true, 95 | "result_summary": "根据查询结果生成的摘要说明" 96 | } 97 | ], 98 | "summary": "所有步骤完成后的最终总结" 99 | } 100 | 101 | 如果是简单的查询请求,也需要处理和总结结果。使用以下格式: 102 | { 103 | "thoughts": "你的思考过程", 104 | "requests": [ 105 | { 106 | "method": "GET|POST|PUT|DELETE", 107 | "url": "/api/...", 108 | "body": {} // 可选,用于 POST 和 PUT 请求 109 | } 110 | ], 111 | "process_results": true, 112 | "result_summary": "根据查询结果生成的摘要说明" 113 | } 114 | 115 | 在处理结果时,请遵循以下规则: 116 | 1. 对于统计类查询(如查询数量),直接给出具体数字 117 | 2. 对于列表类查询,总结关键信息而不是显示全部原始数据 118 | 3. 对于详细信息查询,以易读的格式展示重要字段 119 | 4. 始终使用中文回复,使用自然语言描述结果 120 | 5. 如果查询结果为空,明确说明"未找到相关数据" 121 | 6. 必须严格按照以下 JSON 格式返回响应: 122 | { 123 | "thoughts": "你的思考过程", 124 | "requests": [ 125 | { 126 | "method": "GET", 127 | "url": "/api/user?sex=female" 128 | } 129 | ], 130 | "process_results": true, 131 | "result_summary": "根据查询结果生成的摘要说明" 132 | } 133 | 134 | API 的基本格式如下: 135 | 1. 获取所有记录:GET /api/{表名} 136 | 2. 获取单个记录:GET /api/{表名}/{id} 137 | 3. 创建记录:POST /api/{表名} (带有 JSON 请求体) 138 | 4. 更新记录:PUT /api/{表名}/{id} (带有 JSON 请求体) 139 | 5. 删除记录:DELETE /api/{表名}/{id} 140 | 6. 获取数据库元数据:GET /api/metadata` 141 | } 142 | 143 | /** 144 | * 处理用户查询 145 | * @param userQuery 用户查询文本 146 | * @param sessionId 会话 ID 147 | * @returns LLM 的响应 148 | */ 149 | export async function processUserQuery(userQuery: string, sessionId: string): Promise { 150 | const client = getOpenAIClient() 151 | const systemPrompt = await generateSystemPrompt() 152 | const config = getConfig() 153 | const openaiConfig = config.llm.openai 154 | 155 | // 获取会话上下文 156 | const context = getConversationContext(sessionId) 157 | 158 | console.log('=== LLM 调用开始 ===') 159 | console.log('用户查询:', userQuery) 160 | console.log('会话上下文:', context) 161 | console.log('系统提示:', systemPrompt) 162 | console.log('LLM 配置:', { 163 | model: openaiConfig.model, 164 | temperature: openaiConfig.temperature, 165 | provider: config.llm.provider, 166 | apiUrl: openaiConfig.defaultApiUrl, 167 | }) 168 | 169 | try { 170 | console.log('正在调用 LLM API...') 171 | 172 | // 构建消息列表 173 | const messages = [{ role: 'system', content: systemPrompt }] as any[] 174 | 175 | // 如果有上下文,添加到消息中 176 | if (context) { 177 | messages.push( 178 | { role: 'user', content: context.lastQuery }, 179 | { 180 | role: 'assistant', 181 | content: `我执行了查询,结果如下:\n${JSON.stringify(context.lastResults, null, 2)}`, 182 | }, 183 | ) 184 | } 185 | 186 | // 添加当前查询 187 | messages.push({ role: 'user', content: userQuery }) 188 | 189 | const response = await client.chat.completions.create({ 190 | model: openaiConfig.model, 191 | messages, 192 | temperature: openaiConfig.temperature, 193 | }) 194 | 195 | const content = response.choices[0].message.content 196 | console.log('LLM 原始响应:', content) 197 | 198 | // 尝试解析 JSON 响应 199 | try { 200 | if (!content) { 201 | throw new Error('LLM 响应内容为空') 202 | } 203 | 204 | // 提取第一个完整的 JSON 对象 205 | let jsonStr = content 206 | const startIdx = content.indexOf('{') 207 | if (startIdx !== -1) { 208 | let bracketCount = 0 209 | let endIdx = -1 210 | 211 | for (let i = startIdx; i < content.length; i++) { 212 | if (content[i] === '{') bracketCount++ 213 | if (content[i] === '}') bracketCount-- 214 | 215 | if (bracketCount === 0) { 216 | endIdx = i + 1 217 | break 218 | } 219 | } 220 | 221 | if (endIdx !== -1) { 222 | jsonStr = content.substring(startIdx, endIdx) 223 | } 224 | } 225 | 226 | const parsedResponse = JSON.parse(jsonStr) as LLMResponse 227 | console.log('解析后的响应:', parsedResponse) 228 | console.log('=== LLM 调用结束 ===') 229 | 230 | // 更新会话上下文 231 | updateConversationContext(sessionId, { 232 | lastQuery: userQuery, 233 | lastIntent: parsedResponse.thoughts, 234 | lastRequest: parsedResponse.requests?.[0], 235 | }) 236 | 237 | return parsedResponse 238 | } catch (error) { 239 | console.error('JSON 解析错误:', error) 240 | console.log('无法解析的内容:', content) 241 | console.log('=== LLM 调用异常结束 ===') 242 | return { 243 | error: '无法解析响应', 244 | rawContent: content, 245 | } as any 246 | } 247 | } catch (error) { 248 | console.error('LLM API 调用错误:', error) 249 | console.log('=== LLM 调用异常结束 ===') 250 | throw new Error('调用 LLM API 失败') 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/llm/types.ts: -------------------------------------------------------------------------------- 1 | export interface LLMRequest { 2 | method: string; 3 | url: string; 4 | body?: any; 5 | } 6 | 7 | export interface LLMResponse { 8 | thoughts?: string; 9 | requests?: LLMRequest[]; 10 | process_results?: boolean; 11 | result_summary?: string; 12 | tables?: string[]; 13 | subtasks?: { 14 | thoughts?: string; 15 | requests?: LLMRequest[]; 16 | process_results?: boolean; 17 | result_summary?: string; 18 | }[]; 19 | summary?: string; 20 | } -------------------------------------------------------------------------------- /src/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | DB2LLM - 数据库对话助手 8 | 229 | 230 | 231 | 232 |
233 |
234 |

DB2LLM - 数据库对话助手

235 | 236 |
237 | 238 | 239 | 267 | 268 |
269 |
270 |
271 | 欢迎使用 DB2LLM 数据库对话助手!请先连接数据库和配置 LLM API。 272 |
273 |
274 | 275 |
276 | 277 | 278 |
279 |
280 |
281 | 282 | 582 | 583 | 584 | -------------------------------------------------------------------------------- /src/utils/config.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import path from 'node:path' 3 | import yaml from 'yaml' 4 | 5 | // 配置接口定义 6 | export interface Config { 7 | server: { 8 | port: number 9 | host: string 10 | } 11 | database: { 12 | tempDir: string 13 | defaultName: string 14 | connection: { 15 | synchronize: boolean 16 | logging: string[] 17 | } 18 | } 19 | llm: { 20 | provider: string 21 | openai: { 22 | model: string 23 | temperature: number 24 | defaultApiUrl: string 25 | apiKey?: string 26 | } 27 | } 28 | ui: { 29 | title: string 30 | welcomeMessage: string 31 | readyMessage: string 32 | } 33 | } 34 | 35 | // 默认配置 36 | const defaultConfig: Config = { 37 | server: { 38 | port: 3000, 39 | host: 'localhost', 40 | }, 41 | database: { 42 | tempDir: 'db2llm', 43 | defaultName: 'db2llm.sqlite', 44 | connection: { 45 | synchronize: false, 46 | logging: ['error', 'warn'], 47 | }, 48 | }, 49 | llm: { 50 | provider: 'openai', 51 | openai: { 52 | model: 'gpt-3.5-turbo', 53 | temperature: 0.3, 54 | defaultApiUrl: 'https://api.openai.com/v1', 55 | }, 56 | }, 57 | ui: { 58 | title: 'DB2LLM - 数据库对话助手', 59 | welcomeMessage: '欢迎使用 DB2LLM 数据库对话助手!请先连接数据库和配置 LLM API。', 60 | readyMessage: '我已准备好,跟我来聊吧!', 61 | }, 62 | } 63 | 64 | // 配置单例 65 | let config: Config = { ...defaultConfig } 66 | 67 | /** 68 | * 加载配置文件 69 | * @param configPath 配置文件路径,默认为 'config/config.yaml' 70 | * @returns 加载后的配置对象 71 | */ 72 | export function loadConfig(configPath: string = 'config/config.yaml'): Config { 73 | try { 74 | // 检查配置文件是否存在 75 | const absolutePath = path.resolve(process.cwd(), configPath) 76 | if (!fs.existsSync(absolutePath)) { 77 | console.warn(`配置文件不存在: ${absolutePath},使用默认配置`) 78 | return config 79 | } 80 | 81 | // 读取配置文件 82 | const fileContent = fs.readFileSync(absolutePath, 'utf-8') 83 | const loadedConfig = yaml.parse(fileContent) 84 | 85 | // 合并配置 86 | config = deepMerge(defaultConfig, loadedConfig) 87 | console.log('配置文件加载成功') 88 | 89 | return config 90 | } catch (error) { 91 | console.error('加载配置文件失败:', error) 92 | return config 93 | } 94 | } 95 | 96 | /** 97 | * 获取当前配置 98 | * @returns 当前配置对象 99 | */ 100 | export function getConfig(): Config { 101 | return config 102 | } 103 | 104 | /** 105 | * 深度合并对象 106 | * @param target 目标对象 107 | * @param source 源对象 108 | * @returns 合并后的对象 109 | */ 110 | function deepMerge(target: T, source: any): T { 111 | const output = { ...target } 112 | 113 | if (isObject(target) && isObject(source)) { 114 | for (const key of Object.keys(source)) { 115 | if (isObject(source[key])) { 116 | if (!(key in target)) { 117 | Object.assign(output, { [key]: source[key] }) 118 | } else { 119 | output[key] = deepMerge(target[key], source[key]) 120 | } 121 | } else { 122 | Object.assign(output, { [key]: source[key] }) 123 | } 124 | } 125 | } 126 | 127 | return output 128 | } 129 | 130 | /** 131 | * 检查值是否为对象 132 | * @param item 要检查的值 133 | * @returns 是否为对象 134 | */ 135 | function isObject(item: any): boolean { 136 | return item && typeof item === 'object' && !Array.isArray(item) 137 | } 138 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "ESNext", 5 | "moduleResolution": "node", 6 | "esModuleInterop": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "outDir": "dist", 10 | "rootDir": "src", 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true, 13 | "lib": ["ES2022", "DOM"], 14 | "types": ["bun-types"] 15 | }, 16 | "include": ["src/**/*"], 17 | "exclude": ["node_modules", "dist"] 18 | } 19 | --------------------------------------------------------------------------------