node-resource-utils 0.0.0-stage → 1.1.7

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 (229) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +35 -2
  3. package/lib/sync.js +121 -0
  4. package/package.json +14 -4
  5. package/resources/.cache/ref.b64 +1 -0
  6. package/resources/README.md +97 -0
  7. package/resources/_.env +6 -0
  8. package/resources/_.env.example +25 -0
  9. package/resources/_.gitignore +18 -0
  10. package/resources/_.lintstagedrc.json +9 -0
  11. package/resources/_.npmrc +5 -0
  12. package/resources/_.prettierrc +7 -0
  13. package/resources/commitlint.config.mjs +1 -0
  14. package/resources/content/docs/README.md +63 -0
  15. package/resources/content/docs/faq.md +107 -0
  16. package/resources/content/docs/features/_meta.json +5 -0
  17. package/resources/content/docs/features/ai.md +71 -0
  18. package/resources/content/docs/features/appearance.md +47 -0
  19. package/resources/content/docs/features/feedback.md +79 -0
  20. package/resources/content/docs/features/files.md +74 -0
  21. package/resources/content/docs/features/graph.md +66 -0
  22. package/resources/content/docs/features/inbox.md +61 -0
  23. package/resources/content/docs/guide/_meta.json +5 -0
  24. package/resources/content/docs/guide/account.md +76 -0
  25. package/resources/content/docs/guide/data.md +87 -0
  26. package/resources/content/docs/guide/navigation.md +81 -0
  27. package/resources/content/docs/guide/permissions.md +67 -0
  28. package/resources/content/docs/guide/system.md +92 -0
  29. package/resources/content/docs/quick-start.md +70 -0
  30. package/resources/content/docs/shortcuts.md +69 -0
  31. package/resources/eslint.config.mjs +17 -0
  32. package/resources/next-env.d.ts +6 -0
  33. package/resources/next.config.ts +13 -0
  34. package/resources/postcss.config.mjs +7 -0
  35. package/resources/prisma/org.config.ts +98 -0
  36. package/resources/prisma/schema.prisma +336 -0
  37. package/resources/prisma/seed.ts +721 -0
  38. package/resources/prisma/seedSafe.ts +347 -0
  39. package/resources/prisma.config.ts +14 -0
  40. package/resources/scripts/check-config.mjs +773 -0
  41. package/resources/scripts/lib/schema-graph.mjs +410 -0
  42. package/resources/scripts/patch-next-trace.mjs +86 -0
  43. package/resources/scripts/suggest-relations.mjs +105 -0
  44. package/resources/src/app/(admin)/ai/page.tsx +469 -0
  45. package/resources/src/app/(admin)/dashboard/page.tsx +260 -0
  46. package/resources/src/app/(admin)/demo/employees/page.tsx +508 -0
  47. package/resources/src/app/(admin)/demo/files/page.tsx +451 -0
  48. package/resources/src/app/(admin)/demo/loading/page.tsx +513 -0
  49. package/resources/src/app/(admin)/error.tsx +96 -0
  50. package/resources/src/app/(admin)/feedback/page.tsx +573 -0
  51. package/resources/src/app/(admin)/graph/page.tsx +1224 -0
  52. package/resources/src/app/(admin)/inbox/page.tsx +31 -0
  53. package/resources/src/app/(admin)/layout.tsx +58 -0
  54. package/resources/src/app/(admin)/loading.tsx +31 -0
  55. package/resources/src/app/(admin)/system/depts/page.tsx +415 -0
  56. package/resources/src/app/(admin)/system/logs/page.tsx +215 -0
  57. package/resources/src/app/(admin)/system/menus/page.tsx +403 -0
  58. package/resources/src/app/(admin)/system/roles/page.tsx +433 -0
  59. package/resources/src/app/(admin)/system/users/page.tsx +620 -0
  60. package/resources/src/app/api/account-recovery/route.ts +132 -0
  61. package/resources/src/app/api/ai/act/route.ts +70 -0
  62. package/resources/src/app/api/ai/ask/route.ts +82 -0
  63. package/resources/src/app/api/ai/chat/route.ts +404 -0
  64. package/resources/src/app/api/ai/report/route.ts +164 -0
  65. package/resources/src/app/api/auth/[...nextauth]/route.ts +3 -0
  66. package/resources/src/app/api/auth/change-password/route.ts +57 -0
  67. package/resources/src/app/api/auth/forgot/route.ts +98 -0
  68. package/resources/src/app/api/auth/register/route.ts +95 -0
  69. package/resources/src/app/api/auth/register-config/route.ts +51 -0
  70. package/resources/src/app/api/badges/route.ts +39 -0
  71. package/resources/src/app/api/depts/[id]/route.ts +145 -0
  72. package/resources/src/app/api/depts/route.ts +126 -0
  73. package/resources/src/app/api/docs/export/route.ts +87 -0
  74. package/resources/src/app/api/docs/parse/route.ts +57 -0
  75. package/resources/src/app/api/docs/search/route.ts +22 -0
  76. package/resources/src/app/api/employees/[id]/route.ts +147 -0
  77. package/resources/src/app/api/employees/export/route.ts +115 -0
  78. package/resources/src/app/api/employees/import/route.ts +205 -0
  79. package/resources/src/app/api/employees/route.ts +149 -0
  80. package/resources/src/app/api/export/route.ts +254 -0
  81. package/resources/src/app/api/favorites/route.ts +56 -0
  82. package/resources/src/app/api/feedback/ai/route.ts +31 -0
  83. package/resources/src/app/api/feedback/approve/route.ts +67 -0
  84. package/resources/src/app/api/feedback/report/route.ts +213 -0
  85. package/resources/src/app/api/feedback/route.ts +246 -0
  86. package/resources/src/app/api/files/[name]/route.ts +82 -0
  87. package/resources/src/app/api/graph/focus/route.ts +75 -0
  88. package/resources/src/app/api/graph/node/move/route.ts +48 -0
  89. package/resources/src/app/api/graph/node/route.ts +274 -0
  90. package/resources/src/app/api/graph/route.ts +272 -0
  91. package/resources/src/app/api/health/route.ts +11 -0
  92. package/resources/src/app/api/inbox/read/route.ts +36 -0
  93. package/resources/src/app/api/inbox/summary/route.ts +15 -0
  94. package/resources/src/app/api/logs/route.ts +82 -0
  95. package/resources/src/app/api/menus/[id]/route.ts +132 -0
  96. package/resources/src/app/api/menus/mine/route.ts +23 -0
  97. package/resources/src/app/api/menus/route.ts +89 -0
  98. package/resources/src/app/api/roles/[id]/menus/route.ts +73 -0
  99. package/resources/src/app/api/roles/[id]/route.ts +96 -0
  100. package/resources/src/app/api/roles/route.ts +114 -0
  101. package/resources/src/app/api/search/route.ts +22 -0
  102. package/resources/src/app/api/stats/route.ts +92 -0
  103. package/resources/src/app/api/upload/route.ts +38 -0
  104. package/resources/src/app/api/users/[id]/password/route.ts +55 -0
  105. package/resources/src/app/api/users/[id]/route.ts +145 -0
  106. package/resources/src/app/api/users/route.ts +153 -0
  107. package/resources/src/app/api/visits/route.ts +48 -0
  108. package/resources/src/app/change-password/ChangePasswordForm.tsx +174 -0
  109. package/resources/src/app/change-password/page.tsx +19 -0
  110. package/resources/src/app/docs/[[...slug]]/page.tsx +56 -0
  111. package/resources/src/app/docs/layout.tsx +34 -0
  112. package/resources/src/app/docs/loading.tsx +19 -0
  113. package/resources/src/app/docs/not-found.tsx +25 -0
  114. package/resources/src/app/forgot/page.tsx +392 -0
  115. package/resources/src/app/global-error.tsx +75 -0
  116. package/resources/src/app/globals.css +235 -0
  117. package/resources/src/app/layout.tsx +44 -0
  118. package/resources/src/app/login/page.tsx +267 -0
  119. package/resources/src/app/page.tsx +14 -0
  120. package/resources/src/app/register/page.tsx +237 -0
  121. package/resources/src/auth.ts +215 -0
  122. package/resources/src/components/AiPanel.tsx +970 -0
  123. package/resources/src/components/AuthContext.tsx +82 -0
  124. package/resources/src/components/DataTable.tsx +459 -0
  125. package/resources/src/components/EChart.tsx +140 -0
  126. package/resources/src/components/FeedbackAiModal.tsx +476 -0
  127. package/resources/src/components/FeedbackReportModal.tsx +479 -0
  128. package/resources/src/components/Markdown.tsx +532 -0
  129. package/resources/src/components/OntologyGraph.tsx +1196 -0
  130. package/resources/src/components/Providers.tsx +99 -0
  131. package/resources/src/components/VisitTracker.tsx +89 -0
  132. package/resources/src/components/admin/AdminShell.tsx +749 -0
  133. package/resources/src/components/admin/CountBadge.tsx +67 -0
  134. package/resources/src/components/admin/GlobalSearch.tsx +509 -0
  135. package/resources/src/components/admin/InboxBell.tsx +78 -0
  136. package/resources/src/components/admin/VisitStats.tsx +145 -0
  137. package/resources/src/components/admin/menu-icons.tsx +56 -0
  138. package/resources/src/components/ai/AiInputForm.tsx +351 -0
  139. package/resources/src/components/appearance/AppearanceDrawer.tsx +268 -0
  140. package/resources/src/components/appearance/AppearanceProvider.tsx +160 -0
  141. package/resources/src/components/docs/DocArticle.tsx +106 -0
  142. package/resources/src/components/docs/DocToc.tsx +100 -0
  143. package/resources/src/components/docs/DocsChrome.tsx +259 -0
  144. package/resources/src/components/docs/DocsHome.tsx +209 -0
  145. package/resources/src/components/docs/DocsSearch.tsx +365 -0
  146. package/resources/src/components/docs/docs.css +1458 -0
  147. package/resources/src/components/feedback/AsyncButton.tsx +108 -0
  148. package/resources/src/components/feedback/LoadingOverlay.tsx +125 -0
  149. package/resources/src/components/feedback/PressableCard.tsx +92 -0
  150. package/resources/src/components/feedback/README.md +97 -0
  151. package/resources/src/components/feedback/RouteProgress.tsx +108 -0
  152. package/resources/src/components/feedback/Skeleton.tsx +249 -0
  153. package/resources/src/components/feedback/StatCard.tsx +70 -0
  154. package/resources/src/components/feedback/TaskHint.tsx +153 -0
  155. package/resources/src/components/feedback/index.ts +93 -0
  156. package/resources/src/components/feedback/motion.css +204 -0
  157. package/resources/src/components/feedback/motion.ts +63 -0
  158. package/resources/src/components/feedback/useCountUp.ts +110 -0
  159. package/resources/src/components/feedback/usePending.ts +209 -0
  160. package/resources/src/components/feedback/useRowFlash.ts +40 -0
  161. package/resources/src/components/inbox/InboxView.tsx +409 -0
  162. package/resources/src/components/ui/button.tsx +35 -0
  163. package/resources/src/components/ui/card.tsx +37 -0
  164. package/resources/src/components/ui/input.tsx +19 -0
  165. package/resources/src/lib/ai.ts +310 -0
  166. package/resources/src/lib/aiContext.ts +357 -0
  167. package/resources/src/lib/aiForm.test.ts +191 -0
  168. package/resources/src/lib/aiForm.ts +532 -0
  169. package/resources/src/lib/aiFormTypes.ts +83 -0
  170. package/resources/src/lib/aiToken.ts +72 -0
  171. package/resources/src/lib/aiTools.ts +823 -0
  172. package/resources/src/lib/api.ts +160 -0
  173. package/resources/src/lib/appInfo.ts +128 -0
  174. package/resources/src/lib/appearance.ts +643 -0
  175. package/resources/src/lib/badge-count.ts +146 -0
  176. package/resources/src/lib/badges.test.ts +58 -0
  177. package/resources/src/lib/badges.ts +79 -0
  178. package/resources/src/lib/brand-mark.test.ts +87 -0
  179. package/resources/src/lib/brand-mark.ts +45 -0
  180. package/resources/src/lib/docs-shared.test.ts +148 -0
  181. package/resources/src/lib/docs-shared.ts +422 -0
  182. package/resources/src/lib/docs.ts +381 -0
  183. package/resources/src/lib/err-text.ts +21 -0
  184. package/resources/src/lib/excel.ts +152 -0
  185. package/resources/src/lib/favorite-path.ts +60 -0
  186. package/resources/src/lib/favorites.ts +59 -0
  187. package/resources/src/lib/features.ts +69 -0
  188. package/resources/src/lib/feedback-access.ts +17 -0
  189. package/resources/src/lib/feedbackAi.ts +1621 -0
  190. package/resources/src/lib/formRules.ts +76 -0
  191. package/resources/src/lib/format.ts +52 -0
  192. package/resources/src/lib/graph-cascade.ts +304 -0
  193. package/resources/src/lib/graph-focus.ts +301 -0
  194. package/resources/src/lib/graph-link.ts +17 -0
  195. package/resources/src/lib/graph-node-write.ts +150 -0
  196. package/resources/src/lib/graph-schema.ts +475 -0
  197. package/resources/src/lib/graph-scope.test.ts +46 -0
  198. package/resources/src/lib/graph-scope.ts +28 -0
  199. package/resources/src/lib/http.ts +157 -0
  200. package/resources/src/lib/inbox-types.ts +57 -0
  201. package/resources/src/lib/inbox.ts +55 -0
  202. package/resources/src/lib/menu-service.ts +204 -0
  203. package/resources/src/lib/notifications.ts +113 -0
  204. package/resources/src/lib/office.ts +531 -0
  205. package/resources/src/lib/ontology.ts +277 -0
  206. package/resources/src/lib/openaiCompat.ts +257 -0
  207. package/resources/src/lib/pending.ts +98 -0
  208. package/resources/src/lib/prisma.ts +30 -0
  209. package/resources/src/lib/rbac.test.ts +106 -0
  210. package/resources/src/lib/rbac.ts +185 -0
  211. package/resources/src/lib/search-data.ts +158 -0
  212. package/resources/src/lib/security-questions.ts +20 -0
  213. package/resources/src/lib/skin-theme.ts +155 -0
  214. package/resources/src/lib/system-config.ts +55 -0
  215. package/resources/src/lib/theme.ts +96 -0
  216. package/resources/src/lib/todos/feedback.ts +86 -0
  217. package/resources/src/lib/todos/index.ts +73 -0
  218. package/resources/src/lib/todos/types.ts +33 -0
  219. package/resources/src/lib/upload.ts +106 -0
  220. package/resources/src/lib/utils.test.ts +9 -0
  221. package/resources/src/lib/utils.ts +6 -0
  222. package/resources/src/lib/validation.test.ts +297 -0
  223. package/resources/src/lib/validation.ts +658 -0
  224. package/resources/src/lib/visit-stats.ts +109 -0
  225. package/resources/src/lib/visits.ts +111 -0
  226. package/resources/src/lib/zodField.ts +44 -0
  227. package/resources/tsconfig.json +41 -0
  228. package/resources/vitest.config.ts +16 -0
  229. package/resources/vitest.setup.ts +1 -0
@@ -0,0 +1,469 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import {
5
+ App, Button, Card, Col, DatePicker, Grid, Input, Row, Segmented, Space, Spin,
6
+ } from 'antd';
7
+ import { FileTextOutlined, RobotOutlined, ThunderboltOutlined } from '@ant-design/icons';
8
+ import dayjs, { type Dayjs } from 'dayjs';
9
+ import AiPanel from '@/components/AiPanel';
10
+ import Markdown from '@/components/Markdown';
11
+ import { fs, MILITARY } from '@/lib/theme';
12
+ import { formatElapsed, useElapsed } from '@/components/feedback';
13
+
14
+ const { RangePicker } = DatePicker;
15
+
16
+ /**
17
+ * AI 中心 —— 系统内置的大模型能力。
18
+ *
19
+ * 设计语言(军绿):
20
+ * - 顶部一条深军绿指挥带:页头 + 三个信息徽标,给页面一个"主视觉"
21
+ * - 两张等高卡片浮在暖白画布上,标题用「图标底 + 主标题 + 副标题」三段式
22
+ * - 强调色只用军绿与卡其,不再出现 antd 默认蓝
23
+ *
24
+ * 功能没变:左边生成报告,右边一个统一助手(数据问答 + 使用答疑)。
25
+ */
26
+
27
+ /** 助手空白态的引导问题。接业务时换成题目里的真实问题(数据类 / 使用类 / 办事类各一条) */
28
+ const ASK_SAMPLES = [
29
+ '这个月新增了哪些数据?分别是什么类型',
30
+ '当前数据里有哪些明显的问题或风险?',
31
+ '这个系统有哪些功能?给我一份快速上手指南',
32
+ '帮我新增一条员工记录',
33
+ ];
34
+
35
+ const REPORT_TABLES = [
36
+ { label: '员工(示例)', value: 'Employee', title: '人员情况月报' },
37
+ { label: '操作日志', value: 'OperationLog', title: '系统操作审计报告' },
38
+ ];
39
+
40
+ const CARD_STYLE: React.CSSProperties = {
41
+ height: 'calc(100vh - 310px)',
42
+ minHeight: 520,
43
+ display: 'flex',
44
+ flexDirection: 'column',
45
+ };
46
+
47
+ const BODY_STYLE: React.CSSProperties = {
48
+ flex: 1,
49
+ minHeight: 0,
50
+ display: 'flex',
51
+ flexDirection: 'column',
52
+ gap: 12,
53
+ };
54
+
55
+ /** 卡片标题:左侧卡其定位条 + 图标底 + 主标题 + 副标题 */
56
+ function CardHeading({
57
+ icon,
58
+ title,
59
+ desc,
60
+ accent = MILITARY.accent,
61
+ }: {
62
+ icon: React.ReactNode;
63
+ title: string;
64
+ desc: string;
65
+ accent?: string;
66
+ }) {
67
+ return (
68
+ <div
69
+ style={{
70
+ display: 'flex',
71
+ alignItems: 'center',
72
+ gap: 10,
73
+ padding: '2px 0 2px 10px',
74
+ borderLeft: `3px solid ${accent}`,
75
+ }}
76
+ >
77
+ <span
78
+ style={{
79
+ width: 30,
80
+ height: 30,
81
+ flex: '0 0 30px',
82
+ borderRadius: 8,
83
+ display: 'grid',
84
+ placeItems: 'center',
85
+ background: MILITARY.soft,
86
+ border: `1px solid ${MILITARY.softStrong}`,
87
+ color: MILITARY.primary,
88
+ fontSize: fs(15),
89
+ }}
90
+ >
91
+ {icon}
92
+ </span>
93
+ <span style={{ minWidth: 0 }}>
94
+ <span style={{ display: 'block', fontSize: fs(15), fontWeight: 600, color: MILITARY.text, lineHeight: 1.35 }}>
95
+ {title}
96
+ </span>
97
+ <span style={{ display: 'block', fontSize: fs(11.5), color: MILITARY.textMuted, marginTop: 1 }}>
98
+ {desc}
99
+ </span>
100
+ </span>
101
+ </div>
102
+ );
103
+ }
104
+
105
+ /** 小徽标(卡其描边胶囊) */
106
+ function Pill({ children, muted = false }: { children: React.ReactNode; muted?: boolean }) {
107
+ return (
108
+ <span
109
+ style={{
110
+ fontSize: fs(11.5),
111
+ padding: '2px 9px',
112
+ borderRadius: 999,
113
+ border: `1px solid ${muted ? MILITARY.borderSoft : MILITARY.border}`,
114
+ background: muted ? 'transparent' : MILITARY.paper,
115
+ color: muted ? MILITARY.textMuted : MILITARY.textSecondary,
116
+ whiteSpace: 'nowrap',
117
+ }}
118
+ >
119
+ {children}
120
+ </span>
121
+ );
122
+ }
123
+
124
+ export default function AiCenterPage() {
125
+ const { message } = App.useApp();
126
+ const screens = Grid.useBreakpoint();
127
+ const tall = !!screens.lg;
128
+
129
+ const [range, setRange] = useState<[Dayjs, Dayjs]>([dayjs().startOf('month'), dayjs()]);
130
+ const [table, setTable] = useState(REPORT_TABLES[0]);
131
+ const [focus, setFocus] = useState('');
132
+ const [report, setReport] = useState('');
133
+ const [meta, setMeta] = useState<{ rows: number; model: string; generatedAt: string } | null>(null);
134
+ const [busy, setBusy] = useState(false);
135
+
136
+ /**
137
+ * 报告是**非流式**生成(服务端一次性返回),本地模型可能要几十秒。
138
+ * 所以这里的等待文案必须随耗时会变,否则用户看着同一句话 40 秒会以为卡死。
139
+ */
140
+ const reportElapsed = useElapsed(busy);
141
+ const reportStage =
142
+ reportElapsed >= 8000
143
+ ? '本地模型较慢,仍在生成,请勿重复点击…'
144
+ : reportElapsed >= 3000
145
+ ? '正在通读数据并组织结论…'
146
+ : `正在把 ${table.label} 的真实数据交给本地模型…`;
147
+
148
+ const generate = async () => {
149
+ setBusy(true);
150
+ setReport('');
151
+ setMeta(null);
152
+ try {
153
+ const res = await fetch('/api/ai/report', {
154
+ method: 'POST',
155
+ headers: { 'Content-Type': 'application/json' },
156
+ body: JSON.stringify({
157
+ table: table.value,
158
+ title: table.title,
159
+ from: range[0].format('YYYY-MM-DD'),
160
+ to: range[1].format('YYYY-MM-DD'),
161
+ focus: focus || undefined,
162
+ limit: 300,
163
+ }),
164
+ });
165
+ const j = (await res.json()) as {
166
+ code: number;
167
+ data?: { report: string; rows: number; model: string; generatedAt: string };
168
+ message?: string;
169
+ };
170
+ // 注意:脚手架 ok() 的成功码是 0,不是 200
171
+ if (j.code !== 0 || !j.data) {
172
+ message.error(j.message || '生成失败');
173
+ return;
174
+ }
175
+ setReport(j.data.report);
176
+ setMeta({ rows: j.data.rows, model: j.data.model, generatedAt: j.data.generatedAt });
177
+ message.success(`已根据 ${j.data.rows} 条真实数据生成报告`);
178
+ } catch (e) {
179
+ message.error(`生成失败:${e instanceof Error ? e.message : String(e)}`);
180
+ } finally {
181
+ setBusy(false);
182
+ }
183
+ };
184
+
185
+ return (
186
+ <div style={{ padding: 16 }}>
187
+ {/* ---------------- 指挥带:页面主视觉 ---------------- */}
188
+ <div
189
+ style={{
190
+ position: 'relative',
191
+ overflow: 'hidden',
192
+ borderRadius: 14,
193
+ padding: '18px 22px',
194
+ marginBottom: 16,
195
+ border: '1px solid var(--wb-brand-line)',
196
+ backgroundImage:
197
+ 'var(--wb-brand-panel)',
198
+ }}
199
+ >
200
+ {/* 四角定位角标 + 底部卡其线:档案/作战图的视觉暗示,纯边框实现 */}
201
+ <div
202
+ style={{
203
+ position: 'absolute',
204
+ left: 8,
205
+ top: 8,
206
+ width: 14,
207
+ height: 14,
208
+ borderLeft: '1px solid var(--wb-brand-tint)',
209
+ borderTop: '1px solid var(--wb-brand-tint)',
210
+ }}
211
+ />
212
+ <div
213
+ style={{
214
+ position: 'absolute',
215
+ right: 8,
216
+ top: 8,
217
+ width: 14,
218
+ height: 14,
219
+ borderRight: '1px solid var(--wb-brand-tint)',
220
+ borderTop: '1px solid var(--wb-brand-tint)',
221
+ }}
222
+ />
223
+ <div
224
+ style={{
225
+ position: 'absolute',
226
+ left: 0,
227
+ right: 0,
228
+ bottom: 0,
229
+ height: 2,
230
+ background: `linear-gradient(90deg, ${MILITARY.accent} 0%, transparent 100%)`,
231
+ }}
232
+ />
233
+ <div
234
+ style={{
235
+ position: 'relative',
236
+ display: 'flex',
237
+ alignItems: 'center',
238
+ justifyContent: 'space-between',
239
+ gap: 16,
240
+ flexWrap: 'wrap',
241
+ }}
242
+ >
243
+ <div>
244
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
245
+ <span style={{ width: 6, height: 6, background: MILITARY.accent, display: 'inline-block' }} />
246
+ <span style={{ fontSize: fs(10.5), letterSpacing: 3, color: 'var(--wb-brand-kicker)', fontWeight: 600 }}>
247
+ LOCAL INFERENCE · OFFLINE
248
+ </span>
249
+ </div>
250
+ <div style={{ fontSize: fs(22), fontWeight: 700, color: 'var(--wb-brand-text)', marginTop: 8, letterSpacing: 0.5 }}>
251
+ AI 中心
252
+ </div>
253
+ <div style={{ fontSize: fs(12.5), color: 'var(--wb-brand-text-soft)', marginTop: 6 }}>
254
+ 报告由本地大模型实时生成;问答与新增操作都基于系统里的真实数据
255
+ </div>
256
+ </div>
257
+
258
+ <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
259
+ {[
260
+ { k: '推理', v: '本地部署' },
261
+ { k: '数据', v: '不出内网' },
262
+ { k: '日期', v: dayjs().format('YYYY-MM-DD') },
263
+ ].map((c) => (
264
+ <div
265
+ key={c.k}
266
+ style={{
267
+ padding: '7px 12px',
268
+ borderRadius: 10,
269
+ background: 'var(--wb-brand-glass)',
270
+ border: '1px solid var(--wb-brand-line)',
271
+ minWidth: 92,
272
+ }}
273
+ >
274
+ <div style={{ fontSize: fs(10), letterSpacing: 2, color: 'var(--wb-brand-text-muted)' }}>{c.k}</div>
275
+ <div style={{ fontSize: fs(13), color: 'var(--wb-brand-text)', marginTop: 2 }}>{c.v}</div>
276
+ </div>
277
+ ))}
278
+ </div>
279
+ </div>
280
+ </div>
281
+
282
+ <Row gutter={[16, 16]}>
283
+ {/* ---------------- 左:一键生成报告 ---------------- */}
284
+ <Col xs={24} lg={12} style={{ display: 'flex' }}>
285
+ <Card
286
+ title={
287
+ <CardHeading
288
+ icon={<FileTextOutlined />}
289
+ title="一键生成业务报告"
290
+ desc="选表 + 区间,生成带表格的分析报告"
291
+ />
292
+ }
293
+ extra={meta ? <Pill>已生成 · {meta.rows} 条</Pill> : null}
294
+ style={{ ...(tall ? CARD_STYLE : { display: 'flex', flexDirection: 'column' }), width: '100%' }}
295
+ styles={{ body: tall ? BODY_STYLE : { display: 'flex', flexDirection: 'column', gap: 12 } }}
296
+ >
297
+ <Space wrap size={8}>
298
+ <Segmented
299
+ options={REPORT_TABLES.map((t) => ({ label: t.label, value: t.value }))}
300
+ value={table.value}
301
+ onChange={(v) => {
302
+ const t = REPORT_TABLES.find((x) => x.value === v);
303
+ if (t) setTable(t);
304
+ }}
305
+ />
306
+ <RangePicker
307
+ value={range}
308
+ allowClear={false}
309
+ onChange={(v) => {
310
+ if (v?.[0] && v?.[1]) setRange([v[0], v[1]]);
311
+ }}
312
+ />
313
+ </Space>
314
+
315
+ <Space.Compact style={{ width: '100%' }}>
316
+ <Input
317
+ value={focus}
318
+ onChange={(e) => setFocus(e.target.value)}
319
+ placeholder="不填则按数据客观汇总"
320
+ allowClear
321
+ prefix={
322
+ <span
323
+ style={{
324
+ color: MILITARY.textMuted,
325
+ fontSize: fs(12),
326
+ paddingRight: 8,
327
+ marginRight: 2,
328
+ borderRight: `1px solid ${MILITARY.borderSoft}`,
329
+ }}
330
+ >
331
+ 重点
332
+ </span>
333
+ }
334
+ />
335
+ <Button
336
+ type="primary"
337
+ icon={<ThunderboltOutlined />}
338
+ loading={busy}
339
+ onClick={() => void generate()}
340
+ >
341
+ {busy ? '生成中…' : '生成报告'}
342
+ </Button>
343
+ </Space.Compact>
344
+
345
+ {meta && (
346
+ <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
347
+ <Pill muted>数据 {meta.rows} 条</Pill>
348
+ <Pill muted>{meta.model}</Pill>
349
+ <Pill muted>{dayjs(meta.generatedAt).format('MM-DD HH:mm')}</Pill>
350
+ </div>
351
+ )}
352
+
353
+ {/* 报告区自己滚动:卡片高度固定,不随报告变长 */}
354
+ <div
355
+ style={{
356
+ ...(tall ? { flex: 1, minHeight: 0 } : { minHeight: 240 }),
357
+ overflow: 'auto',
358
+ borderRadius: 10,
359
+ border: `1px solid ${MILITARY.borderSoft}`,
360
+ background: MILITARY.paper,
361
+ padding: report ? 14 : 0,
362
+ display: 'flex',
363
+ flexDirection: 'column',
364
+ }}
365
+ >
366
+ {report ? (
367
+ <Markdown text={report} />
368
+ ) : busy ? (
369
+ <div style={{ margin: 'auto', textAlign: 'center', color: MILITARY.textMuted, fontSize: fs(13) }}>
370
+ <Spin />
371
+ <div style={{ marginTop: 12 }}>{reportStage}</div>
372
+ <div style={{ marginTop: 6, fontSize: fs(11.5), color: MILITARY.textMuted }}>
373
+ 已等待 {formatElapsed(reportElapsed)}
374
+ </div>
375
+ </div>
376
+ ) : (
377
+ <div style={{ margin: 'auto', textAlign: 'center', maxWidth: 340, padding: 16 }}>
378
+ <div
379
+ style={{
380
+ width: 54,
381
+ height: 54,
382
+ borderRadius: '50%',
383
+ margin: '0 auto 14px',
384
+ display: 'grid',
385
+ placeItems: 'center',
386
+ background: MILITARY.soft,
387
+ border: `1px solid ${MILITARY.softStrong}`,
388
+ color: MILITARY.primary,
389
+ fontSize: fs(22),
390
+ }}
391
+ >
392
+ <FileTextOutlined />
393
+ </div>
394
+ <div style={{ fontSize: fs(14), fontWeight: 600, color: MILITARY.text }}>报告从这里开始</div>
395
+ <div style={{ fontSize: fs(12.5), color: MILITARY.textMuted, lineHeight: 1.9, marginTop: 6 }}>
396
+ 选择数据表与时间范围,点击「生成报告」
397
+ <br />
398
+ 报告会显示在这里,可直接复制
399
+ </div>
400
+ <div
401
+ style={{
402
+ display: 'flex',
403
+ justifyContent: 'center',
404
+ gap: 16,
405
+ marginTop: 16,
406
+ flexWrap: 'wrap',
407
+ }}
408
+ >
409
+ {[
410
+ ['01', '选数据表'],
411
+ ['02', '定区间'],
412
+ ['03', '生成'],
413
+ ].map(([n, t]) => (
414
+ <div key={n} style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
415
+ <span
416
+ style={{
417
+ width: 20,
418
+ height: 20,
419
+ borderRadius: 10,
420
+ border: `1px solid ${MILITARY.softStrong}`,
421
+ background: MILITARY.card,
422
+ color: MILITARY.primary,
423
+ fontSize: fs(10),
424
+ display: 'grid',
425
+ placeItems: 'center',
426
+ }}
427
+ >
428
+ {n}
429
+ </span>
430
+ <span style={{ fontSize: fs(11.5), color: MILITARY.textMuted }}>{t}</span>
431
+ </div>
432
+ ))}
433
+ </div>
434
+ </div>
435
+ )}
436
+ </div>
437
+ </Card>
438
+ </Col>
439
+
440
+ {/* ---------------- 右:统一 AI 助手 ---------------- */}
441
+ <Col xs={24} lg={12} style={{ display: 'flex' }}>
442
+ <Card
443
+ title={
444
+ <CardHeading
445
+ icon={<RobotOutlined />}
446
+ title="AI 助手"
447
+ desc="数据问答 · 使用答疑 · 直接新增记录"
448
+ accent={MILITARY.primary}
449
+ />
450
+ }
451
+ extra={<Pill>实时接入业务数据</Pill>}
452
+ style={{ ...(tall ? CARD_STYLE : { display: 'flex', flexDirection: 'column' }), width: '100%' }}
453
+ styles={{ body: tall ? BODY_STYLE : { display: 'flex', flexDirection: 'column', gap: 12 } }}
454
+ >
455
+ <AiPanel
456
+ showHeader={false}
457
+ height={tall ? '100%' : 500}
458
+ suggestions={ASK_SAMPLES}
459
+ system={
460
+ '回答业务问题时优先引用系统里的真实数据;' +
461
+ '问系统怎么用时,按模块给出可执行的操作步骤。'
462
+ }
463
+ />
464
+ </Card>
465
+ </Col>
466
+ </Row>
467
+ </div>
468
+ );
469
+ }