From c2e08794c2a096f894dd7fec7e7b43515e4094a3 Mon Sep 17 00:00:00 2001 From: Kevin Wong Date: Thu, 8 Jan 2026 17:40:59 +0800 Subject: [PATCH] =?UTF-8?q?Init:=20=E5=AF=BC=E5=85=A5=E6=BA=90=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 91 +++- package-lock.json | 1168 ++++++++++++++++++++++++++++++++++++++++++++ package.json | 25 + public/app.js | 102 ++++ public/favicon.png | Bin 0 -> 36771 bytes public/index.html | 103 ++++ public/styles.css | 277 +++++++++++ server.js | 307 ++++++++++++ 8 files changed, 2072 insertions(+), 1 deletion(-) create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 public/app.js create mode 100644 public/favicon.png create mode 100644 public/index.html create mode 100644 public/styles.css create mode 100644 server.js diff --git a/README.md b/README.md index dd50c15..571e564 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,91 @@ -# Suanming-Web +# Suanming Web Gateway +统一的算命网页入口:后端调用 suanming API 获取原始命理数据,再调用 DeepSeek LLM 输出自然语言解读,前端只需请求 `/api/fortune`。 + +## 功能概览 + +- `POST /api/fortune`:校验参数 → 调用 `suanming` → 调用 `DeepSeek` → 返回 `ai_text + raw_suanming`。 +- Express 静态站点:单页表单,包含示例填充、请求状态、AI 文本展示、原始 JSON 展开、免责声明等。 +- `.env` 控制 suanming、DeepSeek、端口与超时,示例参见 [.env.example](./.env.example)。 + +## 文件结构 + +``` +web/ +├── public/ # 静态前端资源(index.html / styles.css / app.js) +├── server.js # Express 网关及 API 实现 +├── package.json # 项目依赖 +└── .env.example # 环境变量模板 +``` + +## 快速开始 + +1. **安装依赖** + ```bash + cd web + npm install + ``` + +2. **配置环境变量** + ```bash + cp .env.example .env + # 修改 .env,填入真实的 suanming 地址、访问令牌以及 DeepSeek Key + ``` + +3. **启动服务** + ```bash + npm run dev + # 浏览器访问 http://localhost:4173 + ``` + +## API 说明 + +``` +POST /api/fortune +Content-Type: application/json +``` + +请求示例: + +```json +{ + "type": "bazi", + "name": "张三", + "birth_date": "1990-01-01", + "birth_time": "12:00", + "gender": "male", + "is_lunar": false, + "question": "想了解近期事业与财运", + "extra_options": { "focus": "career" } +} +``` + +返回示例: + +```json +{ + "success": true, + "data": { + "ai_text": "DeepSeek 的自然语言解读", + "raw_suanming": { "...": "原始 suanming JSON" }, + "meta": { + "type": "bazi", + "model": "deepseek-chat", + "elapsed_ms": 2143, + "warnings": [] + } + } +} +``` + +## 自定义 + +- DeepSeek 调用通过 `openai` SDK 完成,`.env` 中的 `DEEPSEEK_API_URL` 只需设置到根域名(默认 `https://api.deepseek.com`)。 +- 若你的 suanming API 启用了认证,把登录后拿到的 Bearer token 填入 `SUANMING_API_TOKEN`,网关会自动加到 `Authorization` 头。 +- 若暂未配置 DeepSeek Key,会返回提示并保留 `raw_suanming`,方便调试。 +- 如需拓展 `name`、`fortune` 等类型,可在 `server.js` 的 `buildSuanmingRequest` 中补充对应 endpoint/payload。 +- 前端使用原生 JS,便于在任何环境下部署,若后续需要组件化框架,可直接复用当前 API。 + +## 免责声明 + +页面底部及 DeepSeek Prompt 均包含“仅供娱乐参考”的提示,请在生产部署时继续保留。 diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..6102f83 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1168 @@ +{ + "name": "suanming-web", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "suanming-web", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "axios": "^1.7.4", + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express": "^4.19.2", + "openai": "^4.69.0" + } + }, + "node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.4" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "license": "MIT" + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "license": "MIT", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/openai": { + "version": "4.104.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.104.0.tgz", + "integrity": "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==", + "license": "Apache-2.0", + "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" + }, + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..3b49537 --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "suanming-web", + "version": "0.1.0", + "description": "Gateway backend and lightweight web UI for Suanming fortune analysis.", + "main": "server.js", + "scripts": { + "dev": "node server.js", + "start": "node server.js" + }, + "keywords": [ + "suanming", + "fortune", + "deepseek", + "express" + ], + "author": "", + "license": "MIT", + "dependencies": { + "axios": "^1.7.4", + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express": "^4.19.2", + "openai": "^4.69.0" + } +} diff --git a/public/app.js b/public/app.js new file mode 100644 index 0000000..7d2cf3d --- /dev/null +++ b/public/app.js @@ -0,0 +1,102 @@ +const form = document.getElementById('fortune-form'); +const statusEl = document.getElementById('status'); +const aiTextEl = document.getElementById('ai-text'); +const warningsEl = document.getElementById('warnings'); +const submitBtn = document.getElementById('submit-btn'); +const fillDemoBtn = document.getElementById('fill-demo'); + +const STATUS_CLASS = ['idle', 'info', 'success', 'error']; + +function setStatus(text, variant = 'idle') { + statusEl.textContent = text; + STATUS_CLASS.forEach((cls) => statusEl.classList.remove(cls)); + statusEl.classList.add(variant); +} + +function buildPayload() { + const formData = new FormData(form); + + return { + type: formData.get('type') || 'bazi', + name: (formData.get('name') || '').trim(), + birth_date: formData.get('birth_date'), + birth_time: formData.get('birth_time'), + gender: formData.get('gender'), + is_lunar: formData.get('is_lunar') === 'on', + question: (formData.get('question') || '').trim(), + extra_options: {}, + }; +} + +async function submitForm(event) { + event.preventDefault(); + const payload = buildPayload(); + + setStatus('⏳ 正在分析 ...', 'info'); + submitBtn.disabled = true; + + try { + const response = await fetch('/api/fortune', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + }); + + const data = await response.json(); + if (!response.ok || !data.success) { + throw new Error(data.error?.message || '后端服务异常'); + } + + renderResult(data.data); + setStatus('✅ 分析完成', 'success'); + } catch (error) { + console.error(error); + setStatus(`❌ ${error.message}`, 'error'); + } finally { + submitBtn.disabled = false; + } +} + +function renderResult(result) { + aiTextEl.textContent = (result.ai_text || '').trim(); + + const warnings = result.meta?.warnings?.filter(Boolean) || []; + if (warnings.length) { + warningsEl.classList.remove('hidden'); + warningsEl.innerHTML = warnings.map((w) => `

⚠️ ${w}

`).join(''); + } else { + warningsEl.classList.add('hidden'); + warningsEl.textContent = ''; + } +} + +function fillDemo() { + const now = new Date(); + const date = now.toISOString().slice(0, 10); + const time = '08:30'; + + form.type.value = 'bazi'; + form.name.value = '李清扬'; + form.gender.value = 'female'; + form.birth_date.value = date; + form.birth_time.value = time; + form.is_lunar.checked = false; + form.question.value = '想了解未来三个月的事业机会和财务规划建议。'; + setStatus('示例数据已填充,可直接提交。', 'info'); +} + +function init() { + const defaultDate = '1990-01-01'; + const defaultTime = '12:00'; + if (!form.birth_date.value) { + form.birth_date.value = defaultDate; + } + if (!form.birth_time.value) { + form.birth_time.value = defaultTime; + } + + form.addEventListener('submit', submitForm); + fillDemoBtn.addEventListener('click', fillDemo); +} + +init(); diff --git a/public/favicon.png b/public/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..f4bb936bd755391b5c84873cde934651b2f35f87 GIT binary patch literal 36771 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 literal 0 HcmV?d00001 diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..c106ce6 --- /dev/null +++ b/public/index.html @@ -0,0 +1,103 @@ + + + + + + + + 天问 + + + + + +
+
+

天问

+
+ +
+
+
+

填写用户信息

+ +
+
+
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + 若勾选,请确保日期已换算为农历。 +
+ +
+ + +
+ + + +
+ +
+
+
+ +
+
+

AI 解读结果

+
等待提交
+
+ +
+
+ + + + +
+
+ +
+

⚠️ 本服务仅供娱乐参考,任何结论不构成专业建议。

+
+
+ + + + + \ No newline at end of file diff --git a/public/styles.css b/public/styles.css new file mode 100644 index 0000000..3d1c830 --- /dev/null +++ b/public/styles.css @@ -0,0 +1,277 @@ +:root { + color-scheme: light dark; + --bg: #0f1115; + --panel: #181b22; + --panel-light: #1f242f; + --text: #f8fbff; + --muted: #9ca3af; + --accent: #7c5dfa; + --accent-soft: rgba(124, 93, 250, 0.18); + --border: rgba(255, 255, 255, 0.08); + --success: #22c55e; + --error: #f87171; + font-family: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif; +} + +*, +*::before, +*::after { + box-sizing: border-box; +} + +html { + background-color: #0f1115; +} + +body { + margin: 0; + min-height: 100vh; + background: var(--bg); + color: var(--text); +} + +.page { + max-width: 1200px; + margin: 0 auto; + padding: 48px 20px 60px; +} + +.hero { + text-align: center; + margin-bottom: 32px; +} + +.hero h1 { + margin: 0; + font-size: clamp(2rem, 4vw, 3rem); + background: linear-gradient(120deg, var(--accent), #9f6bff); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + + + +.layout { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); + gap: 24px; +} + +.panel { + background: linear-gradient(145deg, var(--panel), var(--panel-light)); + border: 1px solid var(--border); + border-radius: 16px; + padding: 24px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.35); +} + +.panel__header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 24px; + gap: 12px; +} + +.panel__header h2 { + margin: 0; +} + +.ghost { + background: transparent; + border: 1px solid var(--border); + color: var(--text); + border-radius: 999px; + padding: 6px 12px; + cursor: pointer; + transition: border 0.2s, color 0.2s; +} + +.ghost:hover { + border-color: var(--accent); + color: var(--accent); +} + +form { + display: flex; + flex-direction: column; + gap: 16px; +} + +.grid { + display: grid; + gap: 12px; +} + +@media (min-width: 768px) { + .grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +.field-group { + display: flex; + flex-direction: column; + gap: 6px; +} + +.field-group label { + font-weight: 600; +} + +input, +select, +textarea { + padding: 10px 12px; + border-radius: 10px; + border: 1px solid transparent; + background: rgba(255, 255, 255, 0.05); + color: var(--text); + font-size: 1rem; + transition: border 0.2s, background 0.2s; +} + +input:focus, +select:focus, +textarea:focus { + outline: none; + border-color: var(--accent); + background: rgba(255, 255, 255, 0.08); +} + +textarea { + resize: vertical; +} + +.field-group small { + color: var(--muted); +} + +.checkbox { + flex-direction: row; + align-items: center; + justify-content: space-between; + gap: 16px; +} + +.checkbox label { + font-weight: 400; + display: flex; + align-items: center; + gap: 8px; +} + +.form-actions { + display: flex; + align-items: center; + gap: 16px; +} + +.form-actions button { + flex-shrink: 0; +} + +button[type='submit'] { + background: linear-gradient(120deg, var(--accent), #9f6bff); + border: none; + padding: 12px 20px; + border-radius: 12px; + color: white; + font-size: 1rem; + font-weight: 600; + cursor: pointer; + transition: opacity 0.2s, transform 0.1s; +} + +button[type='submit']:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +button[type='submit']:active { + transform: scale(0.99); +} + +.hint { + margin: 0; + color: var(--muted); +} + +.status { + padding: 4px 10px; + border-radius: 999px; + font-size: 0.85rem; + border: 1px solid transparent; +} + +.status.idle { + border-color: var(--border); + color: var(--muted); +} + +.status.info { + border-color: var(--accent); + color: var(--accent); + background: var(--accent-soft); +} + +.status.success { + border-color: rgba(34, 197, 94, 0.5); + color: var(--success); +} + +.status.error { + border-color: rgba(248, 113, 113, 0.4); + color: var(--error); +} + +.ai-text { + min-height: 200px; + background: rgba(0, 0, 0, 0.2); + border-radius: 12px; + padding: 16px; + line-height: 1.6; + white-space: pre-wrap; + border: 1px dashed var(--border); +} + +.ai-text .placeholder { + color: var(--muted); +} + +.warnings { + margin-top: 16px; + padding: 12px; + border-radius: 12px; + border: 1px dashed rgba(248, 196, 113, 0.5); + color: #fbbf24; + background: rgba(251, 191, 36, 0.08); +} + +.warnings.hidden { + display: none; +} + + + +.footer { + margin-top: 40px; + text-align: center; + color: var(--muted); +} + +.footer code { + color: var(--accent); +} + +@media (max-width: 640px) { + .form-actions { + flex-direction: column; + align-items: stretch; + } + + button[type='submit'] { + width: 100%; + } +} \ No newline at end of file diff --git a/server.js b/server.js new file mode 100644 index 0000000..3975c2f --- /dev/null +++ b/server.js @@ -0,0 +1,307 @@ +const express = require('express'); +const path = require('path'); +const axios = require('axios'); +const cors = require('cors'); +const dotenv = require('dotenv'); +const OpenAI = require('openai'); + +dotenv.config(); + +const app = express(); +const PORT = process.env.PORT || 4173; + +const SUANMING_API_BASE = process.env.SUANMING_API_BASE || 'http://localhost:3001/api'; +const DEEPSEEK_API_KEY = process.env.DEEPSEEK_API_KEY || ''; +const DEEPSEEK_API_URL = + process.env.DEEPSEEK_API_URL || 'https://api.deepseek.com'; +const DEEPSEEK_MODEL = process.env.DEEPSEEK_MODEL || 'deepseek-chat'; +const DEEPSEEK_TIMEOUT_MS = Number(process.env.DEEPSEEK_TIMEOUT_MS || 20000); +const SUANMING_TIMEOUT_MS = Number(process.env.SUANMING_TIMEOUT_MS || 15000); +const SUANMING_EMAIL = process.env.SUANMING_EMAIL || ''; +const SUANMING_PASSWORD = process.env.SUANMING_PASSWORD || ''; + +// Token 管理器 +const tokenManager = { + token: null, + loginPromise: null, // 防止并发登录 + + async login() { + // 如果已有登录请求在进行,等待其完成 + if (this.loginPromise) { + return this.loginPromise; + } + + if (!SUANMING_EMAIL || !SUANMING_PASSWORD) { + console.error('[tokenManager] SUANMING_EMAIL 或 SUANMING_PASSWORD 未配置'); + return null; + } + + // 创建登录 Promise 并保存引用 + this.loginPromise = (async () => { + try { + console.log('[tokenManager] 正在登录获取 token...'); + const response = await axios.post( + `${SUANMING_API_BASE}/auth/login`, + { email: SUANMING_EMAIL, password: SUANMING_PASSWORD }, + { timeout: SUANMING_TIMEOUT_MS } + ); + + this.token = response.data?.data?.token; + if (this.token) { + console.log('[tokenManager] Token 获取成功'); + } + return this.token; + } catch (error) { + console.error('[tokenManager] 登录失败:', error.message); + return null; + } finally { + this.loginPromise = null; + } + })(); + + return this.loginPromise; + }, + + async getToken() { + if (!this.token) { + await this.login(); + } + return this.token; + }, + + async refreshToken() { + this.token = null; + return this.login(); + } +}; + +const DEEPSEEK_BASE_URL = DEEPSEEK_API_URL.replace( + /\/v1\/chat\/completions\/?$/, + '' +); + +const openaiClient = + DEEPSEEK_API_KEY && + new OpenAI({ + baseURL: DEEPSEEK_BASE_URL, + apiKey: DEEPSEEK_API_KEY, + timeout: DEEPSEEK_TIMEOUT_MS, + }); + +app.use(cors()); +app.use(express.json({ limit: '1mb' })); + +app.use((req, _res, next) => { + req.requestTime = Date.now(); + next(); +}); + +const publicDir = path.join(__dirname, 'public'); +app.use(express.static(publicDir)); + +app.get('/api/health', (_req, res) => { + res.json({ + status: 'ok', + time: new Date().toISOString(), + }); +}); + +app.post('/api/fortune', async (req, res) => { + try { + const validationError = validateFortuneRequest(req.body); + if (validationError) { + return res.status(400).json({ + success: false, + error: { + code: 'VALIDATION_ERROR', + message: validationError, + }, + }); + } + + const { endpoint, payload } = buildSuanmingRequest(req.body); + + // 调用 suanming API,支持 token 过期自动重试 + let suanmingResponse; + try { + suanmingResponse = await callSuanmingApi(endpoint, payload); + } catch (error) { + // 如果是 401 错误,刷新 token 后重试一次 + if (error.response?.status === 401) { + console.log('[fortune] Token 过期,正在刷新...'); + await tokenManager.refreshToken(); + suanmingResponse = await callSuanmingApi(endpoint, payload); + } else { + throw error; + } + } + + const rawSuanming = suanmingResponse.data; + const deepseekResult = await buildDeepseekInterpretation(req.body, rawSuanming); + + return res.json({ + success: true, + data: { + ai_text: deepseekResult.text, + raw_suanming: rawSuanming, + meta: { + type: req.body.type || 'bazi', + model: deepseekResult.model, + elapsed_ms: Date.now() - req.requestTime, + warnings: deepseekResult.warning ? [deepseekResult.warning] : [], + }, + }, + }); + } catch (error) { + console.error('[fortune] request failed', error.message); + const status = error.response?.status || error.statusCode || 500; + const errorPayload = { + success: false, + error: { + code: deriveErrorCode(error), + message: error.response?.data?.error?.message || error.message || 'Unknown error', + }, + }; + + if (error.response?.data) { + errorPayload.error.details = error.response.data; + } + + return res.status(status).json(errorPayload); + } +}); + +// 调用 suanming API +async function callSuanmingApi(endpoint, payload) { + const token = await tokenManager.getToken(); + const headers = { 'Content-Type': 'application/json' }; + if (token) { + headers.Authorization = `Bearer ${token}`; + } + + return axios.post( + `${SUANMING_API_BASE}${endpoint}`, + payload, + { timeout: SUANMING_TIMEOUT_MS, headers } + ); +} + +app.get('*', (_req, res) => { + res.sendFile(path.join(publicDir, 'index.html')); +}); + +app.listen(PORT, async () => { + console.log(`🧙 Suanming gateway running at http://localhost:${PORT}`); + // 启动时预热 Token + await tokenManager.getToken(); +}); + +function validateFortuneRequest(body = {}) { + const requiredFields = ['name', 'birth_date', 'birth_time', 'gender']; + const missing = requiredFields.filter((key) => !body[key]); + if (missing.length) { + return `缺少字段: ${missing.join(', ')}`; + } + + const allowedTypes = ['bazi']; + const type = body.type || 'bazi'; + if (!allowedTypes.includes(type)) { + return `暂不支持的 type: ${type},目前仅支持 ${allowedTypes.join(', ')}`; + } + + return null; +} + +function buildSuanmingRequest(body) { + const type = body.type || 'bazi'; + + if (type === 'bazi') { + return { + endpoint: '/analysis/bazi', + payload: { + birth_data: { + name: body.name, + birth_date: body.birth_date, + birth_time: body.birth_time, + gender: body.gender, + is_lunar: Boolean(body.is_lunar), + }, + }, + }; + } + + const error = new Error(`Unsupported type: ${type}`); + error.statusCode = 400; + throw error; +} + +async function buildDeepseekInterpretation(body, rawSuanming) { + const baseResult = { + text: '', + warning: null, + model: DEEPSEEK_MODEL, + }; + + if (!DEEPSEEK_API_KEY) { + baseResult.warning = 'DeepSeek API Key 未配置,已跳过 AI 解读'; + baseResult.text = + '⚠️ DeepSeek API Key 未配置,无法生成 AI 解读。\n请在 .env 中设置 DEEPSEEK_API_KEY 后重试。'; + return baseResult; + } + + const prompt = [ + `用户姓名: ${body.name}`, + `出生日期: ${body.birth_date} ${body.birth_time}`, + `性别: ${body.gender}`, + `是否农历: ${body.is_lunar ? '是' : '否'}`, + `用户问题: ${body.question || '无特定问题'}`, + `额外关注点: ${JSON.stringify(body.extra_options || {})}`, + '', + '以下是 suanming 系统返回的原始数据,请结合命理知识给出通俗易懂且积极向上的解读,最后请附上声明:“以上内容由AI生成,仅供参考。”', + JSON.stringify(rawSuanming, null, 2), + ].join('\n'); + + const payload = { + model: DEEPSEEK_MODEL, + temperature: 0.7, + max_tokens: 1024, + messages: [ + { + role: 'system', + content: + '你是一位经验丰富的命理大师,擅长把专业的八字分析转化为温暖、易懂且具有行动建议的文字。回答时保持积极、真诚,并提醒内容仅供参考。', + }, + { + role: 'user', + content: prompt, + }, + ], + }; + + try { + if (!openaiClient) throw new Error('OpenAI client 未初始化'); + + const completion = await openaiClient.chat.completions.create(payload); + + baseResult.text = + completion?.choices?.[0]?.message?.content?.trim() || + 'DeepSeek 没有返回内容,请稍后再试。'; + + return baseResult; + } catch (error) { + console.error('[deepseek] failed', error.message); + const errMsg = + error?.error?.message || error?.message || 'DeepSeek 调用失败'; + baseResult.warning = `DeepSeek 调用失败:${errMsg}`; + baseResult.text = + '⚠️ DeepSeek 调用失败,无法生成 AI 解读。请稍后重试或检查 API Key 配置。'; + return baseResult; + } +} + +function deriveErrorCode(error) { + if (error.code === 'ECONNABORTED') return 'UPSTREAM_TIMEOUT'; + if (error.response?.status === 404) return 'ENDPOINT_NOT_FOUND'; + if (error.response?.status === 401) return 'UNAUTHORIZED'; + return 'GATEWAY_ERROR'; +}