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.
- package/README.en.md +5 -2
- package/README.md +6 -3
- package/dist/assets/index-C3wrtpPY.css +1 -0
- package/dist/assets/{index-C7ql6zvs.js → index-CAyAIgw8.js} +25 -25
- package/dist/assets/index-CAyAIgw8.js.map +1 -0
- package/dist/index.html +2 -2
- package/package.json +2 -2
- package/server/src/data/db.json +147 -0
- package/server/src/index.ts +27 -4
- package/server/src/routes/tarot.ts +4 -4
- package/server/src/routes/users.ts +42 -7
- package/server/src/services/divineStore.ts +1 -5
- package/server/src/services/promptBuilder.ts +2 -2
- package/server/src/types/index.ts +2 -104
- package/{src → shared/core}/data/lifeContext.ts +2 -8
- package/{src → shared/core}/data/spreads.ts +1 -1
- package/{src → shared/core}/data/tarotCards.ts +1 -1
- package/shared/core/engine/bazi.ts +0 -1
- package/shared/core/engine/calendar.ts +1 -1
- package/{server/src/utils → shared/core/engine}/semanticAnalyzer.ts +9 -46
- package/{server/src/utils → shared/core/engine}/tarotEngine.ts +6 -6
- package/shared/core/engine/ziwei.ts +3 -2
- package/shared/core/engine/ziweiInterpret.ts +0 -3
- package/shared/core/types.ts +157 -0
- package/src/arts/registry.ts +1 -1
- package/src/components/QuestionFields.tsx +0 -1
- package/src/components/ReportView.tsx +0 -1
- package/src/components/TimeShichenInput.tsx +1 -0
- package/src/components/arts/BaziArt.tsx +0 -10
- package/src/components/arts/LiurenArt.tsx +1 -1
- package/src/components/arts/LiuyaoArt.tsx +0 -1
- package/src/components/arts/MeihuaArt.tsx +0 -1
- package/src/components/arts/QimenArt.tsx +0 -1
- package/src/components/arts/TarotArt.tsx +2 -2
- package/src/components/arts/XiaoliurenArt.tsx +1 -2
- package/src/components/arts/ZiweiArt.tsx +2 -15
- package/src/pages/AuthPage.tsx +4 -6
- package/src/pages/DemoPage.tsx +1 -2
- package/src/pages/HistoryPage.tsx +10 -9
- package/src/pages/ModulePage.tsx +6 -32
- package/src/pages/SpreadLibrary.tsx +1 -1
- package/src/pages/TarotPage.tsx +7 -9
- package/src/services/api.ts +4 -4
- package/src/stores/useDivinationStore.ts +1 -1
- package/src/types/index.ts +2 -104
- package/tsconfig.json +5 -5
- package/dist/assets/index-C7ql6zvs.js.map +0 -1
- package/dist/assets/index-DQx62TUr.css +0 -1
- package/server/src/data/lifeContext.ts +0 -423
- package/server/src/data/spreads.ts +0 -255
- package/server/src/data/tarotCards.ts +0 -2353
- package/src/utils/semanticAnalyzer.ts +0 -555
- 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-
|
|
10
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
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.
|
|
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": ">=
|
|
94
|
+
"node": ">=22.5.0"
|
|
95
95
|
}
|
|
96
96
|
}
|
package/server/src/data/db.json
CHANGED
|
@@ -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
|
}
|
package/server/src/index.ts
CHANGED
|
@@ -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(
|
|
12
|
-
const
|
|
13
|
-
if (
|
|
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 '
|
|
3
|
-
import { defaultSpreads } from '
|
|
4
|
-
import { drawCards, generateInterpretation } from '
|
|
5
|
-
import { analyzeQuestion } from '
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
//
|
|
92
|
+
// 角色化解读版:9 术 persona(单轮注入)+ 档案 + 排盘结果 → 结构化解读报告
|
|
93
93
|
export function buildReportMessages(
|
|
94
94
|
artId: string,
|
|
95
95
|
question: string,
|
|
@@ -1,104 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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,
|
|
4
|
+
import type { Scene, LifeContext } from '../types.js';
|
|
5
5
|
|
|
6
|
-
export
|
|
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: {
|
|
@@ -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(
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
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 '
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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[],
|
|
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,
|
|
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
|
-
|
|
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({
|