guanwei 1.1.1 → 1.1.2

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/dist/index.html CHANGED
@@ -6,7 +6,7 @@
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>观微 · 玄学问道 —— 九术参详,观微知著</title>
8
8
  <meta name="description" content="以纸墨留白为骨,融四柱八字、紫微斗数、奇门遁甲、梅花易数、六爻、大六壬、小六壬、星盘、塔罗九术于一体。观微知著,以术问道。" />
9
- <script type="module" crossorigin src="/assets/index-CsOD5rSo.js"></script>
9
+ <script type="module" crossorigin src="/assets/index-C7ql6zvs.js"></script>
10
10
  <link rel="stylesheet" crossorigin href="/assets/index-DQx62TUr.css">
11
11
  </head>
12
12
  <body>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "guanwei",
3
3
  "private": false,
4
- "version": "1.1.1",
4
+ "version": "1.1.2",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite",
@@ -1,21 +1,32 @@
1
1
  #!/usr/bin/env bash
2
2
  # ============================================================
3
- # 观微发版脚本:升级版本号 → 同步 lockfile → 打 tag → 推送
4
- # 用法:./scripts/release.sh 1.1.2 (新版本号,不带 v 前缀)
3
+ # 观微发版脚本:升级版本号 → 同步 lockfile → 打 tag → 推送 git → (可选)发布 npm
4
+ # 用法:./scripts/release.sh 1.1.2 仅 git 发版(tag 触发 CI/Release/镜像)
5
+ # ./scripts/release.sh 1.1.2 --npm 额外发布 npm 包(需先配置 npm 登录)
5
6
  # 要求:工作区无未提交改动;CHANGELOG.md 已写好本次版本条目
7
+ # 详见(本地文档):docs/RELEASE-SOP.md
6
8
  # ============================================================
7
9
  set -euo pipefail
8
10
  cd "$(dirname "$0")/.."
9
11
 
10
12
  NEW_VER="${1:-}"
13
+ DO_NPM=0
14
+ if [[ "${2:-}" == "--npm" ]]; then DO_NPM=1; fi
11
15
  if [[ -z "$NEW_VER" ]] || ! [[ "$NEW_VER" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
12
- echo "用法: ./scripts/release.sh <版本号> 例如: ./scripts/release.sh 1.1.2"; exit 1
16
+ echo "用法: ./scripts/release.sh <版本号> [--npm] 例如: ./scripts/release.sh 1.1.2 --npm"; exit 1
13
17
  fi
14
18
 
15
19
  if [[ -n "$(git status --porcelain)" ]]; then
16
20
  echo "⚠️ 工作区有未提交改动,先提交或 stash 再发版"; exit 1
17
21
  fi
18
22
 
23
+ # 0. 发版前必须全量测试通过(后端集成测试会起服务)
24
+ echo "0️⃣ 全量测试(npm test)..."
25
+ if ! npm test >/tmp/guanwei-release-test.log 2>&1; then
26
+ echo " ❌ 测试未通过,见 /tmp/guanwei-release-test.log"; exit 1
27
+ fi
28
+ echo " ✅ 测试通过"
29
+
19
30
  echo "1️⃣ 更新 package.json / server/package.json -> ${NEW_VER}"
20
31
  node -e "
21
32
  const fs = require('fs');
@@ -33,10 +44,23 @@ if ! grep -q "^## \[${NEW_VER}\]" CHANGELOG.md; then
33
44
  read -rp " 已补好?回车继续: " _
34
45
  fi
35
46
 
36
- echo "3️⃣ 提交 + 打 tag + 推送"
47
+ echo "3️⃣ 提交 + 打 tag + 推送 git"
37
48
  git add package.json package-lock.json server/package.json server/package-lock.json CHANGELOG.md
38
49
  git commit -m "chore: 版本升级 ${NEW_VER}"
39
50
  git tag "v${NEW_VER}"
40
51
  git push origin main
41
52
  git push origin "v${NEW_VER}"
42
- echo "🎉 v${NEW_VER} 已推送——CI / Release / 镜像 / Pages 将自动执行"
53
+ echo " ✅ 已推送——CI / Release / 镜像 / Pages 将自动执行"
54
+
55
+ if [[ "$DO_NPM" == "1" ]]; then
56
+ echo "4️⃣ 发布 npm(registry 必须是官方源,本机默认 npmmirror 需显式指定)..."
57
+ # 用本地缓存避免 ~/.npm 写入受限;发布失败不中断后续提示
58
+ npm publish --registry https://registry.npmjs.org --cache ./.npm-cache || echo " ⚠️ npm publish 失败——按 docs/RELEASE-SOP.md 第四节排查(2FA/权限/registry)"
59
+ rm -rf .npm-cache
60
+ echo "5️⃣ 验证线上版本..."
61
+ npm view guanwei version --registry https://registry.npmjs.org --cache ./.npm-cache 2>/dev/null || true
62
+ rm -rf .npm-cache
63
+ else
64
+ echo "(未加 --npm:跳过 npm 发布。如需发布:./scripts/release.sh ${NEW_VER} --npm,或手动 npm publish)"
65
+ fi
66
+ echo "🎉 v${NEW_VER} 发版完成"
@@ -12054,6 +12054,27 @@
12054
12054
  }
12055
12055
  }
12056
12056
  ]
12057
+ },
12058
+ {
12059
+ "username": "联调3661",
12060
+ "passHash": "h7a11ca53",
12061
+ "createdAt": 1787394463725,
12062
+ "profile": {
12063
+ "birthDate": "1996-09-09",
12064
+ "birthHourIndex": 5,
12065
+ "gender": "男",
12066
+ "location": null
12067
+ },
12068
+ "samples": [],
12069
+ "records": [
12070
+ {
12071
+ "artId": "bazi",
12072
+ "createdAt": 1,
12073
+ "result": {
12074
+ "dayGZ": "壬申"
12075
+ }
12076
+ }
12077
+ ]
12057
12078
  }
12058
12079
  ]
12059
12080
  }
@@ -63,6 +63,12 @@ router.post('/interpret', async (req, res) => {
63
63
  if (e.message === 'AI_UNCONFIGURED') {
64
64
  return res.status(503).json({ error: 'AI_UNCONFIGURED', message: '未配置 AI 服务,请设置 LLM_PROVIDER 与对应 Key' });
65
65
  }
66
+ // 透传服务商 HTTP 错误码(401 Key 无效 / 404 模型不存在 / 429 额度限流 / 5xx 故障等),前端给专门引导
67
+ const mapped = mapLlmError(e);
68
+ if (mapped) {
69
+ console.error('[ai/interpret]', e.message);
70
+ return res.status(502).json({ error: mapped.code, message: mapped.message });
71
+ }
66
72
  console.error('[ai/interpret]', e);
67
73
  res.status(502).json({ error: 'AI_FAILED', message: 'AI 解读暂未应机' });
68
74
  }
@@ -141,6 +147,18 @@ router.post('/interpret/stream', async (req, res) => {
141
147
  }
142
148
  return;
143
149
  }
150
+ // 透传服务商 HTTP 错误码,前端给专门引导
151
+ const mapped = mapLlmError(e);
152
+ if (mapped) {
153
+ console.error('[ai/stream]', e.message);
154
+ if (res.headersSent) {
155
+ res.write(`data: ${JSON.stringify({ type: 'error', code: mapped.code, message: mapped.message })}\n\n`);
156
+ res.end();
157
+ } else {
158
+ res.status(502).json({ error: mapped.code, message: mapped.message });
159
+ }
160
+ return;
161
+ }
144
162
  console.error('[ai/stream]', e);
145
163
  if (res.headersSent) {
146
164
  res.write(`data: ${JSON.stringify({ type: 'error', code: 'AI_FAILED', message: 'AI 解读暂未应机' })}\n\n`);
@@ -373,4 +391,23 @@ export function parseSections(text: string): { title: string; content: string }[
373
391
  return [{ title: 'AI 参详', content: cleaned || 'AI 未返回内容' }];
374
392
  }
375
393
 
394
+ // LLM 服务商 HTTP 错误 → 用户可行动的引导码(AI_HTTP_401 等透传给前端)
395
+ export function mapLlmError(e: any): { code: string; message: string } | null {
396
+ const m = /^AI_HTTP_(\d{3})$/.exec(String(e?.message || ''));
397
+ if (!m) return null;
398
+ const status = Number(m[1]);
399
+ switch (status) {
400
+ case 401: return { code: 'AI_HTTP_401', message: 'API Key 无效或已过期——请检查 server/.env 中的 Key 是否正确(guanwei doctor 可自检)' };
401
+ case 403: return { code: 'AI_HTTP_403', message: 'API Key 无权限访问该模型——请检查账号权限或更换模型' };
402
+ case 404: return { code: 'AI_HTTP_404', message: '模型不存在或模型名有误——请检查 .env 中 LLM_*_MODEL 配置' };
403
+ case 402:
404
+ case 429: return { code: 'AI_HTTP_429', message: '请求过于频繁或额度不足——请稍后重试,或检查服务商账户余额' };
405
+ case 500:
406
+ case 502:
407
+ case 503:
408
+ case 504: return { code: 'AI_HTTP_' + status, message: 'AI 服务商暂时不可用——请稍后重试' };
409
+ default: return { code: 'AI_HTTP_' + status, message: 'AI 服务响应异常(HTTP ' + status + ')——请稍后重试' };
410
+ }
411
+ }
412
+
376
413
  export default router;
@@ -54,6 +54,23 @@ const PANEL_META: Record<string, { panelTitle: string; btn: string; btnSmall: st
54
54
  tarot: { panelTitle: '问镜', btn: '洗 牌 抽 牌', btnSmall: '静心默问 · 凭心取牌' },
55
55
  };
56
56
 
57
+ // AI 错误码 → 用户可行动的引导文案(与后端 mapLlmError 呼应;null = 无专门引导)
58
+ function aiErrorHint(code: string): string | null {
59
+ switch (code) {
60
+ case 'AI_HTTP_401': return '💡 多半是 API Key 无效或过期:检查服务端 .env 中 Key 是否正确(可运行 guanwei doctor 自检)。';
61
+ case 'AI_HTTP_403': return '💡 API Key 无权限访问该模型:请检查服务商账号权限,或换用其他服务商 Key。';
62
+ case 'AI_HTTP_404': return '💡 模型名有误:请检查服务端 .env 中 LLM_*_MODEL 配置与所选服务商是否匹配。';
63
+ case 'AI_HTTP_429': return '💡 请求过频或额度不足:稍等片刻再试;若常触发,可检查服务商账户余额。';
64
+ case 'AI_HTTP_500':
65
+ case 'AI_HTTP_502':
66
+ case 'AI_HTTP_503':
67
+ case 'AI_HTTP_504': return '💡 AI 服务商暂时不可用:过一会儿再试即可,无需改动配置。';
68
+ case 'STREAM_TIMEOUT': return '💡 生成超时:网络波动或服务繁忙,请重试。';
69
+ case 'NETWORK': return '💡 网络连接异常:请检查本机网络,或确认服务仍在运行(guanwei status)。';
70
+ default: return null;
71
+ }
72
+ }
73
+
57
74
  export default function ModulePage() {
58
75
  const { artId } = useParams<{ artId: string }>();
59
76
  const art = artId ? artById(artId) : undefined;
@@ -289,6 +306,8 @@ export default function ModulePage() {
289
306
  <li>配置后重启服务即可;也可运行 <code style={{ background: 'rgba(0,0,0,.06)', padding: '0 .3rem' }}>guanwei doctor</code> 检查配置</li>
290
307
  </ol>
291
308
  </div>
309
+ ) : aiErrorHint(ai.errorCode) ? (
310
+ <p className="tiny muted" style={{ marginTop: '.4rem' }}>{aiErrorHint(ai.errorCode)}</p>
292
311
  ) : (
293
312
  <p className="tiny muted">古法规则解读为备,可先依上列排盘自行观照;若为 AI 服务异常,可于服务端检查 LLM 配置后重试。</p>
294
313
  )}