guanwei 1.1.1 → 1.1.3

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.
Files changed (55) hide show
  1. package/README.en.md +5 -2
  2. package/README.md +6 -3
  3. package/dist/assets/index-C3wrtpPY.css +1 -0
  4. package/dist/assets/{index-CsOD5rSo.js → index-CAyAIgw8.js} +25 -25
  5. package/dist/assets/index-CAyAIgw8.js.map +1 -0
  6. package/dist/index.html +2 -2
  7. package/package.json +2 -2
  8. package/scripts/release.sh +29 -5
  9. package/server/src/data/db.json +168 -0
  10. package/server/src/index.ts +27 -4
  11. package/server/src/routes/ai.ts +37 -0
  12. package/server/src/routes/tarot.ts +4 -4
  13. package/server/src/routes/users.ts +42 -7
  14. package/server/src/services/divineStore.ts +1 -5
  15. package/server/src/services/promptBuilder.ts +2 -2
  16. package/server/src/types/index.ts +2 -104
  17. package/{src → shared/core}/data/lifeContext.ts +2 -8
  18. package/{src → shared/core}/data/spreads.ts +1 -1
  19. package/{src → shared/core}/data/tarotCards.ts +1 -1
  20. package/shared/core/engine/bazi.ts +0 -1
  21. package/shared/core/engine/calendar.ts +1 -1
  22. package/{server/src/utils → shared/core/engine}/semanticAnalyzer.ts +9 -46
  23. package/{server/src/utils → shared/core/engine}/tarotEngine.ts +6 -6
  24. package/shared/core/engine/ziwei.ts +3 -2
  25. package/shared/core/engine/ziweiInterpret.ts +0 -3
  26. package/shared/core/types.ts +157 -0
  27. package/src/arts/registry.ts +1 -1
  28. package/src/components/QuestionFields.tsx +0 -1
  29. package/src/components/ReportView.tsx +0 -1
  30. package/src/components/TimeShichenInput.tsx +1 -0
  31. package/src/components/arts/BaziArt.tsx +0 -10
  32. package/src/components/arts/LiurenArt.tsx +1 -1
  33. package/src/components/arts/LiuyaoArt.tsx +0 -1
  34. package/src/components/arts/MeihuaArt.tsx +0 -1
  35. package/src/components/arts/QimenArt.tsx +0 -1
  36. package/src/components/arts/TarotArt.tsx +2 -2
  37. package/src/components/arts/XiaoliurenArt.tsx +1 -2
  38. package/src/components/arts/ZiweiArt.tsx +2 -15
  39. package/src/pages/AuthPage.tsx +4 -6
  40. package/src/pages/DemoPage.tsx +1 -2
  41. package/src/pages/HistoryPage.tsx +10 -9
  42. package/src/pages/ModulePage.tsx +25 -32
  43. package/src/pages/SpreadLibrary.tsx +1 -1
  44. package/src/pages/TarotPage.tsx +7 -9
  45. package/src/services/api.ts +4 -4
  46. package/src/stores/useDivinationStore.ts +1 -1
  47. package/src/types/index.ts +2 -104
  48. package/tsconfig.json +5 -5
  49. package/dist/assets/index-CsOD5rSo.js.map +0 -1
  50. package/dist/assets/index-DQx62TUr.css +0 -1
  51. package/server/src/data/lifeContext.ts +0 -423
  52. package/server/src/data/spreads.ts +0 -255
  53. package/server/src/data/tarotCards.ts +0 -2353
  54. package/src/utils/semanticAnalyzer.ts +0 -555
  55. package/src/utils/tarotEngine.ts +0 -849
@@ -7,40 +7,13 @@ import { useDivine } from '@/hooks/useDivine';
7
7
  import { useAIInterpret } from '@/hooks/useAIInterpret';
8
8
  import { artPairOf } from '@/arts/registry';
9
9
  import { saveRecord } from '@/utils/recordStore';
10
- import { downloadReport, apiDivine, type AIReport } from '@/services/api';
10
+ import { apiDivine, type AIReport } from '@/services/api';
11
11
  import ReportView from '@/components/ReportView';
12
12
  import { currentUser, addSampleProfile } from '@/utils/userStore';
13
13
  import { analyzeQuestionFit } from '@core/engine/questionFit';
14
14
  import type { UserProfile } from '@/utils/userStore';
15
15
  import { useEffect, useRef, useState } from 'react';
16
- import { BaziPanel, BaziResult } from '@/components/arts/BaziArt';
17
- import { ZiweiPanel, ZiweiResult } from '@/components/arts/ZiweiArt';
18
- import { QimenPanel, QimenResultView } from '@/components/arts/QimenArt';
19
- import { MeihuaPanel, MeihuaResult } from '@/components/arts/MeihuaArt';
20
- import { LiuyaoPanel, LiuyaoResult } from '@/components/arts/LiuyaoArt';
21
- import { LiurenPanel, LiurenResult } from '@/components/arts/LiurenArt';
22
- import { XiaoliurenPanel, XiaoliurenResult } from '@/components/arts/XiaoliurenArt';
23
- import { AstrologyPanel, AstrologyResult } from '@/components/arts/AstrologyArt';
24
- import { TarotPanel, TarotResult } from '@/components/arts/TarotArt';
25
- import type { ComponentType } from 'react';
26
16
 
27
- interface ArtPair {
28
- Panel: ComponentType<{ onDivine: (fn: () => unknown) => void }>;
29
- Result: ComponentType<{ data: unknown }>;
30
- placeholder: string;
31
- }
32
-
33
- const ART_PAIRS: Record<string, ArtPair> = {
34
- bazi: { Panel: BaziPanel, Result: BaziResult, placeholder: '静候四柱 · 观五行流转' },
35
- ziwei: { Panel: ZiweiPanel, Result: ZiweiResult, placeholder: '静候星布 · 观十四主星' },
36
- qimen: { Panel: QimenPanel, Result: QimenResultView, placeholder: '静候局成 · 观九宫遁甲' },
37
- meihua: { Panel: MeihuaPanel, Result: MeihuaResult, placeholder: '静候心动 · 观梅花开落' },
38
- liuyao: { Panel: LiuyaoPanel, Result: LiuyaoResult, placeholder: '静候钱落 · 观六爻成象' },
39
- liuren: { Panel: LiurenPanel, Result: LiurenResult, placeholder: '静候课起 · 观天地人盘' },
40
- xiaoliuren: { Panel: XiaoliurenPanel, Result: XiaoliurenResult, placeholder: '静候指落 · 观掌诀玄机' },
41
- astrology: { Panel: AstrologyPanel, Result: AstrologyResult, placeholder: '静候星布 · 观天穹为书' },
42
- tarot: { Panel: TarotPanel, Result: TarotResult, placeholder: '静候牌启 · 观镜照本心' },
43
- };
44
17
 
45
18
  const PANEL_META: Record<string, { panelTitle: string; btn: string; btnSmall: string }> = {
46
19
  bazi: { panelTitle: '布盘', btn: '排 盘', btnSmall: '布四柱 · 观五行 · 推十神' },
@@ -54,6 +27,23 @@ const PANEL_META: Record<string, { panelTitle: string; btn: string; btnSmall: st
54
27
  tarot: { panelTitle: '问镜', btn: '洗 牌 抽 牌', btnSmall: '静心默问 · 凭心取牌' },
55
28
  };
56
29
 
30
+ // AI 错误码 → 用户可行动的引导文案(与后端 mapLlmError 呼应;null = 无专门引导)
31
+ function aiErrorHint(code: string): string | null {
32
+ switch (code) {
33
+ case 'AI_HTTP_401': return '💡 多半是 API Key 无效或过期:检查服务端 .env 中 Key 是否正确(可运行 guanwei doctor 自检)。';
34
+ case 'AI_HTTP_403': return '💡 API Key 无权限访问该模型:请检查服务商账号权限,或换用其他服务商 Key。';
35
+ case 'AI_HTTP_404': return '💡 模型名有误:请检查服务端 .env 中 LLM_*_MODEL 配置与所选服务商是否匹配。';
36
+ case 'AI_HTTP_429': return '💡 请求过频或额度不足:稍等片刻再试;若常触发,可检查服务商账户余额。';
37
+ case 'AI_HTTP_500':
38
+ case 'AI_HTTP_502':
39
+ case 'AI_HTTP_503':
40
+ case 'AI_HTTP_504': return '💡 AI 服务商暂时不可用:过一会儿再试即可,无需改动配置。';
41
+ case 'STREAM_TIMEOUT': return '💡 生成超时:网络波动或服务繁忙,请重试。';
42
+ case 'NETWORK': return '💡 网络连接异常:请检查本机网络,或确认服务仍在运行(guanwei status)。';
43
+ default: return null;
44
+ }
45
+ }
46
+
57
47
  export default function ModulePage() {
58
48
  const { artId } = useParams<{ artId: string }>();
59
49
  const art = artId ? artById(artId) : undefined;
@@ -89,10 +79,11 @@ export default function ModulePage() {
89
79
  };
90
80
  const isProfileArt = ['bazi', 'ziwei', 'astrology'].includes(art?.id || '');
91
81
  const exportReport = (rep: AIReport) => {
82
+ const artName = art?.name || '观微';
92
83
  const lines = [
93
84
  '# ' + (rep.title || '观微解读报告'),
94
85
  '',
95
- '> 术别:' + art.name + ' · ' + new Date().toLocaleString('zh-CN'),
86
+ '> 术别:' + artName + ' · ' + new Date().toLocaleString('zh-CN'),
96
87
  '',
97
88
  '## 总述',
98
89
  '',
@@ -220,11 +211,11 @@ export default function ModulePage() {
220
211
  <p>凝神静气 · 卦象将成</p>
221
212
  </div>
222
213
  )}
223
- {status === 'done' && result && PairResult && <PairResult data={result} />}
214
+ {status === 'done' && result ? (PairResult ? <PairResult data={result} /> : null) : null}
224
215
  {status === 'error' && (
225
216
  <ResultPlaceholder glyph="滞" text={divineError || '推演未应机 · 请稍后再试'} />
226
217
  )}
227
- {status === 'done' && result && (
218
+ {status === 'done' && result ? (
228
219
  <div className="result-card mt-3">
229
220
  <h3>AI 深度解读</h3>
230
221
  {ai.status === 'idle' && (
@@ -289,6 +280,8 @@ export default function ModulePage() {
289
280
  <li>配置后重启服务即可;也可运行 <code style={{ background: 'rgba(0,0,0,.06)', padding: '0 .3rem' }}>guanwei doctor</code> 检查配置</li>
290
281
  </ol>
291
282
  </div>
283
+ ) : aiErrorHint(ai.errorCode) ? (
284
+ <p className="tiny muted" style={{ marginTop: '.4rem' }}>{aiErrorHint(ai.errorCode)}</p>
292
285
  ) : (
293
286
  <p className="tiny muted">古法规则解读为备,可先依上列排盘自行观照;若为 AI 服务异常,可于服务端检查 LLM 配置后重试。</p>
294
287
  )}
@@ -310,7 +303,7 @@ export default function ModulePage() {
310
303
  )}
311
304
  <button className="btn-seal btn-ghost no-print" style={{ marginTop: 'var(--sp-3)', fontSize: '.9rem', padding: '.5rem 1.4rem' }} onClick={() => { ai.reset(); reset(); }}>再 起 一 占</button>
312
305
  </div>
313
- )}
306
+ ) : null}
314
307
  </div>
315
308
  </div>
316
309
  <Disclaimer />
@@ -1,7 +1,7 @@
1
1
  import { useState, useMemo } from 'react';
2
2
  import { Link } from 'react-router-dom';
3
3
  import { Search, Plus, Sparkles, Filter, X, Star } from 'lucide-react';
4
- import { defaultSpreads } from '@/data/spreads';
4
+ import { defaultSpreads } from '@core/data/spreads';
5
5
  import { getCustomSpreads } from '@/utils/spreadStorage';
6
6
  import type { Spread } from '@/types';
7
7
  import { useEffect } from 'react';
@@ -1,13 +1,13 @@
1
1
  import { useState, useCallback, useEffect, useMemo, useRef } from 'react';
2
2
  import { useNavigate } from 'react-router-dom';
3
- import { Sparkles, Shuffle, Edit3, ChevronDown, ChevronUp, RotateCcw, Search, Plus, Library, X, Play, Pause, SkipForward, SkipBack } from 'lucide-react';
4
- import type { QuestionCategory, Spread, DrawnCard, TarotCard as TarotCardType } from '@/types';
5
- import { defaultSpreads } from '@/data/spreads';
6
- import { tarotCards } from '@/data/tarotCards';
7
- import { drawCards, generateInterpretation } from '@/utils/tarotEngine';
3
+ import { Sparkles, Shuffle, Edit3, RotateCcw, Search, Library, X, Play, Pause, SkipForward, SkipBack } from 'lucide-react';
4
+ import type { QuestionCategory, Spread, DrawnCard } from '@/types';
5
+ import { defaultSpreads } from '@core/data/spreads';
6
+ import { tarotCards } from '@core/data/tarotCards';
8
7
  import { generateComboResult } from '@/utils/comboEngine';
9
- import { saveRecord } from '@/utils/storage';
8
+
10
9
  import { getCustomSpreads } from '@/utils/spreadStorage';
10
+ import { saveRecord } from '@/utils/storage';
11
11
  import { api } from '@/services/api';
12
12
  import { useDivinationStore } from '@/stores/useDivinationStore';
13
13
  import TarotCard from '@/components/TarotCard';
@@ -89,7 +89,6 @@ export default function TarotPage() {
89
89
  const [comboResultLocal, setComboResultLocal] = useState<any>(null);
90
90
  const [manualMode, setManualMode] = useState(false);
91
91
  const [manualCards, setManualCards] = useState<{ cardId: number; isReversed: boolean }[]>([]);
92
- const [expandedSection, setExpandedSection] = useState<string | null>(null);
93
92
  const [showLibrary, setShowLibrary] = useState(false);
94
93
  const [cardSearch, setCardSearch] = useState('');
95
94
  const [activeSuit, setActiveSuit] = useState('all');
@@ -213,7 +212,6 @@ export default function TarotPage() {
213
212
  setIsComboEnabled(false);
214
213
  setManualMode(false);
215
214
  setManualCards([]);
216
- setExpandedSection(null);
217
215
  setCurrentSectionIndex(0);
218
216
  setCompletedSections([]);
219
217
  setIsPlaying(false);
@@ -664,7 +662,7 @@ export default function TarotPage() {
664
662
  </div>
665
663
  </div>
666
664
  <div className="mt-4 flex items-center gap-2">
667
- {interpretation.sections.map((_, idx) => (
665
+ {interpretation.sections.map((_: unknown, idx: number) => (
668
666
  <button
669
667
  key={idx}
670
668
  onClick={() => goToSection(idx)}
@@ -1,5 +1,5 @@
1
1
  import type { QuestionCategory, Spread, DrawnCard } from '@/types';
2
- import type { InterpretationResult } from '@/utils/tarotEngine';
2
+ import type { InterpretationResult } from '@core/engine/tarotEngine';
3
3
 
4
4
  // 开发环境直连后端(规避 proxy/IPv6 组合的 SSE 不确定性);生产同域 /api
5
5
  const API_BASE = (import.meta.env.VITE_API_BASE as string | undefined) || '/api';
@@ -189,7 +189,7 @@ export async function aiInterpretStream(
189
189
  // 超时从「收到首个字节」起算(两步管线 Step1 为非流式,首字节前可能等待 30-120 秒,不能误判超时)
190
190
  timeoutTimer = setInterval(() => {
191
191
  if (firstByteAt && Date.now() - firstByteAt > 180000) {
192
- clearInterval(timeoutTimer);
192
+ if (timeoutTimer) clearInterval(timeoutTimer);
193
193
  controller.abort();
194
194
  fail('STREAM_TIMEOUT', 'AI 生成逾时,请重试');
195
195
  }
@@ -240,7 +240,7 @@ export interface DivineResult {
240
240
  display?: unknown;
241
241
  }
242
242
 
243
- export async function apiDivine(username: string, artId: string, inputs: unknown, profile?: unknown, question?: string, profileId?: string): Promise<DivineResult> {
243
+ export async function apiDivine(username: string, artId: string, inputs: unknown, profile?: unknown, question?: string, _profileId?: string): Promise<DivineResult> {
244
244
  const res = await fetch(API_BASE + '/divine', {
245
245
  method: 'POST',
246
246
  headers: { 'Content-Type': 'application/json' },
@@ -290,7 +290,7 @@ export function downloadReport(report: AIReport, artName: string, question: stri
290
290
  '',
291
291
  report.overview,
292
292
  '',
293
- ...report.chapters.flatMap(c => ['## ' + (c.skill || '参详'), '', c.content, '']),
293
+ ...(report.chapters || []).flatMap(c => ['## ' + (c.skill || '参详'), '', c.content, '']),
294
294
  '## 结语',
295
295
  '',
296
296
  report.conclusion,
@@ -1,5 +1,5 @@
1
1
  import { create } from 'zustand';
2
- import type { TarotReading, ComboResult, QuestionCategory, DivinationRecord, DrawnCard } from '@/types';
2
+ import type { TarotReading, ComboResult } from '@/types';
3
3
 
4
4
  interface DivinationState {
5
5
  currentReading: TarotReading | null;
@@ -1,104 +1,2 @@
1
- export interface TarotCard {
2
- id: number;
3
- name: string;
4
- nameEn: string;
5
- arcana: 'major' | 'minor';
6
- suit?: 'wands' | 'cups' | 'swords' | 'pentacles';
7
- number?: number;
8
- keywords: string[];
9
- meanings: {
10
- upright: string;
11
- reversed: string;
12
- };
13
- deepMeaning: {
14
- coreTheme: string;
15
- psychological: string;
16
- spiritual: string;
17
- shadow: string;
18
- lifeAreas: {
19
- love: string;
20
- career: string;
21
- wealth: string;
22
- health: string;
23
- growth: string;
24
- };
25
- advice: string;
26
- warning: string;
27
- };
28
- imageSymbols: string[];
29
- story: string;
30
- astrology: {
31
- zodiac?: string;
32
- planet?: string;
33
- element?: string;
34
- };
35
- kabbalah: {
36
- path?: number;
37
- sephirah?: string;
38
- };
39
- alchemy: {
40
- stage?: string;
41
- element?: string;
42
- };
43
- numerology: {
44
- number: number;
45
- meaning: string;
46
- };
47
- image: string;
48
- }
49
-
50
- export interface SpreadPosition {
51
- id: number;
52
- name: string;
53
- description: string;
54
- x: number;
55
- y: number;
56
- }
57
-
58
- export interface Spread {
59
- id: string;
60
- name: string;
61
- description: string;
62
- positions: SpreadPosition[];
63
- category: 'basic' | 'advanced' | 'custom';
64
- scene?: string[];
65
- isCustom?: boolean;
66
- }
67
-
68
- export interface DrawnCard {
69
- cardId: number;
70
- isReversed: boolean;
71
- positionId: number;
72
- }
73
-
74
- export interface DivinationRecord {
75
- id: string;
76
- type: 'tarot' | 'combo' | 'ziwei' | 'bazi' | 'astrology';
77
- question: string;
78
- category: string;
79
- spreadId?: string;
80
- cards?: DrawnCard[];
81
- comboMethod?: string;
82
- comboResult?: string;
83
- interpretation: string;
84
- timestamp: number;
85
- }
86
-
87
- export interface ComboResult {
88
- method: 'xiaoliuren' | 'daliuren' | 'liuyao' | 'meihua' | 'qimen';
89
- methodName: string;
90
- result: string;
91
- detail: string;
92
- relationToTarot: string;
93
- }
94
-
95
- export type QuestionCategory = 'love' | 'career' | 'wealth' | 'health' | 'study' | 'family' | 'friendship' | 'spiritual' | 'travel' | 'general';
96
-
97
- export interface TarotReading {
98
- question: string;
99
- category: QuestionCategory;
100
- spreadId: string;
101
- cards: DrawnCard[];
102
- interpretation: string;
103
- comboResult?: ComboResult;
104
- }
1
+ // 类型桥(单一副本):全部类型收敛于 shared/core/types.ts,此处仅 re-export 保持旧导入路径兼容
2
+ export * from '@core/types';
package/tsconfig.json CHANGED
@@ -16,10 +16,10 @@
16
16
  "moduleDetection": "force",
17
17
  "noEmit": true,
18
18
  "jsx": "react-jsx",
19
- "strict": false,
20
- "noUnusedLocals": false,
21
- "noUnusedParameters": false,
22
- "noFallthroughCasesInSwitch": false,
19
+ "strict": true,
20
+ "noUnusedLocals": true,
21
+ "noUnusedParameters": true,
22
+ "noFallthroughCasesInSwitch": true,
23
23
  "noUncheckedSideEffectImports": false,
24
24
  "forceConsistentCasingInFileNames": false,
25
25
  "ignoreDeprecations": "5.0",
@@ -37,4 +37,4 @@
37
37
  "src",
38
38
  "api"
39
39
  ]
40
- }
40
+ }