guanwei 1.2.3 → 1.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +9 -5
- package/README.md +13 -8
- package/package.json +2 -2
- package/scripts/release.sh +1 -1
- package/server/src/data/db.json +215 -53
- package/server/src/data/guanwei.db +0 -0
- package/server/src/index.ts +18 -0
- package/server/src/routes/ai.ts +22 -2
- package/server/src/routes/divine.ts +29 -7
- package/server/src/routes/users.ts +6 -2
- package/server/src/services/duanyu.ts +2 -3
- package/server/src/services/promptBuilder.ts +12 -0
- package/tsconfig.json +1 -1
package/README.en.md
CHANGED
|
@@ -78,25 +78,28 @@ AI: multi-LLM adapter (OpenAI-compatible & Google formats; DeepSeek / Gemi
|
|
|
78
78
|
|
|
79
79
|
```bash
|
|
80
80
|
npm i -g guanwei
|
|
81
|
-
guanwei setup
|
|
81
|
+
guanwei setup
|
|
82
82
|
guanwei start
|
|
83
83
|
```
|
|
84
84
|
|
|
85
85
|
Open http://localhost:5173 . Upgrade: `guanwei update`. Stop: `guanwei stop`.
|
|
86
86
|
|
|
87
|
+
> 💡 **Configure your key (`guanwei setup`)**: interactive wizard — pick a provider, paste your key (not echoed), stored in `~/.guanwei/.env` (readable only by you). Or one-shot: `guanwei setup --key YOUR_API_KEY` (5 providers, see below).
|
|
88
|
+
> 💡 **No key? Still works**: `guanwei start` runs fine — charting, demos and classics all work; only AI reading needs a key (the page shows a config hint).
|
|
89
|
+
|
|
87
90
|
### ⚡ Docker one-liner (no Node needed, easiest)
|
|
88
91
|
|
|
89
92
|
```bash
|
|
90
|
-
docker run -d --name guanwei -p 5173:80 -e LLM_DEEPSEEK_KEY=
|
|
93
|
+
docker run -d --name guanwei -p 5173:80 -e LLM_DEEPSEEK_KEY=sk-YOUR_REAL_KEY ghcr.io/rubyccll/guanwei:latest
|
|
91
94
|
```
|
|
92
95
|
|
|
93
|
-
Open http://localhost:5173 . Stop: `docker stop guanwei`. Other providers: `-e LLM_PROVIDER=gemini -e LLM_GEMINI_KEY
|
|
96
|
+
Open http://localhost:5173 . Stop: `docker stop guanwei`. Other providers: `-e LLM_PROVIDER=gemini -e LLM_GEMINI_KEY=sk-YOUR_REAL_KEY` (deepseek / gemini / groq / qwen / custom).
|
|
94
97
|
|
|
95
98
|
### ⚡ Source installer (fallback)
|
|
96
99
|
|
|
97
100
|
```bash
|
|
98
101
|
curl -fsSL https://raw.githubusercontent.com/RubyCcll/guanwei/main/scripts/install.sh | bash
|
|
99
|
-
guanwei setup
|
|
102
|
+
guanwei setup
|
|
100
103
|
guanwei start
|
|
101
104
|
```
|
|
102
105
|
|
|
@@ -140,7 +143,8 @@ The script installs dependencies, writes `server/.env` (key stays local), and st
|
|
|
140
143
|
```bash
|
|
141
144
|
npm link # install globally (or use ./scripts/guanwei directly)
|
|
142
145
|
|
|
143
|
-
guanwei setup
|
|
146
|
+
guanwei setup # configure API key (interactive wizard)
|
|
147
|
+
guanwei setup --key sk-xxx # one-shot (replace sk-xxx with your real key)
|
|
144
148
|
guanwei start # start (--docker for containers)
|
|
145
149
|
guanwei doctor # environment self-check (Node/config/ports/deps/version)
|
|
146
150
|
guanwei update # update to latest (git incremental merge; local config preserved)
|
package/README.md
CHANGED
|
@@ -93,25 +93,28 @@ AI 层:多 LLM 适配(OpenAI 兼容 / Google 格式,DeepSeek / Gemini / Gr
|
|
|
93
93
|
|
|
94
94
|
```bash
|
|
95
95
|
npm i -g guanwei
|
|
96
|
-
guanwei setup
|
|
96
|
+
guanwei setup
|
|
97
97
|
guanwei start
|
|
98
98
|
```
|
|
99
99
|
|
|
100
100
|
打开 http://localhost:5173 即用。国内用户自动走 npmmirror 加速;升级:`guanwei update`。停止:`guanwei stop`。
|
|
101
101
|
|
|
102
|
+
> 💡 **配置 Key(`guanwei setup`)**:交互式引导——选服务商 → 粘贴 Key(不回显),写进 `~/.guanwei/.env` 仅本机可读。也可一步到位:`guanwei setup --key sk-你的真实Key`(DeepSeek 等 5 家任选,Key 申请见下文)。
|
|
103
|
+
> 💡 **不配 Key 也能启动**:`guanwei start` 直接跑,排盘/演示/古籍全部可用,仅 AI 解读不可用(页面会提示配置入口)。
|
|
104
|
+
|
|
102
105
|
### ⚡ 方式二:Docker 一行启动(免装 Node,最省心)
|
|
103
106
|
|
|
104
107
|
```bash
|
|
105
|
-
docker run -d --name guanwei -p 5173:80 -e LLM_DEEPSEEK_KEY
|
|
108
|
+
docker run -d --name guanwei -p 5173:80 -e LLM_DEEPSEEK_KEY=sk-你的真实Key ghcr.io/rubyccll/guanwei:latest
|
|
106
109
|
```
|
|
107
110
|
|
|
108
|
-
打开 http://localhost:5173 即用。停止:`docker stop guanwei`。其他服务商:`-e LLM_PROVIDER=gemini -e LLM_GEMINI_KEY
|
|
111
|
+
打开 http://localhost:5173 即用。停止:`docker stop guanwei`。其他服务商:`-e LLM_PROVIDER=gemini -e LLM_GEMINI_KEY=sk-你的真实Key`(deepseek / gemini / groq / qwen / custom 均可)。
|
|
109
112
|
|
|
110
113
|
### ⚡ 方式三:curl 一键安装(源码方式,备用)
|
|
111
114
|
|
|
112
115
|
```bash
|
|
113
116
|
curl -fsSL https://raw.githubusercontent.com/RubyCcll/guanwei/main/scripts/install.sh | bash
|
|
114
|
-
guanwei setup
|
|
117
|
+
guanwei setup
|
|
115
118
|
guanwei start
|
|
116
119
|
```
|
|
117
120
|
|
|
@@ -126,7 +129,7 @@ guanwei start
|
|
|
126
129
|
点击按钮 → 云端环境自动装好依赖 → 终端执行:
|
|
127
130
|
|
|
128
131
|
```bash
|
|
129
|
-
./scripts/setup.sh --key
|
|
132
|
+
./scripts/setup.sh --key sk-你的真实Key
|
|
130
133
|
```
|
|
131
134
|
|
|
132
135
|
#### 方式五:Docker Compose(多容器)
|
|
@@ -134,7 +137,7 @@ guanwei start
|
|
|
134
137
|
预构建镜像已发布到 GitHub Container Registry(amd64 + arm64 双平台):
|
|
135
138
|
|
|
136
139
|
```bash
|
|
137
|
-
./scripts/setup.sh --docker --key
|
|
140
|
+
./scripts/setup.sh --docker --key sk-你的真实Key # 自动配置 + 拉取镜像 + 启动
|
|
138
141
|
# 或手动:
|
|
139
142
|
# cp server/.env.example server/.env (填入 Key)
|
|
140
143
|
# docker compose up -d (自动拉取 GHCR 镜像)
|
|
@@ -147,7 +150,8 @@ guanwei start
|
|
|
147
150
|
#### 方式六:本地 Node.js(≥ 22.5,需 node:sqlite 内置支持)
|
|
148
151
|
|
|
149
152
|
```bash
|
|
150
|
-
./scripts/setup.sh
|
|
153
|
+
./scripts/setup.sh # 交互式:选服务商 + 输入 Key
|
|
154
|
+
# 或一步到位:./scripts/setup.sh --key sk-你的真实Key
|
|
151
155
|
```
|
|
152
156
|
|
|
153
157
|
脚本自动:安装依赖 → 写入 `server/.env`(Key 仅存本地)→ 启动前后端。打开 http://localhost:5173 → 缘起页注册 → 九术页起占 → 召 AI 成报告。
|
|
@@ -159,7 +163,8 @@ guanwei start
|
|
|
159
163
|
npm link
|
|
160
164
|
# ② 不想全局安装?直接使用:./scripts/guanwei <命令>
|
|
161
165
|
|
|
162
|
-
guanwei setup
|
|
166
|
+
guanwei setup # 配置 API Key(交互式:选服务商 + 粘贴 Key)
|
|
167
|
+
guanwei setup --key sk-xxx # 一步到位(sk-xxx 换成你的真实 Key)
|
|
163
168
|
guanwei start # 启动(--docker 用容器)
|
|
164
169
|
guanwei doctor # 环境自检(Node/配置/占位密钥/端口/依赖/版本)
|
|
165
170
|
guanwei update # 更新到最新版(git 增量合并,.env 等本地配置不覆盖)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "guanwei",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "1.2.
|
|
4
|
+
"version": "1.2.5",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev": "vite",
|
|
@@ -91,6 +91,6 @@
|
|
|
91
91
|
"url": "git+https://github.com/RubyCcll/guanwei.git"
|
|
92
92
|
},
|
|
93
93
|
"engines": {
|
|
94
|
-
"node": ">=22.
|
|
94
|
+
"node": ">=22.13.0"
|
|
95
95
|
}
|
|
96
96
|
}
|
package/scripts/release.sh
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# 用法:./scripts/release.sh 1.1.2 仅 git 发版(tag 触发 CI/Release/镜像)
|
|
5
5
|
# ./scripts/release.sh 1.1.2 --npm 额外发布 npm 包(需先配置 npm 登录)
|
|
6
6
|
# 要求:工作区无未提交改动;CHANGELOG.md 已写好本次版本条目
|
|
7
|
-
#
|
|
7
|
+
# 详见(本地文档,不入库):docs/RELEASE-SOP.md(发版 SOP 详情/踩坑记录)
|
|
8
8
|
# ============================================================
|
|
9
9
|
set -euo pipefail
|
|
10
10
|
cd "$(dirname "$0")/.."
|
package/server/src/data/db.json
CHANGED
|
@@ -6278,57 +6278,165 @@
|
|
|
6278
6278
|
"records": []
|
|
6279
6279
|
},
|
|
6280
6280
|
{
|
|
6281
|
-
"username": "
|
|
6282
|
-
"passHash": "scrypt$
|
|
6283
|
-
"createdAt":
|
|
6281
|
+
"username": "联调2179",
|
|
6282
|
+
"passHash": "scrypt$a51ae5b7ef7ef2aefc5dd7c51adfaa2d$fa9362e9b80497e5955137670334e733bc9c13509e10bde8ac259f5f786ef558d4ebf14c7406a1cb1022a613d87d36f66171b6e1e8c80c4c0cb0e1e7ec0a45c2",
|
|
6283
|
+
"createdAt": 1787974772299,
|
|
6284
|
+
"profile": {
|
|
6285
|
+
"birthDate": "1996-09-09",
|
|
6286
|
+
"birthHourIndex": 5,
|
|
6287
|
+
"gender": "男",
|
|
6288
|
+
"location": null
|
|
6289
|
+
},
|
|
6290
|
+
"samples": [],
|
|
6291
|
+
"records": [
|
|
6292
|
+
{
|
|
6293
|
+
"artId": "bazi",
|
|
6294
|
+
"createdAt": 1,
|
|
6295
|
+
"result": {
|
|
6296
|
+
"dayGZ": "壬申"
|
|
6297
|
+
}
|
|
6298
|
+
}
|
|
6299
|
+
],
|
|
6300
|
+
"token": "55efbbaeaa550c9966f04dda52f998095e463738eb645048c2cdd49701a2627e"
|
|
6301
|
+
},
|
|
6302
|
+
{
|
|
6303
|
+
"username": "鉴权2490",
|
|
6304
|
+
"passHash": "scrypt$bee1f62b05efb97d0af677b90f3569e0$641c60c1aed8e610ce18365ca7d894ca9570e34736695c041bf53be1f7216e7ddadce34fc47291512d73106ca4c8ce1fccfb02be4389cbcbfaa43226efeab9a8",
|
|
6305
|
+
"createdAt": 1787974772532,
|
|
6306
|
+
"profile": {
|
|
6307
|
+
"birthDate": "1999-01-01"
|
|
6308
|
+
},
|
|
6309
|
+
"samples": [],
|
|
6310
|
+
"records": [],
|
|
6311
|
+
"token": "a8ad6a5ebb99a49abc167b0473daf56293ec89b0071a24ed58b37651ab864bf4"
|
|
6312
|
+
},
|
|
6313
|
+
{
|
|
6314
|
+
"username": "他人2562",
|
|
6315
|
+
"passHash": "scrypt$c277c3c2d4cc8a620bba42fc8798bc31$47edb65dae757c4b46d42b18fa3c444fc83f430e69b76dcb5981bf940107ced5652853f401106830a224615cf1250514e57413b41cabf9435af116c1c66d7d29",
|
|
6316
|
+
"createdAt": 1787974772603,
|
|
6317
|
+
"profile": {},
|
|
6318
|
+
"samples": [],
|
|
6319
|
+
"records": [],
|
|
6320
|
+
"token": "4cc7aa02a8fc745eefc12a6027e73348874b5aba79e68137b807108c86198b41"
|
|
6321
|
+
},
|
|
6322
|
+
{
|
|
6323
|
+
"username": "登鉴2612",
|
|
6324
|
+
"passHash": "scrypt$33289372a690e4aff8ce7de07c568905$32b21c770d232278481f71fbade740c225e69598f7d9a945e630c5b2d1aee6d06fe1d04b1d8b7aee51d17e102df0053d4083fc844a4b231b31e540524d37d4cc",
|
|
6325
|
+
"createdAt": 1787974772654,
|
|
6326
|
+
"profile": {},
|
|
6327
|
+
"samples": [],
|
|
6328
|
+
"records": [],
|
|
6329
|
+
"token": "3c6f698637cd4f3f40c7e9d4e44768b1f11127c9efda7358abed5f154ea66ef1"
|
|
6330
|
+
},
|
|
6331
|
+
{
|
|
6332
|
+
"username": "占位2705",
|
|
6333
|
+
"passHash": "scrypt$15ad8f4b87fec28b3eee34257285fc84$c96d3c49363c3ada5a17ed8060e25c7adcc8f4123d38e44677cc3e0a7fa6a5501fa214367a31b96ddfa462c6ef9af1b6d4bd096add513db153d73bd06fdee43f",
|
|
6334
|
+
"createdAt": 1787974772709,
|
|
6335
|
+
"profile": {},
|
|
6336
|
+
"samples": [],
|
|
6337
|
+
"records": [],
|
|
6338
|
+
"token": "65a178f8a72ce1c416c2b438f1a8e43cbf92414384a50fe455fb2e2598c06a8e"
|
|
6339
|
+
},
|
|
6340
|
+
{
|
|
6341
|
+
"username": "甲2793",
|
|
6342
|
+
"passHash": "scrypt$d7292baa21ca8284048bd112f3f22ed2$488c76756d9d27ca895fe122076d2dfb63421542f7b4c801b6d7c1880758bee7ea2f167e576bbb1d4791735c1fe8fbffb35bad503c2d062a3ae942ce508ef356",
|
|
6343
|
+
"createdAt": 1787974772837,
|
|
6284
6344
|
"profile": {},
|
|
6285
6345
|
"samples": [],
|
|
6346
|
+
"records": [],
|
|
6347
|
+
"token": "fd1624569025003dd839884a49d8422adf8c37ce14bcfef385873d8c94e9de12"
|
|
6348
|
+
},
|
|
6349
|
+
{
|
|
6350
|
+
"username": "乙2793",
|
|
6351
|
+
"passHash": "scrypt$241e46222657b2758766400c314c273d$5ee351f2e508dcfb2865b8cc641d063ae04bbb10e94b5d86a958a7ccbbc51d8960d6f5825b327f941f6f0d691ba43bc705a0042afe44fe83e50bc37563373739",
|
|
6352
|
+
"createdAt": 1787974772889,
|
|
6353
|
+
"profile": {},
|
|
6354
|
+
"samples": [],
|
|
6355
|
+
"records": [],
|
|
6356
|
+
"token": "58ce37d6f2e75f23d7307663b26afa7f459cfdf80744d9f43c4a5f83fefb0a85"
|
|
6357
|
+
},
|
|
6358
|
+
{
|
|
6359
|
+
"username": "联调1815",
|
|
6360
|
+
"passHash": "scrypt$7c8e52d78fc5c72c15dd5fd1376cad2f$302ee9e4004dc6bc4de15cc38ca5cc091b9133e7fd0880c2b6aa5a7f2d63bbf2846be2b5ac268f3e76f36fde5aa31e3418be5a3101d6d8d9499b99aae62e3889",
|
|
6361
|
+
"createdAt": 1787974791902,
|
|
6362
|
+
"profile": {
|
|
6363
|
+
"birthDate": "1996-09-09",
|
|
6364
|
+
"birthHourIndex": 5,
|
|
6365
|
+
"gender": "男",
|
|
6366
|
+
"location": null
|
|
6367
|
+
},
|
|
6368
|
+
"samples": [],
|
|
6286
6369
|
"records": [
|
|
6287
6370
|
{
|
|
6288
|
-
"
|
|
6371
|
+
"artId": "bazi",
|
|
6372
|
+
"createdAt": 1,
|
|
6373
|
+
"result": {
|
|
6374
|
+
"dayGZ": "壬申"
|
|
6375
|
+
}
|
|
6289
6376
|
}
|
|
6290
|
-
]
|
|
6377
|
+
],
|
|
6378
|
+
"token": "873d8e6fb689c0a759eb24489302d902d9c582cf63b6eed253efcb7dd289c7eb"
|
|
6379
|
+
},
|
|
6380
|
+
{
|
|
6381
|
+
"username": "鉴权2044",
|
|
6382
|
+
"passHash": "scrypt$a7d28897ecebf00429e38530a1be3ddb$1d05f31b71b52ed3233ebbb1d9c17ea189faf66cdcbed57969a79131efe75f744fd48dde8c807740e200e7f6738aacd0fc142c3377de02cafde91381b6c99a2a",
|
|
6383
|
+
"createdAt": 1787974792091,
|
|
6384
|
+
"profile": {
|
|
6385
|
+
"birthDate": "1999-01-01"
|
|
6386
|
+
},
|
|
6387
|
+
"samples": [],
|
|
6388
|
+
"records": [],
|
|
6389
|
+
"token": "2d9fe3339c4a239c03ec747c5b6a2eb526bec03d4204c692421ede018dc92757"
|
|
6291
6390
|
},
|
|
6292
6391
|
{
|
|
6293
|
-
"username": "他人
|
|
6294
|
-
"passHash": "scrypt$
|
|
6295
|
-
"createdAt":
|
|
6392
|
+
"username": "他人2114",
|
|
6393
|
+
"passHash": "scrypt$ab67c3c80a2c62c559cbe399d0643c63$fa0f39676d8e12ac0e613c00b100564c55b3153bc074cef3e665773b62f23bc6d6d67873543563fac68d59b490055b309e1d371a33c74e52ea13a0d150928b84",
|
|
6394
|
+
"createdAt": 1787974792156,
|
|
6296
6395
|
"profile": {},
|
|
6297
6396
|
"samples": [],
|
|
6298
6397
|
"records": [],
|
|
6299
|
-
"token": "
|
|
6398
|
+
"token": "cd08fe94f891fe49b43106684b342cfba169ffd920bf9f72558842a91bb86b67"
|
|
6300
6399
|
},
|
|
6301
6400
|
{
|
|
6302
|
-
"username": "
|
|
6303
|
-
"passHash": "scrypt$
|
|
6304
|
-
"createdAt":
|
|
6401
|
+
"username": "登鉴2165",
|
|
6402
|
+
"passHash": "scrypt$8fd7b88e6f63f16bd355a799c1fdb507$4d82632c56bf3657bfb1395726d3014272a8dbd61206010f809533b4561a9727e5e4041b6a076c7ae0f70eda21ff4b63bc415a92bbad29538a974e0dc9aa029d",
|
|
6403
|
+
"createdAt": 1787974792209,
|
|
6305
6404
|
"profile": {},
|
|
6306
6405
|
"samples": [],
|
|
6307
6406
|
"records": [],
|
|
6308
|
-
"token": "
|
|
6407
|
+
"token": "4e69bfef54a7c816bf22357687720d112062d7cda9e0a3e483668a5d0ffae04b"
|
|
6309
6408
|
},
|
|
6310
6409
|
{
|
|
6311
|
-
"username": "
|
|
6312
|
-
"passHash": "scrypt$
|
|
6313
|
-
"createdAt":
|
|
6410
|
+
"username": "占位2262",
|
|
6411
|
+
"passHash": "scrypt$9b030e5b8d1c475f3700534c0cea8bcc$5dcff5dd3abe675650bc5c8d7a0bb7402893cb6a7b20582a399dbbb39b5afea61c12525ce6beb080acf26dccb801f707d6c48434689cad680fcc2c37932cecfc",
|
|
6412
|
+
"createdAt": 1787974792265,
|
|
6314
6413
|
"profile": {},
|
|
6315
6414
|
"samples": [],
|
|
6316
6415
|
"records": [],
|
|
6317
|
-
"token": "
|
|
6416
|
+
"token": "06b5d6f063dc265a4ab0181dbeeabfaedd815bcf44a481c5817a84e3403162bd"
|
|
6318
6417
|
},
|
|
6319
6418
|
{
|
|
6320
|
-
"username": "
|
|
6321
|
-
"passHash": "scrypt$
|
|
6322
|
-
"createdAt":
|
|
6419
|
+
"username": "甲2362",
|
|
6420
|
+
"passHash": "scrypt$e0a1334c2e39079fe996d50550b296ac$22b689d05605618743ed4de49781e4920c813fe9773c66b1ffcfd841202e5eea8654bca1cb0722fdd4121abe997fe0d3b2ce6ea52b1b72f616ae4373ca92be42",
|
|
6421
|
+
"createdAt": 1787974792397,
|
|
6323
6422
|
"profile": {},
|
|
6324
6423
|
"samples": [],
|
|
6325
6424
|
"records": [],
|
|
6326
|
-
"token": "
|
|
6425
|
+
"token": "dcb5ab3832984ddb12f1e4c0934a6ec109677cac4c744ee0f43877c6265ff0ce"
|
|
6327
6426
|
},
|
|
6328
6427
|
{
|
|
6329
|
-
"username": "
|
|
6330
|
-
"passHash": "scrypt$
|
|
6331
|
-
"createdAt":
|
|
6428
|
+
"username": "乙2362",
|
|
6429
|
+
"passHash": "scrypt$65bc82b0a841f543f17cdd4db4f06419$5e7609d3c5c1dbc632489007cab5c5aaf1400652b61cb45aa20876ebb1f8274bca0cde13a11e9670beab60613a5676cc7afc82d0c31043df85700e41efe38335",
|
|
6430
|
+
"createdAt": 1787974792452,
|
|
6431
|
+
"profile": {},
|
|
6432
|
+
"samples": [],
|
|
6433
|
+
"records": [],
|
|
6434
|
+
"token": "a4c67d7d395086f836811508e9199d007487ad24f3f92db0492163bcef07ed44"
|
|
6435
|
+
},
|
|
6436
|
+
{
|
|
6437
|
+
"username": "联调4720",
|
|
6438
|
+
"passHash": "scrypt$3f5d6299295a3a9918e0416c626bcdef$67f7d06f536c2dcf312ea7ae15b50565916f97e200b904476ab96b3c4ef5d9fe3bd093734fbcf27a271682972fff3b9dff32bf02c671c496c338cf0df82c95a3",
|
|
6439
|
+
"createdAt": 1787978014815,
|
|
6332
6440
|
"profile": {
|
|
6333
6441
|
"birthDate": "1996-09-09",
|
|
6334
6442
|
"birthHourIndex": 5,
|
|
@@ -6345,41 +6453,68 @@
|
|
|
6345
6453
|
}
|
|
6346
6454
|
}
|
|
6347
6455
|
],
|
|
6348
|
-
"token": "
|
|
6456
|
+
"token": "5acff6d1a3d077e1043b57bfd0e7b197b554e6895c3c5ad7aab91eb6c5e055dd"
|
|
6349
6457
|
},
|
|
6350
6458
|
{
|
|
6351
|
-
"username": "鉴权
|
|
6352
|
-
"passHash": "scrypt$
|
|
6353
|
-
"createdAt":
|
|
6459
|
+
"username": "鉴权5000",
|
|
6460
|
+
"passHash": "scrypt$4abab2b41008b858f4f0d57083e0b00d$1af134638bb86539577633e60e77bae1c31125bc041f7ff372877984ab00c9b917c2b52fb9b2a283a2e0a57576471e879f625ea44bbdd0ab525b95ea15ffaaa9",
|
|
6461
|
+
"createdAt": 1787978015042,
|
|
6354
6462
|
"profile": {
|
|
6355
6463
|
"birthDate": "1999-01-01"
|
|
6356
6464
|
},
|
|
6357
6465
|
"samples": [],
|
|
6358
6466
|
"records": [],
|
|
6359
|
-
"token": "
|
|
6467
|
+
"token": "c483184079880cc968ca31607701d9dcdafbe6a97302345a5193ab6ac4c97a18"
|
|
6468
|
+
},
|
|
6469
|
+
{
|
|
6470
|
+
"username": "他人5082",
|
|
6471
|
+
"passHash": "scrypt$51321757c7a676de91f379a812ad7bce$e1750ec9daf3b5f19c8213c9fb51420746986cbabd9ecd507d805fc957942996011489220975566de6782616b614212a7755f507ae31d21946864c6e3458650b",
|
|
6472
|
+
"createdAt": 1787978015129,
|
|
6473
|
+
"profile": {},
|
|
6474
|
+
"samples": [],
|
|
6475
|
+
"records": [],
|
|
6476
|
+
"token": "07a612c8e5de4e8982efedd10cb9084b446bb3c5ece03bfcd3392a075997b7b0"
|
|
6360
6477
|
},
|
|
6361
6478
|
{
|
|
6362
|
-
"username": "
|
|
6363
|
-
"passHash": "scrypt$
|
|
6364
|
-
"createdAt":
|
|
6479
|
+
"username": "登鉴5138",
|
|
6480
|
+
"passHash": "scrypt$10094d85734baa9671128fc10c5e8d83$ec7d061fac3f81efb0ef5b1e8297372ef9165f2221492185a6d19a8158bf0677ea664d935fb4ccd7742f0e3c1b5caaab715c860b5c4c8fd0c1368ea53eb21521",
|
|
6481
|
+
"createdAt": 1787978015183,
|
|
6365
6482
|
"profile": {},
|
|
6366
6483
|
"samples": [],
|
|
6367
6484
|
"records": [],
|
|
6368
|
-
"token": "
|
|
6485
|
+
"token": "a6c1d3612e8f34e83d735e8dddaa5fadba775fa79b85f2cfc1e0720063b92ad9"
|
|
6369
6486
|
},
|
|
6370
6487
|
{
|
|
6371
|
-
"username": "
|
|
6372
|
-
"passHash": "scrypt$
|
|
6373
|
-
"createdAt":
|
|
6488
|
+
"username": "占位5233",
|
|
6489
|
+
"passHash": "scrypt$86ce84ec1222fe4888a75d2b19324c83$0316d5c423c34fa2bcf1088670bbbe35617ee2f501451af7ec8e3bad915eda8ed49cadf9836f2127b0b72f043a0d24ba9bad598b79d3ff19f451e89a7e8b33d2",
|
|
6490
|
+
"createdAt": 1787978015236,
|
|
6374
6491
|
"profile": {},
|
|
6375
6492
|
"samples": [],
|
|
6376
6493
|
"records": [],
|
|
6377
|
-
"token": "
|
|
6494
|
+
"token": "267fd8173c3e0536a66bf6432ca5b07ed8e87e044be6c33edf41b3d55abb0748"
|
|
6378
6495
|
},
|
|
6379
6496
|
{
|
|
6380
|
-
"username": "
|
|
6381
|
-
"passHash": "scrypt$
|
|
6382
|
-
"createdAt":
|
|
6497
|
+
"username": "甲5347",
|
|
6498
|
+
"passHash": "scrypt$bb2c32b24b75e33435eb8c6910f5ab2f$20a91be38796f811012fb1879a4ffb9da462049340994af0df150206388879c1d34e76ca7b9b9024870b42311062b2c3915c7102e55e74a31a819892f268f0f0",
|
|
6499
|
+
"createdAt": 1787978015397,
|
|
6500
|
+
"profile": {},
|
|
6501
|
+
"samples": [],
|
|
6502
|
+
"records": [],
|
|
6503
|
+
"token": "ba249a40c22dbc4c5a6b9068d3ebc3e1d1afe5212821dbffd0d2b192abf543be"
|
|
6504
|
+
},
|
|
6505
|
+
{
|
|
6506
|
+
"username": "乙5347",
|
|
6507
|
+
"passHash": "scrypt$cfe4fd0384b866b77abed2746d6c882d$c275b3373db1986865e430a392f771e29ed776006d4a7e1a9cef7afe583d81158b0dcb2b86361e863b2fae7273da61d86bbad0c8cadf06eba268b22c14f667b1",
|
|
6508
|
+
"createdAt": 1787978015472,
|
|
6509
|
+
"profile": {},
|
|
6510
|
+
"samples": [],
|
|
6511
|
+
"records": [],
|
|
6512
|
+
"token": "e12f06635335a7d365dca3dc5f4d3c87f0c0c0989199100e3cd40fb1f51b563b"
|
|
6513
|
+
},
|
|
6514
|
+
{
|
|
6515
|
+
"username": "联调1046",
|
|
6516
|
+
"passHash": "scrypt$9212a84cc0e275b12e32aba82966195f$943a2029711318c91a68ca0e9f3393a128d753b6ad00936ad9f08d14ae0abf033b6b0b808f2defecc2f284bccdbe7da6e4624ce15e75c7eb670d6d8486380dd1",
|
|
6517
|
+
"createdAt": 1787978041147,
|
|
6383
6518
|
"profile": {
|
|
6384
6519
|
"birthDate": "1996-09-09",
|
|
6385
6520
|
"birthHourIndex": 5,
|
|
@@ -6396,36 +6531,63 @@
|
|
|
6396
6531
|
}
|
|
6397
6532
|
}
|
|
6398
6533
|
],
|
|
6399
|
-
"token": "
|
|
6534
|
+
"token": "2b8f9cdcbeae6f368028bbdac2171f56975d41542f440b98faea51ea24348eb0"
|
|
6400
6535
|
},
|
|
6401
6536
|
{
|
|
6402
|
-
"username": "鉴权
|
|
6403
|
-
"passHash": "scrypt$
|
|
6404
|
-
"createdAt":
|
|
6537
|
+
"username": "鉴权1356",
|
|
6538
|
+
"passHash": "scrypt$d1c69b653d8c59bac97a60cc564a2812$85b38421cc4a8449957a390bea3fafedbec2f1426a3c59a17fd60045a67ea25840da9c03e49159012a18bae42ea997ad98b585a504cfaf1dba41d159c91a6a88",
|
|
6539
|
+
"createdAt": 1787978041401,
|
|
6405
6540
|
"profile": {
|
|
6406
6541
|
"birthDate": "1999-01-01"
|
|
6407
6542
|
},
|
|
6408
6543
|
"samples": [],
|
|
6409
6544
|
"records": [],
|
|
6410
|
-
"token": "
|
|
6545
|
+
"token": "03ae064372554daa1be328b5f435b7a9aa9f5eb5f57d3fdf0e1775de05a42c8f"
|
|
6546
|
+
},
|
|
6547
|
+
{
|
|
6548
|
+
"username": "他人1429",
|
|
6549
|
+
"passHash": "scrypt$d0ff8f7529212c5927843355c53719da$1fa2d57a5ee18124b8752222f5587a02372060f2b2b9faba6ed4ff0b77a90b0c7178f9d182dedd9c8ae300dd20ce1e5f7ec79f368ed21610f5f8756ff2d7da35",
|
|
6550
|
+
"createdAt": 1787978041467,
|
|
6551
|
+
"profile": {},
|
|
6552
|
+
"samples": [],
|
|
6553
|
+
"records": [],
|
|
6554
|
+
"token": "611138970fc695680cbaf59a4e85dc1a121c58aad81a540111b6f548eb607784"
|
|
6555
|
+
},
|
|
6556
|
+
{
|
|
6557
|
+
"username": "登鉴1475",
|
|
6558
|
+
"passHash": "scrypt$faf0d6d072301c2442a54f390f47e6f3$6f5727c69f5164d10607626e7197c4a0532945f9f881a7f9d8526188b106836c274d1e9d0552d4d8b4ae3c2627014fdc7423ee99a81bb61049845ab6e96d8738",
|
|
6559
|
+
"createdAt": 1787978041510,
|
|
6560
|
+
"profile": {},
|
|
6561
|
+
"samples": [],
|
|
6562
|
+
"records": [],
|
|
6563
|
+
"token": "c315d05d8d733976a00e43a89b62fc324251051badbb2ab7921b14f168bc2fe8"
|
|
6564
|
+
},
|
|
6565
|
+
{
|
|
6566
|
+
"username": "占位1550",
|
|
6567
|
+
"passHash": "scrypt$95a6313661da39b23785cc467c9e6acc$d40d094d3f0c92b9d3382640fcc073163aa45aaaaa13dc3adc8379e25239e4aae0ff4a8f85c4115e8ad50e72fff0a317aaeb648c8e5b5fff76b93a08e5f8b38d",
|
|
6568
|
+
"createdAt": 1787978041553,
|
|
6569
|
+
"profile": {},
|
|
6570
|
+
"samples": [],
|
|
6571
|
+
"records": [],
|
|
6572
|
+
"token": "0ad1e0459861b10d3c123ff5e8738ab688b31c258b418f19a11ed55ccc83b365"
|
|
6411
6573
|
},
|
|
6412
6574
|
{
|
|
6413
|
-
"username": "
|
|
6414
|
-
"passHash": "scrypt$
|
|
6415
|
-
"createdAt":
|
|
6575
|
+
"username": "甲1644",
|
|
6576
|
+
"passHash": "scrypt$b2815193a5c0e718566dabbb6beb7387$3663b9efe26b1e02eac7b5130920f6709847853d06b80857ff09205b9757d1ce9534d8da5bd440763f45c16cbdd1078099c8f76e9e6672fd039b35936cb813d3",
|
|
6577
|
+
"createdAt": 1787978041683,
|
|
6416
6578
|
"profile": {},
|
|
6417
6579
|
"samples": [],
|
|
6418
6580
|
"records": [],
|
|
6419
|
-
"token": "
|
|
6581
|
+
"token": "586d4a39d7b0cd5d4a4196ccacf8225459e94c9b07947988640e41ebfb0fe390"
|
|
6420
6582
|
},
|
|
6421
6583
|
{
|
|
6422
|
-
"username": "
|
|
6423
|
-
"passHash": "scrypt$
|
|
6424
|
-
"createdAt":
|
|
6584
|
+
"username": "乙1644",
|
|
6585
|
+
"passHash": "scrypt$f22718cad108fe66f6c2e822a15d80b2$833ac84622054781111427dff13b732a5fb2f68f01fa95d7176ac3d6c168bd4a314e566e232b6481fa77e431e6feae1f0878ebfe909a0c062429247fb6e9ee49",
|
|
6586
|
+
"createdAt": 1787978041733,
|
|
6425
6587
|
"profile": {},
|
|
6426
6588
|
"samples": [],
|
|
6427
6589
|
"records": [],
|
|
6428
|
-
"token": "
|
|
6590
|
+
"token": "957487f61daf7c8a30abe1a6d8c7f18615a60ad1f482b41603a847bf4aa02af8"
|
|
6429
6591
|
}
|
|
6430
6592
|
]
|
|
6431
6593
|
}
|
|
Binary file
|
package/server/src/index.ts
CHANGED
|
@@ -53,6 +53,24 @@ const PORT = process.env.PORT || 3018;
|
|
|
53
53
|
app.use(cors());
|
|
54
54
|
app.use(express.json());
|
|
55
55
|
|
|
56
|
+
// ─── AI 接口限流(防 BYOK Key 被刷爆):per-IP 令牌桶 ───
|
|
57
|
+
const AI_RATE_LIMIT = { windowMs: 60_000, max: 30 }; // 每分钟 30 次(本地单用户无感,公网防刷)
|
|
58
|
+
const aiHits = new Map<string, { count: number; resetAt: number }>();
|
|
59
|
+
app.use('/api/ai', (req, res, next) => {
|
|
60
|
+
const ip = req.ip || req.socket.remoteAddress || 'unknown';
|
|
61
|
+
const now = Date.now();
|
|
62
|
+
const rec = aiHits.get(ip);
|
|
63
|
+
if (!rec || now > rec.resetAt) {
|
|
64
|
+
aiHits.set(ip, { count: 1, resetAt: now + AI_RATE_LIMIT.windowMs });
|
|
65
|
+
return next();
|
|
66
|
+
}
|
|
67
|
+
rec.count++;
|
|
68
|
+
if (rec.count > AI_RATE_LIMIT.max) {
|
|
69
|
+
return res.status(429).json({ error: 'AI_RATE_LIMITED', message: '请求过于频繁,请稍后再试' });
|
|
70
|
+
}
|
|
71
|
+
next();
|
|
72
|
+
});
|
|
73
|
+
|
|
56
74
|
// 请求日志(联调排查用)
|
|
57
75
|
app.use((req, res, next) => {
|
|
58
76
|
res.on('finish', () => {
|
package/server/src/routes/ai.ts
CHANGED
|
@@ -1,10 +1,28 @@
|
|
|
1
1
|
// AI 解读路由:/api/ai/interpret(非流式)+ /api/ai/interpret/stream(SSE)
|
|
2
2
|
import { Router } from 'express';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import crypto from 'crypto';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
3
7
|
import { buildMessages, buildReportMessages, buildStep1Messages, buildStep2Messages } from '../services/promptBuilder.js';
|
|
4
8
|
import { chatOnce, chatStream, activeProvider, providerStatus, lastFinishReason } from '../services/llmProvider.js';
|
|
5
9
|
import { getDivination, attachReport, markAiFailed } from '../services/divineStore.js';
|
|
6
10
|
import { verifyRelatives, fixFamilyRelatives } from '../services/relativesCheck.js';
|
|
7
11
|
|
|
12
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
|
|
14
|
+
// 请求 token → 用户名(与 divine.ts 同策略:有 token 以 token 为准,防 body 自报他人名)
|
|
15
|
+
function authedUsername(req: any): string | null {
|
|
16
|
+
const tk = String(req.headers['x-guanwei-token'] || '');
|
|
17
|
+
if (!tk) return null;
|
|
18
|
+
try {
|
|
19
|
+
const db = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'data', 'db.json'), 'utf-8'));
|
|
20
|
+
const user = db.users.find((u: any) => u.token && u.token.length === tk.length && crypto.timingSafeEqual(Buffer.from(u.token), Buffer.from(tk)));
|
|
21
|
+
return user ? user.username : null;
|
|
22
|
+
} catch { return null; }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 非流式:token 优先归属
|
|
8
26
|
const router = Router();
|
|
9
27
|
|
|
10
28
|
// Provider 健康状态
|
|
@@ -23,7 +41,8 @@ router.post('/interpret', async (req, res) => {
|
|
|
23
41
|
if (divineId) {
|
|
24
42
|
const rec = getDivination(divineId);
|
|
25
43
|
if (!rec) return res.status(400).json({ error: 'DIVINE_NOT_FOUND', message: '起占记录不存在,请重新起占' });
|
|
26
|
-
|
|
44
|
+
const authed = authedUsername(req);
|
|
45
|
+
if (authed ? authed !== rec.username : (username && rec.username !== username)) return res.status(403).json({ error: 'FORBIDDEN' });
|
|
27
46
|
resultRaw = rec.resultRaw;
|
|
28
47
|
recProfile = rec.profile;
|
|
29
48
|
} else {
|
|
@@ -87,7 +106,8 @@ router.post('/interpret/stream', async (req, res) => {
|
|
|
87
106
|
res.status(400).json({ error: 'DIVINE_NOT_FOUND', message: '起占记录不存在,请重新起占' });
|
|
88
107
|
return;
|
|
89
108
|
}
|
|
90
|
-
|
|
109
|
+
const authed = authedUsername(req);
|
|
110
|
+
if (authed ? authed !== rec.username : (username && rec.username !== username)) {
|
|
91
111
|
res.status(403).json({ error: 'FORBIDDEN' });
|
|
92
112
|
return;
|
|
93
113
|
}
|
|
@@ -13,6 +13,7 @@ import { allTarotSpreads } from '../../../shared/core/data/tarotSpreads.js';
|
|
|
13
13
|
import { createDivination, listDivinations, getDivination, deleteDivination } from '../services/divineStore.js';
|
|
14
14
|
import fs from 'fs';
|
|
15
15
|
import path from 'path';
|
|
16
|
+
import crypto from 'crypto';
|
|
16
17
|
import { fileURLToPath } from 'url';
|
|
17
18
|
|
|
18
19
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -21,24 +22,42 @@ const DB_FILE = path.join(__dirname, '..', 'data', 'db.json');
|
|
|
21
22
|
const router = Router();
|
|
22
23
|
const MINGPAN_ARTS = ['bazi', 'ziwei', 'astrology'];
|
|
23
24
|
|
|
24
|
-
// 宽松建档:前端本地注册的用户在此同步建档(与 users.ts upsert
|
|
25
|
+
// 宽松建档:前端本地注册的用户在此同步建档(与 users.ts upsert 同策略;建档即发 claimToken 防抢占)
|
|
25
26
|
function ensureUser(username: string): boolean {
|
|
26
27
|
if (!username || typeof username !== 'string') return false;
|
|
27
28
|
try {
|
|
28
29
|
const db = JSON.parse(fs.readFileSync(DB_FILE, 'utf-8'));
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
const exist = db.users.find((u: any) => u.username === username);
|
|
31
|
+
if (exist) return true;
|
|
32
|
+
const token = crypto.randomBytes(32).toString('hex');
|
|
33
|
+
db.users.push({ username, passHash: '', createdAt: Date.now(), profile: {}, samples: [], records: [], token });
|
|
31
34
|
fs.writeFileSync(DB_FILE, JSON.stringify(db, null, 2));
|
|
32
35
|
console.log('[divine] 自动建档:', username);
|
|
33
36
|
return true;
|
|
34
|
-
} catch
|
|
37
|
+
} catch (e: any) {
|
|
38
|
+
console.error('[divine] ensureUser 异常:', e?.message || e);
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 请求携带的 token → 对应用户名(有 token 则以其为准,堵「query/body 自报 username」越权)
|
|
44
|
+
function authedUsername(req: any): string | null {
|
|
45
|
+
const tk = String(req.headers['x-guanwei-token'] || '');
|
|
46
|
+
if (!tk) return null;
|
|
47
|
+
try {
|
|
48
|
+
const db = JSON.parse(fs.readFileSync(DB_FILE, 'utf-8'));
|
|
49
|
+
const user = db.users.find((u: any) => u.token && u.token.length === tk.length && crypto.timingSafeEqual(Buffer.from(u.token), Buffer.from(tk)));
|
|
50
|
+
return user ? user.username : null;
|
|
51
|
+
} catch { return null; }
|
|
35
52
|
}
|
|
36
53
|
|
|
37
54
|
// POST /api/divine —— 起占入库
|
|
38
55
|
router.post('/', (req, res) => {
|
|
39
56
|
const { artId, inputs, profile, question, username, profileId } = req.body || {};
|
|
57
|
+
const authed = authedUsername(req);
|
|
58
|
+
const owner = authed || String(username || ''); // 有 token 以 token 为准,防自报他人名
|
|
40
59
|
// 游客不允许
|
|
41
|
-
if (!ensureUser(
|
|
60
|
+
if (!ensureUser(owner)) {
|
|
42
61
|
return res.status(401).json({ error: 'UNAUTHORIZED', message: '请先入馆(登录)再起占' });
|
|
43
62
|
}
|
|
44
63
|
if (!artId || !inputs) return res.status(400).json({ error: '缺少必要参数' });
|
|
@@ -124,8 +143,11 @@ router.get('/', (req, res) => {
|
|
|
124
143
|
router.get('/:id', (req, res) => {
|
|
125
144
|
const rec = getDivination(req.params.id);
|
|
126
145
|
if (!rec) return res.status(404).json({ error: 'DIVINE_NOT_FOUND' });
|
|
127
|
-
// 归属校验:query username
|
|
128
|
-
|
|
146
|
+
// 归属校验:token 优先;无 token 时 query username 必须与记录一致(向后兼容本地旧前端)
|
|
147
|
+
const authed = authedUsername(req);
|
|
148
|
+
if (authed ? authed !== rec.username : String(req.query.username || '') !== rec.username) {
|
|
149
|
+
return res.status(403).json({ error: 'FORBIDDEN' });
|
|
150
|
+
}
|
|
129
151
|
res.json({ ...rec, resultRaw: rec.resultRaw, display: rec.display, report: rec.report || null });
|
|
130
152
|
});
|
|
131
153
|
|
|
@@ -84,7 +84,7 @@ function upsertUser(username: string): DbUser {
|
|
|
84
84
|
const db = loadDb();
|
|
85
85
|
let user = db.users.find((x: any) => x.username === username);
|
|
86
86
|
if (!user) {
|
|
87
|
-
user = { username, passHash: '', createdAt: Date.now(), profile: {}, samples: [], records: [] };
|
|
87
|
+
user = { username, passHash: '', createdAt: Date.now(), profile: {}, samples: [], records: [], token: newToken() };
|
|
88
88
|
db.users.push(user);
|
|
89
89
|
saveDb(db);
|
|
90
90
|
console.log(`[users] 自动建档: ${username}`);
|
|
@@ -100,8 +100,12 @@ router.post('/register', async (req, res) => {
|
|
|
100
100
|
const db = loadDb();
|
|
101
101
|
const existing = db.users.find(u => u.username === name);
|
|
102
102
|
if (existing) {
|
|
103
|
-
//
|
|
103
|
+
// 占位账号(自动建档、无密语):升级须持有建档时发放的 claimToken,防任意抢占
|
|
104
104
|
if (!existing.passHash) {
|
|
105
|
+
const claimToken = String(req.body.claimToken || '');
|
|
106
|
+
if (!tokenMatches(existing, claimToken)) {
|
|
107
|
+
return res.status(409).json({ error: 'ACCOUNT_CLAIMED', message: '此名号已被自动建档占用,需持有建档凭据方可注册(或更换名号)' });
|
|
108
|
+
}
|
|
105
109
|
existing.passHash = await hashPassword(String(password));
|
|
106
110
|
existing.token = newToken();
|
|
107
111
|
if (profile) existing.profile = { ...existing.profile, ...profile };
|
|
@@ -49,10 +49,9 @@ export function matchDuanyu(artId: string, resultRaw: unknown, limit = 3): Duany
|
|
|
49
49
|
const hit = d.tags.filter(t => kws.includes(t)).length + (d.factors || []).filter(f => kws.includes(f)).length;
|
|
50
50
|
return { d, hit };
|
|
51
51
|
}).sort((a, b) => b.hit - a.hit);
|
|
52
|
-
// 至少命中 1
|
|
52
|
+
// 至少命中 1 个关键词才注入(避免无关引证);零命中不注入
|
|
53
53
|
const matched = scored.filter(s => s.hit > 0);
|
|
54
|
-
|
|
55
|
-
return picked.slice(0, limit).map(s => s.d);
|
|
54
|
+
return matched.slice(0, limit).map(s => s.d);
|
|
56
55
|
}
|
|
57
56
|
|
|
58
57
|
// 生成 prompt 引证段(仅 reviewed 条目;未校核 seed 一律不注入)
|
|
@@ -79,6 +79,14 @@ function hourUnknownNote(profile?: unknown): string {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
// 命主已知人生经历 → 解读校准注入(报告须呼应该年份事件、不得与其矛盾)
|
|
82
|
+
// 当前公历时间事实:注入给 AI,防止其自行推算年份(占问类无出生档案,AI 不知道"现在是哪年")
|
|
83
|
+
function nowFact(): string {
|
|
84
|
+
const d = new Date();
|
|
85
|
+
const p = (n: number) => String(n).padStart(2, '0');
|
|
86
|
+
const week = '日一二三四五六'[d.getDay()];
|
|
87
|
+
return '【当前时间 · 事实】现在是公历 ' + d.getFullYear() + ' 年 ' + (d.getMonth() + 1) + ' 月 ' + d.getDate() + ' 日 ' + p(d.getHours()) + ':' + p(d.getMinutes()) + '(星期' + week + ')。\n凡涉及年份/日期/时令的表述(如"今年""去年""明年""当下节令"),必须以此为准,不得自行推算或编造年份。';
|
|
88
|
+
}
|
|
89
|
+
|
|
82
90
|
function lifeEventsNote(profile?: unknown): string {
|
|
83
91
|
const evs: { year: number; text: string }[] = (profile as any)?.lifeEvents;
|
|
84
92
|
if (!Array.isArray(evs) || evs.length === 0) return '';
|
|
@@ -166,6 +174,8 @@ export function buildReportMessages(
|
|
|
166
174
|
'【盘面事实(以下为不可更改的排盘结果,解读必须逐字引用、不得编造)】',
|
|
167
175
|
chartBrief(artId, resultRaw),
|
|
168
176
|
'',
|
|
177
|
+
nowFact(),
|
|
178
|
+
'',
|
|
169
179
|
lifeEventsNote(profile),
|
|
170
180
|
'',
|
|
171
181
|
semantic ? '【问题语义分析】' + JSON.stringify(semantic, null, 1) : '',
|
|
@@ -213,6 +223,8 @@ export function buildStep1Messages(
|
|
|
213
223
|
'【盘面事实】',
|
|
214
224
|
chartBrief(artId, resultRaw),
|
|
215
225
|
'',
|
|
226
|
+
nowFact(),
|
|
227
|
+
'',
|
|
216
228
|
'请输出盘面解析 json。',
|
|
217
229
|
].filter(Boolean).join('\n');
|
|
218
230
|
return [
|