yt-chat-components 1.0.0 → 1.0.2

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/index.pb +3 -31
  3. package/dist/build/static/js/bundle.min.js +1 -0
  4. package/package.json +76 -76
  5. package/public/index.html +108 -108
  6. package/src/YtChatView/chatWidget/chatWindow/chatMessage/index.tsx +464 -464
  7. package/src/YtChatView/chatWidget/chatWindow/controllers/index.ts +249 -249
  8. package/src/YtChatView/chatWidget/chatWindow/index.module.css +196 -196
  9. package/src/YtChatView/chatWidget/chatWindow/index.tsx +1054 -1041
  10. package/src/YtChatView/chatWidget/chatWindow/types/chatWidget/index.ts +50 -50
  11. package/src/YtChatView/chatWidget/index.tsx +2586 -2586
  12. package/src/YtChatView/logoBtn/index.css +3 -3
  13. package/src/YtChatView/logoBtn/index.jsx +103 -103
  14. package/src/YtChatView/logoSplitBtn/index.css +3 -3
  15. package/src/YtChatView/logoSplitBtn/index.jsx +105 -105
  16. package/src/YtChatView/mobileChat/index.jsx +944 -848
  17. package/src/YtChatView/mobileChat/index.module.css +253 -253
  18. package/src/YtChatView/previewDialog/index.jsx +600 -600
  19. package/src/YtChatView/previewDialog/index.module.css +253 -253
  20. package/src/chatWidget/chatWindow/index.tsx +426 -426
  21. package/src/chatWidget/index.tsx +2193 -2193
  22. package/src/index.tsx +10 -10
  23. package/webpack.config.js +50 -50
  24. package/.idea/sonarlint/issuestore/3/6/364385cedcce4c06de1901392ffeeac0caef0f3c +0 -0
  25. package/.idea/sonarlint/issuestore/3/9/39129446b425a1d640160c068e4194e96639eedf +0 -0
  26. package/.idea/sonarlint/issuestore/4/a/4a2f33951ce07c1ff7184f91877aa13db05d3785 +0 -0
  27. package/.idea/sonarlint/issuestore/4/a/4a7b99bdbee5792679d347b6474463bf5e14b66d +0 -0
  28. package/.idea/sonarlint/issuestore/4/b/4b015aa5428c4d4c3d672893ec23f5fe3969f9be +0 -0
  29. package/.idea/sonarlint/issuestore/4/b/4b6989b8ccae808ebc45d02230d336ea53800365 +0 -0
  30. package/.idea/sonarlint/issuestore/6/1/61ebb9fd6e8cf9082658121d5d81e297791dacd0 +0 -0
  31. package/.idea/sonarlint/issuestore/6/c/6c024c1d0ad64656b9d4b0695ec3c49c0454addf +0 -0
  32. package/.idea/sonarlint/issuestore/6/e/6e75fc1c07c3a427a86fc213ca9479caaaff00ea +0 -0
  33. package/.idea/sonarlint/issuestore/8/d/8d6123af13a140f93e06299fff7ea23c547e9ec8 +0 -0
  34. package/.idea/sonarlint/issuestore/c/c/cc2352788140b6778ac06df4b33f50b390d2d8be +0 -0
  35. package/.idea/sonarlint/issuestore/d/5/d5595158cc48f9bf3e51b06f6e6805a8fd2d6262 +0 -0
  36. package/.idea/sonarlint/issuestore/d/7/d747cbed4201192dfa83a1a51345b020a050b647 +0 -0
  37. package/.idea/sonarlint/issuestore/d/9/d938938695d447dadda115e28781c6541f53fc4f +0 -0
  38. package/build/static/js/bundle.min.js +0 -2
  39. package/build/static/js/bundle.min.js.LICENSE.txt +0 -132
  40. /package/.idea/{langflow-embedded-chat.iml → langflow-embedded-chat-clone.iml} +0 -0
  41. /package/.idea/sonarlint/issuestore/{0/f/0f8c0c92cf798431ebb931ff6e997b1af86ecee5 → 7/0/7030d0b2f71b999ff89a343de08c414af32fc93a} +0 -0
  42. /package/.idea/sonarlint/issuestore/{2/7/27e69cb561aeea20c1afbdd32d260dd60b89a81b → 9/c/9cfff9a6d27bd6c255aa751213163c7901fb8ce7} +0 -0
@@ -1,464 +1,464 @@
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 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('', () => {
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 {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 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
+ }