yt-chat-components 1.1.2 → 1.1.3

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 (42) hide show
  1. package/.idea/modules.xml +1 -1
  2. package/.idea/sonarlint/issuestore/3/6/364385cedcce4c06de1901392ffeeac0caef0f3c +0 -0
  3. package/.idea/sonarlint/issuestore/3/9/39129446b425a1d640160c068e4194e96639eedf +0 -0
  4. package/.idea/sonarlint/issuestore/4/a/4a2f33951ce07c1ff7184f91877aa13db05d3785 +0 -0
  5. package/.idea/sonarlint/issuestore/4/a/4a7b99bdbee5792679d347b6474463bf5e14b66d +0 -0
  6. package/.idea/sonarlint/issuestore/4/b/4b015aa5428c4d4c3d672893ec23f5fe3969f9be +0 -0
  7. package/.idea/sonarlint/issuestore/4/b/4b6989b8ccae808ebc45d02230d336ea53800365 +0 -0
  8. package/.idea/sonarlint/issuestore/6/1/61ebb9fd6e8cf9082658121d5d81e297791dacd0 +0 -0
  9. package/.idea/sonarlint/issuestore/6/c/6c024c1d0ad64656b9d4b0695ec3c49c0454addf +0 -0
  10. package/.idea/sonarlint/issuestore/6/e/6e75fc1c07c3a427a86fc213ca9479caaaff00ea +0 -0
  11. package/.idea/sonarlint/issuestore/8/d/8d6123af13a140f93e06299fff7ea23c547e9ec8 +0 -0
  12. package/.idea/sonarlint/issuestore/c/c/cc2352788140b6778ac06df4b33f50b390d2d8be +0 -0
  13. package/.idea/sonarlint/issuestore/d/5/d5595158cc48f9bf3e51b06f6e6805a8fd2d6262 +0 -0
  14. package/.idea/sonarlint/issuestore/d/7/d747cbed4201192dfa83a1a51345b020a050b647 +0 -0
  15. package/.idea/sonarlint/issuestore/d/9/d938938695d447dadda115e28781c6541f53fc4f +0 -0
  16. package/.idea/sonarlint/issuestore/index.pb +31 -3
  17. package/build/static/js/bundle.min.js +2 -0
  18. package/build/static/js/bundle.min.js.LICENSE.txt +132 -0
  19. package/package.json +79 -78
  20. package/public/index.html +108 -108
  21. package/src/YtChatView/chatWidget/chatWindow/chatMessage/index.tsx +498 -464
  22. package/src/YtChatView/chatWidget/chatWindow/controllers/index.ts +249 -249
  23. package/src/YtChatView/chatWidget/chatWindow/index.module.css +196 -196
  24. package/src/YtChatView/chatWidget/chatWindow/index.tsx +1099 -1086
  25. package/src/YtChatView/chatWidget/chatWindow/types/chatWidget/index.ts +50 -50
  26. package/src/YtChatView/chatWidget/index.tsx +2593 -2591
  27. package/src/YtChatView/logoBtn/index.css +3 -3
  28. package/src/YtChatView/logoBtn/index.jsx +103 -103
  29. package/src/YtChatView/logoSplitBtn/index.css +3 -3
  30. package/src/YtChatView/logoSplitBtn/index.jsx +105 -105
  31. package/src/YtChatView/mobileChat/index.jsx +945 -945
  32. package/src/YtChatView/mobileChat/index.module.css +253 -253
  33. package/src/YtChatView/previewDialog/index.jsx +600 -600
  34. package/src/YtChatView/previewDialog/index.module.css +253 -253
  35. package/src/chatWidget/chatWindow/index.tsx +426 -426
  36. package/src/chatWidget/index.tsx +2193 -2193
  37. package/src/index.tsx +10 -10
  38. package/webpack.config.js +50 -50
  39. package/dist/build/static/js/bundle.min.js +0 -1
  40. /package/.idea/{langflow-embedded-chat-clone.iml → langflow-embedded-chat.iml} +0 -0
  41. /package/.idea/sonarlint/issuestore/{7/0/7030d0b2f71b999ff89a343de08c414af32fc93a → 0/f/0f8c0c92cf798431ebb931ff6e997b1af86ecee5} +0 -0
  42. /package/.idea/sonarlint/issuestore/{9/c/9cfff9a6d27bd6c255aa751213163c7901fb8ce7 → 2/7/27e69cb561aeea20c1afbdd32d260dd60b89a81b} +0 -0
@@ -1,464 +1,498 @@
1
- // @ts-nocheck
2
- import Markdown from 'react-markdown';
3
- import {ChatMessageType, InputValueType, MessageType} from '../types/chatWidget';
4
- import remarkGfm from 'remark-gfm';
5
- import rehypeMathjax from 'rehype-mathjax';
6
- // import './index.module.css';
7
- import upFilePng from '../../../../assets/aicenter/upfile.png';
8
- import {Form, Image, Input, message as messageTip, Select, DatePicker, Button} from 'antd';
9
- import React, {useState, useRef} from 'react';
10
- import typePdfPng from '../../../../assets/aicenter/type-pdf.png';
11
- import typeWordPng from '../../../../assets/aicenter/type-word.png';
12
- import typeExcelPng from '../../../../assets/aicenter/type-excel.png';
13
- import typeMarkdownPng from '../../../../assets/aicenter/type-markdown.png';
14
- import typeTextPng from '../../../../assets/aicenter/type-text.png';
15
- import typeMobiPng from '../../../../assets/aicenter/type-mobi.png';
16
- import typeRPubPng from '../../../../assets/aicenter/type-rpub.png';
17
- import playPng from '../../../../assets/aicenter/play.png';
18
- import playRunGif from '../../../../assets/aicenter/play-run.gif';
19
- import copyPng from '../../../../assets/aicenter/copy.png';
20
-
21
- let speechSynth = window.speechSynthesis;
22
- let utterance = null;
23
-
24
- export default function ChatMessage({
25
- type = MessageType.text,
26
- rawInfo,
27
- message,
28
- isSend,
29
- error,
30
- host_url,
31
- user_message_style,
32
- bot_message_style,
33
- error_message_style,
34
- handleSendMessage
35
- }: ChatMessageType) {
36
- const parseFileName = (
37
- text: string,
38
- ): { fileName: string | null; fileType: string | null; isImg: boolean } => {
39
- // 检查输入是否有效
40
- if (!text || typeof text !== 'string') {
41
- return { fileName: null, fileType: null };
42
- }
43
-
44
- // 找到最后一个反斜杠的位置
45
- const lastBackslashIndex = text.lastIndexOf('\\');
46
- if (lastBackslashIndex === -1) {
47
- return { fileName: null, fileType: null }; // 如果没有找到反斜杠,返回空结果
48
- }
49
-
50
- // 截取反斜杠后面的部分
51
- const fileNameWithExtension = text.substring(lastBackslashIndex + 1);
52
-
53
- // 分割文件名和文件类型
54
- const parts = fileNameWithExtension.split('.');
55
- const fileName = parts.slice(0, -1).join('.'); // 文件名(不含扩展名)
56
- const fileType = parts.length > 1 ? parts.pop() : null; // 文件类型
57
-
58
- const isImg = fileType && ['jpg', 'jpeg', 'png', 'gif'].includes(fileType.toLowerCase());
59
- // 返回结果对象
60
- return {
61
- fileName: fileName,
62
- fileType: fileType,
63
- isImg,
64
- };
65
- };
66
-
67
- const [isPlay, setIsPlay] = useState(false) // 是否正在播放文字
68
- const [isSubmittingForm, setIsSubmittingForm] = useState(false) // 正在提交form
69
- const [isShowFormBtns, setIsShowFormBtns] = useState(true) // 正在提交form
70
- const formRef = useRef(null);
71
-
72
- /**
73
- * 根据文件URL获取文件类型
74
- * @param url
75
- */
76
- const getFileTypeByUrl = (url) => {
77
- if (!url) {
78
- return 'file';
79
- }
80
- switch (url.split('.').pop().toLowerCase()) {
81
- case 'jpg':
82
- case 'jpeg':
83
- case 'png':
84
- case 'gif':
85
- return 'image';
86
- case 'pdf':
87
- return 'pdf';
88
- case 'doc':
89
- case 'docx':
90
- return 'word';
91
- case 'xls':
92
- case 'xlsx':
93
- return 'excel';
94
- case 'md':
95
- return 'markdown';
96
- case 'txt':
97
- return 'txt';
98
- case 'mobi':
99
- return 'mobi';
100
- case 'rpub':
101
- return 'rpub';
102
- default:
103
- return 'file';
104
- }
105
- };
106
-
107
- /**
108
- * 播放文字
109
- * @param text 文字
110
- */
111
- const playVoice = (text)=>{
112
- if (isPlay) {
113
- speechSynth.cancel();
114
- setIsPlay(false)
115
- }else{
116
- if (text) {
117
- utterance = new SpeechSynthesisUtterance(text);
118
- const voices = speechSynth.getVoices();
119
- utterance.voice = voices[61];
120
- utterance.rate = 1;
121
- utterance.pitch = 1;
122
- speechSynth.speak(utterance);
123
- setIsPlay(true)
124
- }
125
- }
126
- }
127
-
128
- /**
129
- * 复制文字
130
- * @param text 文字
131
- */
132
- const copyText = async (text) => {
133
- if(text){
134
- await navigator.clipboard.writeText(text);
135
- messageTip.info('复制成功')
136
- }else{
137
- messageTip.error("消息框没有文字")
138
- }
139
- }
140
-
141
- const renderUserMessage = () => {
142
- return (
143
- <div style={user_message_style} className="msg_userMessageBox">
144
- <div style={user_message_style} className="cl-user_message">
145
- {message.message}
146
- </div>
147
- <div className="msg_messageImgBox">
148
- {message.rawInfo?.files.map((item, index) => {
149
- return (
150
- <div key={item} className="msg_fileBox">
151
- {parseFileName(item).isImg && (
152
- <Image
153
- height={40}
154
- width={40}
155
- className="msg_messageImg"
156
- src={`${host_url}/api/v1/files/images/${item}`}
157
- alt={item}
158
- preview={{
159
- mask: <span className="custom-mask"></span>,
160
- }}
161
- />
162
- )}
163
- {getFileTypeByUrl(item) == 'pdf' && (
164
- <img style={{ width: 40, height: 40 }} src={typePdfPng} />
165
- )}
166
- {getFileTypeByUrl(item) == 'excel' && (
167
- <img style={{ width: 40, height: 40 }} src={typeExcelPng} />
168
- )}
169
- {getFileTypeByUrl(item) == 'markdown' && (
170
- <img style={{ width: 40, height: 40 }} src={typeMarkdownPng} />
171
- )}
172
- {getFileTypeByUrl(item) == 'txt' && (
173
- <img style={{ width: 40, height: 40 }} src={typeTextPng} />
174
- )}
175
- {getFileTypeByUrl(item) == 'word' && (
176
- <img style={{ width: 40, height: 40 }} src={typeWordPng} />
177
- )}
178
- {getFileTypeByUrl(item) == 'mobi' && (
179
- <img style={{ width: 40, height: 40 }} src={typeMobiPng} />
180
- )}
181
- {getFileTypeByUrl(item) == 'rpub' && (
182
- <img style={{ width: 40, height: 40 }} src={typeRPubPng} />
183
- )}
184
- {getFileTypeByUrl(item) == 'file' && (
185
- <img style={{ width: 40, height: 40 }} src={upFilePng} />
186
- )}
187
- <div className="msg_fileInfoBox">
188
- <div className="msg_fileInfoFileName">{parseFileName(item).fileName}</div>
189
- </div>
190
- </div>
191
- );
192
- })}
193
- </div>
194
- </div>
195
- )
196
- }
197
-
198
- const renderErrorMessage = () => {
199
- return (
200
- <div style={error_message_style} className={'cl-error_message'}>
201
- {message.message}
202
- </div>
203
- )
204
- }
205
-
206
- const renderBotTextMessage = () => {
207
- return (
208
- <div>
209
- <div style={bot_message_style} className={'cl-bot_message'}>
210
- <Markdown
211
- className={'markdown-body prose flex flex-col word-break-break-word'}
212
- remarkPlugins={[remarkGfm]}
213
- rehypePlugins={[rehypeMathjax]}
214
- >
215
- {message.message}
216
- </Markdown>
217
- </div>
218
- <div className="msg_operateBox">
219
- <img src={isPlay?playRunGif:playPng} onClick={()=>playVoice(message.message)} />
220
- <img src={copyPng} onClick={()=>copyText(message.message)} />
221
- </div>
222
- </div>
223
- )
224
- }
225
-
226
- const renderBotEchartMessage = () => {
227
- return (
228
- <div>
229
- echart
230
- </div>
231
- )
232
- }
233
-
234
- const renderBotFormMessage = () => {
235
- const { form_config } = rawInfo;
236
-
237
- // 处理表单提交
238
- const handleSubmit = (values) => {
239
- console.log('表单提交数据:', values);
240
- if (handleSendMessage) {
241
- setIsSubmittingForm(true);
242
- // TODO 提交的数据需要多一些
243
- handleSendMessage(JSON.stringify(values), () => {
244
- messageTip.success('表单提交成功');
245
- setIsSubmittingForm(false);
246
- setIsShowFormBtns(false);
247
- }, InputValueType.form);
248
- }
249
- };
250
-
251
- // 处理表单取消
252
- const handleCancel = () => {
253
- if (formRef.current) {
254
- formRef.current.resetFields();
255
- }
256
- if (handleSendMessage) {
257
- setIsSubmittingForm(true);
258
- handleSendMessage('USER CANCELED', () => {
259
- messageTip.success('取消成功');
260
- setIsSubmittingForm(false);
261
- setIsShowFormBtns(false);
262
- }, InputValueType.form);
263
- }
264
- };
265
-
266
- return (
267
- <div className="form-container">
268
- <div className="form-title">{form_config?.name || '表单'}</div>
269
- <Form
270
- ref={formRef}
271
- layout="vertical"
272
- onFinish={handleSubmit}
273
- className="dynamic-form"
274
- >
275
- {form_config?.config?.map((item, index) => {
276
- const { key, name, type, options } = item;
277
- // 根据不同的表单项类型渲染不同的组件
278
- switch (type) {
279
- case 'text':
280
- return (
281
- <Form.Item
282
- key={index}
283
- label={name}
284
- name={key}
285
- rules={[{ required: true, message: `请输入${name}` }]}
286
- >
287
- <Input placeholder={`请输入${name}`} />
288
- </Form.Item>
289
- );
290
- case 'date':
291
- return (
292
- <Form.Item
293
- key={index}
294
- label={name}
295
- name={key}
296
- rules={[{ required: true, message: `请选择${name}` }]}
297
- >
298
- <DatePicker style={{ width: '100%' }} placeholder={`请选择${name}`} />
299
- </Form.Item>
300
- );
301
- case 'select':
302
- return (
303
- <Form.Item
304
- key={index}
305
- label={name}
306
- name={key}
307
- rules={[{ required: true, message: `请选择${name}` }]}
308
- >
309
- <Select
310
- placeholder={`请选择${name}`}
311
- style={{ width: '100%' }}
312
- >
313
- {options?.map((option, optIndex) => (
314
- <Select.Option key={optIndex} value={option}>
315
- {option}
316
- </Select.Option>
317
- ))}
318
- </Select>
319
- </Form.Item>
320
- );
321
- default:
322
- return (
323
- <Form.Item
324
- key={index}
325
- label={name}
326
- name={name}
327
- >
328
- <Input placeholder={`请输入${name}`} />
329
- </Form.Item>
330
- );
331
- }
332
- })}
333
-
334
- {
335
- isShowFormBtns && (
336
- <div className="form-buttons">
337
- <Form.Item>
338
- <div style={{ display: 'flex', justifyContent: 'flex-end', gap: '12px' }}>
339
- <Button
340
- loading={isSubmittingForm}
341
- onClick={handleCancel}
342
- >
343
- 取消
344
- </Button>
345
- <Button
346
- loading={isSubmittingForm}
347
- type="primary"
348
- htmlType="submit"
349
- style={{ backgroundColor: '#8064f6' }}
350
- >
351
- 提交
352
- </Button>
353
- </div>
354
- </Form.Item>
355
- </div>
356
- )
357
- }
358
- </Form>
359
-
360
- <style jsx>{`
361
- .form-container {
362
- width: 100%;
363
- padding: 16px;
364
- border-radius: 8px;
365
- background-color:rgb(244, 244, 244);
366
- }
367
-
368
- .form-title {
369
- font-size: 18px;
370
- font-weight: bold;
371
- margin-bottom: 16px;
372
- color: #333;
373
- }
374
-
375
- .dynamic-form {
376
- width: 100%;
377
- }
378
-
379
- .form-select {
380
- width: 100%;
381
- height: 32px;
382
- border: 1px solid #d9d9d9;
383
- border-radius: 4px;
384
- padding: 4px 11px;
385
- }
386
-
387
- .form-buttons {
388
- display: flex;
389
- justify-content: flex-end;
390
- margin-top: 20px;
391
- gap: 12px;
392
- }
393
-
394
- .cancel-button {
395
- padding: 6px 16px;
396
- background-color: #f0f0f0;
397
- border: 1px solid #d9d9d9;
398
- border-radius: 4px;
399
- cursor: pointer;
400
- }
401
-
402
- .submit-button {
403
- padding: 6px 16px;
404
- background-color: #8064f6;
405
- color: white;
406
- border: none;
407
- border-radius: 4px;
408
- cursor: pointer;
409
- }
410
-
411
- /* 移动端适配 */
412
- @media (max-width: 768px) {
413
- .form-container {
414
- padding: 12px;
415
- }
416
-
417
- .form-buttons {
418
- flex-direction: column;
419
- gap: 8px;
420
- }
421
-
422
- .cancel-button,
423
- .submit-button {
424
- width: 100%;
425
- padding: 8px 0;
426
- }
427
- }
428
- `}</style>
429
- </div>
430
- );
431
- };
432
-
433
- const renderBotMessage = () => {
434
- switch (type) {
435
- case MessageType.echart:
436
- return renderBotEchartMessage()
437
- case MessageType.form:
438
- return renderBotFormMessage()
439
- case MessageType.text:
440
- default:
441
- return renderBotTextMessage()
442
- }
443
- }
444
-
445
- const render = () => {
446
- if (isSend){
447
- return renderUserMessage()
448
- }else {
449
- if (error){
450
- return renderErrorMessage()
451
- }else{
452
- return renderBotMessage()
453
- }
454
- }
455
- }
456
-
457
- return (
458
- <div className={'cl-chat-message ' + (isSend ? ' cl-justify-end' : ' cl-justify-start')}>
459
- {
460
- render()
461
- }
462
- </div>
463
- );
464
- }
1
+ // @ts-nocheck
2
+ import Markdown from 'react-markdown';
3
+ import EChartsReact from 'echarts-for-react';
4
+ import {ChatMessageType, InputValueType, MessageType} from '../types/chatWidget';
5
+ import remarkGfm from 'remark-gfm';
6
+ import rehypeMathjax from 'rehype-mathjax';
7
+ // import './index.module.css';
8
+ import upFilePng from '../../../../assets/aicenter/upfile.png';
9
+ import {Form, Image, Input, message as messageTip, Select, DatePicker, Button, Spin, Skeleton} from 'antd';
10
+ import React, {useState, useRef} from 'react';
11
+ import typePdfPng from '../../../../assets/aicenter/type-pdf.png';
12
+ import typeWordPng from '../../../../assets/aicenter/type-word.png';
13
+ import typeExcelPng from '../../../../assets/aicenter/type-excel.png';
14
+ import typeMarkdownPng from '../../../../assets/aicenter/type-markdown.png';
15
+ import typeTextPng from '../../../../assets/aicenter/type-text.png';
16
+ import typeMobiPng from '../../../../assets/aicenter/type-mobi.png';
17
+ import typeRPubPng from '../../../../assets/aicenter/type-rpub.png';
18
+ import playPng from '../../../../assets/aicenter/play.png';
19
+ import playRunGif from '../../../../assets/aicenter/play-run.gif';
20
+ import copyPng from '../../../../assets/aicenter/copy.png';
21
+
22
+ let speechSynth = window.speechSynthesis;
23
+ let utterance = null;
24
+
25
+ export default function ChatMessage({
26
+ type = MessageType.text,
27
+ rawInfo,
28
+ message,
29
+ isSend,
30
+ error,
31
+ host_url,
32
+ user_message_style,
33
+ bot_message_style,
34
+ error_message_style,
35
+ handleSendMessage
36
+ }: ChatMessageType) {
37
+ const parseFileName = (
38
+ text: string,
39
+ ): { fileName: string | null; fileType: string | null; isImg: boolean } => {
40
+ // 检查输入是否有效
41
+ if (!text || typeof text !== 'string') {
42
+ return { fileName: null, fileType: null };
43
+ }
44
+
45
+ // 找到最后一个反斜杠的位置
46
+ const lastBackslashIndex = text.lastIndexOf('\\');
47
+ if (lastBackslashIndex === -1) {
48
+ return { fileName: null, fileType: null }; // 如果没有找到反斜杠,返回空结果
49
+ }
50
+
51
+ // 截取反斜杠后面的部分
52
+ const fileNameWithExtension = text.substring(lastBackslashIndex + 1);
53
+
54
+ // 分割文件名和文件类型
55
+ const parts = fileNameWithExtension.split('.');
56
+ const fileName = parts.slice(0, -1).join('.'); // 文件名(不含扩展名)
57
+ const fileType = parts.length > 1 ? parts.pop() : null; // 文件类型
58
+
59
+ const isImg = fileType && ['jpg', 'jpeg', 'png', 'gif'].includes(fileType.toLowerCase());
60
+ // 返回结果对象
61
+ return {
62
+ fileName: fileName,
63
+ fileType: fileType,
64
+ isImg,
65
+ };
66
+ };
67
+
68
+ const [isPlay, setIsPlay] = useState(false) // 是否正在播放文字
69
+ const [isSubmittingForm, setIsSubmittingForm] = useState(false) // 正在提交form
70
+ const [isShowFormBtns, setIsShowFormBtns] = useState(true) // 正在提交form
71
+ const formRef = useRef(null);
72
+
73
+ /**
74
+ * 根据文件URL获取文件类型
75
+ * @param url
76
+ */
77
+ const getFileTypeByUrl = (url) => {
78
+ if (!url) {
79
+ return 'file';
80
+ }
81
+ switch (url.split('.').pop().toLowerCase()) {
82
+ case 'jpg':
83
+ case 'jpeg':
84
+ case 'png':
85
+ case 'gif':
86
+ return 'image';
87
+ case 'pdf':
88
+ return 'pdf';
89
+ case 'doc':
90
+ case 'docx':
91
+ return 'word';
92
+ case 'xls':
93
+ case 'xlsx':
94
+ return 'excel';
95
+ case 'md':
96
+ return 'markdown';
97
+ case 'txt':
98
+ return 'txt';
99
+ case 'mobi':
100
+ return 'mobi';
101
+ case 'rpub':
102
+ return 'rpub';
103
+ default:
104
+ return 'file';
105
+ }
106
+ };
107
+
108
+ /**
109
+ * 播放文字
110
+ * @param text 文字
111
+ */
112
+ const playVoice = (text)=>{
113
+ if (isPlay) {
114
+ speechSynth.cancel();
115
+ setIsPlay(false)
116
+ }else{
117
+ if (text) {
118
+ utterance = new SpeechSynthesisUtterance(text);
119
+ const voices = speechSynth.getVoices();
120
+ utterance.voice = voices[61];
121
+ utterance.rate = 1;
122
+ utterance.pitch = 1;
123
+ speechSynth.speak(utterance);
124
+ setIsPlay(true)
125
+ }
126
+ }
127
+ }
128
+
129
+ /**
130
+ * 复制文字
131
+ * @param text 文字
132
+ */
133
+ const copyText = async (text) => {
134
+ if(text){
135
+ await navigator.clipboard.writeText(text);
136
+ messageTip.info('复制成功')
137
+ }else{
138
+ messageTip.error("消息框没有文字")
139
+ }
140
+ }
141
+
142
+ const renderUserMessage = () => {
143
+ return (
144
+ <div style={user_message_style} className="msg_userMessageBox">
145
+ <div style={user_message_style} className="cl-user_message">
146
+ {message.message}
147
+ </div>
148
+ <div className="msg_messageImgBox">
149
+ {message.rawInfo?.files.map((item, index) => {
150
+ return (
151
+ <div key={item} className="msg_fileBox">
152
+ {parseFileName(item).isImg && (
153
+ <Image
154
+ height={40}
155
+ width={40}
156
+ className="msg_messageImg"
157
+ src={`${host_url}/api/v1/files/images/${item}`}
158
+ alt={item}
159
+ preview={{
160
+ mask: <span className="custom-mask"></span>,
161
+ }}
162
+ />
163
+ )}
164
+ {getFileTypeByUrl(item) == 'pdf' && (
165
+ <img style={{ width: 40, height: 40 }} src={typePdfPng} />
166
+ )}
167
+ {getFileTypeByUrl(item) == 'excel' && (
168
+ <img style={{ width: 40, height: 40 }} src={typeExcelPng} />
169
+ )}
170
+ {getFileTypeByUrl(item) == 'markdown' && (
171
+ <img style={{ width: 40, height: 40 }} src={typeMarkdownPng} />
172
+ )}
173
+ {getFileTypeByUrl(item) == 'txt' && (
174
+ <img style={{ width: 40, height: 40 }} src={typeTextPng} />
175
+ )}
176
+ {getFileTypeByUrl(item) == 'word' && (
177
+ <img style={{ width: 40, height: 40 }} src={typeWordPng} />
178
+ )}
179
+ {getFileTypeByUrl(item) == 'mobi' && (
180
+ <img style={{ width: 40, height: 40 }} src={typeMobiPng} />
181
+ )}
182
+ {getFileTypeByUrl(item) == 'rpub' && (
183
+ <img style={{ width: 40, height: 40 }} src={typeRPubPng} />
184
+ )}
185
+ {getFileTypeByUrl(item) == 'file' && (
186
+ <img style={{ width: 40, height: 40 }} src={upFilePng} />
187
+ )}
188
+ <div className="msg_fileInfoBox">
189
+ <div className="msg_fileInfoFileName">{parseFileName(item).fileName}</div>
190
+ </div>
191
+ </div>
192
+ );
193
+ })}
194
+ </div>
195
+ </div>
196
+ )
197
+ }
198
+
199
+ const renderErrorMessage = () => {
200
+ return (
201
+ <div style={error_message_style} className={'cl-error_message'}>
202
+ {message.message}
203
+ </div>
204
+ )
205
+ }
206
+ const renderLoading = (type:MessageType, height:number) => {
207
+ const skeletonContainerId = `skeleton-container-${Math.random().toString(36).substring(2, 9)}`;
208
+
209
+ return (
210
+ <div
211
+ id={skeletonContainerId}
212
+ style={{minWidth: 500, width: '100%', height, display: 'flex', justifyContent: 'center', alignItems: 'center'}}
213
+ >
214
+ {
215
+ // 其他类型暂不处理
216
+ type == MessageType.echart && (
217
+ <Skeleton.Image active={true} style={{width: '100%', height: '100%'}}/>
218
+ )
219
+ }
220
+ <style jsx>{`
221
+ #${skeletonContainerId} .ant-skeleton.ant-skeleton-element {
222
+ width: 100% !important;
223
+ height: 100% !important;
224
+ }
225
+ `}</style>
226
+ </div>
227
+ )
228
+ }
229
+
230
+ // 自定义渲染器
231
+ const EchartRender = React.useMemo(() => ({
232
+ code({ node, inline, className, children, ...props }) {
233
+ // 检查是否是代码块,并且语言为 'echart'
234
+ if (className && className.includes('language-echart')) {
235
+ try {
236
+ // 解析代码块内容为 JSON 配置
237
+ const chartOptions = JSON.parse(children.toString().trim());
238
+ // 渲染 ECharts 组件
239
+ return <EChartsReact option={chartOptions} style={{minWidth: 500, width: '100%', height: 300, backgroundColor: '#fff' }} />;
240
+ } catch (error) {
241
+ return renderLoading(MessageType.echart, 300);
242
+ }
243
+ }
244
+ // 默认渲染其他代码块
245
+ return <code className={className} {...props}>{children}</code>;
246
+ },
247
+ }), []); // 空依赖数组,只创建一次
248
+
249
+ const renderBotTextMessage = () => {
250
+ return (
251
+ <div>
252
+ <div style={bot_message_style} className={'cl-bot_message'}>
253
+ <Markdown
254
+ className={'markdown-body prose flex flex-col word-break-break-word'}
255
+ remarkPlugins={[remarkGfm]}
256
+ rehypePlugins={[rehypeMathjax]}
257
+ components={EchartRender}
258
+ >
259
+ {message.message}
260
+ </Markdown>
261
+ </div>
262
+ <div className="msg_operateBox">
263
+ <img src={isPlay?playRunGif:playPng} onClick={()=>playVoice(message.message)} />
264
+ <img src={copyPng} onClick={()=>copyText(message.message)} />
265
+ </div>
266
+ </div>
267
+ )
268
+ }
269
+
270
+ const renderBotFormMessage = () => {
271
+ const { form_config } = rawInfo;
272
+
273
+ // 处理表单提交
274
+ const handleSubmit = (values) => {
275
+ console.log('表单提交数据:', values);
276
+ if (handleSendMessage) {
277
+ setIsSubmittingForm(true);
278
+ // TODO 提交的数据需要多一些
279
+ handleSendMessage(JSON.stringify(values), () => {
280
+ messageTip.success('表单提交成功');
281
+ setIsSubmittingForm(false);
282
+ setIsShowFormBtns(false);
283
+ }, InputValueType.form);
284
+ }
285
+ };
286
+
287
+ // 处理表单取消
288
+ const handleCancel = () => {
289
+ if (formRef.current) {
290
+ formRef.current.resetFields();
291
+ }
292
+ if (handleSendMessage) {
293
+ setIsSubmittingForm(true);
294
+ handleSendMessage('USER CANCELED', () => {
295
+ messageTip.success('取消成功');
296
+ setIsSubmittingForm(false);
297
+ setIsShowFormBtns(false);
298
+ }, InputValueType.form);
299
+ }
300
+ };
301
+
302
+ return (
303
+ <div className="form-container">
304
+ <div className="form-title">{form_config?.name || '表单'}</div>
305
+ <Form
306
+ ref={formRef}
307
+ layout="vertical"
308
+ onFinish={handleSubmit}
309
+ className="dynamic-form"
310
+ >
311
+ {form_config?.config?.map((item, index) => {
312
+ const { key, name, type, options } = item;
313
+ // 根据不同的表单项类型渲染不同的组件
314
+ switch (type) {
315
+ case 'text':
316
+ return (
317
+ <Form.Item
318
+ key={index}
319
+ label={name}
320
+ name={key}
321
+ rules={[{ required: true, message: `请输入${name}` }]}
322
+ >
323
+ <Input placeholder={`请输入${name}`} />
324
+ </Form.Item>
325
+ );
326
+ case 'date':
327
+ return (
328
+ <Form.Item
329
+ key={index}
330
+ label={name}
331
+ name={key}
332
+ rules={[{ required: true, message: `请选择${name}` }]}
333
+ >
334
+ <DatePicker style={{ width: '100%' }} placeholder={`请选择${name}`} />
335
+ </Form.Item>
336
+ );
337
+ case 'select':
338
+ return (
339
+ <Form.Item
340
+ key={index}
341
+ label={name}
342
+ name={key}
343
+ rules={[{ required: true, message: `请选择${name}` }]}
344
+ >
345
+ <Select
346
+ placeholder={`请选择${name}`}
347
+ style={{ width: '100%' }}
348
+ >
349
+ {options?.map((option, optIndex) => (
350
+ <Select.Option key={optIndex} value={option}>
351
+ {option}
352
+ </Select.Option>
353
+ ))}
354
+ </Select>
355
+ </Form.Item>
356
+ );
357
+ default:
358
+ return (
359
+ <Form.Item
360
+ key={index}
361
+ label={name}
362
+ name={name}
363
+ >
364
+ <Input placeholder={`请输入${name}`} />
365
+ </Form.Item>
366
+ );
367
+ }
368
+ })}
369
+
370
+ {
371
+ isShowFormBtns && (
372
+ <div className="form-buttons">
373
+ <Form.Item>
374
+ <div style={{ display: 'flex', justifyContent: 'flex-end', gap: '12px' }}>
375
+ <Button
376
+ loading={isSubmittingForm}
377
+ onClick={handleCancel}
378
+ >
379
+ 取消
380
+ </Button>
381
+ <Button
382
+ loading={isSubmittingForm}
383
+ type="primary"
384
+ htmlType="submit"
385
+ style={{ backgroundColor: '#8064f6' }}
386
+ >
387
+ 提交
388
+ </Button>
389
+ </div>
390
+ </Form.Item>
391
+ </div>
392
+ )
393
+ }
394
+ </Form>
395
+
396
+ <style jsx>{`
397
+ .form-container {
398
+ width: 100%;
399
+ padding: 16px;
400
+ border-radius: 8px;
401
+ background-color:rgb(244, 244, 244);
402
+ }
403
+
404
+ .form-title {
405
+ font-size: 18px;
406
+ font-weight: bold;
407
+ margin-bottom: 16px;
408
+ color: #333;
409
+ }
410
+
411
+ .dynamic-form {
412
+ width: 100%;
413
+ }
414
+
415
+ .form-select {
416
+ width: 100%;
417
+ height: 32px;
418
+ border: 1px solid #d9d9d9;
419
+ border-radius: 4px;
420
+ padding: 4px 11px;
421
+ }
422
+
423
+ .form-buttons {
424
+ display: flex;
425
+ justify-content: flex-end;
426
+ margin-top: 20px;
427
+ gap: 12px;
428
+ }
429
+
430
+ .cancel-button {
431
+ padding: 6px 16px;
432
+ background-color: #f0f0f0;
433
+ border: 1px solid #d9d9d9;
434
+ border-radius: 4px;
435
+ cursor: pointer;
436
+ }
437
+
438
+ .submit-button {
439
+ padding: 6px 16px;
440
+ background-color: #8064f6;
441
+ color: white;
442
+ border: none;
443
+ border-radius: 4px;
444
+ cursor: pointer;
445
+ }
446
+
447
+ /* 移动端适配 */
448
+ @media (max-width: 768px) {
449
+ .form-container {
450
+ padding: 12px;
451
+ }
452
+
453
+ .form-buttons {
454
+ flex-direction: column;
455
+ gap: 8px;
456
+ }
457
+
458
+ .cancel-button,
459
+ .submit-button {
460
+ width: 100%;
461
+ padding: 8px 0;
462
+ }
463
+ }
464
+ `}</style>
465
+ </div>
466
+ );
467
+ };
468
+
469
+ const renderBotMessage = () => {
470
+ switch (type) {
471
+ case MessageType.form:
472
+ return renderBotFormMessage()
473
+ case MessageType.text:
474
+ default:
475
+ return renderBotTextMessage()
476
+ }
477
+ }
478
+
479
+ const render = () => {
480
+ if (isSend){
481
+ return renderUserMessage()
482
+ }else {
483
+ if (error){
484
+ return renderErrorMessage()
485
+ }else{
486
+ return renderBotMessage()
487
+ }
488
+ }
489
+ }
490
+
491
+ return (
492
+ <div className={'cl-chat-message ' + (isSend ? ' cl-justify-end' : ' cl-justify-start')}>
493
+ {
494
+ render()
495
+ }
496
+ </div>
497
+ );
498
+ }