guanwei 1.1.2 → 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 (53) 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-C7ql6zvs.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/server/src/data/db.json +147 -0
  9. package/server/src/index.ts +27 -4
  10. package/server/src/routes/tarot.ts +4 -4
  11. package/server/src/routes/users.ts +42 -7
  12. package/server/src/services/divineStore.ts +1 -5
  13. package/server/src/services/promptBuilder.ts +2 -2
  14. package/server/src/types/index.ts +2 -104
  15. package/{src → shared/core}/data/lifeContext.ts +2 -8
  16. package/{src → shared/core}/data/spreads.ts +1 -1
  17. package/{src → shared/core}/data/tarotCards.ts +1 -1
  18. package/shared/core/engine/bazi.ts +0 -1
  19. package/shared/core/engine/calendar.ts +1 -1
  20. package/{server/src/utils → shared/core/engine}/semanticAnalyzer.ts +9 -46
  21. package/{server/src/utils → shared/core/engine}/tarotEngine.ts +6 -6
  22. package/shared/core/engine/ziwei.ts +3 -2
  23. package/shared/core/engine/ziweiInterpret.ts +0 -3
  24. package/shared/core/types.ts +157 -0
  25. package/src/arts/registry.ts +1 -1
  26. package/src/components/QuestionFields.tsx +0 -1
  27. package/src/components/ReportView.tsx +0 -1
  28. package/src/components/TimeShichenInput.tsx +1 -0
  29. package/src/components/arts/BaziArt.tsx +0 -10
  30. package/src/components/arts/LiurenArt.tsx +1 -1
  31. package/src/components/arts/LiuyaoArt.tsx +0 -1
  32. package/src/components/arts/MeihuaArt.tsx +0 -1
  33. package/src/components/arts/QimenArt.tsx +0 -1
  34. package/src/components/arts/TarotArt.tsx +2 -2
  35. package/src/components/arts/XiaoliurenArt.tsx +1 -2
  36. package/src/components/arts/ZiweiArt.tsx +2 -15
  37. package/src/pages/AuthPage.tsx +4 -6
  38. package/src/pages/DemoPage.tsx +1 -2
  39. package/src/pages/HistoryPage.tsx +10 -9
  40. package/src/pages/ModulePage.tsx +6 -32
  41. package/src/pages/SpreadLibrary.tsx +1 -1
  42. package/src/pages/TarotPage.tsx +7 -9
  43. package/src/services/api.ts +4 -4
  44. package/src/stores/useDivinationStore.ts +1 -1
  45. package/src/types/index.ts +2 -104
  46. package/tsconfig.json +5 -5
  47. package/dist/assets/index-C7ql6zvs.js.map +0 -1
  48. package/dist/assets/index-DQx62TUr.css +0 -1
  49. package/server/src/data/lifeContext.ts +0 -423
  50. package/server/src/data/spreads.ts +0 -255
  51. package/server/src/data/tarotCards.ts +0 -2353
  52. package/src/utils/semanticAnalyzer.ts +0 -555
  53. package/src/utils/tarotEngine.ts +0 -849
package/dist/index.html CHANGED
@@ -6,8 +6,8 @@
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-C7ql6zvs.js"></script>
10
- <link rel="stylesheet" crossorigin href="/assets/index-DQx62TUr.css">
9
+ <script type="module" crossorigin src="/assets/index-CAyAIgw8.js"></script>
10
+ <link rel="stylesheet" crossorigin href="/assets/index-C3wrtpPY.css">
11
11
  </head>
12
12
  <body>
13
13
  <div id="root"></div>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "guanwei",
3
3
  "private": false,
4
- "version": "1.1.2",
4
+ "version": "1.1.3",
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": ">=18"
94
+ "node": ">=22.5.0"
95
95
  }
96
96
  }
@@ -12075,6 +12075,153 @@
12075
12075
  }
12076
12076
  }
12077
12077
  ]
12078
+ },
12079
+ {
12080
+ "username": "联调7035",
12081
+ "passHash": "h7a11ca53",
12082
+ "createdAt": 1787462187089,
12083
+ "profile": {
12084
+ "birthDate": "1996-09-09",
12085
+ "birthHourIndex": 5,
12086
+ "gender": "男",
12087
+ "location": null
12088
+ },
12089
+ "samples": [],
12090
+ "records": [
12091
+ {
12092
+ "artId": "bazi",
12093
+ "createdAt": 1,
12094
+ "result": {
12095
+ "dayGZ": "壬申"
12096
+ }
12097
+ }
12098
+ ]
12099
+ },
12100
+ {
12101
+ "username": "联调6052",
12102
+ "passHash": "h7a11ca53",
12103
+ "createdAt": 1787463476120,
12104
+ "profile": {
12105
+ "birthDate": "1996-09-09",
12106
+ "birthHourIndex": 5,
12107
+ "gender": "男",
12108
+ "location": null
12109
+ },
12110
+ "samples": [],
12111
+ "records": [
12112
+ {
12113
+ "artId": "bazi",
12114
+ "createdAt": 1,
12115
+ "result": {
12116
+ "dayGZ": "壬申"
12117
+ }
12118
+ }
12119
+ ]
12120
+ },
12121
+ {
12122
+ "username": "联调4217",
12123
+ "passHash": "scrypt$5d770ea33c351c28afc8ee563d5712a2$7d00490154d70d549eba3a28d0a94a8cf2b4b23fa2fcde990200e850566993779a3854ed20858039690fb21a59ac814942947d95d4bad8f4ae328317d3e35137",
12124
+ "createdAt": 1787463514296,
12125
+ "profile": {
12126
+ "birthDate": "1996-09-09",
12127
+ "birthHourIndex": 5,
12128
+ "gender": "男",
12129
+ "location": null
12130
+ },
12131
+ "samples": [],
12132
+ "records": [
12133
+ {
12134
+ "artId": "bazi",
12135
+ "createdAt": 1,
12136
+ "result": {
12137
+ "dayGZ": "壬申"
12138
+ }
12139
+ }
12140
+ ]
12141
+ },
12142
+ {
12143
+ "username": "联调3642",
12144
+ "passHash": "scrypt$e68ed2ead08e6da65060e6a7eaa44d4f$494bf83f166632db29cc3d85b334b5a7e64bec70949721cf3ef77c71b3e2b4f2b6a9abf6fe3bf8371ab506df450ead79ba01bda5005beaba47fdc269b9492c64",
12145
+ "createdAt": 1787464003708,
12146
+ "profile": {
12147
+ "birthDate": "1996-09-09",
12148
+ "birthHourIndex": 5,
12149
+ "gender": "男",
12150
+ "location": null
12151
+ },
12152
+ "samples": [],
12153
+ "records": [
12154
+ {
12155
+ "artId": "bazi",
12156
+ "createdAt": 1,
12157
+ "result": {
12158
+ "dayGZ": "壬申"
12159
+ }
12160
+ }
12161
+ ]
12162
+ },
12163
+ {
12164
+ "username": "联调7150",
12165
+ "passHash": "scrypt$a691ebce2d89d81489e7209925cda9ed$4f9aaf62d714d4ce7bc8f1df2566461161bd8ad32bad88d23680534bede52d3a99efd28897ddbd75eedcf5eb08bc7b748020cc3d242acc8966c96d52fbf53c0d",
12166
+ "createdAt": 1787464067260,
12167
+ "profile": {
12168
+ "birthDate": "1996-09-09",
12169
+ "birthHourIndex": 5,
12170
+ "gender": "男",
12171
+ "location": null
12172
+ },
12173
+ "samples": [],
12174
+ "records": [
12175
+ {
12176
+ "artId": "bazi",
12177
+ "createdAt": 1,
12178
+ "result": {
12179
+ "dayGZ": "壬申"
12180
+ }
12181
+ }
12182
+ ]
12183
+ },
12184
+ {
12185
+ "username": "联调7275",
12186
+ "passHash": "scrypt$5f91de795c181fe55ef00de36e94e7b8$5d3bd0f8d7097ea4fac56be03934a4a8638642c784f82d066d7b4b582669024c9d38ae82e19d7ab73781a7ab80130858b03a7490f979e04d7ed72294079571e6",
12187
+ "createdAt": 1787468567400,
12188
+ "profile": {
12189
+ "birthDate": "1996-09-09",
12190
+ "birthHourIndex": 5,
12191
+ "gender": "男",
12192
+ "location": null
12193
+ },
12194
+ "samples": [],
12195
+ "records": [
12196
+ {
12197
+ "artId": "bazi",
12198
+ "createdAt": 1,
12199
+ "result": {
12200
+ "dayGZ": "壬申"
12201
+ }
12202
+ }
12203
+ ]
12204
+ },
12205
+ {
12206
+ "username": "联调9581",
12207
+ "passHash": "scrypt$16272308b8058bf6ccbe9d3a2ed81a4d$52177ab888d594c6da154cf421557b10d8ab4fd170ee975cbfa432309bdd41cd0fb07c1a58f7d239662e7d36d035db3ce62bc9c50aeb36c0355779a8cf38fac5",
12208
+ "createdAt": 1787468589681,
12209
+ "profile": {
12210
+ "birthDate": "1996-09-09",
12211
+ "birthHourIndex": 5,
12212
+ "gender": "男",
12213
+ "location": null
12214
+ },
12215
+ "samples": [],
12216
+ "records": [
12217
+ {
12218
+ "artId": "bazi",
12219
+ "createdAt": 1,
12220
+ "result": {
12221
+ "dayGZ": "壬申"
12222
+ }
12223
+ }
12224
+ ]
12078
12225
  }
12079
12226
  ]
12080
12227
  }
@@ -3,15 +3,38 @@ import path from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
 
5
5
  // 轻量 .env 加载(无第三方依赖)
6
+ // 支持:# 注释行、空行、KEY=value(可带双引号/单引号,内嵌转义 \n \t \" \\ 与注释剥离)
6
7
  (() => {
7
8
  try {
8
9
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
10
  const envFile = path.join(__dirname, '..', '.env');
10
11
  const content = fs.readFileSync(envFile, 'utf-8');
11
- content.split('\n').forEach(line => {
12
- const m = /^\s*([A-Z_]+)\s*=\s*(.+)\s*$/.exec(line);
13
- if (m && process.env[m[1]] === undefined) process.env[m[1]] = m[2].replace(/^["']|["']$/g, '');
14
- });
12
+ for (const rawLine of content.split(/\r?\n/)) {
13
+ const line = rawLine.trim();
14
+ if (!line || line.startsWith('#')) continue; // 空行 / 注释行
15
+ const eq = line.indexOf('=');
16
+ if (eq <= 0) continue; // 无等号 → 跳过
17
+ const key = line.slice(0, eq).trim();
18
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue; // 非法键名 → 跳过
19
+ if (process.env[key] !== undefined) continue; // 环境变量优先,不覆盖
20
+ let val = line.slice(eq + 1).trim();
21
+ // 行内注释剥离(引号外的 # 视为注释起点)
22
+ if (!val.startsWith('"') && !val.startsWith("'")) {
23
+ const hash = val.indexOf(' #');
24
+ if (hash >= 0) val = val.slice(0, hash);
25
+ }
26
+ // 引号包裹 → 整体剥引号 + 处理转义(仅双引号内做转义展开)
27
+ const q = val.startsWith('"') ? '"' : val.startsWith("'") ? "'" : null;
28
+ if (q && val.endsWith(q) && val.length >= 2) {
29
+ val = val.slice(1, -1);
30
+ if (q === '"') {
31
+ val = val.replace(/\\n/g, '\n').replace(/\\t/g, '\t').replace(/\\"/g, '"').replace(/\\\\/g, '\\');
32
+ }
33
+ } else {
34
+ val = val.trim();
35
+ }
36
+ process.env[key] = val;
37
+ }
15
38
  console.log('· 已加载 .env(LLM_PROVIDER=' + (process.env.LLM_PROVIDER || '未配置') + ')');
16
39
  } catch { /* 无 .env 时忽略 */ }
17
40
  })();
@@ -1,8 +1,8 @@
1
1
  import { Router } from 'express';
2
- import { tarotCards } from '../data/tarotCards.js';
3
- import { defaultSpreads } from '../data/spreads.js';
4
- import { drawCards, generateInterpretation } from '../utils/tarotEngine.js';
5
- import { analyzeQuestion } from '../utils/semanticAnalyzer.js';
2
+ import { tarotCards } from '../../../shared/core/data/tarotCards.js';
3
+ import { defaultSpreads } from '../../../shared/core/data/spreads.js';
4
+ import { drawCards, generateInterpretation } from '../../../shared/core/engine/tarotEngine.js';
5
+ import { analyzeQuestion } from '../../../shared/core/engine/semanticAnalyzer.js';
6
6
  import type { QuestionCategory, Spread } from '../types/index.js';
7
7
 
8
8
  const router = Router();
@@ -2,11 +2,18 @@
2
2
  import { Router } from 'express';
3
3
  import fs from 'fs';
4
4
  import path from 'path';
5
+ import crypto from 'crypto';
5
6
  import { fileURLToPath } from 'url';
6
7
 
7
8
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
9
  const DB_FILE = path.join(__dirname, '..', 'data', 'db.json');
9
10
 
11
+ function scryptAsync(pw: string, salt: Buffer, keylen: number): Promise<Buffer> {
12
+ return new Promise((resolve, reject) => {
13
+ crypto.scrypt(pw, salt, keylen, (err, key) => err ? reject(err) : resolve(key));
14
+ });
15
+ }
16
+
10
17
  interface DbUser {
11
18
  username: string;
12
19
  passHash: string;
@@ -28,11 +35,31 @@ function saveDb(db: Db): void {
28
35
  fs.writeFileSync(DB_FILE, JSON.stringify(db, null, 2));
29
36
  }
30
37
 
31
- function hash(pw: string): string {
38
+ // 密码哈希:scrypt(带随机盐,防彩虹表/暴力破解)。
39
+ // 格式:scrypt$<saltHex>$<hashHex>;登录时对存量旧哈希(djb2 前缀 'h')做兼容校验,命中即升级为 scrypt。
40
+ const SCRYPT_N = 16384, SCRYPT_R = 8, SCRYPT_P = 1, SCRYPT_KEYLEN = 64;
41
+
42
+ async function hashPassword(pw: string): Promise<string> {
43
+ const salt = crypto.randomBytes(16);
44
+ const key = await scryptAsync(pw, salt, SCRYPT_KEYLEN);
45
+ return 'scrypt$' + salt.toString('hex') + '$' + key.toString('hex');
46
+ }
47
+
48
+ async function verifyPassword(pw: string, stored: string): Promise<boolean> {
49
+ if (!stored) return false;
50
+ if (stored.startsWith('scrypt$')) {
51
+ const parts = stored.split('$');
52
+ if (parts.length !== 3) return false;
53
+ const salt = Buffer.from(parts[1], 'hex');
54
+ const expected = Buffer.from(parts[2], 'hex');
55
+ const key = await scryptAsync(pw, salt, expected.length);
56
+ return key.length === expected.length && crypto.timingSafeEqual(key, expected);
57
+ }
58
+ // 旧格式(djb2 变体):仅做兼容校验,命中后由调用方升级
32
59
  let h = 5381;
33
60
  const s = 'guanwei::' + pw;
34
61
  for (let i = 0; i < s.length; i++) h = ((h << 5) + h + s.charCodeAt(i)) >>> 0;
35
- return 'h' + h.toString(16);
62
+ return 'h' + h.toString(16) === stored;
36
63
  }
37
64
 
38
65
  const router = Router();
@@ -51,7 +78,7 @@ function upsertUser(username: string): DbUser {
51
78
  return user;
52
79
  }
53
80
 
54
- router.post('/register', (req, res) => {
81
+ router.post('/register', async (req, res) => {
55
82
  const { username, password, profile } = req.body;
56
83
  const name = String(username || '').trim();
57
84
  if (name.length < 2) return res.status(400).json({ error: '名号至少二字' });
@@ -61,25 +88,33 @@ router.post('/register', (req, res) => {
61
88
  if (existing) {
62
89
  // 云同步自动建档的占位账号(无密语)可被正式注册升级
63
90
  if (!existing.passHash) {
64
- existing.passHash = hash(String(password));
91
+ existing.passHash = await hashPassword(String(password));
65
92
  if (profile) existing.profile = { ...existing.profile, ...profile };
66
93
  saveDb(db);
67
94
  return res.json({ ok: true, upgraded: true, user: { username: existing.username, profile: existing.profile, samples: existing.samples } });
68
95
  }
69
96
  return res.status(400).json({ error: '此名号已有人用' });
70
97
  }
71
- const user: DbUser = { username: name, passHash: hash(String(password)), createdAt: Date.now(), profile: profile || {}, samples: [], records: [] };
98
+ const user: DbUser = { username: name, passHash: await hashPassword(String(password)), createdAt: Date.now(), profile: profile || {}, samples: [], records: [] };
72
99
  db.users.push(user);
73
100
  saveDb(db);
74
101
  res.json({ ok: true, user: { username: user.username, profile: user.profile, samples: user.samples } });
75
102
  });
76
103
 
77
104
  // 登录
78
- router.post('/login', (req, res) => {
105
+ router.post('/login', async (req, res) => {
79
106
  const { username, password } = req.body;
80
107
  const db = loadDb();
81
108
  const user = db.users.find(u => u.username === String(username || '').trim());
82
- if (!user || user.passHash !== hash(String(password || ''))) return res.status(401).json({ error: '名号或密语未合' });
109
+ if (!user) return res.status(401).json({ error: '名号或密语未合' });
110
+ const ok = await verifyPassword(String(password || ''), user.passHash);
111
+ if (!ok) return res.status(401).json({ error: '名号或密语未合' });
112
+ // 旧哈希命中 → 升级为 scrypt 存储
113
+ if (user.passHash && !user.passHash.startsWith('scrypt$')) {
114
+ user.passHash = await hashPassword(String(password));
115
+ saveDb(db);
116
+ console.log(`[users] 密码哈希已升级为 scrypt: ${user.username}`);
117
+ }
83
118
  res.json({ ok: true, user: { username: user.username, profile: user.profile, samples: user.samples } });
84
119
  });
85
120
 
@@ -30,11 +30,7 @@ export function getDb(): DatabaseSync {
30
30
  report_quality TEXT,
31
31
  status TEXT NOT NULL DEFAULT 'divined',
32
32
  created_at INTEGER NOT NULL,
33
- updated_at INTEGER NOT NULL,
34
- plan_tier TEXT NOT NULL DEFAULT 'free',
35
- quota_key TEXT,
36
- quota_consumed INTEGER NOT NULL DEFAULT 1,
37
- billing_meta TEXT
33
+ updated_at INTEGER NOT NULL
38
34
  );
39
35
  CREATE INDEX IF NOT EXISTS idx_div_user_time ON divinations(username, created_at DESC);
40
36
 
@@ -1,4 +1,4 @@
1
- // 提示词构建:9 术 agent + Skills 编排 + 两套报告 Schema 模板 + 档案注入
1
+ // 提示词构建:9 术角色 persona(单轮注入,多 Agent 编排规划中)+ 两套报告 Schema 模板 + 档案注入
2
2
  import type { ChatMessage } from './llmProvider';
3
3
  import { agentOf } from './skills';
4
4
  import { chartBrief } from './chartBrief';
@@ -89,7 +89,7 @@ function lifeEventsNote(profile?: unknown): string {
89
89
  return '【命主已知人生经历 · 解读校准】以下为命主本人确认的人生事件,解读必须与此相符:\n' + clean.join('\n') + '\n要求:① 凡解读涉及这些年份(或与之相邻的大运流年)时,必须呼应对应事件(如该年患病手术,则论健康/流年时须呼应);② 严禁写出与已知经历相矛盾的内容(如已知 2024 年重大健康事件,就不得断言该年平安无事);③ 已知经历未覆盖的年份正常论命,不得为了呼应而编造具体事件细节。';
90
90
  }
91
91
 
92
- // Skills 编排版:9 术 agent + 档案 + 排盘结果 → 结构化解读报告
92
+ // 角色化解读版:9 术 persona(单轮注入)+ 档案 + 排盘结果 → 结构化解读报告
93
93
  export function buildReportMessages(
94
94
  artId: string,
95
95
  question: string,
@@ -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 '../../../shared/core/types.js';
@@ -1,15 +1,9 @@
1
1
  // 生活化场景语料库
2
2
  // 根据场景提供具体的日常落地建议
3
3
 
4
- import type { Scene, Emotion } from '@/utils/semanticAnalyzer';
4
+ import type { Scene, LifeContext } from '../types.js';
5
5
 
6
- export interface LifeContext {
7
- scenes: string[];
8
- actions: string[];
9
- warnings: string[];
10
- timeline: { short: string; mid: string; long: string };
11
- emotionAdvice: Record<Emotion, string>;
12
- }
6
+ export type { LifeContext };
13
7
 
14
8
  export const LIFE_CONTEXTS: Record<Scene, LifeContext> = {
15
9
  love: {
@@ -1,4 +1,4 @@
1
- import type { Spread } from '@/types';
1
+ import type { Spread } from '../types.js';
2
2
 
3
3
  export const defaultSpreads: Spread[] = [
4
4
  // ===== 极简牌阵 =====
@@ -1,4 +1,4 @@
1
- import type { TarotCard } from '@/types';
1
+ import type { TarotCard } from '../types.js';
2
2
 
3
3
  export const tarotCards: TarotCard[] = [
4
4
  // ===== 大阿卡纳 Major Arcana =====
@@ -104,7 +104,6 @@ export function baziCalc(input: BaziInput): BaziResult {
104
104
  const nayin = NAYIN[Math.floor(jiaziIndex(y) / 2) % 30];
105
105
  const support = wxCount[dayGanWx] + wxCount[WX_ORDER[(WX_ORDER.indexOf(dayGanWx) + 4) % 5]];
106
106
  const drain = gzList.length * 2 - support;
107
- const strength = support > drain ? '身强' : (support < drain ? '身弱' : '中和');
108
107
 
109
108
  // ─── 7. 地支藏干 + 藏干十神 + 加权五行 ───
110
109
  const canggan = gzList.map(gz => ({
@@ -16,7 +16,7 @@ export function daysSince(y: number, m: number, d: number): number {
16
16
 
17
17
  /* ---------- 节气 ---------- */
18
18
  // 近似表取法(奇门定遁 / 展示用):
19
- export function getJieqiApproxName(y: number, m: number, d: number): string {
19
+ export function getJieqiApproxName(_y: number, m: number, d: number): string {
20
20
  const v = m * 100 + d;
21
21
  let name = '大雪';
22
22
  for (let i = JIEQI_APPROX.length - 1; i >= 0; i--) {
@@ -1,53 +1,16 @@
1
1
  // 问题语义分析器
2
2
  // 在解读阶段对用户问题进行多维度分析
3
+ // 类型定义收敛自 shared/core/types.ts(单一副本),此处仅保留分析与函数逻辑
3
4
 
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
- }
5
+ import type {
6
+ Scene, Emotion, Tense, Subject, Depth,
7
+ QuestionType, ResponseNeed, KeyEntity, QuestionAnalysis, SemanticAnalysis,
8
+ } from '../types.js';
28
9
 
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
- }
10
+ export type {
11
+ Scene, Emotion, Tense, Subject, Depth,
12
+ QuestionType, ResponseNeed, KeyEntity, QuestionAnalysis, SemanticAnalysis,
13
+ };
51
14
 
52
15
  // 场景关键词库
53
16
  const SCENE_KEYWORDS: Record<Scene, { label: string; words: string[]; weight: number }> = {
@@ -1,6 +1,6 @@
1
- import type { TarotCard, DrawnCard, Spread, QuestionCategory } from '../types/index.js';
1
+ import type { TarotCard, DrawnCard, Spread, QuestionCategory } from '../types.js';
2
2
  import { tarotCards } from '../data/tarotCards.js';
3
- import { analyzeQuestion, type SemanticAnalysis, type Scene, type Emotion } from '../utils/semanticAnalyzer.js';
3
+ import { analyzeQuestion, type SemanticAnalysis, type Scene, type Emotion } from './semanticAnalyzer.js';
4
4
  import { LIFE_CONTEXTS } from '../data/lifeContext.js';
5
5
 
6
6
  type LifeAreaKey = 'love' | 'career' | 'wealth' | 'health' | 'growth';
@@ -157,7 +157,7 @@ ${revealLines[Math.floor(Math.random() * revealLines.length)]}`;
157
157
  function generateOverviewSection(
158
158
  cards: DrawnCard[],
159
159
  spread: Spread,
160
- question: string,
160
+ _question: string,
161
161
  semantic: SemanticAnalysis
162
162
  ): InterpretationSection {
163
163
  const cardData = cards.map((drawn) => {
@@ -316,7 +316,7 @@ function generateOverviewSection(
316
316
 
317
317
  function generateOverviewStoryFlow(
318
318
  cardData: { card: TarotCard; drawn: DrawnCard; position: { name: string; description: string } }[],
319
- semantic: SemanticAnalysis
319
+ _semantic: SemanticAnalysis
320
320
  ): string {
321
321
  if (cardData.length === 0) return '';
322
322
 
@@ -362,7 +362,7 @@ function generateCardSection(
362
362
  position: { name: string; description: string },
363
363
  index: number,
364
364
  semantic: SemanticAnalysis,
365
- lifeContext: typeof LIFE_CONTEXTS.love,
365
+ _lifeContext: typeof LIFE_CONTEXTS.love,
366
366
  totalCards: number
367
367
  ): InterpretationSection {
368
368
  const orientation = drawn.isReversed ? '逆位' : '正位';
@@ -429,7 +429,7 @@ ${selectedSymbols[0] || '画面里的核心意象'}在这个位置倒过来,
429
429
  };
430
430
  }
431
431
 
432
- function generateEsotericSection(cards: DrawnCard[], semantic: SemanticAnalysis): InterpretationSection {
432
+ function generateEsotericSection(cards: DrawnCard[], _semantic: SemanticAnalysis): InterpretationSection {
433
433
  const elements: Record<string, number> = { 火: 0, 水: 0, 风: 0, 土: 0, 以太: 0 };
434
434
  const planets = new Set<string>();
435
435
  const sephiroth = new Set<string>();
@@ -1,8 +1,9 @@
1
1
  // 紫微斗数:简式星盘(命宫/五行局/紫微定位/十四主星)+ 补齐层(辅星/身宫/四化/庙旺/格局)
2
- import { NAYIN, NAYIN_JU, ganZhiIndex, mod } from '../data/ganzhi';
2
+ import { NAYIN, ganZhiIndex, mod } from '../data/ganzhi';
3
+ import { GAN } from '../data/ganzhi';
3
4
  import { trueSolarTime } from './trueSolarTime';
4
5
  import type { ZiweiInput, ZiweiResult } from '../types';
5
- import { GAN, ZHI } from '../data/ganzhi';
6
+
6
7
  import { PALACE_NAMES, ZW_SIHUA, ZW_BRIGHTNESS, LU_CUN, KUI_YUE, TIAN_MA, HUO_START, LING_START, ZW_GEJU } from '../data/ziwei';
7
8
 
8
9
  // 地支位:0=寅 起(与既有约定一致)
@@ -73,9 +73,6 @@ export function interpretZiwei(r: ZiweiResult): ZiweiInsight[] {
73
73
  const stars = starsOf(r, pos);
74
74
  if (stars.length === 0) continue;
75
75
  const pname = PALACE_NAMES[i];
76
- const brief = stars.map(starBrief).join(';');
77
- const line = pname + '宫(' + DIZHI[pos] + ')见' + stars.join('、') + '——' + brief + '。此宫主' + palaceTheme(pname) + '。'
78
- + stars[0] + '入' + pname + ',' + (GOOD_STARS.includes(stars[0]) ? '为得地之象,多主顺遂。' : '须留意分寸,化其棱角则吉。');
79
76
  overview.push({ name: pname, stars, pos });
80
77
  }
81
78
  out.push({