@fe-free/core 2.0.3 → 2.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # @fe-free/core
2
2
 
3
+ ## 2.0.4
4
+
5
+ ### Patch Changes
6
+
7
+ - feat: md chat
8
+ - @fe-free/tool@2.0.4
9
+
3
10
  ## 2.0.3
4
11
 
5
12
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fe-free/core",
3
- "version": "2.0.3",
3
+ "version": "2.0.4",
4
4
  "description": "",
5
5
  "main": "./src/index.ts",
6
6
  "author": "",
@@ -11,7 +11,7 @@
11
11
  },
12
12
  "dependencies": {
13
13
  "@ant-design/icons": "^5.2.6",
14
- "@ant-design/plots": "^2.2.5",
14
+ "@ant-design/plots": "^2.5.0",
15
15
  "@codemirror/lang-html": "^6.4.9",
16
16
  "@codemirror/lang-javascript": "^6.2.4",
17
17
  "@codemirror/lang-json": "^6.0.1",
@@ -38,7 +38,7 @@
38
38
  "remark-gfm": "^4.0.1",
39
39
  "vanilla-jsoneditor": "^0.23.1",
40
40
  "zustand": "^4.5.4",
41
- "@fe-free/tool": "2.0.3"
41
+ "@fe-free/tool": "2.0.4"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "@ant-design/pro-components": "^2.8.7",
@@ -0,0 +1,261 @@
1
+ import { Column, Line, Pie, Scatter } from '@ant-design/plots';
2
+ import React, { useMemo } from 'react';
3
+
4
+ // 类型定义
5
+ interface ChartData {
6
+ columns: string[];
7
+ rows: (string | number)[][];
8
+ }
9
+
10
+ interface ChartConfig {
11
+ chart_type: 'line' | 'bar' | 'pie' | 'table' | 'scatter';
12
+ x_field?: string;
13
+ y_field?: string;
14
+ angle_field?: string;
15
+ color_field?: string;
16
+ title: string;
17
+ }
18
+
19
+ // 错误处理组件
20
+ function ChatError(props: { children?: React.ReactNode }) {
21
+ const { children } = props;
22
+ return (
23
+ <div className="markdown-body-chat-block">
24
+ <div style={{ textAlign: 'center', padding: '20px' }}>{children || '图表发生错误'}</div>
25
+ </div>
26
+ );
27
+ }
28
+
29
+ class ErrorBoundary extends React.Component {
30
+ state = { hasError: false };
31
+
32
+ static getDerivedStateFromError() {
33
+ return { hasError: true };
34
+ }
35
+
36
+ componentDidCatch(error, info) {
37
+ console.error('Error caught:', error, info);
38
+ }
39
+
40
+ render() {
41
+ if (this.state.hasError) {
42
+ return <ChatError />;
43
+ }
44
+ return this.props.children;
45
+ }
46
+ }
47
+
48
+ // 图表容器组件
49
+ function ChartContainer(props: { title: string; children: React.ReactNode }) {
50
+ const { title, children } = props;
51
+ return (
52
+ <div className="markdown-body-chat-block">
53
+ <div className="markdown-body-chat-block-title">{title}</div>
54
+ {children}
55
+ </div>
56
+ );
57
+ }
58
+
59
+ // 饼图组件
60
+ function PieChart(props: { data: ChartData; chart: ChartConfig }) {
61
+ const { data, chart } = props;
62
+ const { columns, rows } = data;
63
+ const { angle_field, color_field } = chart;
64
+
65
+ if (!angle_field || !color_field) {
66
+ return <ChatError />;
67
+ }
68
+
69
+ const angleIndex = columns.indexOf(angle_field);
70
+ const colorIndex = columns.indexOf(color_field);
71
+
72
+ if (angleIndex === -1 || colorIndex === -1) {
73
+ return <ChatError />;
74
+ }
75
+
76
+ // 转换数据格式为 Ant Design Charts 需要的格式
77
+ const chartData = rows.map((row) => ({
78
+ [color_field]: row[colorIndex],
79
+ [angle_field]: Number(row[angleIndex]),
80
+ }));
81
+
82
+ const config = {
83
+ data: chartData,
84
+ angleField: angle_field,
85
+ colorField: color_field,
86
+ label: {
87
+ text: angle_field,
88
+ style: {
89
+ fontWeight: 'bold',
90
+ },
91
+ },
92
+ legend: {
93
+ color: {
94
+ title: false,
95
+ position: 'right',
96
+ rowPadding: 5,
97
+ },
98
+ },
99
+ };
100
+
101
+ return <Pie {...config} />;
102
+ }
103
+
104
+ // 折线图组件
105
+ function LineChart(props: { data: ChartData; chart: ChartConfig }) {
106
+ const { data, chart } = props;
107
+ const { columns, rows } = data;
108
+ const { x_field, y_field } = chart;
109
+
110
+ if (!x_field || !y_field) {
111
+ return <ChatError />;
112
+ }
113
+
114
+ const xIndex = columns.indexOf(x_field);
115
+ const yIndex = columns.indexOf(y_field);
116
+
117
+ if (xIndex === -1 || yIndex === -1) {
118
+ return <ChatError />;
119
+ }
120
+
121
+ // 转换数据格式为 Ant Design Charts 需要的格式
122
+ const chartData = rows.map((row) => ({
123
+ [x_field]: row[xIndex],
124
+ [y_field]: Number(row[yIndex]),
125
+ }));
126
+
127
+ const config = {
128
+ data: chartData,
129
+ xField: x_field,
130
+ yField: y_field,
131
+ };
132
+
133
+ return <Line {...config} />;
134
+ }
135
+
136
+ // 柱状图组件
137
+ function BarChart(props: { data: ChartData; chart: ChartConfig }) {
138
+ const { data, chart } = props;
139
+ const { columns, rows } = data;
140
+ const { x_field, y_field } = chart;
141
+
142
+ if (!x_field || !y_field) {
143
+ return <ChatError />;
144
+ }
145
+
146
+ const xIndex = columns.indexOf(x_field);
147
+ const yIndex = columns.indexOf(y_field);
148
+
149
+ if (xIndex === -1 || yIndex === -1) {
150
+ return <ChatError />;
151
+ }
152
+
153
+ // 转换数据格式为 Ant Design Charts 需要的格式
154
+ const chartData = rows.map((row) => ({
155
+ [x_field]: row[xIndex],
156
+ [y_field]: Number(row[yIndex]),
157
+ }));
158
+
159
+ const config = {
160
+ data: chartData,
161
+ xField: x_field,
162
+ yField: y_field,
163
+ };
164
+
165
+ return <Column {...config} />;
166
+ }
167
+
168
+ function ScatterChart(props: { data: ChartData; chart: ChartConfig }) {
169
+ const { data, chart } = props;
170
+ const { columns, rows } = data;
171
+ const { x_field, y_field } = chart;
172
+
173
+ if (!x_field || !y_field) {
174
+ return <ChatError />;
175
+ }
176
+
177
+ const xIndex = columns.indexOf(x_field);
178
+ const yIndex = columns.indexOf(y_field);
179
+
180
+ if (xIndex === -1 || yIndex === -1) {
181
+ return <ChatError />;
182
+ }
183
+
184
+ const chartData = rows.map((row) => ({
185
+ [x_field]: row[xIndex],
186
+ [y_field]: row[yIndex],
187
+ }));
188
+
189
+ const config = {
190
+ data: chartData,
191
+ xField: x_field,
192
+ yField: y_field,
193
+ };
194
+
195
+ return <Scatter {...config} />;
196
+ }
197
+
198
+ // 主 ChatBlock 组件
199
+ function ChatBlockBase(props: any) {
200
+ const { children } = props;
201
+
202
+ const chatData = useMemo(() => {
203
+ try {
204
+ return JSON.parse(children);
205
+ } catch (error) {
206
+ console.error('Failed to parse chat data:', error);
207
+ return null;
208
+ }
209
+ }, [children]);
210
+
211
+ if (!chatData) {
212
+ return <ChatError />;
213
+ }
214
+
215
+ const { data, chart } = chatData;
216
+ const { chart_type, title } = chart;
217
+
218
+ switch (chart_type) {
219
+ case 'pie':
220
+ return (
221
+ <ChartContainer title={title}>
222
+ <PieChart data={data} chart={chart} />
223
+ </ChartContainer>
224
+ );
225
+ case 'line':
226
+ return (
227
+ <ChartContainer title={title}>
228
+ <LineChart data={data} chart={chart} />
229
+ </ChartContainer>
230
+ );
231
+ case 'bar':
232
+ return (
233
+ <ChartContainer title={title}>
234
+ <BarChart data={data} chart={chart} />
235
+ </ChartContainer>
236
+ );
237
+ case 'scatter':
238
+ return (
239
+ <ChartContainer title={title}>
240
+ <ScatterChart data={data} chart={chart} />
241
+ </ChartContainer>
242
+ );
243
+ case 'table':
244
+ // 表格类型暂不处理
245
+ return null;
246
+ default:
247
+ return <ChatError>不支持的图表类型:{chart_type}</ChatError>;
248
+ }
249
+ }
250
+
251
+ function ChatBlock(props: any) {
252
+ const { children } = props;
253
+
254
+ return (
255
+ <ErrorBoundary>
256
+ <ChatBlockBase>{children}</ChatBlockBase>
257
+ </ErrorBoundary>
258
+ );
259
+ }
260
+
261
+ export { ChatBlock };
@@ -1,10 +1,16 @@
1
1
  import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
2
2
  import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
3
+ import { ChatBlock } from './chat';
3
4
 
4
5
  function CodeBlock(props: any) {
5
6
  const { children, className, ...rest } = props;
6
7
  const match = /language-(\w+)/.exec(props.className || '');
7
8
 
9
+ // 如果是 chat 类型的代码块,使用 ChatBlock 组件
10
+ if (match && match[1] === 'chat') {
11
+ return <ChatBlock>{children}</ChatBlock>;
12
+ }
13
+
8
14
  return (
9
15
  <div className="markdown-body-code-block">
10
16
  {match ? (
@@ -138,3 +138,160 @@ export const DeepSeekPending: Story = {
138
138
  `,
139
139
  },
140
140
  };
141
+
142
+ export const Chat: Story = {
143
+ args: {
144
+ children: `这是一个使用 @ant-design/plots 的 chat 示例
145
+
146
+ ## 折线图示例
147
+
148
+ \`\`\`chat
149
+ {
150
+ "data": {
151
+ "columns": ["time", "amount"],
152
+ "rows": [
153
+ ["2025-06-03", 18.23],
154
+ ["2025-06-10", 112.2],
155
+ ["2025-06-23", 10],
156
+ ["2025-07-01", 85.5],
157
+ ["2025-07-15", 156.8]
158
+ ]
159
+ },
160
+ "chart": {
161
+ "chart_type": "line",
162
+ "x_field": "time",
163
+ "y_field": "amount",
164
+ "title": "张三金额变化趋势图"
165
+ }
166
+ }
167
+ \`\`\`
168
+
169
+ ## 柱状图示例
170
+
171
+ \`\`\`chat
172
+ {
173
+ "data": {
174
+ "columns": ["time", "amount"],
175
+ "rows": [
176
+ ["2025-06-03", 18.23],
177
+ ["2025-06-10", 112.2],
178
+ ["2025-06-23", 10],
179
+ ["2025-07-01", 85.5],
180
+ ["2025-07-15", 156.8]
181
+ ]
182
+ },
183
+ "chart": {
184
+ "chart_type": "bar",
185
+ "x_field": "time",
186
+ "y_field": "amount",
187
+ "title": "张三金额变化趋势图"
188
+ }
189
+ }
190
+ \`\`\`
191
+
192
+ ## 饼图示例
193
+
194
+ \`\`\`chat
195
+ {
196
+ "data": {
197
+ "columns": ["category", "sales"],
198
+ "rows": [
199
+ ["电子产品", 45],
200
+ ["服装", 30],
201
+ ["食品", 15],
202
+ ["家居", 10]
203
+ ]
204
+ },
205
+ "chart": {
206
+ "chart_type": "pie",
207
+ "angle_field": "sales",
208
+ "color_field": "category",
209
+ "title": "销售品类分布"
210
+ }
211
+ }
212
+ \`\`\`
213
+
214
+ ## 散点图示例
215
+
216
+ \`\`\`chat
217
+ {
218
+ "data": {
219
+ "columns": ["width", "height"],
220
+ "rows": [
221
+ [100, 200],
222
+ [300, 400],
223
+ [500, 600]
224
+ ]
225
+ },
226
+ "chart": {
227
+ "chart_type": "scatter",
228
+ "x_field": "width",
229
+ "y_field": "height",
230
+ "title": "散点图示例"
231
+ }
232
+ }
233
+ \`\`\`
234
+
235
+ ## 错误处理示例
236
+
237
+ ### JSON 解析错误
238
+
239
+ \`\`\`chat
240
+ {
241
+ "data": {
242
+ "columns": ["time", "amount"],
243
+ "rows": [
244
+ ["2025-06-03", 18.23],
245
+ ["2025-06-10", 112.2]
246
+ ]
247
+ },
248
+ "chart": {
249
+ "chart_type": "line",
250
+ "x_field": "time",
251
+ "y_field": "amount",
252
+ "title": "张三金额变化趋势图"
253
+ }
254
+ \`\`\`
255
+
256
+ ### 字段不存在错误
257
+
258
+ \`\`\`chat
259
+ {
260
+ "data": {
261
+ "columns": ["time", "amount"],
262
+ "rows": [
263
+ ["2025-06-03", 18.23],
264
+ ["2025-06-10", 112.2]
265
+ ]
266
+ },
267
+ "chart": {
268
+ "chart_type": "line",
269
+ "x_field": "nonexistent_field",
270
+ "y_field": "amount",
271
+ "title": "张三金额变化趋势图"
272
+ }
273
+ }
274
+ \`\`\`
275
+
276
+ ### 不支持的图表类型
277
+
278
+ \`\`\`chat
279
+ {
280
+ "data": {
281
+ "columns": ["time", "amount"],
282
+ "rows": [
283
+ ["2025-06-03", 18.23],
284
+ ["2025-06-10", 112.2]
285
+ ]
286
+ },
287
+ "chart": {
288
+ "chart_type": "some_chart_type",
289
+ "x_field": "time",
290
+ "y_field": "amount",
291
+ "title": "张三金额变化趋势图"
292
+ }
293
+ }
294
+ \`\`\`
295
+ `,
296
+ },
297
+ };
@@ -8,4 +8,15 @@
8
8
  margin: 0 !important;
9
9
  }
10
10
  }
11
+
12
+ .markdown-body-chat-block {
13
+ background: white;
14
+ border-radius: 6px;
15
+
16
+ .markdown-body-chat-block-title {
17
+ font-size: 16px;
18
+ font-weight: bold;
19
+ margin-bottom: 10px;
20
+ }
21
+ }
11
22
  }