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/shared/core/types.ts
CHANGED
|
@@ -198,6 +198,163 @@ export interface TarotCardData {
|
|
|
198
198
|
reversed: boolean;
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
+
// ─── 塔罗解读链路(原 src/types + src/utils/semanticAnalyzer 收敛至此,双端单一副本)───
|
|
202
|
+
export interface TarotCard {
|
|
203
|
+
id: number;
|
|
204
|
+
name: string;
|
|
205
|
+
nameEn: string;
|
|
206
|
+
arcana: 'major' | 'minor';
|
|
207
|
+
suit?: 'wands' | 'cups' | 'swords' | 'pentacles';
|
|
208
|
+
number?: number;
|
|
209
|
+
keywords: string[];
|
|
210
|
+
meanings: {
|
|
211
|
+
upright: string;
|
|
212
|
+
reversed: string;
|
|
213
|
+
};
|
|
214
|
+
deepMeaning: {
|
|
215
|
+
coreTheme: string;
|
|
216
|
+
psychological: string;
|
|
217
|
+
spiritual: string;
|
|
218
|
+
shadow: string;
|
|
219
|
+
lifeAreas: {
|
|
220
|
+
love: string;
|
|
221
|
+
career: string;
|
|
222
|
+
wealth: string;
|
|
223
|
+
health: string;
|
|
224
|
+
growth: string;
|
|
225
|
+
};
|
|
226
|
+
advice: string;
|
|
227
|
+
warning: string;
|
|
228
|
+
};
|
|
229
|
+
imageSymbols: string[];
|
|
230
|
+
story: string;
|
|
231
|
+
astrology: {
|
|
232
|
+
zodiac?: string;
|
|
233
|
+
planet?: string;
|
|
234
|
+
element?: string;
|
|
235
|
+
};
|
|
236
|
+
kabbalah: {
|
|
237
|
+
path?: number;
|
|
238
|
+
sephirah?: string;
|
|
239
|
+
};
|
|
240
|
+
alchemy: {
|
|
241
|
+
stage?: string;
|
|
242
|
+
element?: string;
|
|
243
|
+
};
|
|
244
|
+
numerology: {
|
|
245
|
+
number: number;
|
|
246
|
+
meaning: string;
|
|
247
|
+
};
|
|
248
|
+
image: string;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export interface SpreadPosition {
|
|
252
|
+
id: number;
|
|
253
|
+
name: string;
|
|
254
|
+
description: string;
|
|
255
|
+
x: number;
|
|
256
|
+
y: number;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export interface Spread {
|
|
260
|
+
id: string;
|
|
261
|
+
name: string;
|
|
262
|
+
description: string;
|
|
263
|
+
positions: SpreadPosition[];
|
|
264
|
+
category: 'basic' | 'advanced' | 'custom';
|
|
265
|
+
scene?: string[];
|
|
266
|
+
isCustom?: boolean;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export interface DrawnCard {
|
|
270
|
+
cardId: number;
|
|
271
|
+
isReversed: boolean;
|
|
272
|
+
positionId: number;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export interface DivinationRecord {
|
|
276
|
+
id: string;
|
|
277
|
+
type: 'tarot' | 'combo' | 'ziwei' | 'bazi' | 'astrology';
|
|
278
|
+
question: string;
|
|
279
|
+
category: string;
|
|
280
|
+
spreadId?: string;
|
|
281
|
+
cards?: DrawnCard[];
|
|
282
|
+
comboMethod?: string;
|
|
283
|
+
comboResult?: string;
|
|
284
|
+
interpretation: string;
|
|
285
|
+
timestamp: number;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export interface ComboResult {
|
|
289
|
+
method: 'xiaoliuren' | 'daliuren' | 'liuyao' | 'meihua' | 'qimen';
|
|
290
|
+
methodName: string;
|
|
291
|
+
result: string;
|
|
292
|
+
detail: string;
|
|
293
|
+
relationToTarot: string;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export type QuestionCategory = 'love' | 'career' | 'wealth' | 'health' | 'study' | 'family' | 'friendship' | 'spiritual' | 'travel' | 'general';
|
|
297
|
+
|
|
298
|
+
export interface TarotReading {
|
|
299
|
+
question: string;
|
|
300
|
+
category: QuestionCategory;
|
|
301
|
+
spreadId: string;
|
|
302
|
+
cards: DrawnCard[];
|
|
303
|
+
interpretation: string;
|
|
304
|
+
comboResult?: ComboResult;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// 问题语义分析(原 src/utils/semanticAnalyzer 类型部分)
|
|
308
|
+
export type Scene = 'love' | 'career' | 'wealth' | 'study' | 'health' | 'family' | 'friendship' | 'spiritual' | 'travel' | 'general';
|
|
309
|
+
export type Emotion = 'anxious' | 'hopeful' | 'confused' | 'lost' | 'grateful' | 'calm' | 'angry' | 'sad' | 'curious';
|
|
310
|
+
export type Tense = 'past' | 'present' | 'future' | 'hypothetical' | 'ongoing';
|
|
311
|
+
export type Subject = 'self' | 'other' | 'mutual' | 'third-party';
|
|
312
|
+
export type Depth = 'surface' | 'mid' | 'deep';
|
|
313
|
+
export type QuestionType = 'decision' | 'prediction' | 'reason' | 'advice' | 'mindreading' | 'timing' | 'general';
|
|
314
|
+
export type ResponseNeed = 'comfort' | 'direction' | 'confirmation' | 'warning';
|
|
315
|
+
|
|
316
|
+
export interface KeyEntity {
|
|
317
|
+
type: 'person' | 'event' | 'time';
|
|
318
|
+
value: string;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export interface QuestionAnalysis {
|
|
322
|
+
realQuestion: string;
|
|
323
|
+
emotionalNeed: string;
|
|
324
|
+
responseNeed: ResponseNeed;
|
|
325
|
+
responseNeedLabel: string;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export interface SemanticAnalysis {
|
|
329
|
+
scene: Scene;
|
|
330
|
+
sceneLabel: string;
|
|
331
|
+
emotion: Emotion;
|
|
332
|
+
emotionLabel: string;
|
|
333
|
+
tense: Tense;
|
|
334
|
+
tenseLabel: string;
|
|
335
|
+
subject: Subject;
|
|
336
|
+
subjectLabel: string;
|
|
337
|
+
depth: Depth;
|
|
338
|
+
depthLabel: string;
|
|
339
|
+
keywords: string[];
|
|
340
|
+
intensity: number; // 0-1,问题强度
|
|
341
|
+
confidence: number; // 0-1,分析置信度
|
|
342
|
+
questionType: QuestionType; // 问题类型
|
|
343
|
+
questionTypeLabel: string; // 问题类型中文标签
|
|
344
|
+
questionCore: string; // 问题的核心意图
|
|
345
|
+
questionSummary: string; // 一句话概括用户问题的本质
|
|
346
|
+
keyEntities: KeyEntity[]; // 问题中的关键实体
|
|
347
|
+
questionAnalysis: QuestionAnalysis; // 深度分析问题的潜台词
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export interface LifeContext {
|
|
351
|
+
scenes: string[];
|
|
352
|
+
actions: string[];
|
|
353
|
+
warnings: string[];
|
|
354
|
+
timeline: { short: string; mid: string; long: string };
|
|
355
|
+
emotionAdvice: Record<Emotion, string>;
|
|
356
|
+
}
|
|
357
|
+
|
|
201
358
|
// 通用引擎契约
|
|
202
359
|
export interface ArtEngine<TInput, TResult> {
|
|
203
360
|
id: ArtId;
|
package/src/arts/registry.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { AstrologyPanel, AstrologyResult } from '@/components/arts/AstrologyArt'
|
|
|
11
11
|
import { TarotPanel, TarotResult } from '@/components/arts/TarotArt';
|
|
12
12
|
|
|
13
13
|
export interface ArtPair {
|
|
14
|
-
Panel: ComponentType<{ onDivine: (
|
|
14
|
+
Panel: ComponentType<{ onDivine: (...args: any[]) => void }>;
|
|
15
15
|
Result: ComponentType<{ data: any }>;
|
|
16
16
|
placeholder: string;
|
|
17
17
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// 四柱八字:输入面板(含出生地点→真太阳时)+ 结果渲染(四柱/五行/十神)
|
|
2
2
|
import { useState } from 'react';
|
|
3
|
-
import { baziCalc } from '@core/engine/bazi';
|
|
4
3
|
import { SHISHEN_MAP, WUXING } from '@core/data/ganzhi';
|
|
5
4
|
import type { BaziResult, GeoLocation } from '@core/types';
|
|
6
5
|
import type { UserProfile } from '@/utils/userStore';
|
|
@@ -258,12 +257,3 @@ export function BaziResult({ data }: { data: BaziResult }) {
|
|
|
258
257
|
</>
|
|
259
258
|
);
|
|
260
259
|
}
|
|
261
|
-
// 十神格局点睛
|
|
262
|
-
function shishenFocus(r: any): string {
|
|
263
|
-
const names = r.shishen.map((s: any) => s.name);
|
|
264
|
-
if (names.includes('正官') || names.includes('七杀')) return '官杀透干,主担当与约束,成事在于守正。';
|
|
265
|
-
if (names.includes('正财') || names.includes('偏财')) return '财星显露,主经营与所得,宜务实聚财。';
|
|
266
|
-
if (names.includes('正印') || names.includes('偏印')) return '印星为用,主学识与荫庇,宜向学深造。';
|
|
267
|
-
if (names.includes('食神') || names.includes('伤官')) return '食伤泄秀,主才情与输出,宜展露所长。';
|
|
268
|
-
return '比劫同气,主自立与同侪,宜广结善缘。';
|
|
269
|
-
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// 大六壬:起课面板 + 天地盘/四课三传渲染
|
|
2
2
|
import { useState } from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { jiangName } from '@core/engine/liuren';
|
|
4
4
|
import { ZHI } from '@core/data/ganzhi';
|
|
5
5
|
import { LR_JIANG_SYMBOL, LR_JIANG_WX } from '@core/data/liuren';
|
|
6
6
|
import type { LiurenResult } from '@core/types';
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// 梅花易数:起卦面板(时间/报数)+ 卦象渲染(本卦/互卦/变卦 + 体用)
|
|
2
2
|
import { useState } from 'react';
|
|
3
|
-
import { meihuaCalc } from '@core/engine/meihua';
|
|
4
3
|
import { BAGUA } from '@core/data/gua64';
|
|
5
4
|
import type { MeihuaResult } from '@core/types';
|
|
6
5
|
import { ResultCard } from '@/components/ResultCard';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// 塔罗:问镜面板(牌阵选择)+ 扇形翻牌(未翻开不示牌名)+ 牌阵之示
|
|
2
2
|
import { useState } from 'react';
|
|
3
3
|
import { Link } from 'react-router-dom';
|
|
4
|
-
import {
|
|
4
|
+
import { SUIT } from '@core/engine/tarot';
|
|
5
5
|
import { allTarotSpreads, type TarotSpread } from '@core/data/tarotSpreads';
|
|
6
6
|
import type { TarotCardData } from '@core/types';
|
|
7
7
|
import { ResultCard } from '@/components/ResultCard';
|
|
@@ -96,7 +96,7 @@ export function TarotResult({ data }: { data: DrawResult }) {
|
|
|
96
96
|
);
|
|
97
97
|
}
|
|
98
98
|
// 塔罗全阵参详(正逆位比例 + 元素侧重,现代语言)
|
|
99
|
-
function tarotSummary(cards: any[],
|
|
99
|
+
function tarotSummary(cards: any[], _spread: any): string {
|
|
100
100
|
const rev = cards.filter(c => c.reversed).length;
|
|
101
101
|
const majors = cards.filter(c => c.major).length;
|
|
102
102
|
const parts: string[] = [];
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// 小六壬:掐指占时面板 + 掌诀/推演轨迹渲染
|
|
2
2
|
import { useState } from 'react';
|
|
3
|
-
import {
|
|
4
|
-
import { XLR_ORDER, XLR } from '@core/data/xiaoliuren';
|
|
3
|
+
import { XLR_ORDER } from '@core/data/xiaoliuren';
|
|
5
4
|
import { mod } from '@core/data/ganzhi';
|
|
6
5
|
import type { XiaoliurenResult } from '@core/types';
|
|
7
6
|
import { ResultCard } from '@/components/ResultCard';
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
// 紫微斗数:输入面板(干支年/农历月日时 + 地点经度校正)+ 紫微盘渲染
|
|
2
2
|
import { useState } from 'react';
|
|
3
|
-
import {
|
|
4
|
-
import { ZW_STARS, ZW_STAR_MEAN, PALACE_NAMES } from '@core/data/ziwei';
|
|
3
|
+
import { PALACE_NAMES } from '@core/data/ziwei';
|
|
5
4
|
import { interpretZiwei } from '@core/engine/ziweiInterpret';
|
|
6
|
-
import { ganZhiIndex, GAN, ZHI, NAYIN } from '@core/data/ganzhi';
|
|
7
5
|
import { timeToHourIndex, hourIndexLabel } from '@/data/shichen';
|
|
8
6
|
import TimeShichenInput from '@/components/TimeShichenInput';
|
|
9
7
|
import type { ZiweiResult, GeoLocation } from '@core/types';
|
|
@@ -22,16 +20,6 @@ import LifeEventsInput, { type LifeEvent } from '@/components/LifeEventsInput';
|
|
|
22
20
|
const DIZHI = ['寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥', '子', '丑'];
|
|
23
21
|
const mod = (a: number, n: number) => ((a % n) + n) % n;
|
|
24
22
|
|
|
25
|
-
// 干支年选项(1950-2010)
|
|
26
|
-
function ganzhiOptions() {
|
|
27
|
-
const out: { gz: string; year: number }[] = [];
|
|
28
|
-
for (let y = 1950; y <= 2010; y++) {
|
|
29
|
-
const i = ((y - 4) % 60 + 60) % 60;
|
|
30
|
-
out.push({ gz: GAN[i % 10] + ZHI[i % 12], year: y });
|
|
31
|
-
}
|
|
32
|
-
return out;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
23
|
interface PanelProps { onDivine: (inputs: unknown, profile?: UserProfile, question?: string, profileId?: string) => void; }
|
|
36
24
|
|
|
37
25
|
export function ZiweiPanel({ onDivine }: PanelProps) {
|
|
@@ -53,7 +41,7 @@ export function ZiweiPanel({ onDivine }: PanelProps) {
|
|
|
53
41
|
if (new Date(yy, mm - 1, dd).getMonth() !== mm - 1) { setFormMsg('⚠ 出生日期不合法,请检查'); return; }
|
|
54
42
|
setFormMsg('');
|
|
55
43
|
// 公历 → 农历(万年历),紫微依农历排盘
|
|
56
|
-
let lm = 1, ld = 1, gz = '甲子'
|
|
44
|
+
let lm = 1, ld = 1, gz = '甲子';
|
|
57
45
|
try {
|
|
58
46
|
const lunar = Solar.fromYmd(yy, mm, dd).getLunar();
|
|
59
47
|
const rawMonth = lunar.getMonth();
|
|
@@ -61,7 +49,6 @@ export function ZiweiPanel({ onDivine }: PanelProps) {
|
|
|
61
49
|
// 闰月(lunar 返回负数):按 iztro fixLeap 规则,后半月算下月
|
|
62
50
|
lm = Math.abs(rawMonth) + (rawMonth < 0 && ld > 15 ? 1 : 0);
|
|
63
51
|
gz = lunar.getYearInGanZhi();
|
|
64
|
-
birthYear = lunar.getYear();
|
|
65
52
|
} catch { /* 非法日期回退默认 */ }
|
|
66
53
|
// 时辰未知:紫微以午时示例排盘(仅命局五行参考),档案标记未知供 AI 解读约束
|
|
67
54
|
const hourIdx = hourUnknown ? 6 : timeToHourIndex(birthTime);
|
package/src/pages/AuthPage.tsx
CHANGED
|
@@ -5,15 +5,13 @@ import { apiDivineHistory, apiDivineDetail, apiDivineDelete, downloadReport } fr
|
|
|
5
5
|
import ReportView from '@/components/ReportView';
|
|
6
6
|
import {
|
|
7
7
|
register, login, logout, currentUser, updateProfile,
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
updateSampleProfile, removeSampleProfile, promoteSampleProfile,
|
|
9
|
+
type UserProfile, type NamedProfile,
|
|
10
10
|
} from '@/utils/userStore';
|
|
11
11
|
import { useConfirm } from '@/components/SongDialog';
|
|
12
12
|
import { syncRecordsFromServer } from '@/utils/recordStore';
|
|
13
13
|
import { syncProfileToServer } from '@/utils/userStore';
|
|
14
14
|
import ProfileForm from '@/components/ProfileForm';
|
|
15
|
-
import SongSelect from '@/components/SongSelect';
|
|
16
|
-
|
|
17
15
|
export default function AuthPage() {
|
|
18
16
|
const navigate = useNavigate();
|
|
19
17
|
const confirm = useConfirm();
|
|
@@ -220,12 +218,12 @@ export default function AuthPage() {
|
|
|
220
218
|
<p>此占尚未召 AI 成文,仅存排盘之象</p>
|
|
221
219
|
</div>
|
|
222
220
|
)}
|
|
223
|
-
{histDetail.resultRaw
|
|
221
|
+
{histDetail.resultRaw ? (
|
|
224
222
|
<details className="mh-intro" style={{ marginTop: 'var(--sp-2)' }}>
|
|
225
223
|
<summary>排盘原始数据</summary>
|
|
226
224
|
<pre style={{ fontSize: '.72rem', lineHeight: 1.7, whiteSpace: 'pre-wrap', wordBreak: 'break-all' }}>{JSON.stringify(histDetail.resultRaw, null, 1)}</pre>
|
|
227
225
|
</details>
|
|
228
|
-
)}
|
|
226
|
+
) : null}
|
|
229
227
|
</div>
|
|
230
228
|
</div>
|
|
231
229
|
)}
|
package/src/pages/DemoPage.tsx
CHANGED
|
@@ -67,7 +67,6 @@ export default function DemoPage() {
|
|
|
67
67
|
const [artId, setArtId] = useState('bazi');
|
|
68
68
|
const [localResult, setLocalResult] = useState<unknown>(null);
|
|
69
69
|
const art = ARTS.find(a => a.id === artId)!;
|
|
70
|
-
const chart = baziCalc(DEMO_BIRTH);
|
|
71
70
|
|
|
72
71
|
const reset = () => {
|
|
73
72
|
setPhase('idle');
|
|
@@ -136,7 +135,7 @@ export default function DemoPage() {
|
|
|
136
135
|
{phase === 'done' && (
|
|
137
136
|
<>
|
|
138
137
|
<div className="altar-result stagger">
|
|
139
|
-
{localResult
|
|
138
|
+
{localResult ? RESULT_VIEWS[artId]?.(localResult) : null}
|
|
140
139
|
{report && (
|
|
141
140
|
<div className="result-card mt-3">
|
|
142
141
|
<h3>AI 深度解读 <span className="tag-cool" style={{ marginLeft: '.4rem', fontSize: '.72rem' }}>示例报告</span></h3>
|
|
@@ -61,7 +61,7 @@ export default function HistoryPage() {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
const records = useMemo(() => getRecords(), [tick, artFilter, search]);
|
|
64
|
-
const [profileFilter
|
|
64
|
+
const [profileFilter] = useState('all');
|
|
65
65
|
const filtered = records.filter(r => {
|
|
66
66
|
if (profileFilter !== 'all' && (r.profileId || 'main') !== profileFilter) return false;
|
|
67
67
|
if (artFilter !== 'all' && r.artId !== artFilter) return false;
|
|
@@ -115,15 +115,16 @@ export default function HistoryPage() {
|
|
|
115
115
|
function summarize(result: unknown): string {
|
|
116
116
|
const r = result as Record<string, any>;
|
|
117
117
|
if (!r) return '(结果不可读)';
|
|
118
|
-
|
|
119
|
-
if (
|
|
120
|
-
if (
|
|
121
|
-
if (
|
|
122
|
-
if (
|
|
123
|
-
if (
|
|
118
|
+
const o: Record<string, any> = Array.isArray(r) ? {} : r;
|
|
119
|
+
if (o.yearGZ) return `${o.yearGZ}年 ${o.monthGZ}月 ${o.dayGZ}日 ${o.hourGZ}时 · ${o.strength || ''}`;
|
|
120
|
+
if (o.benGua) return `本卦${o.benGua.name} · ${o.bianGua ? '变' + o.bianGua.name : ''} · ${o.shengke || ''}`;
|
|
121
|
+
if (o.ju) return `${o.jqName} · ${o.yin ? '阴' : '阳'}遁${o.ju}局 · ${o.dayGZ}日`;
|
|
122
|
+
if (o.name && o.detail) return `${o.name} · ${o.detail.ji || ''} · ${o.detail.wx || ''}`;
|
|
123
|
+
if (o.jiang) return `${o.jiang}将加时 · 四课${o.ke1}/${o.ke2}/${o.ke3}/${o.ke4}`;
|
|
124
|
+
if (o.ming !== undefined) return `命宫@${o.ming} · ${o.juName || ''} · 紫微@${o.zwPos}`;
|
|
124
125
|
if (Array.isArray(r) && r[0] && r[0].name) return `${r.length} 张牌:${r.map((c: any) => c.name).join('、')}`;
|
|
125
|
-
if (
|
|
126
|
-
if (
|
|
126
|
+
if (o.planets) return `太阳落${signOf(o.sun)} · 月亮落${signOf(o.moon)}`;
|
|
127
|
+
if (o.yin !== undefined) return `${o.jqName || ''} · ${o.yin ? '阴' : '阳'}遁${o.ju}局`;
|
|
127
128
|
return JSON.stringify(r).slice(0, 40);
|
|
128
129
|
}
|
|
129
130
|
|
package/src/pages/ModulePage.tsx
CHANGED
|
@@ -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 {
|
|
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: '布四柱 · 观五行 · 推十神' },
|
|
@@ -106,10 +79,11 @@ export default function ModulePage() {
|
|
|
106
79
|
};
|
|
107
80
|
const isProfileArt = ['bazi', 'ziwei', 'astrology'].includes(art?.id || '');
|
|
108
81
|
const exportReport = (rep: AIReport) => {
|
|
82
|
+
const artName = art?.name || '观微';
|
|
109
83
|
const lines = [
|
|
110
84
|
'# ' + (rep.title || '观微解读报告'),
|
|
111
85
|
'',
|
|
112
|
-
'> 术别:' +
|
|
86
|
+
'> 术别:' + artName + ' · ' + new Date().toLocaleString('zh-CN'),
|
|
113
87
|
'',
|
|
114
88
|
'## 总述',
|
|
115
89
|
'',
|
|
@@ -237,11 +211,11 @@ export default function ModulePage() {
|
|
|
237
211
|
<p>凝神静气 · 卦象将成</p>
|
|
238
212
|
</div>
|
|
239
213
|
)}
|
|
240
|
-
{status === 'done' && result
|
|
214
|
+
{status === 'done' && result ? (PairResult ? <PairResult data={result} /> : null) : null}
|
|
241
215
|
{status === 'error' && (
|
|
242
216
|
<ResultPlaceholder glyph="滞" text={divineError || '推演未应机 · 请稍后再试'} />
|
|
243
217
|
)}
|
|
244
|
-
{status === 'done' && result
|
|
218
|
+
{status === 'done' && result ? (
|
|
245
219
|
<div className="result-card mt-3">
|
|
246
220
|
<h3>AI 深度解读</h3>
|
|
247
221
|
{ai.status === 'idle' && (
|
|
@@ -329,7 +303,7 @@ export default function ModulePage() {
|
|
|
329
303
|
)}
|
|
330
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>
|
|
331
305
|
</div>
|
|
332
|
-
)}
|
|
306
|
+
) : null}
|
|
333
307
|
</div>
|
|
334
308
|
</div>
|
|
335
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 '
|
|
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';
|
package/src/pages/TarotPage.tsx
CHANGED
|
@@ -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,
|
|
4
|
-
import type { QuestionCategory, Spread, DrawnCard
|
|
5
|
-
import { defaultSpreads } from '
|
|
6
|
-
import { tarotCards } from '
|
|
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
|
-
|
|
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)}
|
package/src/services/api.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { QuestionCategory, Spread, DrawnCard } from '@/types';
|
|
2
|
-
import type { InterpretationResult } from '
|
|
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,
|
|
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
|
|
2
|
+
import type { TarotReading, ComboResult } from '@/types';
|
|
3
3
|
|
|
4
4
|
interface DivinationState {
|
|
5
5
|
currentReading: TarotReading | null;
|