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,234 @@
1
+ // 本地用户体系(演示级):注册/登录/会话/出生档案
2
+ // 注意:仅本地存储(localStorage),密码为演示级哈希,不适用于生产环境
3
+ import type { GeoLocation } from '@core/types';
4
+
5
+ export interface UserProfile {
6
+ birthDate: string; // YYYY-MM-DD(公历)
7
+ birthTime: string; // 精确出生时刻 HH:MM(东玄匹配时辰,星盘直接用)
8
+ birthHourIndex: number; // 0-11 时辰序(兼容旧数据;新数据由 birthTime 派生)
9
+ birthTimeUnknown?: boolean; // 时辰未知(true 时不排时柱,AI 解读仅依年月日三柱)
10
+ gender: '男' | '女';
11
+ location: GeoLocation | null;
12
+ /** 命主已知人生经历(用于 AI 解读校准:报告须呼应该年份事件,不得与其矛盾) */
13
+ lifeEvents?: { year: number; text: string }[];
14
+ }
15
+
16
+ export interface NamedProfile {
17
+ id: string;
18
+ name: string; // 示例档案名称
19
+ profile: UserProfile;
20
+ }
21
+
22
+ export interface User {
23
+ username: string;
24
+ passHash: string;
25
+ createdAt: number;
26
+ profile: UserProfile; // 主档案(唯一,默认账号档案)
27
+ samples: NamedProfile[]; // 示例档案(命名保存)
28
+ }
29
+
30
+ const USERS_KEY = 'guanwei_users';
31
+
32
+ // ===== 云同步(前后端联调:后端可用时自动同步) =====
33
+ const API = '/api/users';
34
+
35
+ async function apiOk(): Promise<boolean> {
36
+ try {
37
+ const res = await fetch('/api/health');
38
+ return res.ok;
39
+ } catch { return false; }
40
+ }
41
+
42
+ export async function syncProfileToServer(username: string, profile: UserProfile, samples: NamedProfile[]): Promise<void> {
43
+ if (!(await apiOk())) return;
44
+ try {
45
+ await fetch(API + '/' + encodeURIComponent(username) + '/profile', {
46
+ method: 'PUT',
47
+ headers: { 'Content-Type': 'application/json' },
48
+ body: JSON.stringify({ profile, samples }),
49
+ });
50
+ } catch { /* 离线忽略 */ }
51
+ }
52
+
53
+ export async function pushRecordsToServer(username: string, records: unknown[]): Promise<void> {
54
+ if (!(await apiOk())) return;
55
+ try {
56
+ await fetch(API + '/' + encodeURIComponent(username) + '/records', {
57
+ method: 'PUT',
58
+ headers: { 'Content-Type': 'application/json' },
59
+ body: JSON.stringify({ records }),
60
+ });
61
+ } catch { /* 离线忽略 */ }
62
+ }
63
+
64
+ export async function pullRecordsFromServer(username: string): Promise<unknown[] | null> {
65
+ if (!(await apiOk())) return null;
66
+ try {
67
+ const res = await fetch(API + '/' + encodeURIComponent(username) + '/records');
68
+ if (!res.ok) return null;
69
+ const data = await res.json();
70
+ return data.records || null;
71
+ } catch { return null; }
72
+ }
73
+ const SESSION_KEY = 'guanwei_session';
74
+
75
+ // 演示级确定性哈希(djb2)
76
+ export function hashPassword(pw: string): string {
77
+ let h = 5381;
78
+ const s = 'guanwei::' + pw;
79
+ for (let i = 0; i < s.length; i++) h = ((h << 5) + h + s.charCodeAt(i)) >>> 0;
80
+ return 'h' + h.toString(16);
81
+ }
82
+
83
+ function loadUsers(): User[] {
84
+ try {
85
+ const users: User[] = JSON.parse(localStorage.getItem(USERS_KEY) || '[]');
86
+ // 数据迁移:旧版本用户无 samples 字段
87
+ return users.map(u => ({
88
+ ...u,
89
+ profile: migrateProfile(u.profile),
90
+ samples: (u.samples || []).map(s => ({ ...s, profile: migrateProfile(s.profile) })),
91
+ }));
92
+ }
93
+ catch { return []; }
94
+ }
95
+
96
+ function saveUsers(users: User[]): void {
97
+ localStorage.setItem(USERS_KEY, JSON.stringify(users));
98
+ }
99
+
100
+ export const DEFAULT_PROFILE: UserProfile = {
101
+ birthDate: '1990-06-15',
102
+ birthTime: '12:00',
103
+ birthHourIndex: 6,
104
+ gender: '男',
105
+ location: null,
106
+ }
107
+
108
+ export interface AuthResult { ok: boolean; message: string; user?: User }
109
+
110
+ export function register(username: string, password: string, profile: UserProfile = DEFAULT_PROFILE): AuthResult {
111
+ const name = username.trim();
112
+ if (name.length < 2) return { ok: false, message: '名号至少二字' };
113
+ if (password.length < 4) return { ok: false, message: '密语至少四位' };
114
+ const users = loadUsers();
115
+ if (users.some(u => u.username === name)) return { ok: false, message: '此名号已有人用' };
116
+ const user: User = { username: name, passHash: hashPassword(password), createdAt: Date.now(), profile, samples: [] };
117
+ users.push(user);
118
+ saveUsers(users);
119
+ setSession(name);
120
+ return { ok: true, message: '入馆成功', user };
121
+ }
122
+
123
+ export function login(username: string, password: string): AuthResult {
124
+ const users = loadUsers();
125
+ const user = users.find(u => u.username === username.trim());
126
+ if (!user || user.passHash !== hashPassword(password)) return { ok: false, message: '名号或密语未合' };
127
+ setSession(user.username);
128
+ return { ok: true, message: '入馆成功', user };
129
+ }
130
+
131
+ export function logout(): void {
132
+ localStorage.removeItem(SESSION_KEY);
133
+ }
134
+
135
+ function setSession(username: string): void {
136
+ localStorage.setItem(SESSION_KEY, JSON.stringify({ username, loginAt: Date.now() }));
137
+ }
138
+
139
+ export function currentUser(): User | null {
140
+ try {
141
+ const sess = JSON.parse(localStorage.getItem(SESSION_KEY) || 'null');
142
+ if (!sess?.username) return null;
143
+ return loadUsers().find(u => u.username === sess.username) || null;
144
+ } catch { return null; }
145
+ }
146
+
147
+ export function updateProfile(profile: Partial<UserProfile>): User | null {
148
+ const user = currentUser();
149
+ if (!user) return null;
150
+ const users = loadUsers();
151
+ const idx = users.findIndex(u => u.username === user.username);
152
+ if (idx < 0) return null;
153
+ users[idx] = { ...users[idx], profile: { ...users[idx].profile, ...profile } };
154
+ saveUsers(users);
155
+ return users[idx];
156
+ }
157
+
158
+ // ===== 示例档案管理 =====
159
+
160
+ // 追加示例档案(命名保存)
161
+ export function addSampleProfile(name: string, profile: UserProfile): User | null {
162
+ const user = currentUser();
163
+ if (!user) return null;
164
+ const users = loadUsers();
165
+ const idx = users.findIndex(u => u.username === user.username);
166
+ if (idx < 0) return null;
167
+ users[idx] = {
168
+ ...users[idx],
169
+ samples: [...users[idx].samples, { id: 's' + Date.now() + '-' + Math.random().toString(36).slice(2, 7), name: name.trim() || '未名档案', profile }],
170
+ };
171
+ saveUsers(users);
172
+ return users[idx];
173
+ }
174
+
175
+ // 编辑示例档案
176
+ export function updateSampleProfile(id: string, profile: UserProfile): User | null {
177
+ const user = currentUser();
178
+ if (!user) return null;
179
+ const users = loadUsers();
180
+ const idx = users.findIndex(u => u.username === user.username);
181
+ if (idx < 0) return null;
182
+ users[idx] = {
183
+ ...users[idx],
184
+ samples: users[idx].samples.map(s => s.id === id ? { ...s, profile: { ...s.profile, ...profile } } : s),
185
+ };
186
+ saveUsers(users);
187
+ return users[idx];
188
+ }
189
+
190
+ // 删除示例档案
191
+ export function removeSampleProfile(id: string): User | null {
192
+ const user = currentUser();
193
+ if (!user) return null;
194
+ const users = loadUsers();
195
+ const idx = users.findIndex(u => u.username === user.username);
196
+ if (idx < 0) return null;
197
+ users[idx] = { ...users[idx], samples: users[idx].samples.filter(s => s.id !== id) };
198
+ saveUsers(users);
199
+ return users[idx];
200
+ }
201
+
202
+ // 示例档案设为主档案(原主档案转为示例档案)
203
+ export function promoteSampleProfile(id: string): User | null {
204
+ const user = currentUser();
205
+ if (!user) return null;
206
+ const users = loadUsers();
207
+ const idx = users.findIndex(u => u.username === user.username);
208
+ if (idx < 0) return null;
209
+ const sample = users[idx].samples.find(s => s.id === id);
210
+ if (!sample) return null;
211
+ const oldPrimary = users[idx].profile;
212
+ users[idx] = {
213
+ ...users[idx],
214
+ profile: sample.profile,
215
+ samples: [
216
+ ...users[idx].samples.filter(s => s.id !== id),
217
+ { id: 's' + Date.now() + '-' + Math.random().toString(36).slice(2, 7), name: '旧主档案', profile: oldPrimary },
218
+ ],
219
+ };
220
+ saveUsers(users);
221
+ return users[idx];
222
+ }
223
+
224
+ // 记录隔离 key(未登录 → guest)
225
+ export function recordsKey(): string {
226
+ return 'guanwei_records_' + (currentUser()?.username || 'guest');
227
+ }
228
+ // 旧档案迁移:无 birthTime 时由时辰序推算(取时辰中点)
229
+ function migrateProfile(p: UserProfile): UserProfile {
230
+ if (!p) return DEFAULT_PROFILE;
231
+ if (p.birthTime) return p;
232
+ const hour = ((p.birthHourIndex ?? 4) * 2) % 24;
233
+ return { ...p, birthTime: String(hour).padStart(2, '0') + ':00' };
234
+ }
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
@@ -0,0 +1,52 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+
3
+ export default {
4
+ darkMode: "class",
5
+ content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
6
+ theme: {
7
+ container: {
8
+ center: true,
9
+ },
10
+ extend: {
11
+ colors: {
12
+ void: "#0a0a0f",
13
+ abyss: "#1a1a2e",
14
+ ancient: "#c9a227",
15
+ stardust: "#e0e0e0",
16
+ mystic: "#7b68ee",
17
+ crimson: "#8b0000",
18
+ goldGlow: "#ffd700",
19
+ },
20
+ fontFamily: {
21
+ display: ["Cinzel", "serif"],
22
+ body: ["Noto Sans SC", "sans-serif"],
23
+ },
24
+ animation: {
25
+ "spin-slow": "spin 8s linear infinite",
26
+ "pulse-glow": "pulseGlow 2s ease-in-out infinite",
27
+ "float": "float 6s ease-in-out infinite",
28
+ "flip": "flip 0.8s ease-in-out forwards",
29
+ "shimmer": "shimmer 2s linear infinite",
30
+ },
31
+ keyframes: {
32
+ pulseGlow: {
33
+ "0%, 100%": { boxShadow: "0 0 5px rgba(201, 162, 39, 0.3)" },
34
+ "50%": { boxShadow: "0 0 20px rgba(201, 162, 39, 0.6)" },
35
+ },
36
+ float: {
37
+ "0%, 100%": { transform: "translateY(0px)" },
38
+ "50%": { transform: "translateY(-10px)" },
39
+ },
40
+ flip: {
41
+ "0%": { transform: "rotateY(0deg)" },
42
+ "100%": { transform: "rotateY(180deg)" },
43
+ },
44
+ shimmer: {
45
+ "0%": { backgroundPosition: "-200% 0" },
46
+ "100%": { backgroundPosition: "200% 0" },
47
+ },
48
+ },
49
+ },
50
+ },
51
+ plugins: [],
52
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
+ "target": "ES2020",
5
+ "useDefineForClassFields": true,
6
+ "lib": [
7
+ "ES2020",
8
+ "DOM",
9
+ "DOM.Iterable"
10
+ ],
11
+ "module": "ESNext",
12
+ "skipLibCheck": true,
13
+ "moduleResolution": "bundler",
14
+ "allowImportingTsExtensions": true,
15
+ "verbatimModuleSyntax": false,
16
+ "moduleDetection": "force",
17
+ "noEmit": true,
18
+ "jsx": "react-jsx",
19
+ "strict": false,
20
+ "noUnusedLocals": false,
21
+ "noUnusedParameters": false,
22
+ "noFallthroughCasesInSwitch": false,
23
+ "noUncheckedSideEffectImports": false,
24
+ "forceConsistentCasingInFileNames": false,
25
+ "ignoreDeprecations": "5.0",
26
+ "baseUrl": "./",
27
+ "paths": {
28
+ "@/*": [
29
+ "./src/*"
30
+ ],
31
+ "@core/*": [
32
+ "./shared/core/*"
33
+ ]
34
+ }
35
+ },
36
+ "include": [
37
+ "src",
38
+ "api"
39
+ ]
40
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,34 @@
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+ import tsconfigPaths from "vite-tsconfig-paths";
4
+
5
+ // https://vite.dev/config/
6
+ export default defineConfig({
7
+ test: {
8
+ environment: 'jsdom',
9
+ },
10
+ build: {
11
+ sourcemap: 'hidden',
12
+ },
13
+ server: {
14
+ proxy: {
15
+ '/api': {
16
+ target: 'http://localhost:3018',
17
+ changeOrigin: true,
18
+ },
19
+ },
20
+ },
21
+ // 生产模式(vite preview / CLI 一键启动)下同样代理 /api 到后端
22
+ preview: {
23
+ proxy: {
24
+ '/api': {
25
+ target: 'http://localhost:3018',
26
+ changeOrigin: true,
27
+ },
28
+ },
29
+ },
30
+ plugins: [
31
+ react(),
32
+ tsconfigPaths()
33
+ ],
34
+ })