@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 +18 -0
- package/package.json +8 -6
- package/src/ai.stories.tsx +37 -34
- package/src/index.ts +2 -0
- package/src/m_sender/index.tsx +3 -0
- package/src/markdown/chart.tsx +318 -0
- package/src/markdown/code.tsx +29 -0
- package/src/markdown/custom_markdown.stories.tsx +440 -0
- package/src/markdown/hm_chart.tsx +176 -0
- package/src/markdown/index.tsx +99 -0
- package/src/markdown/knowledge_ref.tsx +54 -0
- package/src/markdown/markdown.stories.tsx +184 -0
- package/src/markdown/think.tsx +55 -0
- package/src/messages/index.tsx +1 -1
- package/src/messages/message_think.tsx +20 -6
- package/src/sender/index.tsx +3 -1
- package/src/style.scss +77 -0
- package/src/sender/style.scss +0 -26
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
import { CustomMarkdown } from '@fe-free/ai';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof CustomMarkdown> = {
|
|
5
|
+
title: '@fe-free/ai/CustomMarkdown',
|
|
6
|
+
component: CustomMarkdown,
|
|
7
|
+
tags: ['autodocs'],
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default meta;
|
|
11
|
+
|
|
12
|
+
type Story = StoryObj<typeof CustomMarkdown>;
|
|
13
|
+
|
|
14
|
+
export const Default: Story = {
|
|
15
|
+
args: {
|
|
16
|
+
content: `<think>
|
|
17
|
+
好的,我现在需要处理用户的问题。用户一开始用中文打招呼“你好”,然后我的回应应该遵循之前设定的角色和技能。首先,我需要确认用户是否是前端工程师求职者,或者他们需要哪方面的帮助。根据角色设定,我应该先了解他们的具体情况,比如技术栈、工作年限、项目经验和目标城市。
|
|
18
|
+
|
|
19
|
+
用户可能没有详细说明他们的需求,所以我需要引导他们提供更多信息。根据技能1中的步骤,我应该先询问他们的技术栈、工作年限、项目经验和目标城市。同时,我需要保持友好和专业,确保用户感到被理解和支持。
|
|
20
|
+
|
|
21
|
+
另外,用户可能对生成卡通头像感兴趣,但根据限制,我需要先处理求职相关的问题,再提及头像生成。因此,在初始回复中,我应该先专注于求职建议,然后在适当的时候提到头像生成选项。
|
|
22
|
+
|
|
23
|
+
我需要确保回复符合Markdown格式,使用适当的标题和列表结构,使信息清晰易读。同时,避免使用复杂术语,保持语言简洁明了,适合不同水平的用户。
|
|
24
|
+
|
|
25
|
+
现在,我需要按照模板组织回复,先欢迎用户,然后分步骤询问必要的信息,最后提到头像生成的选项。确保每个问题都明确,帮助用户逐步提供所需的信息,以便我能够提供有针对性的建议。
|
|
26
|
+
</think>
|
|
27
|
+
|
|
28
|
+
你好!欢迎来到前端工程师求职助手。为了提供更有针对性的建议,请告诉我以下信息:
|
|
29
|
+
|
|
30
|
+
### 1. 你的技术栈
|
|
31
|
+
- 是否掌握核心三件套(HTML/CSS/JavaScript)?
|
|
32
|
+
- 熟悉哪些前端框架(React/Vue/Angular等)?
|
|
33
|
+
- 是否有Node.js/Webpack/Vite等工程化经验?
|
|
34
|
+
|
|
35
|
+
### 2. 工作年限
|
|
36
|
+
- 应届毕业生/1-3年经验/3-5年经验/5年以上?
|
|
37
|
+
|
|
38
|
+
### 3. 项目亮点
|
|
39
|
+
- 请用一句话描述你最具代表性的项目(如:主导开发日均UV百万级的电商前端架构)
|
|
40
|
+
|
|
41
|
+
### 4. 目标城市
|
|
42
|
+
- 期望在哪个城市发展?
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
💡 **小贴士**:需要生成专属前端工程师卡通头像吗?可同步告知:
|
|
47
|
+
1. 意向职位(如:高级前端开发)
|
|
48
|
+
2. 工作年限
|
|
49
|
+
3. 目标城市
|
|
50
|
+
|
|
51
|
+
我会根据你的信息提供:求职策略+技术提升路径+简历优化建议+面试指南+定制头像(如需)
|
|
52
|
+
`,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const Chart: Story = {
|
|
57
|
+
args: {
|
|
58
|
+
content: `
|
|
59
|
+
## 折线图示例
|
|
60
|
+
|
|
61
|
+
\`\`\`chart
|
|
62
|
+
{
|
|
63
|
+
"data": {
|
|
64
|
+
"columns": ["time", "amount"],
|
|
65
|
+
"rows": [
|
|
66
|
+
["2025-06-03", 18.23],
|
|
67
|
+
["2025-06-10", 112.2],
|
|
68
|
+
["2025-06-23", 10],
|
|
69
|
+
["2025-07-01", 85.5],
|
|
70
|
+
["2025-07-15", 156.8]
|
|
71
|
+
]
|
|
72
|
+
},
|
|
73
|
+
"chart": {
|
|
74
|
+
"chart_type": "line",
|
|
75
|
+
"x_field": "time",
|
|
76
|
+
"y_field": "amount",
|
|
77
|
+
"title": "张三金额变化趋势图"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
\`\`\`
|
|
81
|
+
|
|
82
|
+
多条折线
|
|
83
|
+
|
|
84
|
+
\`\`\`chart
|
|
85
|
+
{
|
|
86
|
+
"data": {
|
|
87
|
+
"columns": ["quarter", "L'Oreal", "P&G", "Proya"],
|
|
88
|
+
"rows": [
|
|
89
|
+
["FY23-Q4/23-Q2", 8, 7, null],
|
|
90
|
+
["FY24-Q1/23-Q3", 7, 6, null],
|
|
91
|
+
["FY24-Q2/23-Q4", 4, 3, null],
|
|
92
|
+
["FY24-Q3/24-Q1", 3, null, null],
|
|
93
|
+
["FY24-Q4/24-Q2", null, null, null],
|
|
94
|
+
["FY25-Q1/24-Q34",null,null,null]
|
|
95
|
+
]
|
|
96
|
+
},
|
|
97
|
+
"chart": {
|
|
98
|
+
"chart_type": "line",
|
|
99
|
+
"x_field": "quarter",
|
|
100
|
+
"y_field": ["L'Oreal", "P&G","Proya"],
|
|
101
|
+
"title": "欧莱雅、宝洁及珀莱雅增长率变化趋势"
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
\`\`\`
|
|
105
|
+
|
|
106
|
+
## 柱状图示例
|
|
107
|
+
|
|
108
|
+
\`\`\`chart
|
|
109
|
+
{
|
|
110
|
+
"data": {
|
|
111
|
+
"columns": ["time", "amount"],
|
|
112
|
+
"rows": [
|
|
113
|
+
["2025-06-03", 18.23],
|
|
114
|
+
["2025-06-10", 112.2],
|
|
115
|
+
["2025-06-23", 10],
|
|
116
|
+
["2025-07-01", 85.5],
|
|
117
|
+
["2025-07-15", 156.8]
|
|
118
|
+
]
|
|
119
|
+
},
|
|
120
|
+
"chart": {
|
|
121
|
+
"chart_type": "bar",
|
|
122
|
+
"x_field": "time",
|
|
123
|
+
"y_field": "amount",
|
|
124
|
+
"title": "张三金额变化趋势图"
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
\`\`\`
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
## 饼图示例
|
|
131
|
+
|
|
132
|
+
\`\`\`chart
|
|
133
|
+
{
|
|
134
|
+
"data": {
|
|
135
|
+
"columns": ["category", "sales"],
|
|
136
|
+
"rows": [
|
|
137
|
+
["电子产品", 45],
|
|
138
|
+
["服装", 30],
|
|
139
|
+
["食品", 15],
|
|
140
|
+
["家居", 10]
|
|
141
|
+
]
|
|
142
|
+
},
|
|
143
|
+
"chart": {
|
|
144
|
+
"chart_type": "pie",
|
|
145
|
+
"angle_field": "sales",
|
|
146
|
+
"color_field": "category",
|
|
147
|
+
"title": "销售品类分布"
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
\`\`\`
|
|
151
|
+
|
|
152
|
+
## 散点图示例
|
|
153
|
+
|
|
154
|
+
\`\`\`chart
|
|
155
|
+
{
|
|
156
|
+
"data": {
|
|
157
|
+
"columns": ["width", "height"],
|
|
158
|
+
"rows": [
|
|
159
|
+
[100, 200],
|
|
160
|
+
[300, 400],
|
|
161
|
+
[500, 600]
|
|
162
|
+
]
|
|
163
|
+
},
|
|
164
|
+
"chart": {
|
|
165
|
+
"chart_type": "scatter",
|
|
166
|
+
"x_field": "width",
|
|
167
|
+
"y_field": "height",
|
|
168
|
+
"title": "散点图示例"
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
\`\`\`
|
|
172
|
+
|
|
173
|
+
## 错误处理示例
|
|
174
|
+
|
|
175
|
+
### JSON 解析错误
|
|
176
|
+
|
|
177
|
+
\`\`\`chart
|
|
178
|
+
{
|
|
179
|
+
"data": {
|
|
180
|
+
"columns": ["time", "amount"],
|
|
181
|
+
"rows": [
|
|
182
|
+
["2025-06-03", 18.23],
|
|
183
|
+
["2025-06-10", 112.2]
|
|
184
|
+
]
|
|
185
|
+
},
|
|
186
|
+
"chart": {
|
|
187
|
+
"chart_type": "line",
|
|
188
|
+
"x_field": "time",
|
|
189
|
+
"y_field": "amount",
|
|
190
|
+
"title": "张三金额变化趋势图"
|
|
191
|
+
}
|
|
192
|
+
\`\`\`
|
|
193
|
+
|
|
194
|
+
### 字段不存在错误
|
|
195
|
+
|
|
196
|
+
\`\`\`chart
|
|
197
|
+
{
|
|
198
|
+
"data": {
|
|
199
|
+
"columns": ["time", "amount"],
|
|
200
|
+
"rows": [
|
|
201
|
+
["2025-06-03", 18.23],
|
|
202
|
+
["2025-06-10", 112.2]
|
|
203
|
+
]
|
|
204
|
+
},
|
|
205
|
+
"chart": {
|
|
206
|
+
"chart_type": "line",
|
|
207
|
+
"x_field": "nonexistent_field",
|
|
208
|
+
"y_field": "amount",
|
|
209
|
+
"title": "张三金额变化趋势图"
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
\`\`\`
|
|
213
|
+
|
|
214
|
+
### 不支持的图表类型
|
|
215
|
+
|
|
216
|
+
\`\`\`chart
|
|
217
|
+
{
|
|
218
|
+
"data": {
|
|
219
|
+
"columns": ["time", "amount"],
|
|
220
|
+
"rows": [
|
|
221
|
+
["2025-06-03", 18.23],
|
|
222
|
+
["2025-06-10", 112.2]
|
|
223
|
+
]
|
|
224
|
+
},
|
|
225
|
+
"chart": {
|
|
226
|
+
"chart_type": "some_chart_type",
|
|
227
|
+
"x_field": "time",
|
|
228
|
+
"y_field": "amount",
|
|
229
|
+
"title": "张三金额变化趋势图"
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
\`\`\`
|
|
233
|
+
`,
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
export const HMChart: Story = {
|
|
238
|
+
args: {
|
|
239
|
+
content: `
|
|
240
|
+
类型定义见:https://github.com/frontend-free/free/blob/main/packages/core/src/markdown/hm_chart.tsx
|
|
241
|
+
|
|
242
|
+
## 折线图示例
|
|
243
|
+
|
|
244
|
+
\`\`\`hmchart
|
|
245
|
+
{
|
|
246
|
+
"title": "趋势变化",
|
|
247
|
+
"type": "line",
|
|
248
|
+
"data": [
|
|
249
|
+
{ "year": "1991", "value": 3 },
|
|
250
|
+
{ "year": "1992", "value": 4 },
|
|
251
|
+
{ "year": "1993", "value": 3.5 }
|
|
252
|
+
],
|
|
253
|
+
"xField": "year",
|
|
254
|
+
"yField": "value"
|
|
255
|
+
}
|
|
256
|
+
\`\`\`
|
|
257
|
+
|
|
258
|
+
\`\`\`hmchart
|
|
259
|
+
{
|
|
260
|
+
"title": "多条趋势变化",
|
|
261
|
+
"type": "line",
|
|
262
|
+
"data": [
|
|
263
|
+
{ "year": "1991", "type": "A", "value": 3 },
|
|
264
|
+
{ "year": "1991", "type": "B", "value": 4 },
|
|
265
|
+
{ "year": "1992", "type": "A", "value": 4 },
|
|
266
|
+
{ "year": "1992", "type": "B", "value": 4.5 },
|
|
267
|
+
{ "year": "1993", "type": "A", "value": 3.5 },
|
|
268
|
+
{ "year": "1993", "type": "B", "value": 6 }
|
|
269
|
+
],
|
|
270
|
+
"xField": "year",
|
|
271
|
+
"yField": "value",
|
|
272
|
+
"seriesField": "type"
|
|
273
|
+
}
|
|
274
|
+
\`\`\`
|
|
275
|
+
|
|
276
|
+
\`\`\`hmchart
|
|
277
|
+
{
|
|
278
|
+
"title": "柱状图",
|
|
279
|
+
"type": "bar",
|
|
280
|
+
"data": [
|
|
281
|
+
{ "year": "1991", "value": 3 },
|
|
282
|
+
{ "year": "1992", "value": 4 },
|
|
283
|
+
{ "year": "1993", "value": 3.5 }
|
|
284
|
+
],
|
|
285
|
+
"xField": "year",
|
|
286
|
+
"yField": "value"
|
|
287
|
+
}
|
|
288
|
+
\`\`\`
|
|
289
|
+
|
|
290
|
+
\`\`\`hmchart
|
|
291
|
+
{
|
|
292
|
+
"title": "多条柱状图",
|
|
293
|
+
"type": "bar",
|
|
294
|
+
"data": [
|
|
295
|
+
{ "year": "1991", "type": "A", "value": 3 },
|
|
296
|
+
{ "year": "1991", "type": "B", "value": 4 },
|
|
297
|
+
{ "year": "1992", "type": "A", "value": 4 },
|
|
298
|
+
{ "year": "1992", "type": "B", "value": 4.5 },
|
|
299
|
+
{ "year": "1993", "type": "A", "value": 3.5 },
|
|
300
|
+
{ "year": "1993", "type": "B", "value": 6 }
|
|
301
|
+
],
|
|
302
|
+
"xField": "year",
|
|
303
|
+
"yField": "value",
|
|
304
|
+
"seriesField": "type"
|
|
305
|
+
}
|
|
306
|
+
\`\`\`
|
|
307
|
+
|
|
308
|
+
\`\`\`hmchart
|
|
309
|
+
{
|
|
310
|
+
"title": "饼图",
|
|
311
|
+
"type": "pie",
|
|
312
|
+
"data": [
|
|
313
|
+
{ "year": "1991", "value": 3 },
|
|
314
|
+
{ "year": "1992", "value": 4 },
|
|
315
|
+
{ "year": "1993", "value": 3.5 }
|
|
316
|
+
],
|
|
317
|
+
"angleField": "value",
|
|
318
|
+
"colorField": "year"
|
|
319
|
+
}
|
|
320
|
+
\`\`\`
|
|
321
|
+
|
|
322
|
+
\`\`\`hmchart
|
|
323
|
+
{
|
|
324
|
+
"title": "散点图示例",
|
|
325
|
+
"type": "scatter",
|
|
326
|
+
"data": [
|
|
327
|
+
{
|
|
328
|
+
"gender": "female",
|
|
329
|
+
"height": 161.2,
|
|
330
|
+
"weight": 51.6
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
"gender": "male",
|
|
334
|
+
"height": 167.5,
|
|
335
|
+
"weight": 59
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
"gender": "female",
|
|
339
|
+
"height": 159.5,
|
|
340
|
+
"weight": 49.2
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
"gender": "male",
|
|
344
|
+
"height": 157,
|
|
345
|
+
"weight": 63
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
"gender": "male",
|
|
349
|
+
"height": 155.8,
|
|
350
|
+
"weight": 53.6
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
"gender": "female",
|
|
354
|
+
"height": 170,
|
|
355
|
+
"weight": 59
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
"gender": "female",
|
|
359
|
+
"height": 159.1,
|
|
360
|
+
"weight": 47.6
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
"gender": "female",
|
|
364
|
+
"height": 166,
|
|
365
|
+
"weight": 69.8
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
"gender": "female",
|
|
369
|
+
"height": 176.2,
|
|
370
|
+
"weight": 66.8
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
"gender": "female",
|
|
374
|
+
"height": 160.2,
|
|
375
|
+
"weight": 75.2
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
"gender": "female",
|
|
379
|
+
"height": 172.5,
|
|
380
|
+
"weight": 55.2
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
"gender": "female",
|
|
384
|
+
"height": 170.9,
|
|
385
|
+
"weight": 54.2
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
"gender": "female",
|
|
389
|
+
"height": 172.9,
|
|
390
|
+
"weight": 62.5
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
"gender": "male",
|
|
394
|
+
"height": 153.4,
|
|
395
|
+
"weight": 42
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
"gender": "male",
|
|
399
|
+
"height": 160,
|
|
400
|
+
"weight": 50
|
|
401
|
+
}
|
|
402
|
+
],
|
|
403
|
+
"xField": "height",
|
|
404
|
+
"yField": "weight",
|
|
405
|
+
"colorField": "gender"
|
|
406
|
+
}
|
|
407
|
+
\`\`\`
|
|
408
|
+
|
|
409
|
+
`,
|
|
410
|
+
},
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
export const Knowledge: Story = {
|
|
414
|
+
args: {
|
|
415
|
+
content: `
|
|
416
|
+
根据检索结果,知识库中包含多个酒店的信息。[^knowledge:1-0]例如:\n\n- **杭州城中香格里拉**:位于杭州长寿路6号,提供多种餐饮选择,包括非凡酒廊、美食汇、城中啤酒餐吧以及城中小馆中餐厅[^knowledge:0-0]。\n- **凯恩斯香格里拉**:位于澳大利亚Pierpoint Road Cairns, Queensland,提供纯正澳式待客之道,靠近马林码头与珊瑚海交汇处[^knowledge:2-0]。\n- **马斯喀特 Al Husn 香格里拉**:位于阿曼P.O. Box 644, Muscat 100,是一个成人专享的世外桃源,拥有私人海滩和海湾[^knowledge:3-0]。\n\n此外,还有一家未直接命名但详细描述了其服务特色的豪华酒店,该酒店以江南丝绸商贸之家为灵感设计,提供独特的入住体验,包括定制贴心体验、精致餐饮及新颖的康养项目[^knowledge:4-0][^knowledge:14-0]。
|
|
417
|
+
`,
|
|
418
|
+
knowledgeRefs: [
|
|
419
|
+
{ id: '0-0', title: '杭州城中香格里拉' },
|
|
420
|
+
{ id: '1-0', title: '巴拉巴拉酒店' },
|
|
421
|
+
{ id: '2-0', title: '凯恩斯香格里拉' },
|
|
422
|
+
{ id: '3-0', title: '马斯喀特 Al Husn 香格里拉' },
|
|
423
|
+
{ id: '4-0', title: '江南丝绸商贸之家' },
|
|
424
|
+
],
|
|
425
|
+
onKnowledgeRef: (id?: string) => {
|
|
426
|
+
console.log('onKnowledgeRef', id);
|
|
427
|
+
},
|
|
428
|
+
},
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
export const KnowledgeWithoutData: Story = {
|
|
432
|
+
args: {
|
|
433
|
+
content: `
|
|
434
|
+
根据检索结果,知识库中包含多个酒店的信息。[^knowledge:1-0]例如:\n\n- **杭州城中香格里拉**:位于杭州长寿路6号,提供多种餐饮选择,包括非凡酒廊、美食汇、城中啤酒餐吧以及城中小馆中餐厅[^knowledge:0-0]。\n- **凯恩斯香格里拉**:位于澳大利亚Pierpoint Road Cairns, Queensland,提供纯正澳式待客之道,靠近马林码头与珊瑚海交汇处[^knowledge:2-0]。\n- **马斯喀特 Al Husn 香格里拉**:位于阿曼P.O. Box 644, Muscat 100,是一个成人专享的世外桃源,拥有私人海滩和海湾[^knowledge:3-0]。\n\n此外,还有一家未直接命名但详细描述了其服务特色的豪华酒店,该酒店以江南丝绸商贸之家为灵感设计,提供独特的入住体验,包括定制贴心体验、精致餐饮及新颖的康养项目[^knowledge:4-0][^knowledge:14-0]。
|
|
435
|
+
`,
|
|
436
|
+
onKnowledgeRef: (id?: string) => {
|
|
437
|
+
console.log('onKnowledgeRef', id);
|
|
438
|
+
},
|
|
439
|
+
},
|
|
440
|
+
};
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { Column, Line, Pie, Scatter } from '@ant-design/plots';
|
|
2
|
+
import React, { useMemo } from 'react';
|
|
3
|
+
|
|
4
|
+
enum EnumType {
|
|
5
|
+
/** 折线图 */
|
|
6
|
+
LINE = 'line',
|
|
7
|
+
/** 柱状图 */
|
|
8
|
+
BAR = 'bar',
|
|
9
|
+
/** 饼图 */
|
|
10
|
+
PIE = 'pie',
|
|
11
|
+
/** 散点图 */
|
|
12
|
+
SCATTER = 'scatter',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface HMChartConfigLine<D> {
|
|
16
|
+
/** 折线图 */
|
|
17
|
+
type: EnumType.LINE;
|
|
18
|
+
/** 标题 */
|
|
19
|
+
title?: string;
|
|
20
|
+
/** 就是纯粹的数据 */
|
|
21
|
+
data: D[];
|
|
22
|
+
/** 横坐标字段 */
|
|
23
|
+
xField: keyof D;
|
|
24
|
+
/** 纵坐标字段 */
|
|
25
|
+
yField: keyof D;
|
|
26
|
+
/** 系列字段,多折线图用 */
|
|
27
|
+
seriesField?: keyof D;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface HMChartConfigBar<D> {
|
|
31
|
+
/** 柱状图 */
|
|
32
|
+
type: EnumType.BAR;
|
|
33
|
+
/** 标题 */
|
|
34
|
+
title?: string;
|
|
35
|
+
/** 就是纯粹的数据 */
|
|
36
|
+
data: D[];
|
|
37
|
+
/** 横坐标字段 */
|
|
38
|
+
xField: keyof D;
|
|
39
|
+
/** 纵坐标字段 */
|
|
40
|
+
yField: keyof D;
|
|
41
|
+
/** 系列字段,多折线图用 */
|
|
42
|
+
seriesField?: keyof D;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface HMChartConfigPie<D> {
|
|
46
|
+
/** 饼图 */
|
|
47
|
+
type: EnumType.PIE;
|
|
48
|
+
/** 标题 */
|
|
49
|
+
title?: string;
|
|
50
|
+
/** 就是纯粹的数据 */
|
|
51
|
+
data: D[];
|
|
52
|
+
/** 角度字段 */
|
|
53
|
+
angleField: keyof D;
|
|
54
|
+
/** 颜色字段 */
|
|
55
|
+
colorField: keyof D;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface HMChartConfigScatter<D> {
|
|
59
|
+
/** 散点图 */
|
|
60
|
+
type: EnumType.SCATTER;
|
|
61
|
+
/** 标题 */
|
|
62
|
+
title?: string;
|
|
63
|
+
/** 就是纯粹的数据 */
|
|
64
|
+
data: D[];
|
|
65
|
+
/** 横坐标字段 */
|
|
66
|
+
xField: keyof D;
|
|
67
|
+
/** 纵坐标字段 */
|
|
68
|
+
yField: keyof D;
|
|
69
|
+
/** 颜色字段 */
|
|
70
|
+
colorField?: keyof D;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
type HMChartConfig<D> =
|
|
74
|
+
| HMChartConfigLine<D>
|
|
75
|
+
| HMChartConfigBar<D>
|
|
76
|
+
| HMChartConfigPie<D>
|
|
77
|
+
| HMChartConfigScatter<D>;
|
|
78
|
+
|
|
79
|
+
// 错误处理组件
|
|
80
|
+
function ChartError(props: { children?: React.ReactNode }) {
|
|
81
|
+
const { children } = props;
|
|
82
|
+
return (
|
|
83
|
+
<div className="fea-markdown-body-block-hmchart">
|
|
84
|
+
<div style={{ textAlign: 'center', padding: '20px' }}>{children || '图表发生错误'}</div>
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
class ErrorBoundary extends React.Component {
|
|
90
|
+
state = { hasError: false };
|
|
91
|
+
|
|
92
|
+
static getDerivedStateFromError(error) {
|
|
93
|
+
console.error('ErrorBoundary:', error);
|
|
94
|
+
return { hasError: true };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
componentDidCatch(error, info) {
|
|
98
|
+
console.error('Error caught:', error, info);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
render() {
|
|
102
|
+
if (this.state.hasError) {
|
|
103
|
+
return <ChartError />;
|
|
104
|
+
}
|
|
105
|
+
return this.props.children;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function ChartOfLine(props: { config: HMChartConfigLine<any> }) {
|
|
110
|
+
return <Line colorField={props.config.seriesField} {...props.config} />;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function ChartOfBar(props: { config: HMChartConfigBar<any> }) {
|
|
114
|
+
return <Column colorField={props.config.seriesField} {...props.config} />;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function ChartOfPie(props: { config: HMChartConfigPie<any> }) {
|
|
118
|
+
return <Pie {...props.config} />;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function ChartOfScatter(props: { config: HMChartConfigScatter<any> }) {
|
|
122
|
+
return <Scatter {...props.config} />;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 主 ChartBlock 组件
|
|
126
|
+
function HMChartBlockBase(props: any) {
|
|
127
|
+
const { children } = props;
|
|
128
|
+
|
|
129
|
+
const chartConfig = useMemo(() => {
|
|
130
|
+
try {
|
|
131
|
+
return JSON.parse(children);
|
|
132
|
+
} catch (error) {
|
|
133
|
+
console.error('Failed to parse chart data:', error);
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}, [children]);
|
|
137
|
+
|
|
138
|
+
if (!chartConfig) {
|
|
139
|
+
return <ChartError />;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const { type, ...config } = chartConfig;
|
|
143
|
+
|
|
144
|
+
switch (type) {
|
|
145
|
+
case EnumType.LINE:
|
|
146
|
+
return <ChartOfLine config={config} />;
|
|
147
|
+
|
|
148
|
+
case EnumType.BAR:
|
|
149
|
+
return <ChartOfBar config={config} />;
|
|
150
|
+
|
|
151
|
+
case EnumType.PIE:
|
|
152
|
+
return <ChartOfPie config={config} />;
|
|
153
|
+
|
|
154
|
+
case EnumType.SCATTER:
|
|
155
|
+
return <ChartOfScatter config={config} />;
|
|
156
|
+
|
|
157
|
+
default:
|
|
158
|
+
return <ChartError>不支持的图表类型:{type}</ChartError>;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function HMChartBlock(props: any) {
|
|
163
|
+
const { children } = props;
|
|
164
|
+
|
|
165
|
+
// eslint-disable-next-line no-irregular-whitespace
|
|
166
|
+
const content = children?.replace(/ /g, '');
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
<ErrorBoundary>
|
|
170
|
+
<HMChartBlockBase>{content}</HMChartBlockBase>
|
|
171
|
+
</ErrorBoundary>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export { HMChartBlock };
|
|
176
|
+
export type { HMChartConfig };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { XMarkdown } from '@ant-design/x-markdown';
|
|
2
|
+
import type { XMarkdownProps } from '@ant-design/x-markdown/es/XMarkdown/interface';
|
|
3
|
+
import Latex from '@ant-design/x-markdown/plugins/Latex';
|
|
4
|
+
import '@ant-design/x-markdown/themes/light.css';
|
|
5
|
+
import { theme } from 'antd';
|
|
6
|
+
import classNames from 'classnames';
|
|
7
|
+
import { useMemo } from 'react';
|
|
8
|
+
import { CodeComponent } from './code';
|
|
9
|
+
import { KnowledgeRefBlock, processWithKnowledgeRef } from './knowledge_ref';
|
|
10
|
+
import { ThinkComponent, compatibleWithDeepSeek } from './think';
|
|
11
|
+
|
|
12
|
+
interface MarkdownProps {
|
|
13
|
+
content?: string;
|
|
14
|
+
isStreaming?: boolean;
|
|
15
|
+
components?: XMarkdownProps['components'];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function useMarkdownTheme() {
|
|
19
|
+
const token = theme.useToken();
|
|
20
|
+
|
|
21
|
+
// 使用 Ant Design 的主题系统判断亮色还是暗色
|
|
22
|
+
const isLightMode = useMemo(() => {
|
|
23
|
+
return token?.theme?.id === 0;
|
|
24
|
+
}, [token]);
|
|
25
|
+
|
|
26
|
+
const className = useMemo(() => {
|
|
27
|
+
return isLightMode ? 'x-markdown-light' : 'x-markdown-dark';
|
|
28
|
+
}, [isLightMode]);
|
|
29
|
+
|
|
30
|
+
return [className];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function Markdown({ content, isStreaming, components: propsComponents }: MarkdownProps) {
|
|
34
|
+
const [className] = useMarkdownTheme();
|
|
35
|
+
|
|
36
|
+
const config = useMemo(() => {
|
|
37
|
+
return {
|
|
38
|
+
extensions: Latex(),
|
|
39
|
+
};
|
|
40
|
+
}, []);
|
|
41
|
+
const streaming = useMemo(() => {
|
|
42
|
+
return {
|
|
43
|
+
enableAnimation: true,
|
|
44
|
+
hasNextChunk: isStreaming,
|
|
45
|
+
};
|
|
46
|
+
}, [isStreaming]);
|
|
47
|
+
|
|
48
|
+
const components = useMemo(() => {
|
|
49
|
+
return {
|
|
50
|
+
code: CodeComponent,
|
|
51
|
+
think: ThinkComponent,
|
|
52
|
+
...(propsComponents || {}),
|
|
53
|
+
};
|
|
54
|
+
}, [propsComponents]);
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<XMarkdown
|
|
58
|
+
className={classNames('fea-markdown', className)}
|
|
59
|
+
content={content}
|
|
60
|
+
paragraphTag="div"
|
|
61
|
+
openLinksInNewTab
|
|
62
|
+
config={config}
|
|
63
|
+
streaming={streaming}
|
|
64
|
+
components={components}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface CustomMarkdownProps extends MarkdownProps {
|
|
70
|
+
knowledgeRefs?: { id: string; [key: string]: any }[];
|
|
71
|
+
onKnowledgeRef?: (id?: string) => void;
|
|
72
|
+
}
|
|
73
|
+
function CustomMarkdown(props: CustomMarkdownProps) {
|
|
74
|
+
const { content: propsContent = '', knowledgeRefs, onKnowledgeRef } = props;
|
|
75
|
+
|
|
76
|
+
const content = useMemo(() => {
|
|
77
|
+
return processWithKnowledgeRef(compatibleWithDeepSeek(propsContent));
|
|
78
|
+
}, [propsContent]);
|
|
79
|
+
|
|
80
|
+
const KnowledgeRefComponent = useMemo(
|
|
81
|
+
() => (p: any) => {
|
|
82
|
+
return (
|
|
83
|
+
<KnowledgeRefBlock {...p} knowledgeRefs={knowledgeRefs} onKnowledgeRef={onKnowledgeRef} />
|
|
84
|
+
);
|
|
85
|
+
},
|
|
86
|
+
[knowledgeRefs, onKnowledgeRef],
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const components = useMemo(() => {
|
|
90
|
+
return {
|
|
91
|
+
'knowledge-ref': KnowledgeRefComponent,
|
|
92
|
+
};
|
|
93
|
+
}, [KnowledgeRefComponent]);
|
|
94
|
+
|
|
95
|
+
return <Markdown {...props} content={content} components={components} />;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export { CustomMarkdown, Markdown };
|
|
99
|
+
export type { CustomMarkdownProps, MarkdownProps };
|