@nocobase/portal-template-default 1.0.3 → 1.0.4

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 (170) hide show
  1. package/README.MD +10 -5
  2. package/package.json +1 -1
  3. package/registry/nocobase-ai/components/chat/chat-message.tsx +8 -6
  4. package/registry/nocobase-ai/components/chat/chat-messages.tsx +8 -3
  5. package/registry/nocobase-ai/components/chat/markdown-message.tsx +26 -0
  6. package/registry/nocobase-ai/components/chat/sub-agent-conversation.tsx +3 -3
  7. package/registry/nocobase-ai/demo/configuration-gate.tsx +83 -35
  8. package/registry/nocobase-i18n/README.md +23 -32
  9. package/registry/nocobase-i18n/components/language-switcher.tsx +1 -1
  10. package/registry/nocobase-i18n/demo/index.tsx +2 -2
  11. package/registry/nocobase-i18n/demo/prompt-generator.tsx +3 -2
  12. package/registry/nocobase-i18n/extension.tsx +3 -4
  13. package/registry/nocobase-i18n/index.ts +4 -3
  14. package/registry/nocobase-i18n/locales/en-US.ts +2 -52
  15. package/registry/nocobase-i18n/locales/index.ts +8 -0
  16. package/registry/nocobase-i18n/locales/zh-CN.ts +2 -52
  17. package/registry/nocobase-i18n/provider.tsx +18 -6
  18. package/registry/nocobase-i18n/server-resources.ts +13 -31
  19. package/registry/nocobase-i18n/tests/i18n-regression.mjs +14 -5
  20. package/registry/nocobase-route-surfaces/README.md +21 -0
  21. package/registry/nocobase-route-surfaces/demo/index.tsx +266 -0
  22. package/{src/extensions/nocobase-acl → registry/nocobase-route-surfaces}/demo/lazy-route.tsx +2 -1
  23. package/registry/nocobase-route-surfaces/demo/prompt-generator.tsx +90 -0
  24. package/registry/nocobase-route-surfaces/demo/scenarios.ts +95 -0
  25. package/registry/nocobase-route-surfaces/extension.tsx +119 -0
  26. package/registry/nocobase-route-surfaces/index.ts +7 -0
  27. package/registry/nocobase-route-surfaces/route-dialog.tsx +145 -0
  28. package/registry/nocobase-route-surfaces/route-drawer.tsx +186 -0
  29. package/registry/nocobase-route-surfaces/route-page.tsx +29 -0
  30. package/registry/nocobase-route-surfaces/route-surface-context.ts +13 -0
  31. package/registry/nocobase-route-surfaces/use-refine-unsaved-changes.tsx +106 -0
  32. package/registry/nocobase-route-surfaces/use-route-surface-close.ts +15 -0
  33. package/registry/nocobase-route-surfaces/use-route-surface-state.ts +65 -0
  34. package/registry.config.json +30 -3
  35. package/scripts/registry.mjs +0 -2
  36. package/src/App.tsx +65 -35
  37. package/src/app/extension.ts +1 -2
  38. package/src/app/extensions.tsx +0 -14
  39. package/src/components/app-shell/breadcrumb.tsx +67 -2
  40. package/src/components/app-shell/sidebar.tsx +2 -2
  41. package/src/components/resources/views/list-view.tsx +3 -2
  42. package/src/components/ui/sheet.tsx +22 -21
  43. package/src/lib/nocobase/client.ts +6 -1
  44. package/src/locales/en-US.ts +81 -0
  45. package/src/locales/zh-CN.ts +77 -0
  46. package/src/pages/users/create.tsx +76 -53
  47. package/src/pages/users/edit.tsx +87 -56
  48. package/src/pages/users/index.ts +3 -0
  49. package/src/pages/users/layout.tsx +15 -0
  50. package/src/pages/users/list.tsx +19 -21
  51. package/src/pages/users/role-badges.tsx +43 -0
  52. package/src/pages/users/role-detail.tsx +272 -0
  53. package/src/pages/users/role-utils.ts +6 -0
  54. package/src/pages/users/routes.ts +24 -0
  55. package/src/pages/users/show.tsx +195 -104
  56. package/src/pages/users/types.ts +13 -0
  57. package/src/providers/i18n/index.ts +3 -0
  58. package/{registry/nocobase-i18n → src/providers/i18n}/runtime.ts +63 -16
  59. package/src/providers/system-settings/context.ts +26 -0
  60. package/src/providers/system-settings/index.ts +2 -0
  61. package/src/providers/system-settings/provider.tsx +94 -0
  62. package/src/extensions/nocobase-acl/README.md +0 -19
  63. package/src/extensions/nocobase-acl/components/acl-boundary.tsx +0 -106
  64. package/src/extensions/nocobase-acl/components/index.ts +0 -3
  65. package/src/extensions/nocobase-acl/components/role-options.ts +0 -36
  66. package/src/extensions/nocobase-acl/components/role-switcher.tsx +0 -135
  67. package/src/extensions/nocobase-acl/demo/boundary-api.tsx +0 -186
  68. package/src/extensions/nocobase-acl/demo/components.tsx +0 -231
  69. package/src/extensions/nocobase-acl/demo/index.tsx +0 -505
  70. package/src/extensions/nocobase-acl/demo/prompt-generator.tsx +0 -134
  71. package/src/extensions/nocobase-acl/demo/role-switcher-prompt-generator.tsx +0 -198
  72. package/src/extensions/nocobase-acl/demo/scenario-section.tsx +0 -37
  73. package/src/extensions/nocobase-acl/extension.tsx +0 -71
  74. package/src/extensions/nocobase-acl/index.ts +0 -1
  75. package/src/extensions/nocobase-ai/README.md +0 -70
  76. package/src/extensions/nocobase-ai/adapters/react-hook-form.ts +0 -20
  77. package/src/extensions/nocobase-ai/assets/nocobase-ai-chat.svg +0 -50
  78. package/src/extensions/nocobase-ai/components/ai-root-provider.tsx +0 -32
  79. package/src/extensions/nocobase-ai/components/chat/ai-employee-avatar.tsx +0 -31
  80. package/src/extensions/nocobase-ai/components/chat/chat-attachment.tsx +0 -92
  81. package/src/extensions/nocobase-ai/components/chat/chat-compact.tsx +0 -90
  82. package/src/extensions/nocobase-ai/components/chat/chat-composer.tsx +0 -429
  83. package/src/extensions/nocobase-ai/components/chat/chat-empty-state.tsx +0 -51
  84. package/src/extensions/nocobase-ai/components/chat/chat-header.tsx +0 -88
  85. package/src/extensions/nocobase-ai/components/chat/chat-history-dialog.tsx +0 -77
  86. package/src/extensions/nocobase-ai/components/chat/chat-message.tsx +0 -252
  87. package/src/extensions/nocobase-ai/components/chat/chat-messages.tsx +0 -173
  88. package/src/extensions/nocobase-ai/components/chat/chat-window.tsx +0 -118
  89. package/src/extensions/nocobase-ai/components/chat/conversation-list.tsx +0 -343
  90. package/src/extensions/nocobase-ai/components/chat/markdown-message.tsx +0 -165
  91. package/src/extensions/nocobase-ai/components/chat/model-select-options.tsx +0 -30
  92. package/src/extensions/nocobase-ai/components/chat/reasoning-panel.tsx +0 -29
  93. package/src/extensions/nocobase-ai/components/chat/sub-agent-conversation.tsx +0 -142
  94. package/src/extensions/nocobase-ai/components/chat/tool-call-card.tsx +0 -316
  95. package/src/extensions/nocobase-ai/components/chat/user-prompt-editor.tsx +0 -99
  96. package/src/extensions/nocobase-ai/components/chat/work-context-chip.tsx +0 -114
  97. package/src/extensions/nocobase-ai/components/index.ts +0 -59
  98. package/src/extensions/nocobase-ai/components/page-elements/ai-form.tsx +0 -44
  99. package/src/extensions/nocobase-ai/components/page-elements/page-element-provider.tsx +0 -469
  100. package/src/extensions/nocobase-ai/components/surfaces/chat-dialog.tsx +0 -31
  101. package/src/extensions/nocobase-ai/components/surfaces/chat-inline.tsx +0 -18
  102. package/src/extensions/nocobase-ai/components/surfaces/chat-page.tsx +0 -18
  103. package/src/extensions/nocobase-ai/components/surfaces/chat-side-panel-layout.tsx +0 -54
  104. package/src/extensions/nocobase-ai/components/surfaces/chat-side-panel.tsx +0 -74
  105. package/src/extensions/nocobase-ai/components/surfaces/chat-surface-actions.tsx +0 -54
  106. package/src/extensions/nocobase-ai/components/tools/builtin-tool-renderers.tsx +0 -31
  107. package/src/extensions/nocobase-ai/components/tools/business-report-dialog.tsx +0 -364
  108. package/src/extensions/nocobase-ai/components/tools/business-report-renderer.tsx +0 -122
  109. package/src/extensions/nocobase-ai/components/tools/business-report-utils.tsx +0 -282
  110. package/src/extensions/nocobase-ai/components/tools/chart-renderer.tsx +0 -99
  111. package/src/extensions/nocobase-ai/components/tools/echarts-preview.tsx +0 -59
  112. package/src/extensions/nocobase-ai/components/tools/echarts-runtime-advanced.ts +0 -18
  113. package/src/extensions/nocobase-ai/components/tools/echarts-runtime-common.ts +0 -20
  114. package/src/extensions/nocobase-ai/components/tools/echarts-runtime-components.ts +0 -34
  115. package/src/extensions/nocobase-ai/components/tools/echarts-runtime-hierarchy.ts +0 -24
  116. package/src/extensions/nocobase-ai/components/tools/echarts-runtime.ts +0 -138
  117. package/src/extensions/nocobase-ai/components/tools/sub-agent-renderer.tsx +0 -67
  118. package/src/extensions/nocobase-ai/components/tools/suggestions-renderer.tsx +0 -59
  119. package/src/extensions/nocobase-ai/components/tools/tool-renderer-provider.tsx +0 -55
  120. package/src/extensions/nocobase-ai/components/tools/tool-renderer-utils.ts +0 -18
  121. package/src/extensions/nocobase-ai/components/tools/workflow-renderer.tsx +0 -108
  122. package/src/extensions/nocobase-ai/components/triggers/ai-chat-floating-trigger.tsx +0 -73
  123. package/src/extensions/nocobase-ai/components/triggers/ai-employee-shortcut.tsx +0 -149
  124. package/src/extensions/nocobase-ai/demo/configuration-gate.tsx +0 -49
  125. package/src/extensions/nocobase-ai/demo/container-showcase.tsx +0 -210
  126. package/src/extensions/nocobase-ai/demo/floating.tsx +0 -134
  127. package/src/extensions/nocobase-ai/demo/index.tsx +0 -346
  128. package/src/extensions/nocobase-ai/demo/interaction-showcase.tsx +0 -151
  129. package/src/extensions/nocobase-ai/demo/page-context-prompt-generator.tsx +0 -355
  130. package/src/extensions/nocobase-ai/demo/page-context.tsx +0 -884
  131. package/src/extensions/nocobase-ai/demo/page-element-showcase.tsx +0 -197
  132. package/src/extensions/nocobase-ai/demo/prompt-card.tsx +0 -41
  133. package/src/extensions/nocobase-ai/demo/prompt-generator.tsx +0 -452
  134. package/src/extensions/nocobase-ai/demo/shortcut.tsx +0 -1207
  135. package/src/extensions/nocobase-ai/demo/tool-cards.tsx +0 -530
  136. package/src/extensions/nocobase-ai/extension.tsx +0 -158
  137. package/src/extensions/nocobase-ai/global-ai-chat.tsx +0 -213
  138. package/src/extensions/nocobase-ai/index.ts +0 -3
  139. package/src/extensions/nocobase-ai/providers/ai-provider.tsx +0 -323
  140. package/src/extensions/nocobase-ai/providers/avatars.ts +0 -817
  141. package/src/extensions/nocobase-ai/providers/chat-context.tsx +0 -109
  142. package/src/extensions/nocobase-ai/providers/chat-controller.ts +0 -84
  143. package/src/extensions/nocobase-ai/providers/chat-message-utils.ts +0 -198
  144. package/src/extensions/nocobase-ai/providers/chat-provider.tsx +0 -871
  145. package/src/extensions/nocobase-ai/providers/chat-reducer.ts +0 -147
  146. package/src/extensions/nocobase-ai/providers/chat-task-utils.ts +0 -105
  147. package/src/extensions/nocobase-ai/providers/chat-transport.ts +0 -184
  148. package/src/extensions/nocobase-ai/providers/form-registry.tsx +0 -271
  149. package/src/extensions/nocobase-ai/providers/frontend-tool-registry.tsx +0 -231
  150. package/src/extensions/nocobase-ai/providers/index.ts +0 -61
  151. package/src/extensions/nocobase-ai/providers/model.ts +0 -32
  152. package/src/extensions/nocobase-ai/providers/page-context.tsx +0 -136
  153. package/src/extensions/nocobase-ai/providers/stream-coalescer.ts +0 -60
  154. package/src/extensions/nocobase-ai/providers/stream-event-utils.ts +0 -82
  155. package/src/extensions/nocobase-ai/providers/stream-parser.ts +0 -61
  156. package/src/extensions/nocobase-ai/providers/sub-agent-stream.ts +0 -316
  157. package/src/extensions/nocobase-ai/providers/types.ts +0 -183
  158. package/src/extensions/nocobase-ai/providers/ui-message-stream.ts +0 -463
  159. package/src/extensions/nocobase-ai/providers/use-automatic-tool-approval.ts +0 -94
  160. package/src/extensions/nocobase-ai/providers/use-chat-attachments.ts +0 -206
  161. package/src/extensions/nocobase-ai/providers/use-chat-message-actions.ts +0 -554
  162. package/src/extensions/nocobase-ai/providers/use-chat-runtime.ts +0 -210
  163. package/src/extensions/nocobase-ai/providers/use-chat-work-context.ts +0 -93
  164. package/src/extensions/nocobase-ai/providers/use-conversation-catalog.ts +0 -108
  165. package/src/extensions/nocobase-ai/providers/use-conversation-history.ts +0 -178
  166. package/src/extensions/nocobase-ai/services/index.ts +0 -7
  167. package/src/extensions/nocobase-ai/services/nocobase-ai-service.ts +0 -454
  168. package/src/extensions/nocobase-ai/services/types.ts +0 -82
  169. /package/{registry/nocobase-i18n → src/providers/i18n}/i18n-provider.ts +0 -0
  170. /package/{registry/nocobase-i18n → src/providers/i18n}/locale-store.ts +0 -0
@@ -1,282 +0,0 @@
1
- import ReactMarkdown from "react-markdown";
2
- import remarkGfm from "remark-gfm";
3
-
4
- export type BusinessReportChart = {
5
- title?: string;
6
- summary?: string;
7
- options: Record<string, unknown>;
8
- };
9
-
10
- export type BusinessReportData = {
11
- title: string;
12
- summary?: string;
13
- markdown?: string;
14
- charts: BusinessReportChart[];
15
- fileName?: string;
16
- };
17
-
18
- export type BusinessReportMarkdownPart =
19
- | { type: "markdown"; content: string }
20
- | { type: "chart"; options: Record<string, unknown> };
21
-
22
- const chartTagPattern = /<echarts>([\s\S]*?)<\/echarts>/gi;
23
- const chartPlaceholderPattern = /\{\{\s*chart\s*:\s*(\d+)\s*\}\}/gi;
24
-
25
- const isRecord = (value: unknown): value is Record<string, unknown> =>
26
- Boolean(value) && typeof value === "object" && !Array.isArray(value);
27
-
28
- export function normalizeBusinessReportCharts(
29
- value: unknown
30
- ): BusinessReportChart[] {
31
- let charts = value;
32
- if (typeof value === "string") {
33
- try {
34
- charts = JSON.parse(value) as unknown;
35
- } catch {
36
- return [];
37
- }
38
- }
39
- if (!Array.isArray(charts)) return [];
40
- return charts.flatMap((item) => {
41
- if (!isRecord(item) || !isRecord(item.options)) return [];
42
- return [
43
- {
44
- title: typeof item.title === "string" ? item.title : undefined,
45
- summary: typeof item.summary === "string" ? item.summary : undefined,
46
- options: item.options,
47
- },
48
- ];
49
- });
50
- }
51
-
52
- const buildChartMarkdownBlock = (
53
- chart: BusinessReportChart,
54
- index: number
55
- ) => {
56
- const parts = [`## ${chart.title || `Chart ${index + 1}`}`];
57
- if (chart.summary) parts.push(chart.summary);
58
- parts.push(`<echarts>${JSON.stringify(chart.options, null, 2)}</echarts>`);
59
- return parts.join("\n\n");
60
- };
61
-
62
- export function buildBusinessReportMarkdown(report: BusinessReportData) {
63
- const usedIndexes = new Set<number>();
64
- const body = (report.markdown ?? "").replace(
65
- chartPlaceholderPattern,
66
- (placeholder, rawIndex: string) => {
67
- const index = Number(rawIndex) - 1;
68
- const chart = report.charts[index];
69
- if (!chart) return placeholder;
70
- usedIndexes.add(index);
71
- return buildChartMarkdownBlock(chart, index);
72
- }
73
- );
74
- const sections = [`# ${report.title}`];
75
- if (report.summary) sections.push(`> ${report.summary}`);
76
- if (body.trim()) sections.push(body.trim());
77
- report.charts.forEach((chart, index) => {
78
- if (!usedIndexes.has(index)) {
79
- sections.push(buildChartMarkdownBlock(chart, index));
80
- }
81
- });
82
- return sections.join("\n\n");
83
- }
84
-
85
- export function splitBusinessReportMarkdown(
86
- markdown: string
87
- ): BusinessReportMarkdownPart[] {
88
- const result: BusinessReportMarkdownPart[] = [];
89
- let offset = 0;
90
- for (const match of markdown.matchAll(chartTagPattern)) {
91
- const index = match.index ?? 0;
92
- const before = markdown.slice(offset, index);
93
- if (before.trim()) result.push({ type: "markdown", content: before });
94
- try {
95
- const options = JSON.parse(match[1]) as unknown;
96
- if (isRecord(options)) result.push({ type: "chart", options });
97
- } catch {
98
- result.push({ type: "markdown", content: match[0] });
99
- }
100
- offset = index + match[0].length;
101
- }
102
- const remainder = markdown.slice(offset);
103
- if (remainder.trim()) result.push({ type: "markdown", content: remainder });
104
- return result;
105
- }
106
-
107
- export function getBusinessReportFileName(report: BusinessReportData) {
108
- return (report.fileName || report.title || "business-analysis-report")
109
- .replace(/[\\/:*?"<>|]+/g, "-")
110
- .trim();
111
- }
112
-
113
- export function downloadBusinessReportFile(
114
- filename: string,
115
- content: string,
116
- mimeType: string
117
- ) {
118
- const blob = new Blob([content], { type: mimeType });
119
- const url = URL.createObjectURL(blob);
120
- const link = document.createElement("a");
121
- link.href = url;
122
- link.download = filename;
123
- link.click();
124
- window.setTimeout(() => URL.revokeObjectURL(url), 0);
125
- }
126
-
127
- export async function renderBusinessReportMarkdownToHtml(markdown: string) {
128
- // Static rendering is intentionally loaded only for HTML export/preview.
129
- // Rendering through a temporary client root can lose a section when the
130
- // root is unmounted immediately after a synchronous render.
131
- const { prerender } = await import("react-dom/static.browser");
132
- const { prelude } = await prerender(
133
- <ReactMarkdown remarkPlugins={[remarkGfm]}>{markdown}</ReactMarkdown>
134
- );
135
- return new Response(prelude as unknown as BodyInit).text();
136
- }
137
-
138
- const nextFrame = () =>
139
- new Promise<void>((resolve) => requestAnimationFrame(() => resolve()));
140
-
141
- async function renderChartImage(options: Record<string, unknown>) {
142
- const { prepareEChartsRuntime } = await import("./echarts-runtime");
143
- const echarts = await prepareEChartsRuntime(options);
144
- const host = document.createElement("div");
145
- host.style.position = "fixed";
146
- host.style.left = "-100000px";
147
- host.style.top = "0";
148
- host.style.width = "900px";
149
- host.style.height = "360px";
150
- host.style.pointerEvents = "none";
151
- document.body.appendChild(host);
152
- let chart: ReturnType<typeof echarts.init> | undefined;
153
- try {
154
- chart = echarts.init(host, "default", { renderer: "canvas" });
155
- chart.setOption(
156
- {
157
- ...options,
158
- animation: false,
159
- backgroundColor: "#ffffff",
160
- toolbox: { show: false },
161
- },
162
- true
163
- );
164
- await nextFrame();
165
- await nextFrame();
166
- const source = chart.getDataURL({
167
- type: "png",
168
- pixelRatio: 2,
169
- backgroundColor: "#ffffff",
170
- excludeComponents: ["toolbox"],
171
- });
172
- return source;
173
- } finally {
174
- chart?.dispose();
175
- host.remove();
176
- }
177
- }
178
-
179
- const escapeHtml = (value: string) =>
180
- value
181
- .replace(/&/g, "&amp;")
182
- .replace(/</g, "&lt;")
183
- .replace(/>/g, "&gt;")
184
- .replace(/"/g, "&quot;")
185
- .replace(/'/g, "&#39;");
186
-
187
- export async function buildBusinessReportHtml(
188
- report: BusinessReportData,
189
- options: { autoPrint?: boolean; printMode?: boolean } = {}
190
- ) {
191
- const markdown = buildBusinessReportMarkdown(report);
192
- const parts = splitBusinessReportMarkdown(markdown);
193
- const body: string[] = [];
194
- for (const part of parts) {
195
- if (part.type === "markdown") {
196
- body.push(await renderBusinessReportMarkdownToHtml(part.content));
197
- continue;
198
- }
199
- try {
200
- const source = await renderChartImage(part.options);
201
- body.push(`<img class="report-chart" src="${source}" alt="" />`);
202
- } catch (error) {
203
- body.push(
204
- `<pre class="chart-error">${escapeHtml(
205
- error instanceof Error ? error.message : "Unable to render chart"
206
- )}</pre>`
207
- );
208
- }
209
- }
210
- const printMode = options.printMode === true;
211
- return `<!doctype html>
212
- <html lang="en">
213
- <head>
214
- <meta charset="UTF-8" />
215
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
216
- <title>${escapeHtml(report.title)}</title>
217
- <style>
218
- * { box-sizing: border-box; }
219
- html { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
220
- body { margin: 0; color: #1f2937; background: ${
221
- printMode ? "#fff" : "#f5f5f5"
222
- }; font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
223
- .report-shell { max-width: ${
224
- printMode ? "190mm" : "960px"
225
- }; margin: 0 auto; padding: ${printMode ? "0" : "32px 24px 64px"}; }
226
- .report-paper { background: #fff; border: ${
227
- printMode ? "0" : "1px solid #d4d4d4"
228
- }; border-radius: ${printMode ? "0" : "16px"}; padding: ${
229
- printMode ? "0" : "40px 48px"
230
- }; }
231
- h1, h2, h3 { color: #171717; break-after: avoid-page; }
232
- h1 { margin-top: 0; font-size: 32px; }
233
- p, li { line-height: 1.7; }
234
- blockquote { margin: 16px 0; padding: 12px 16px; border-left: 4px solid #525252; background: #f5f5f5; color: #525252; }
235
- table { width: 100%; border-collapse: collapse; }
236
- th, td { border: 1px solid #d4d4d4; padding: 10px 12px; text-align: left; }
237
- pre { overflow: auto; white-space: pre-wrap; word-break: break-word; }
238
- .report-chart { display: block; width: 100%; height: auto; margin: 24px 0 32px; border: 1px solid #d4d4d4; break-inside: avoid; }
239
- .chart-error { color: #b91c1c; }
240
- @page { size: A4; margin: 12mm; }
241
- @media print { body { background: #fff; } .report-shell { max-width: none; padding: 0; } .report-paper { border: 0; padding: 0; } }
242
- </style>
243
- </head>
244
- <body>
245
- <main class="report-shell"><article class="report-paper">${body.join(
246
- ""
247
- )}</article></main>
248
- ${
249
- options.autoPrint
250
- ? `<script>
251
- window.addEventListener('load', async () => {
252
- if (document.fonts?.ready) {
253
- try {
254
- await document.fonts.ready;
255
- } catch {}
256
- }
257
- requestAnimationFrame(() => {
258
- requestAnimationFrame(() => window.print());
259
- });
260
- });
261
- </script>`
262
- : ""
263
- }
264
- </body>
265
- </html>`;
266
- }
267
-
268
- export async function printBusinessReport(report: BusinessReportData) {
269
- const html = await buildBusinessReportHtml(report, {
270
- autoPrint: true,
271
- printMode: true,
272
- });
273
- const blob = new Blob([html], { type: "text/html;charset=utf-8" });
274
- const url = URL.createObjectURL(blob);
275
- const opened = window.open(url, "_blank", "noopener,noreferrer");
276
- if (!opened) {
277
- URL.revokeObjectURL(url);
278
- return false;
279
- }
280
- window.setTimeout(() => URL.revokeObjectURL(url), 60_000);
281
- return true;
282
- }
@@ -1,99 +0,0 @@
1
- import { Button } from "@/components/ui/button";
2
- import { LoadingState } from "@/components/app-shell/loading-state";
3
- import { CheckCircle2, Copy, LoaderCircle } from "lucide-react";
4
- import { Component, lazy, Suspense, useState, type ReactNode } from "react";
5
- import type { AIToolRendererProps } from "./tool-renderer-provider";
6
- import { asRecord } from "./tool-renderer-utils";
7
-
8
- const EChartsPreview = lazy(() => import("./echarts-preview"));
9
-
10
- class ChartErrorBoundary extends Component<
11
- {
12
- children: ReactNode;
13
- fallback: (error: Error) => ReactNode;
14
- resetKey: unknown;
15
- },
16
- { error: Error | null; resetKey: unknown }
17
- > {
18
- state = { error: null, resetKey: this.props.resetKey } as {
19
- error: Error | null;
20
- resetKey: unknown;
21
- };
22
-
23
- static getDerivedStateFromError(error: Error) {
24
- return { error };
25
- }
26
-
27
- static getDerivedStateFromProps(
28
- props: { resetKey: unknown },
29
- state: { error: Error | null; resetKey: unknown }
30
- ) {
31
- return props.resetKey !== state.resetKey
32
- ? { error: null, resetKey: props.resetKey }
33
- : null;
34
- }
35
-
36
- render() {
37
- return this.state.error
38
- ? this.props.fallback(this.state.error)
39
- : this.props.children;
40
- }
41
- }
42
-
43
- function ChartError({ error }: { error: Error }) {
44
- const [copied, setCopied] = useState(false);
45
- return (
46
- <div className="rounded-lg border border-destructive/30 bg-destructive/5 p-3 text-xs text-destructive">
47
- <div className="font-medium">Invalid chart options</div>
48
- <div className="mt-1 flex items-start justify-between gap-3">
49
- <span className="break-all leading-5">{error.message}</span>
50
- <Button
51
- variant="ghost"
52
- size="icon-xs"
53
- aria-label="Copy chart error"
54
- onClick={async () => {
55
- await navigator.clipboard.writeText(error.message);
56
- setCopied(true);
57
- window.setTimeout(() => setCopied(false), 1200);
58
- }}
59
- >
60
- {copied ? <CheckCircle2 /> : <Copy />}
61
- </Button>
62
- </div>
63
- </div>
64
- );
65
- }
66
-
67
- export function ChartPreview({
68
- options,
69
- }: {
70
- options: Record<string, unknown>;
71
- }) {
72
- return (
73
- <Suspense fallback={<LoadingState className="h-[280px]" />}>
74
- <EChartsPreview options={options} />
75
- </Suspense>
76
- );
77
- }
78
-
79
- export function ChartRenderer({ part }: AIToolRendererProps) {
80
- const input = asRecord(part.input);
81
- const options = asRecord(input.options);
82
- if (!Object.keys(options).length) {
83
- return (
84
- <div className="flex items-center gap-2 py-2 text-xs text-muted-foreground">
85
- <LoaderCircle className="size-4 animate-spin" /> Generating chart…
86
- </div>
87
- );
88
- }
89
- return (
90
- <ChartErrorBoundary
91
- resetKey={part.input}
92
- fallback={(error) => <ChartError error={error} />}
93
- >
94
- <div className="rounded-lg border bg-background p-3">
95
- <ChartPreview options={options} />
96
- </div>
97
- </ChartErrorBoundary>
98
- );
99
- }
@@ -1,59 +0,0 @@
1
- import ReactEChartsCore from "echarts-for-react/lib/core";
2
- import { useTheme } from "next-themes";
3
- import { useEffect, useState } from "react";
4
- import { LoadingState } from "@/components/app-shell/loading-state";
5
- import echarts, {
6
- getEChartsRuntimeSignature,
7
- prepareEChartsRuntime,
8
- } from "./echarts-runtime";
9
-
10
- export default function EChartsPreview({
11
- options,
12
- }: {
13
- options: Record<string, unknown>;
14
- }) {
15
- const { resolvedTheme } = useTheme();
16
- const signature = getEChartsRuntimeSignature(options);
17
- const [preparedSignature, setPreparedSignature] = useState<string>();
18
- const [error, setError] = useState<unknown>();
19
-
20
- useEffect(() => {
21
- let active = true;
22
- setError(undefined);
23
- void prepareEChartsRuntime(options)
24
- .then(() => {
25
- if (active) setPreparedSignature(signature);
26
- })
27
- .catch((runtimeError: unknown) => {
28
- if (active) setError(runtimeError);
29
- });
30
- return () => {
31
- active = false;
32
- };
33
- }, [options, signature]);
34
-
35
- if (error) throw error;
36
- if (preparedSignature !== signature) {
37
- return <LoadingState className="h-[280px]" />;
38
- }
39
-
40
- return (
41
- <ReactEChartsCore
42
- echarts={echarts}
43
- option={{
44
- ...options,
45
- animation: false,
46
- backgroundColor: options.backgroundColor ?? "transparent",
47
- toolbox: {
48
- show: true,
49
- feature: {
50
- saveAsImage: { title: "Save as image" },
51
- },
52
- },
53
- }}
54
- theme={resolvedTheme === "dark" ? "nocobase-dark" : undefined}
55
- notMerge
56
- style={{ height: 280, width: "100%" }}
57
- />
58
- );
59
- }
@@ -1,18 +0,0 @@
1
- import {
2
- CustomChart,
3
- HeatmapChart,
4
- LinesChart,
5
- MapChart,
6
- ParallelChart,
7
- ThemeRiverChart,
8
- } from "echarts/charts";
9
- import { use as registerEChartsModules } from "echarts/core";
10
-
11
- registerEChartsModules([
12
- CustomChart,
13
- HeatmapChart,
14
- LinesChart,
15
- MapChart,
16
- ParallelChart,
17
- ThemeRiverChart,
18
- ]);
@@ -1,20 +0,0 @@
1
- import {
2
- BarChart,
3
- BoxplotChart,
4
- CandlestickChart,
5
- EffectScatterChart,
6
- LineChart,
7
- PictorialBarChart,
8
- ScatterChart,
9
- } from "echarts/charts";
10
- import { use as registerEChartsModules } from "echarts/core";
11
-
12
- registerEChartsModules([
13
- BarChart,
14
- BoxplotChart,
15
- CandlestickChart,
16
- EffectScatterChart,
17
- LineChart,
18
- PictorialBarChart,
19
- ScatterChart,
20
- ]);
@@ -1,34 +0,0 @@
1
- import {
2
- BrushComponent,
3
- CalendarComponent,
4
- DataZoomComponent,
5
- GeoComponent,
6
- GraphicComponent,
7
- MarkAreaComponent,
8
- MarkLineComponent,
9
- MarkPointComponent,
10
- ParallelComponent,
11
- PolarComponent,
12
- RadarComponent,
13
- SingleAxisComponent,
14
- TimelineComponent,
15
- VisualMapComponent,
16
- } from "echarts/components";
17
- import { use as registerEChartsModules } from "echarts/core";
18
-
19
- registerEChartsModules([
20
- BrushComponent,
21
- CalendarComponent,
22
- DataZoomComponent,
23
- GeoComponent,
24
- GraphicComponent,
25
- MarkAreaComponent,
26
- MarkLineComponent,
27
- MarkPointComponent,
28
- ParallelComponent,
29
- PolarComponent,
30
- RadarComponent,
31
- SingleAxisComponent,
32
- TimelineComponent,
33
- VisualMapComponent,
34
- ]);
@@ -1,24 +0,0 @@
1
- import {
2
- FunnelChart,
3
- GaugeChart,
4
- GraphChart,
5
- PieChart,
6
- RadarChart,
7
- SankeyChart,
8
- SunburstChart,
9
- TreeChart,
10
- TreemapChart,
11
- } from "echarts/charts";
12
- import { use as registerEChartsModules } from "echarts/core";
13
-
14
- registerEChartsModules([
15
- FunnelChart,
16
- GaugeChart,
17
- GraphChart,
18
- PieChart,
19
- RadarChart,
20
- SankeyChart,
21
- SunburstChart,
22
- TreeChart,
23
- TreemapChart,
24
- ]);
@@ -1,138 +0,0 @@
1
- import {
2
- AriaComponent,
3
- DatasetComponent,
4
- GridComponent,
5
- LegendComponent,
6
- TitleComponent,
7
- ToolboxComponent,
8
- TooltipComponent,
9
- TransformComponent,
10
- } from "echarts/components";
11
- import * as echarts from "echarts/core";
12
- import { CanvasRenderer } from "echarts/renderers";
13
-
14
- echarts.use([
15
- AriaComponent,
16
- DatasetComponent,
17
- GridComponent,
18
- LegendComponent,
19
- TitleComponent,
20
- ToolboxComponent,
21
- TooltipComponent,
22
- TransformComponent,
23
- CanvasRenderer,
24
- ]);
25
-
26
- echarts.registerTheme("nocobase-dark", {
27
- darkMode: true,
28
- backgroundColor: "transparent",
29
- textStyle: { color: "#d4d4d8" },
30
- title: { textStyle: { color: "#f4f4f5" } },
31
- legend: { textStyle: { color: "#d4d4d8" } },
32
- categoryAxis: {
33
- axisLine: { lineStyle: { color: "#52525b" } },
34
- axisLabel: { color: "#a1a1aa" },
35
- splitLine: { lineStyle: { color: "#27272a" } },
36
- },
37
- valueAxis: {
38
- axisLine: { lineStyle: { color: "#52525b" } },
39
- axisLabel: { color: "#a1a1aa" },
40
- splitLine: { lineStyle: { color: "#27272a" } },
41
- },
42
- });
43
-
44
- const commonChartTypes = new Set([
45
- "bar",
46
- "boxplot",
47
- "candlestick",
48
- "effectScatter",
49
- "line",
50
- "pictorialBar",
51
- "scatter",
52
- ]);
53
- const hierarchyChartTypes = new Set([
54
- "funnel",
55
- "gauge",
56
- "graph",
57
- "pie",
58
- "radar",
59
- "sankey",
60
- "sunburst",
61
- "tree",
62
- "treemap",
63
- ]);
64
- const advancedChartTypes = new Set([
65
- "custom",
66
- "heatmap",
67
- "lines",
68
- "map",
69
- "parallel",
70
- "themeRiver",
71
- ]);
72
-
73
- const isRecord = (value: unknown): value is Record<string, unknown> =>
74
- Boolean(value) && typeof value === "object" && !Array.isArray(value);
75
-
76
- const getSeries = (options: Record<string, unknown>) => {
77
- const value = options.series;
78
- if (Array.isArray(value)) return value.filter(isRecord);
79
- return isRecord(value) ? [value] : [];
80
- };
81
-
82
- const getRequiredGroups = (options: Record<string, unknown>) => {
83
- const groups = new Set<string>();
84
- const series = getSeries(options);
85
- for (const item of series) {
86
- const type = typeof item.type === "string" ? item.type : "line";
87
- if (commonChartTypes.has(type)) groups.add("common");
88
- else if (hierarchyChartTypes.has(type)) groups.add("hierarchy");
89
- else if (advancedChartTypes.has(type)) groups.add("advanced");
90
- }
91
- const needsOptionalComponents =
92
- [
93
- "brush",
94
- "calendar",
95
- "dataZoom",
96
- "geo",
97
- "graphic",
98
- "parallel",
99
- "polar",
100
- "radar",
101
- "singleAxis",
102
- "timeline",
103
- "visualMap",
104
- ].some((key) => options[key] !== undefined) ||
105
- series.some(
106
- (item) =>
107
- ["lines", "map", "parallel", "radar"].includes(
108
- typeof item.type === "string" ? item.type : ""
109
- ) ||
110
- item.markArea !== undefined ||
111
- item.markLine !== undefined ||
112
- item.markPoint !== undefined
113
- );
114
- if (needsOptionalComponents) groups.add("components");
115
- return [...groups].sort();
116
- };
117
-
118
- const groupLoaders: Record<string, () => Promise<unknown>> = {
119
- common: () => import("./echarts-runtime-common"),
120
- hierarchy: () => import("./echarts-runtime-hierarchy"),
121
- advanced: () => import("./echarts-runtime-advanced"),
122
- components: () => import("./echarts-runtime-components"),
123
- };
124
-
125
- export const getEChartsRuntimeSignature = (
126
- options: Record<string, unknown>
127
- ) => getRequiredGroups(options).join(":");
128
-
129
- export async function prepareEChartsRuntime(
130
- options: Record<string, unknown>
131
- ) {
132
- await Promise.all(
133
- getRequiredGroups(options).map((group) => groupLoaders[group]())
134
- );
135
- return echarts;
136
- }
137
-
138
- export default echarts;