guanwei 1.1.1

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 (157) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +202 -0
  3. package/README.md +247 -0
  4. package/dist/assets/index-CsOD5rSo.js +102 -0
  5. package/dist/assets/index-CsOD5rSo.js.map +1 -0
  6. package/dist/assets/index-DQx62TUr.css +1 -0
  7. package/dist/favicon.svg +12 -0
  8. package/dist/index.html +15 -0
  9. package/eslint.config.js +28 -0
  10. package/index.html +25 -0
  11. package/package.json +96 -0
  12. package/postcss.config.js +10 -0
  13. package/public/favicon.svg +12 -0
  14. package/scripts/guanwei +245 -0
  15. package/scripts/install.sh +79 -0
  16. package/scripts/release.sh +42 -0
  17. package/scripts/setup.bat +36 -0
  18. package/scripts/setup.sh +123 -0
  19. package/server/src/data/db.json +12059 -0
  20. package/server/src/data/guanwei.db +0 -0
  21. package/server/src/data/lifeContext.ts +423 -0
  22. package/server/src/data/spreads.ts +255 -0
  23. package/server/src/data/tarotCards.ts +2353 -0
  24. package/server/src/index.ts +71 -0
  25. package/server/src/routes/ai.ts +376 -0
  26. package/server/src/routes/divine.ts +140 -0
  27. package/server/src/routes/hour.ts +40 -0
  28. package/server/src/routes/tarot.ts +129 -0
  29. package/server/src/routes/users.ts +140 -0
  30. package/server/src/services/chartBrief.ts +131 -0
  31. package/server/src/services/divineStore.ts +188 -0
  32. package/server/src/services/hourInference.ts +150 -0
  33. package/server/src/services/llmProvider.ts +181 -0
  34. package/server/src/services/promptBuilder.ts +327 -0
  35. package/server/src/services/relativesCheck.ts +139 -0
  36. package/server/src/services/sixRelatives.ts +70 -0
  37. package/server/src/services/skills.ts +116 -0
  38. package/server/src/types/index.ts +104 -0
  39. package/server/src/utils/semanticAnalyzer.ts +555 -0
  40. package/server/src/utils/tarotEngine.ts +849 -0
  41. package/server/tsconfig.json +18 -0
  42. package/shared/core/data/classics.ts +276 -0
  43. package/shared/core/data/courses.ts +211 -0
  44. package/shared/core/data/duanyu.ts +148 -0
  45. package/shared/core/data/ganzhi.ts +53 -0
  46. package/shared/core/data/gua64.ts +93 -0
  47. package/shared/core/data/liuren.ts +18 -0
  48. package/shared/core/data/liuyao.ts +73 -0
  49. package/shared/core/data/qimen.ts +27 -0
  50. package/shared/core/data/region-full.ts +3 -0
  51. package/shared/core/data/region.ts +310 -0
  52. package/shared/core/data/tarotSpreads.ts +78 -0
  53. package/shared/core/data/xiaoliuren.ts +18 -0
  54. package/shared/core/data/ziwei.ts +97 -0
  55. package/shared/core/data/zodiac.ts +20 -0
  56. package/shared/core/engine/astrology.ts +169 -0
  57. package/shared/core/engine/bazi.ts +306 -0
  58. package/shared/core/engine/calendar.ts +127 -0
  59. package/shared/core/engine/daily.ts +133 -0
  60. package/shared/core/engine/liuren.ts +86 -0
  61. package/shared/core/engine/liuyao.ts +93 -0
  62. package/shared/core/engine/meihua.ts +87 -0
  63. package/shared/core/engine/qimen.ts +78 -0
  64. package/shared/core/engine/questionFit.ts +94 -0
  65. package/shared/core/engine/tarot.ts +69 -0
  66. package/shared/core/engine/trueSolarTime.ts +86 -0
  67. package/shared/core/engine/xiaoliuren.ts +19 -0
  68. package/shared/core/engine/ziwei.ts +217 -0
  69. package/shared/core/engine/ziweiInterpret.ts +161 -0
  70. package/shared/core/types.ts +206 -0
  71. package/src/App.tsx +69 -0
  72. package/src/arts/registry.ts +31 -0
  73. package/src/assets/react.svg +1 -0
  74. package/src/components/Backdrop.tsx +27 -0
  75. package/src/components/BambooArt.tsx +41 -0
  76. package/src/components/CalendarInput.tsx +131 -0
  77. package/src/components/DateInput.tsx +103 -0
  78. package/src/components/Disclaimer.tsx +12 -0
  79. package/src/components/Empty.tsx +8 -0
  80. package/src/components/ErrorBoundary.tsx +38 -0
  81. package/src/components/HomeSideAnchor.tsx +62 -0
  82. package/src/components/InkVein.tsx +10 -0
  83. package/src/components/LifeEventsInput.tsx +47 -0
  84. package/src/components/LocationPicker.tsx +117 -0
  85. package/src/components/Motes.tsx +65 -0
  86. package/src/components/Navigation.tsx +80 -0
  87. package/src/components/ProfileForm.tsx +40 -0
  88. package/src/components/ProfilePicker.tsx +40 -0
  89. package/src/components/QuestionFields.tsx +47 -0
  90. package/src/components/ReportView.tsx +211 -0
  91. package/src/components/ResultCard.tsx +40 -0
  92. package/src/components/SealButton.tsx +18 -0
  93. package/src/components/SiteFooter.tsx +17 -0
  94. package/src/components/SiteNav.tsx +103 -0
  95. package/src/components/SongDialog.tsx +64 -0
  96. package/src/components/SongSearchSelect.tsx +81 -0
  97. package/src/components/SongSelect.tsx +61 -0
  98. package/src/components/StarField.tsx +119 -0
  99. package/src/components/TarotCard.tsx +129 -0
  100. package/src/components/TimeShichenInput.tsx +29 -0
  101. package/src/components/arts/AstrologyArt.tsx +141 -0
  102. package/src/components/arts/BaziArt.tsx +269 -0
  103. package/src/components/arts/LiurenArt.tsx +87 -0
  104. package/src/components/arts/LiuyaoArt.tsx +111 -0
  105. package/src/components/arts/MeihuaArt.tsx +105 -0
  106. package/src/components/arts/QimenArt.tsx +86 -0
  107. package/src/components/arts/TarotArt.tsx +110 -0
  108. package/src/components/arts/XiaoliurenArt.tsx +93 -0
  109. package/src/components/arts/ZiweiArt.tsx +296 -0
  110. package/src/data/arts.ts +84 -0
  111. package/src/data/lifeContext.ts +423 -0
  112. package/src/data/sample-report.json +146 -0
  113. package/src/data/shichen.ts +21 -0
  114. package/src/data/spreads.ts +255 -0
  115. package/src/data/tarotCards.ts +2353 -0
  116. package/src/hooks/useAIInterpret.ts +76 -0
  117. package/src/hooks/useDivine.ts +39 -0
  118. package/src/hooks/useTheme.ts +29 -0
  119. package/src/index.css +190 -0
  120. package/src/lib/utils.ts +6 -0
  121. package/src/main.tsx +12 -0
  122. package/src/pages/AboutPage.tsx +25 -0
  123. package/src/pages/AcademyPage.tsx +128 -0
  124. package/src/pages/AstrologyPage.tsx +33 -0
  125. package/src/pages/AuthPage.tsx +263 -0
  126. package/src/pages/BaZiPage.tsx +33 -0
  127. package/src/pages/ClassicsPage.tsx +120 -0
  128. package/src/pages/DemoPage.tsx +161 -0
  129. package/src/pages/HistoryPage.tsx +133 -0
  130. package/src/pages/Home.tsx +179 -0
  131. package/src/pages/HomePage.tsx +174 -0
  132. package/src/pages/ModulePage.tsx +320 -0
  133. package/src/pages/SpreadEditor.tsx +230 -0
  134. package/src/pages/SpreadEditorPage.tsx +105 -0
  135. package/src/pages/SpreadLibrary.tsx +199 -0
  136. package/src/pages/SpreadLibraryPage.tsx +17 -0
  137. package/src/pages/TarotPage.tsx +738 -0
  138. package/src/pages/ZiWeiPage.tsx +33 -0
  139. package/src/services/api.ts +342 -0
  140. package/src/stores/useDivinationStore.ts +22 -0
  141. package/src/styles/song.css +1066 -0
  142. package/src/styles/tokens.css +60 -0
  143. package/src/types/index.ts +104 -0
  144. package/src/utils/astrologyCalc.ts +61 -0
  145. package/src/utils/comboEngine.ts +134 -0
  146. package/src/utils/dst.ts +24 -0
  147. package/src/utils/panTone.ts +206 -0
  148. package/src/utils/recordStore.ts +60 -0
  149. package/src/utils/semanticAnalyzer.ts +555 -0
  150. package/src/utils/spreadStorage.ts +32 -0
  151. package/src/utils/storage.ts +27 -0
  152. package/src/utils/tarotEngine.ts +849 -0
  153. package/src/utils/userStore.ts +234 -0
  154. package/src/vite-env.d.ts +1 -0
  155. package/tailwind.config.js +52 -0
  156. package/tsconfig.json +40 -0
  157. package/vite.config.ts +34 -0
@@ -0,0 +1,555 @@
1
+ // 问题语义分析器
2
+ // 在解读阶段对用户问题进行多维度分析
3
+
4
+ export type Scene = 'love' | 'career' | 'wealth' | 'study' | 'health' | 'family' | 'friendship' | 'spiritual' | 'travel' | 'general';
5
+ export type Emotion = 'anxious' | 'hopeful' | 'confused' | 'lost' | 'grateful' | 'calm' | 'angry' | 'sad' | 'curious';
6
+ export type Tense = 'past' | 'present' | 'future' | 'hypothetical' | 'ongoing';
7
+ export type Subject = 'self' | 'other' | 'mutual' | 'third-party';
8
+ export type Depth = 'surface' | 'mid' | 'deep';
9
+
10
+ // 问题类型:决策 / 预测 / 原因 / 建议 / 读心 / 时间 / 通用
11
+ export type QuestionType = 'decision' | 'prediction' | 'reason' | 'advice' | 'mindreading' | 'timing' | 'general';
12
+ // 用户需要的回应方式:安慰 / 方向 / 确认 / 警示
13
+ export type ResponseNeed = 'comfort' | 'direction' | 'confirmation' | 'warning';
14
+
15
+ // 关键实体
16
+ export interface KeyEntity {
17
+ type: 'person' | 'event' | 'time';
18
+ value: string;
19
+ }
20
+
21
+ // 问题的深度分析
22
+ export interface QuestionAnalysis {
23
+ realQuestion: string; // 用户真正想问的是什么
24
+ emotionalNeed: string; // 问题背后的情绪需求
25
+ responseNeed: ResponseNeed; // 用户需要什么样的回应
26
+ responseNeedLabel: string;
27
+ }
28
+
29
+ export interface SemanticAnalysis {
30
+ scene: Scene;
31
+ sceneLabel: string;
32
+ emotion: Emotion;
33
+ emotionLabel: string;
34
+ tense: Tense;
35
+ tenseLabel: string;
36
+ subject: Subject;
37
+ subjectLabel: string;
38
+ depth: Depth;
39
+ depthLabel: string;
40
+ keywords: string[];
41
+ intensity: number; // 0-1,问题强度
42
+ confidence: number; // 0-1,分析置信度
43
+ // —— 以下为新增字段 ——
44
+ questionType: QuestionType; // 问题类型
45
+ questionTypeLabel: string; // 问题类型中文标签
46
+ questionCore: string; // 问题的核心意图
47
+ questionSummary: string; // 一句话概括用户问题的本质
48
+ keyEntities: KeyEntity[]; // 问题中的关键实体
49
+ questionAnalysis: QuestionAnalysis; // 深度分析问题的潜台词
50
+ }
51
+
52
+ // 场景关键词库
53
+ const SCENE_KEYWORDS: Record<Scene, { label: string; words: string[]; weight: number }> = {
54
+ love: {
55
+ label: '爱情',
56
+ words: ['爱情', '恋爱', '感情', '喜欢', '暗恋', '表白', '分手', '复合', '出轨', '暧昧', '前任', '相亲', '对象', '男朋友', '女朋友', '老公', '老婆', '夫妻', '婚姻', '嫁', '娶', '婚外', '小三', '第三者', '暧昧对象', '心动', '追', '拒', '恋爱脑', '桃花', '真爱', '情场', '求爱', '在一起', '分手后', '前男友', '前女友', '约会', '表白', '相亲'],
57
+ weight: 1.0,
58
+ },
59
+ career: {
60
+ label: '事业',
61
+ words: ['工作', '事业', '职场', '上司', '老板', '同事', '下属', '跳槽', '辞职', '面试', 'offer', '晋升', '加薪', '裁员', '辞退', '被开', '入职', '新公司', '创业', '合伙', '项目', '客户', '业绩', 'KPI', '加班', '转行', '副业', '工位', '办公', '部门', '团队', '领导', '面试官', 'hr', '招聘'],
62
+ weight: 1.0,
63
+ },
64
+ wealth: {
65
+ label: '财运',
66
+ words: ['钱', '财运', '财富', '收入', '工资', '薪水', '存款', '投资', '股票', '基金', '理财', '亏', '赚', '赔', '副业', '兼职', '失业', '还债', '债务', '贷款', '房贷', '房租', '车贷', '信用卡', '花呗', '借呗', '奖金', '遗产', '意外之财', '彩票', '赌博', '破产'],
67
+ weight: 1.0,
68
+ },
69
+ study: {
70
+ label: '学业',
71
+ words: ['考试', '高考', '中考', '考研', '留学', '托福', '雅思', '论文', '毕业', '成绩', '挂科', '录取', '面试', '专业', '学校', '老师', '同学', '学习', '读书', '上课', '复习', '学位', '学历', '考证', '面试', '答辩'],
72
+ weight: 1.0,
73
+ },
74
+ health: {
75
+ label: '健康',
76
+ words: ['身体', '健康', '生病', '病', '不舒服', '失眠', '焦虑', '抑郁', '压力', '疲惫', '累', '瘦', '胖', '减肥', '健身', '运动', '心理', '情绪', '治疗', '医生', '医院', '药', '检查', '体检', '睡眠', '亚健康'],
77
+ weight: 1.0,
78
+ },
79
+ family: {
80
+ label: '家庭',
81
+ words: ['家', '父母', '爸爸', '妈妈', '孩子', '儿子', '女儿', '老公', '老婆', '家人', '婆媳', '妯娌', '亲戚', '长辈', '子女', '育儿', '教育', '买房', '搬家', '家庭关系', '原生家庭', '离家', '父母催'],
82
+ weight: 1.0,
83
+ },
84
+ friendship: {
85
+ label: '友谊',
86
+ words: ['朋友', '闺蜜', '兄弟', '哥们', '姐妹', '友情', '友谊', '社交', '圈子', '社团', '聚会', '相处', '吃醋', '背叛', '疏远', '误会', '和好', '冷暴力'],
87
+ weight: 0.9,
88
+ },
89
+ spiritual: {
90
+ label: '灵性',
91
+ words: ['灵性', '灵魂', '前世', '轮回', '业力', '开悟', '觉醒', '冥想', '禅', '道', '佛', '缘分', '命运', '使命', '天赋', '使命召唤', '内在成长', '自我实现', '人生意义', '宇宙', '高我'],
92
+ weight: 1.0,
93
+ },
94
+ travel: {
95
+ label: '旅行',
96
+ words: ['旅行', '出差', '搬家', '留学', '移民', '签证', '远行', '出发', '目的地', '出发', '旅途', '机票', '酒店', '城市', '国家', '留居', '回去', '回乡'],
97
+ weight: 0.9,
98
+ },
99
+ general: {
100
+ label: '综合',
101
+ words: ['未来', '现在', '以后', '运势', '整体', '人生', '生活', '最近', '接下来', '应该', '怎么办', '怎么做', '该不该', '要不要', '是否'],
102
+ weight: 0.5,
103
+ },
104
+ };
105
+
106
+ // 情绪关键词
107
+ const EMOTION_KEYWORDS: Record<Emotion, { label: string; words: string[] }> = {
108
+ anxious: { label: '焦虑', words: ['焦虑', '担心', '紧张', '害怕', '恐惧', '不安', '心慌', '纠结', '犹豫', '忐忑', '怕', '忧', '愁', '急', '压力大', '紧绷'] },
109
+ hopeful: { label: '期待', words: ['希望', '期待', '想要', '盼望', '渴望', '憧憬', '会', '能', '想要', '愿望', '梦想'] },
110
+ confused: { label: '迷茫', words: ['迷茫', '困惑', '不知道', '不清楚', '糊涂', '不知所措', '没有方向', '迷失', '找不到', '不懂', '怎么办', '该怎么做'] },
111
+ lost: { label: '失落', words: ['失落', '失望', '绝望', '心累', '崩溃', '不想', '放弃', '没意思', '糟糕', '糟糕透顶', '完蛋', '无望'] },
112
+ grateful: { label: '感恩', words: ['感谢', '感恩', '谢谢', '幸运', '幸福', '开心', '美好', '顺利', '谢谢', '感谢'] },
113
+ calm: { label: '平静', words: ['平静', '淡定', '从容', '还好', '正常', '平常', '一般', '差不多', '稳定'] },
114
+ angry: { label: '愤怒', words: ['生气', '愤怒', '气死', '讨厌', '烦', '凭什么', '不公平', '恼火', '恨', '可恨'] },
115
+ sad: { label: '悲伤', words: ['难过', '伤心', '哭', '痛苦', '心碎', '伤感', '悲哀', '痛', '泪水', '受伤'] },
116
+ curious: { label: '好奇', words: ['为什么', '怎么回事', '会怎样', '想了解', '好奇', '有意思'] },
117
+ };
118
+
119
+ // 时态关键词
120
+ const TENSE_KEYWORDS: Record<Tense, { label: string; words: string[] }> = {
121
+ past: { label: '过去', words: ['之前', '以前', '过去', '当时', '曾经', '已经', '发生过了', '之前发生', '早前', '上个月', '去年', '为什么', '当时为什么', '过去怎么'] },
122
+ present: { label: '现在', words: ['现在', '目前', '当前', '眼下', '此刻', '这阵子', '最近', '正在', '眼下', '当下', '如今'] },
123
+ future: { label: '未来', words: ['以后', '未来', '将来', '接下来', '之后', '会', '将', '明年', '下个月', '以后会', '未来会', '未来会怎样'] },
124
+ hypothetical: { label: '假设', words: ['如果', '假设', '要是', '假若', '万一', '假设我', '假如', '若'] },
125
+ ongoing: { label: '持续', words: ['一直', '持续', '总是', '长期', '经常', '反复', '一直这样', '持续中', '正在进行'] },
126
+ };
127
+
128
+ // 对象关键词
129
+ const SUBJECT_KEYWORDS: Record<Subject, { label: string; words: string[] }> = {
130
+ self: { label: '自己', words: ['我', '我自己', '我应该', '我能', '我要', '我是否', '我该不该', '我要不要', '我的', '本人'] },
131
+ other: { label: '对方', words: ['他', '她', '对方', '那个人', '他/她', 'ta', 'TA', '他/她是否', '他/她会不会', '他/她的', '我对象的', '我男朋友', '我女朋友', '我老公', '我老婆', '我爸妈', '我朋友', '我同事'] },
132
+ mutual: { label: '双方', words: ['我们', '我们俩', '两个人', '彼此', '双方', '我们的', '我们能', '我们是否', '我们之间'] },
133
+ 'third-party': { label: '第三方', words: ['第三方', '外人', '他们', '别人', '其他人', '公司', '单位', '环境', '外部', '他人', '外人'] },
134
+ };
135
+
136
+ // ===== 问题类型关键词库 =====
137
+ const QUESTION_TYPE_KEYWORDS: Record<QuestionType, { label: string; words: string[]; weight: number }> = {
138
+ decision: {
139
+ label: '决策类',
140
+ words: ['该不该', '要不要', '是否要', '是否应该', '该不该继续', '要不要继续', '该不该分手', '要不要分手', '该不该辞职', '要不要辞职', '该不该复合', '要不要复合', '该不该离开', '该不该坚持', '要不要放弃', '该不该接受', '要不要接受', '该不该去', '要不要去', '该不该换', '要不要换', '纠结', '犹豫', '抉择', '二选一', '应该吗', '可以吗', '行不行', '合适吗', '值得吗'],
141
+ weight: 1.2,
142
+ },
143
+ prediction: {
144
+ label: '预测类',
145
+ words: ['会怎样', '会怎么样', '运势', '能不能', '会不会', '未来', '结果', '结局', '发展', '走向', '趋势', '前景', '会成功吗', '能成吗', '能成功', '会好吗', '会变好', '会回来', '能复合', '会复合', '能结婚', '会结婚', '能怀孕', '会怀孕', '会顺利', '能顺利', '怎么样', '好吗', '好不好', '走势', '前景如何', '走向如何'],
146
+ weight: 1.0,
147
+ },
148
+ reason: {
149
+ label: '原因类',
150
+ words: ['为什么', '怎么回事', '为何', '怎么会', '凭什么', '为啥', '为何会', '为什么会', '原因', '为何突然', '为什么突然', '为什么总是', '为何总是', '怎么变成', '为什么会这样', '为何这样', '为何不', '为什么不'],
151
+ weight: 1.2,
152
+ },
153
+ advice: {
154
+ label: '建议类',
155
+ words: ['怎么办', '怎么做', '该如何', '该怎样', '怎么处理', '怎么解决', '怎么应对', '该怎么', '要怎么', '该如何做', '怎么走', '怎么选', '怎么挽回', '怎么改善', '怎么调整', '如何', '怎么办才好', '该怎么做', '怎么才能', '怎么样才能', '如何才能', '怎样才好'],
156
+ weight: 1.0,
157
+ },
158
+ mindreading: {
159
+ label: '读心类',
160
+ words: ['爱不爱', '喜不喜欢', '怎么想', '心里有', '心里没', '心里有没有', '他是不是', '她是不是', '他到底', '她到底', '对方怎么想', '对方到底', '对方是不是', '对方真的', '他真的', '她真的', '他在想', '她在想', '他对我', '她对我', '他喜欢', '她喜欢', '在乎我', '在乎不在乎', '心里想', '真心吗', '真心的吗', '有没有我', '他怎么看', '她怎么看', '他到底爱不爱', '她到底爱不爱'],
161
+ weight: 1.3,
162
+ },
163
+ timing: {
164
+ label: '时间类',
165
+ words: ['什么时候', '多久', '何时', '几点', '哪天', '什么时候能', '什么时候会', '多久能', '多久会', '什么时候可以', '要多久', '等多久', '几岁', '哪一年', '哪年', '什么时候才'],
166
+ weight: 1.1,
167
+ },
168
+ general: {
169
+ label: '通用类',
170
+ words: [],
171
+ weight: 0.3,
172
+ },
173
+ };
174
+
175
+ // 不同问题类型自带的情绪倾向(在没有明显情绪词时作为兜底)
176
+ const TYPE_EMOTION_BIAS: Record<QuestionType, Emotion> = {
177
+ decision: 'anxious',
178
+ prediction: 'curious',
179
+ reason: 'confused',
180
+ advice: 'confused',
181
+ mindreading: 'anxious',
182
+ timing: 'anxious',
183
+ general: 'curious',
184
+ };
185
+
186
+ // ===== 关键实体词库 =====
187
+ const PERSON_ENTITY_WORDS = ['前任', '前男友', '前女友', '男朋友', '女朋友', '老公', '老婆', '对象', '老板', '上司', '领导', '同事', '下属', '父母', '妈妈', '爸爸', '母亲', '父亲', '孩子', '儿子', '女儿', '朋友', '闺蜜', '兄弟', '室友', '相亲对象', '暗恋对象', '第三者', '对方', '客户', '面试官', '导师', '老师', '同学', '他', '她', 'ta', '那个人'];
188
+ const EVENT_ENTITY_WORDS = ['分手', '复合', '表白', '告白', '结婚', '离婚', '复婚', '求婚', '辞职', '跳槽', '离职', '入职', '面试', '考试', '高考', '考研', '出国', '留学', '搬家', '创业', '晋升', '加薪', '出轨', '暧昧', '相亲', '毕业', '挂科', '录取', '还债', '投资', '买房', '冷战', '吵架'];
189
+ const TIME_ENTITY_WORDS = ['最近', '这阵子', '眼下', '当下', '现在', '目前', '接下来', '未来', '以后', '将来', '明年', '今年', '去年', '上个月', '下个月', '之前', '当时', '此刻', '今天', '明天', '昨天', '后天', '年底', '年初', '过去', '不久', '迟早', '早晚'];
190
+
191
+ // ===== 问题核心意图(按问题类型) =====
192
+ const QUESTION_CORE: Record<QuestionType, string> = {
193
+ decision: '你在权衡一个选择,想确认哪条路更值得走',
194
+ prediction: '你想提前看见未来的走向,好让自己心里有底',
195
+ reason: '你想弄明白事情为何会发展成现在这样',
196
+ advice: '你在寻找一个能让自己安心的行动方向',
197
+ mindreading: '你想读懂对方心里真实的想法',
198
+ timing: '你想知道那个关键的时刻何时才会到来',
199
+ general: '你想为当下的处境找到一份指引',
200
+ };
201
+
202
+ // ===== 一句话概括模板(场景 × 问题类型) =====
203
+ const QUESTION_SUMMARY_TEMPLATES: Record<string, string> = {
204
+ 'love-mindreading': '你想确认对方对你的真实感受',
205
+ 'love-prediction': '你想了解这段感情未来的走向',
206
+ 'love-decision': '你在犹豫是否要在这段感情上做出选择',
207
+ 'love-reason': '你想弄清这段感情出现状况的原因',
208
+ 'love-advice': '你想知道该如何面对这段感情',
209
+ 'love-timing': '你想知道这段感情的关键转折何时到来',
210
+ 'career-decision': '你在面临一个重要的职业抉择',
211
+ 'career-prediction': '你想了解事业未来的发展趋势',
212
+ 'career-reason': '你想弄清职场现状背后的原因',
213
+ 'career-advice': '你想获得职业发展的方向建议',
214
+ 'career-mindreading': '你想了解领导或同事对你的真实看法',
215
+ 'career-timing': '你想知道事业转机何时出现',
216
+ 'wealth-prediction': '你想了解近期的财运走势',
217
+ 'wealth-decision': '你在考虑一个财务上的选择',
218
+ 'wealth-advice': '你想获得改善财务状况的建议',
219
+ 'wealth-reason': '你想弄清财运起伏背后的原因',
220
+ 'study-prediction': '你想了解学业考试的结果走向',
221
+ 'study-decision': '你在纠结学业方向的选择',
222
+ 'study-advice': '你想获得学习备考的建议',
223
+ 'study-reason': '你想弄清学业受阻的原因',
224
+ 'health-prediction': '你想了解健康状况的走向',
225
+ 'health-advice': '你想获得改善健康的建议',
226
+ 'health-reason': '你想弄清身体出状况的原因',
227
+ 'family-decision': '你在面临一个家庭关系的抉择',
228
+ 'family-reason': '你想弄清家庭矛盾的根源',
229
+ 'family-advice': '你想获得处理家庭关系的建议',
230
+ 'family-prediction': '你想了解家庭关系未来的走向',
231
+ 'friendship-advice': '你想获得处理友情关系的建议',
232
+ 'friendship-reason': '你想弄清朋友之间出现问题的原因',
233
+ 'friendship-prediction': '你想了解这段友谊未来的走向',
234
+ 'spiritual-advice': '你想获得灵性成长的指引',
235
+ 'spiritual-prediction': '你想了解灵性旅程的走向',
236
+ 'travel-decision': '你在考虑一次出行或变动',
237
+ 'travel-prediction': '你想了解这次出行的走向',
238
+ 'general-prediction': '你想了解近期整体的运势走向',
239
+ 'general-decision': '你在面临一个人生方向的选择',
240
+ 'general-advice': '你想获得一些生活的指引',
241
+ 'general-reason': '你想弄清现状背后的原因',
242
+ 'general-mindreading': '你想确认对方对你的真实感受',
243
+ 'general-timing': '你想知道转机何时才会出现',
244
+ };
245
+
246
+ const QUESTION_SUMMARY_FALLBACK: Record<QuestionType, string> = {
247
+ decision: '你在面临一个需要做出选择的情况',
248
+ prediction: '你想了解未来的发展趋势',
249
+ reason: '你想弄清事情背后的原因',
250
+ advice: '你在寻求一个行动的方向',
251
+ mindreading: '你想读懂对方的真实想法',
252
+ timing: '你想知道时机何时到来',
253
+ general: '你想为当下的处境寻找一个答案',
254
+ };
255
+
256
+ // ===== 深度分析模板 =====
257
+ const REAL_QUESTION: Record<QuestionType, string> = {
258
+ decision: '你真正想问的,是这个选择是否符合你内心深处的期待,哪条路才能让你走得更踏实',
259
+ prediction: '你真正想问的,是未来能否给你一个值得期待、也让你安心的答案',
260
+ reason: '你真正想问的,是这件事背后是否还藏着你没看见的真相',
261
+ advice: '你真正想问的,是下一步该往哪个方向走,才能走出当下的困局',
262
+ mindreading: '你真正想问的,是对方心里到底有没有你、你是否真的被在乎',
263
+ timing: '你真正想问的,是还要等多久,那个转机才会真正到来',
264
+ general: '你真正想问的,是当下的生活究竟该往何处去',
265
+ };
266
+
267
+ const EMOTIONAL_NEED_BASE: Record<Emotion, string> = {
268
+ anxious: '你心里有些不安,需要一个能让你踏实下来的答案',
269
+ hopeful: '你心怀期待,想确认这份期待是否会被未来温柔接住',
270
+ confused: '你有些理不清头绪,需要有人帮你拨开眼前的迷雾',
271
+ lost: '你感到有些无力,需要一份不带评判的陪伴与方向',
272
+ grateful: '你心怀感念,想确认眼前的美好能否一直延续',
273
+ calm: '你心态相对平稳,只是想多一份确认与参考',
274
+ angry: '你心里有一股不平,需要被理解,也需要看清真相',
275
+ sad: '你心里有放不下的难过,需要被温柔接住,也需要一个释怀的理由',
276
+ curious: '你带着好奇与期待,想提前窥见未来的可能画面',
277
+ };
278
+
279
+ const TYPE_NEED_CLAUSE: Record<QuestionType, string> = {
280
+ decision: ',想看清哪条路才是自己真正想走的',
281
+ prediction: ',想把未来的画面看得更清楚一些',
282
+ reason: ',需要一个能让自己释怀的解释',
283
+ advice: ',想得到一个能落地的方向',
284
+ mindreading: ',渴望确认自己是否真的被在乎',
285
+ timing: ',想知道这份等待有没有尽头',
286
+ general: ',想在当下找到一个着力点',
287
+ };
288
+
289
+ const RESPONSE_NEED_LABEL: Record<ResponseNeed, string> = {
290
+ comfort: '安慰',
291
+ direction: '方向',
292
+ confirmation: '确认',
293
+ warning: '警示',
294
+ };
295
+
296
+ // 识别问题类型
297
+ function detectQuestionType(text: string): QuestionType {
298
+ const scores: Record<string, number> = {};
299
+ Object.entries(QUESTION_TYPE_KEYWORDS).forEach(([key, info]) => {
300
+ let score = 0;
301
+ info.words.forEach((word) => {
302
+ score += (text.split(word).length - 1) * info.weight;
303
+ });
304
+ scores[key] = score;
305
+ });
306
+ let type: QuestionType = 'general';
307
+ let max = 0;
308
+ Object.entries(scores).forEach(([key, score]) => {
309
+ if (score > max) {
310
+ max = score;
311
+ type = key as QuestionType;
312
+ }
313
+ });
314
+ return type;
315
+ }
316
+
317
+ // 情绪识别:关键词打分 + 问题类型倾向 + 语境增强
318
+ function detectEmotion(text: string, questionType: QuestionType): { emotion: Emotion; score: number } {
319
+ const keywordScores: Record<string, number> = {};
320
+ Object.entries(EMOTION_KEYWORDS).forEach(([key, info]) => {
321
+ let s = 0;
322
+ info.words.forEach((word) => {
323
+ s += text.split(word).length - 1;
324
+ });
325
+ keywordScores[key] = s;
326
+ });
327
+
328
+ // 原始关键词最高分,保留给强度/置信度计算
329
+ let rawScore = 0;
330
+ Object.values(keywordScores).forEach((s) => {
331
+ if (s > rawScore) rawScore = s;
332
+ });
333
+
334
+ const scores: Record<string, number> = { ...keywordScores };
335
+
336
+ // 1) 问题类型自带的情绪倾向
337
+ const bias = TYPE_EMOTION_BIAS[questionType];
338
+ scores[bias] = (scores[bias] || 0) + 0.6;
339
+
340
+ // 2) 预测类 + 期待语境 → 期待
341
+ if (questionType === 'prediction' && /运势|好不好|顺利|成功|会好|能成|桃花/.test(text)) {
342
+ scores.hopeful = (scores.hopeful || 0) + 0.8;
343
+ }
344
+ // 3) 预测类 + 负向语境 → 焦虑
345
+ if (questionType === 'prediction' && /会不会失败|会不会出|会不会分|会不会离|会不会丢|会不会没|能撑|撑得住/.test(text)) {
346
+ scores.anxious = (scores.anxious || 0) + 0.8;
347
+ }
348
+ // 4) 原因类 + 负面事件 → 悲伤 + 困惑
349
+ if (questionType === 'reason' && /分手|离婚|出轨|辞职|破产|挂科|失败|离开|失去|去世|生病|冷战|吵架|疏远|背叛/.test(text)) {
350
+ scores.sad = (scores.sad || 0) + 1.2;
351
+ scores.confused = (scores.confused || 0) + 0.5;
352
+ }
353
+ // 5) 读心类 + 强调词 → 焦虑
354
+ if (questionType === 'mindreading' && /到底|究竟|真的|是不是|到底爱不爱|到底喜不喜欢/.test(text)) {
355
+ scores.anxious = (scores.anxious || 0) + 0.8;
356
+ }
357
+ // 6) 决策类 + 纠结词 → 焦虑 / 迷茫
358
+ if (questionType === 'decision' && /纠结|犹豫|不知道怎么选|舍不得|放不下/.test(text)) {
359
+ scores.anxious = (scores.anxious || 0) + 0.5;
360
+ scores.confused = (scores.confused || 0) + 0.5;
361
+ }
362
+
363
+ let emotion: Emotion = 'curious';
364
+ let best = -1;
365
+ Object.entries(scores).forEach(([key, s]) => {
366
+ if (s > best) {
367
+ best = s;
368
+ emotion = key as Emotion;
369
+ }
370
+ });
371
+ return { emotion, score: rawScore };
372
+ }
373
+
374
+ // 提取关键实体
375
+ function extractKeyEntities(text: string): KeyEntity[] {
376
+ const entities: KeyEntity[] = [];
377
+ const seen = new Set<string>();
378
+ const push = (type: KeyEntity['type'], words: string[]) => {
379
+ for (const w of words) {
380
+ if (text.includes(w) && !seen.has(w)) {
381
+ seen.add(w);
382
+ entities.push({ type, value: w });
383
+ }
384
+ }
385
+ };
386
+ push('person', PERSON_ENTITY_WORDS);
387
+ push('event', EVENT_ENTITY_WORDS);
388
+ push('time', TIME_ENTITY_WORDS);
389
+ return entities.slice(0, 8);
390
+ }
391
+
392
+ // 生成一句话概括
393
+ function generateQuestionSummary(scene: Scene, questionType: QuestionType): string {
394
+ return QUESTION_SUMMARY_TEMPLATES[`${scene}-${questionType}`] || QUESTION_SUMMARY_FALLBACK[questionType];
395
+ }
396
+
397
+ // 判断用户需要的回应方式
398
+ function getResponseNeed(questionType: QuestionType, emotion: Emotion, text: string): ResponseNeed {
399
+ // 涉及明显风险 → 警示
400
+ const hasRisk = /出轨|破产|危险|借钱|投资|赌博|裸辞|网贷|骗局|被骗|第三者|婚外|套现|透支|高利贷/.test(text);
401
+ if (hasRisk && (questionType === 'decision' || questionType === 'advice' || questionType === 'prediction')) {
402
+ return 'warning';
403
+ }
404
+ // 情绪低落 → 先安慰
405
+ if (emotion === 'sad' || emotion === 'lost') return 'comfort';
406
+ // 愤怒 → 需要被看见、被确认
407
+ if (emotion === 'angry') return 'confirmation';
408
+ // 按问题类型给出
409
+ switch (questionType) {
410
+ case 'decision':
411
+ case 'advice':
412
+ case 'timing':
413
+ return 'direction';
414
+ case 'prediction':
415
+ case 'reason':
416
+ case 'mindreading':
417
+ return 'confirmation';
418
+ default:
419
+ return emotion === 'anxious' ? 'comfort' : 'direction';
420
+ }
421
+ }
422
+
423
+ // 构建深度分析
424
+ function buildQuestionAnalysis(questionType: QuestionType, emotion: Emotion, text: string): QuestionAnalysis {
425
+ const responseNeed = getResponseNeed(questionType, emotion, text);
426
+ return {
427
+ realQuestion: REAL_QUESTION[questionType],
428
+ emotionalNeed: EMOTIONAL_NEED_BASE[emotion] + TYPE_NEED_CLAUSE[questionType],
429
+ responseNeed,
430
+ responseNeedLabel: RESPONSE_NEED_LABEL[responseNeed],
431
+ };
432
+ }
433
+
434
+ export function analyzeQuestion(question: string, category?: string): SemanticAnalysis {
435
+ const text = question.toLowerCase().trim();
436
+
437
+ // 1. 场景识别
438
+ const sceneScores: Record<string, number> = {};
439
+ Object.entries(SCENE_KEYWORDS).forEach(([key, info]) => {
440
+ let score = 0;
441
+ info.words.forEach((word) => {
442
+ const matches = text.split(word).length - 1;
443
+ score += matches * info.weight;
444
+ });
445
+ sceneScores[key] = score;
446
+ });
447
+
448
+ // 用户分类优先
449
+ let scene: Scene = 'general';
450
+ let maxScore = 0;
451
+ if (category && category !== 'general') {
452
+ scene = category as Scene;
453
+ } else {
454
+ Object.entries(sceneScores).forEach(([key, score]) => {
455
+ if (score > maxScore) {
456
+ maxScore = score;
457
+ scene = key as Scene;
458
+ }
459
+ });
460
+ if (maxScore < 0.5) scene = 'general';
461
+ }
462
+
463
+ // 2. 问题类型识别(用于增强情绪与深度分析)
464
+ const questionType = detectQuestionType(text);
465
+
466
+ // 3. 情绪识别(关键词 + 问题类型 + 语境)
467
+ const { emotion, score: emotionScore } = detectEmotion(text, questionType);
468
+
469
+ // 4. 时态识别
470
+ let tense: Tense = 'present';
471
+ let tenseScore = 0;
472
+ Object.entries(TENSE_KEYWORDS).forEach(([key, info]) => {
473
+ let score = 0;
474
+ info.words.forEach((word) => {
475
+ const matches = text.split(word).length - 1;
476
+ score += matches * 2; // 时态权重更高
477
+ });
478
+ if (score > tenseScore) {
479
+ tenseScore = score;
480
+ tense = key as Tense;
481
+ }
482
+ });
483
+
484
+ // 5. 对象识别
485
+ let subject: Subject = 'self';
486
+ let subjectScore = 0;
487
+ Object.entries(SUBJECT_KEYWORDS).forEach(([key, info]) => {
488
+ let score = 0;
489
+ info.words.forEach((word) => {
490
+ const matches = text.split(word).length - 1;
491
+ score += matches;
492
+ });
493
+ if (score > subjectScore) {
494
+ subjectScore = score;
495
+ subject = key as Subject;
496
+ }
497
+ });
498
+
499
+ // 6. 深度判断(基于问题长度、问号数、关键词)
500
+ const questionMarks = (text.match(/[??]/g) || []).length;
501
+ const length = text.length;
502
+ let depth: Depth = 'mid';
503
+ if (length < 15 && questionMarks <= 1) depth = 'surface';
504
+ else if (length > 30 || questionMarks > 1) depth = 'deep';
505
+
506
+ // 7. 关键词提取(按权重)
507
+ const keywords: string[] = [];
508
+ Object.entries(SCENE_KEYWORDS).forEach(([key, info]) => {
509
+ if (key === scene) {
510
+ info.words.forEach((word) => {
511
+ if (text.includes(word) && !keywords.includes(word)) {
512
+ keywords.push(word);
513
+ }
514
+ });
515
+ }
516
+ });
517
+
518
+ // 8. 关键实体提取
519
+ const keyEntities = extractKeyEntities(text);
520
+
521
+ // 9. 问题核心意图 + 一句话概括
522
+ const questionCore = QUESTION_CORE[questionType];
523
+ const questionSummary = generateQuestionSummary(scene, questionType);
524
+
525
+ // 10. 深度分析(潜台词)
526
+ const questionAnalysis = buildQuestionAnalysis(questionType, emotion, text);
527
+
528
+ // 11. 强度
529
+ const intensity = Math.min(1, (emotionScore * 0.3 + questionMarks * 0.2 + length / 50 * 0.2 + maxScore * 0.3));
530
+
531
+ // 12. 置信度
532
+ const confidence = Math.min(1, (maxScore * 0.4 + (emotionScore + tenseScore + subjectScore) * 0.2 + 0.3));
533
+
534
+ return {
535
+ scene,
536
+ sceneLabel: SCENE_KEYWORDS[scene].label,
537
+ emotion,
538
+ emotionLabel: EMOTION_KEYWORDS[emotion].label,
539
+ tense,
540
+ tenseLabel: TENSE_KEYWORDS[tense].label,
541
+ subject,
542
+ subjectLabel: SUBJECT_KEYWORDS[subject].label,
543
+ depth,
544
+ depthLabel: depth === 'surface' ? '浅层' : depth === 'mid' ? '中层' : '深层',
545
+ keywords: keywords.slice(0, 5),
546
+ intensity,
547
+ confidence,
548
+ questionType,
549
+ questionTypeLabel: QUESTION_TYPE_KEYWORDS[questionType].label,
550
+ questionCore,
551
+ questionSummary,
552
+ keyEntities,
553
+ questionAnalysis,
554
+ };
555
+ }
@@ -0,0 +1,32 @@
1
+ import type { Spread } from '@/types';
2
+
3
+ const STORAGE_KEY = 'xuanming_custom_spreads';
4
+
5
+ export function saveCustomSpread(spread: Spread): void {
6
+ const spreads = getCustomSpreads();
7
+ const existingIndex = spreads.findIndex(s => s.id === spread.id);
8
+ if (existingIndex >= 0) {
9
+ spreads[existingIndex] = spread;
10
+ } else {
11
+ spreads.push(spread);
12
+ }
13
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(spreads));
14
+ }
15
+
16
+ export function getCustomSpreads(): Spread[] {
17
+ try {
18
+ const data = localStorage.getItem(STORAGE_KEY);
19
+ return data ? JSON.parse(data) : [];
20
+ } catch {
21
+ return [];
22
+ }
23
+ }
24
+
25
+ export function deleteCustomSpread(id: string): void {
26
+ const spreads = getCustomSpreads().filter(s => s.id !== id);
27
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(spreads));
28
+ }
29
+
30
+ export function getAllSpreads(): Spread[] {
31
+ return [...getCustomSpreads()];
32
+ }
@@ -0,0 +1,27 @@
1
+ import type { DivinationRecord } from '@/types';
2
+
3
+ const STORAGE_KEY = 'xuanming_divination_records';
4
+
5
+ export function saveRecord(record: DivinationRecord): void {
6
+ const records = getRecords();
7
+ records.unshift(record);
8
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(records.slice(0, 100)));
9
+ }
10
+
11
+ export function getRecords(): DivinationRecord[] {
12
+ try {
13
+ const data = localStorage.getItem(STORAGE_KEY);
14
+ return data ? JSON.parse(data) : [];
15
+ } catch {
16
+ return [];
17
+ }
18
+ }
19
+
20
+ export function deleteRecord(id: string): void {
21
+ const records = getRecords().filter((r) => r.id !== id);
22
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(records));
23
+ }
24
+
25
+ export function clearRecords(): void {
26
+ localStorage.removeItem(STORAGE_KEY);
27
+ }