@fe-free/ai 4.1.12 → 4.1.14

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,23 @@
1
1
  # @fe-free/ai
2
2
 
3
+ ## 4.1.14
4
+
5
+ ### Patch Changes
6
+
7
+ - feat: ai
8
+ - @fe-free/core@4.1.14
9
+ - @fe-free/icons@4.1.14
10
+ - @fe-free/tool@4.1.14
11
+
12
+ ## 4.1.13
13
+
14
+ ### Patch Changes
15
+
16
+ - feat: ai
17
+ - @fe-free/core@4.1.13
18
+ - @fe-free/icons@4.1.13
19
+ - @fe-free/tool@4.1.13
20
+
3
21
  ## 4.1.12
4
22
 
5
23
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fe-free/ai",
3
- "version": "4.1.12",
3
+ "version": "4.1.14",
4
4
  "description": "",
5
5
  "main": "./src/index.ts",
6
6
  "author": "",
@@ -10,16 +10,18 @@
10
10
  "registry": "https://registry.npmjs.org/"
11
11
  },
12
12
  "dependencies": {
13
+ "@ant-design/plots": "^2.5.0",
14
+ "@ant-design/x-markdown": "^2.1.3",
15
+ "@ant-design/x-sdk": "^2.1.3",
16
+ "@ant-design/x": "^2.1.3",
13
17
  "ahooks": "^3.7.8",
14
18
  "classnames": "^2.5.1",
15
19
  "lodash-es": "^4.17.21",
16
20
  "uuid": "^13.0.0",
17
21
  "zustand": "^4.5.7",
18
- "@fe-free/core": "4.1.12"
22
+ "@fe-free/core": "4.1.14"
19
23
  },
20
24
  "peerDependencies": {
21
- "@ant-design/x-sdk": "^2.1.3",
22
- "@ant-design/x": "^2.1.3",
23
25
  "antd": "^5.27.1",
24
26
  "dayjs": "~1.11.10",
25
27
  "i18next": "^25.7.2",
@@ -27,8 +29,8 @@
27
29
  "i18next-icu": "^2.4.1",
28
30
  "react": "^19.2.0",
29
31
  "react-i18next": "^16.4.0",
30
- "@fe-free/icons": "4.1.12",
31
- "@fe-free/tool": "4.1.12"
32
+ "@fe-free/icons": "4.1.14",
33
+ "@fe-free/tool": "4.1.14"
32
34
  },
33
35
  "scripts": {
34
36
  "test": "echo \"Error: no test specified\" && exit 1",
@@ -64,40 +64,43 @@ function Component() {
64
64
  const loading =
65
65
  chatStatus === EnumChatMessageStatus.PENDING || chatStatus === EnumChatMessageStatus.STREAMING;
66
66
 
67
- const handleSubmit = useCallback((v) => {
68
- console.log('onSubmit', v);
69
-
70
- const message: ChatMessage<AIData> = {
71
- uuid: generateUUID(),
72
- status: EnumChatMessageStatus.PENDING,
73
- user: {
74
- ...v,
75
- },
76
- };
77
-
78
- addMessage(message);
79
-
80
- // fake
81
- fakeFetchStream(v, {
82
- onUpdate: ({ event, data }) => {
83
- if (event === 'message') {
84
- message.status = EnumChatMessageStatus.STREAMING;
85
-
86
- const preText = message.ai?.data?.text || '';
87
- set(message, 'ai.data.text', preText + data);
88
-
89
- // 假设有 session_id
90
- set(message, 'ai.session_id', '123');
91
-
92
- updateMessage(message);
93
- }
94
- if (event === 'done') {
95
- message.status = EnumChatMessageStatus.DONE;
96
- updateMessage(message);
97
- }
98
- },
99
- });
100
- }, []);
67
+ const handleSubmit = useCallback(
68
+ (v) => {
69
+ console.log('onSubmit', v);
70
+
71
+ const message: ChatMessage<AIData> = {
72
+ uuid: generateUUID(),
73
+ status: EnumChatMessageStatus.PENDING,
74
+ user: {
75
+ ...v,
76
+ },
77
+ };
78
+
79
+ addMessage(message);
80
+
81
+ // fake
82
+ fakeFetchStream(v, {
83
+ onUpdate: ({ event, data }) => {
84
+ if (event === 'message') {
85
+ message.status = EnumChatMessageStatus.STREAMING;
86
+
87
+ const preText = message.ai?.data?.text || '';
88
+ set(message, 'ai.data.text', preText + data);
89
+
90
+ // 假设有 session_id
91
+ set(message, 'ai.session_id', '123');
92
+
93
+ updateMessage(message);
94
+ }
95
+ if (event === 'done') {
96
+ message.status = EnumChatMessageStatus.DONE;
97
+ updateMessage(message);
98
+ }
99
+ },
100
+ });
101
+ },
102
+ [addMessage, updateMessage],
103
+ );
101
104
 
102
105
  return (
103
106
  <div>
package/src/index.ts CHANGED
@@ -4,6 +4,8 @@ export { FileView, FileViewList } from './files';
4
4
  export { generateUUID } from './helper';
5
5
  export { MSender } from './m_sender';
6
6
  export type { MSenderProps, MSenderRef } from './m_sender';
7
+ export { CustomMarkdown, Markdown } from './markdown';
8
+ export type { CustomMarkdownProps, MarkdownProps } from './markdown';
7
9
  export { MessageActions, MessageThink, Messages } from './messages';
8
10
  export type { MessageThinkProps, MessagesProps } from './messages';
9
11
  export { Sender } from './sender';
@@ -55,6 +55,9 @@ function MSender(originProps: MSenderProps) {
55
55
  className={classNames(
56
56
  'fea-m-sender relative flex items-end rounded-xl border border-01 bg-white p-2.5',
57
57
  )}
58
+ style={{
59
+ boxShadow: '0px 2px 12px 0px #00000014',
60
+ }}
58
61
  >
59
62
  <div className="flex flex-1">
60
63
  <Text {...props} refText={refText} />
@@ -0,0 +1,318 @@
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 ChartConfigBase {
11
+ chart_type: '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
+ interface LineChartConfig {
20
+ chart_type: 'line';
21
+ x_field?: string;
22
+ y_field: string | string[];
23
+ angle_field?: string;
24
+ color_field?: string;
25
+ title: string;
26
+ }
27
+
28
+ // @ts-ignore
29
+ interface ChartConfig extends ChartConfigBase, ChartConfigLine {}
30
+
31
+ // 错误处理组件
32
+ function ChartError(props: { children?: React.ReactNode }) {
33
+ const { children } = props;
34
+ return (
35
+ <div className="fea-markdown-body-block-chart">
36
+ <div style={{ textAlign: 'center', padding: '20px' }}>{children || '图表发生错误'}</div>
37
+ </div>
38
+ );
39
+ }
40
+
41
+ class ErrorBoundary extends React.Component {
42
+ state = { hasError: false };
43
+
44
+ static getDerivedStateFromError(error) {
45
+ console.error('ErrorBoundary:', error);
46
+ return { hasError: true };
47
+ }
48
+
49
+ componentDidCatch(error, info) {
50
+ console.error('Error caught:', error, info);
51
+ }
52
+
53
+ render() {
54
+ if (this.state.hasError) {
55
+ return <ChartError />;
56
+ }
57
+ return this.props.children;
58
+ }
59
+ }
60
+
61
+ // 图表容器组件
62
+ function ChartContainer(props: { title: string; children: React.ReactNode }) {
63
+ const { title, children } = props;
64
+ return (
65
+ <div className="fea-markdown-body-block-chart">
66
+ <div className="fea-markdown-body-block-chart-title">{title}</div>
67
+ {children}
68
+ </div>
69
+ );
70
+ }
71
+
72
+ // 饼图组件
73
+ function PieChart(props: { data: ChartData; chart: ChartConfig }) {
74
+ const { data, chart } = props;
75
+ const { columns, rows } = data;
76
+ const { angle_field, color_field } = chart;
77
+
78
+ if (!angle_field || !color_field) {
79
+ return <ChartError />;
80
+ }
81
+
82
+ const angleIndex = columns.indexOf(angle_field);
83
+ const colorIndex = columns.indexOf(color_field);
84
+
85
+ if (angleIndex === -1 || colorIndex === -1) {
86
+ return <ChartError />;
87
+ }
88
+
89
+ // 转换数据格式为 Ant Design Charts 需要的格式
90
+ const chartData = rows.map((row) => ({
91
+ [color_field]: row[colorIndex],
92
+ [angle_field]: Number(row[angleIndex]),
93
+ }));
94
+
95
+ const config = {
96
+ data: chartData,
97
+ angleField: angle_field,
98
+ colorField: color_field,
99
+ label: {
100
+ text: angle_field,
101
+ style: {
102
+ fontWeight: 'bold',
103
+ },
104
+ },
105
+ legend: {
106
+ color: {
107
+ title: false,
108
+ position: 'right',
109
+ rowPadding: 5,
110
+ },
111
+ },
112
+ };
113
+
114
+ return <Pie {...config} />;
115
+ }
116
+
117
+ // 折线图组件
118
+ function LineChart(props: { data: ChartData; chart: LineChartConfig }) {
119
+ const { data, chart } = props;
120
+ const { columns, rows } = data;
121
+ const { x_field, y_field } = chart;
122
+
123
+ if (!x_field || !y_field) {
124
+ return <ChartError />;
125
+ }
126
+
127
+ const xIndex = columns.indexOf(x_field);
128
+ if (xIndex === -1) {
129
+ return <ChartError />;
130
+ }
131
+
132
+ // 处理 y_field 为数组的情况
133
+ if (Array.isArray(y_field)) {
134
+ // 验证所有 y_field 是否存在于 columns 中
135
+ const yIndices = y_field.map((field) => columns.indexOf(field));
136
+ if (yIndices.some((index) => index === -1)) {
137
+ return <ChartError />;
138
+ }
139
+
140
+ // 转换数据格式为 Ant Design Charts 需要的格式(长数据格式)
141
+ const chartData: any[] = [];
142
+ rows.forEach((row) => {
143
+ y_field.forEach((field, index) => {
144
+ const value = row[yIndices[index]];
145
+ if (value !== null && value !== undefined) {
146
+ chartData.push({
147
+ [x_field]: row[xIndex],
148
+ type: field,
149
+ value: Number(value),
150
+ });
151
+ }
152
+ });
153
+ });
154
+
155
+ const config = {
156
+ data: chartData,
157
+ xField: x_field,
158
+ yField: 'value',
159
+ seriesField: 'type',
160
+ };
161
+
162
+ console.log('config', config);
163
+
164
+ return <Line {...config} />;
165
+ } else {
166
+ // 处理单个 y_field 的情况(保持向后兼容)
167
+ const yIndex = columns.indexOf(y_field);
168
+ if (yIndex === -1) {
169
+ return <ChartError />;
170
+ }
171
+
172
+ // 转换数据格式为 Ant Design Charts 需要的格式
173
+ const chartData = rows.map((row) => ({
174
+ [x_field]: row[xIndex],
175
+ [y_field]: Number(row[yIndex]),
176
+ }));
177
+
178
+ const config = {
179
+ data: chartData,
180
+ xField: x_field,
181
+ yField: y_field,
182
+ };
183
+
184
+ return <Line {...config} />;
185
+ }
186
+ }
187
+
188
+ // 柱状图组件
189
+ function BarChart(props: { data: ChartData; chart: ChartConfig }) {
190
+ const { data, chart } = props;
191
+ const { columns, rows } = data;
192
+ const { x_field, y_field } = chart;
193
+
194
+ if (!x_field || !y_field) {
195
+ return <ChartError />;
196
+ }
197
+
198
+ const xIndex = columns.indexOf(x_field);
199
+ const yIndex = columns.indexOf(y_field);
200
+
201
+ if (xIndex === -1 || yIndex === -1) {
202
+ return <ChartError />;
203
+ }
204
+
205
+ // 转换数据格式为 Ant Design Charts 需要的格式
206
+ const chartData = rows.map((row) => ({
207
+ [x_field]: row[xIndex],
208
+ [y_field]: Number(row[yIndex]),
209
+ }));
210
+
211
+ const config = {
212
+ data: chartData,
213
+ xField: x_field,
214
+ yField: y_field,
215
+ };
216
+
217
+ return <Column {...config} />;
218
+ }
219
+
220
+ function ScatterChart(props: { data: ChartData; chart: ChartConfig }) {
221
+ const { data, chart } = props;
222
+ const { columns, rows } = data;
223
+ const { x_field, y_field } = chart;
224
+
225
+ if (!x_field || !y_field) {
226
+ return <ChartError />;
227
+ }
228
+
229
+ const xIndex = columns.indexOf(x_field);
230
+ const yIndex = columns.indexOf(y_field);
231
+
232
+ if (xIndex === -1 || yIndex === -1) {
233
+ return <ChartError />;
234
+ }
235
+
236
+ const chartData = rows.map((row) => ({
237
+ [x_field]: row[xIndex],
238
+ [y_field]: row[yIndex],
239
+ }));
240
+
241
+ const config = {
242
+ data: chartData,
243
+ xField: x_field,
244
+ yField: y_field,
245
+ };
246
+
247
+ return <Scatter {...config} />;
248
+ }
249
+
250
+ // 主 ChartBlock 组件
251
+ function ChartBlockBase(props: any) {
252
+ const { children } = props;
253
+
254
+ const chartData = useMemo(() => {
255
+ try {
256
+ return JSON.parse(children);
257
+ } catch (error) {
258
+ console.error('Failed to parse chart data:', error);
259
+ return null;
260
+ }
261
+ }, [children]);
262
+
263
+ if (!chartData) {
264
+ return <ChartError />;
265
+ }
266
+
267
+ const { data, chart } = chartData;
268
+ const { chart_type, title } = chart;
269
+
270
+ switch (chart_type) {
271
+ case 'pie':
272
+ return (
273
+ <ChartContainer title={title}>
274
+ <PieChart data={data} chart={chart} />
275
+ </ChartContainer>
276
+ );
277
+ case 'line':
278
+ return (
279
+ <ChartContainer title={title}>
280
+ <LineChart data={data} chart={chart} />
281
+ </ChartContainer>
282
+ );
283
+ case 'bar':
284
+ return (
285
+ <ChartContainer title={title}>
286
+ <BarChart data={data} chart={chart} />
287
+ </ChartContainer>
288
+ );
289
+ case 'scatter':
290
+ return (
291
+ <ChartContainer title={title}>
292
+ <ScatterChart data={data} chart={chart} />
293
+ </ChartContainer>
294
+ );
295
+ case 'table':
296
+ // 表格类型暂不处理
297
+ return null;
298
+ default:
299
+ return <ChartError>不支持的图表类型:{chart_type}</ChartError>;
300
+ }
301
+ }
302
+
303
+ function ChartBlock(props: any) {
304
+ const { children } = props;
305
+
306
+ // 大模型会返回一些奇怪字符,需要去掉
307
+ // 不间断空格
308
+ // eslint-disable-next-line no-irregular-whitespace
309
+ const content = children?.replace(/ /g, '');
310
+
311
+ return (
312
+ <ErrorBoundary>
313
+ <ChartBlockBase>{content}</ChartBlockBase>
314
+ </ErrorBoundary>
315
+ );
316
+ }
317
+
318
+ export { ChartBlock };
@@ -0,0 +1,29 @@
1
+ import { CodeHighlighter, Mermaid } from '@ant-design/x';
2
+ import type { ComponentProps } from '@ant-design/x-markdown';
3
+ import { memo } from 'react';
4
+ import { ChartBlock } from './chart';
5
+ import { HMChartBlock } from './hm_chart';
6
+
7
+ const CodeComponent = memo((props: ComponentProps) => {
8
+ const { className, children } = props;
9
+
10
+ const lang = className?.match(/language-(\w+)/)?.[1] || '';
11
+
12
+ if (typeof children !== 'string') return null;
13
+
14
+ if (lang === 'mermaid') {
15
+ return <Mermaid>{children}</Mermaid>;
16
+ }
17
+
18
+ if (lang === 'chart') {
19
+ return <ChartBlock>{children}</ChartBlock>;
20
+ }
21
+
22
+ if (lang === 'hmchart') {
23
+ return <HMChartBlock>{children}</HMChartBlock>;
24
+ }
25
+
26
+ return <CodeHighlighter lang={lang}>{children}</CodeHighlighter>;
27
+ });
28
+
29
+ export { CodeComponent };