yt-chat-components 0.1.0

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 (89) hide show
  1. package/.idea/langflow-embedded-chat.iml +12 -0
  2. package/.idea/modules.xml +8 -0
  3. package/.idea/sonarlint/issuestore/0/f/0f8c0c92cf798431ebb931ff6e997b1af86ecee5 +0 -0
  4. package/.idea/sonarlint/issuestore/3/9/39129446b425a1d640160c068e4194e96639eedf +0 -0
  5. package/.idea/sonarlint/issuestore/4/a/4a2f33951ce07c1ff7184f91877aa13db05d3785 +0 -0
  6. package/.idea/sonarlint/issuestore/4/a/4a7b99bdbee5792679d347b6474463bf5e14b66d +0 -0
  7. package/.idea/sonarlint/issuestore/4/b/4b6989b8ccae808ebc45d02230d336ea53800365 +0 -0
  8. package/.idea/sonarlint/issuestore/6/c/6c024c1d0ad64656b9d4b0695ec3c49c0454addf +0 -0
  9. package/.idea/sonarlint/issuestore/8/d/8d6123af13a140f93e06299fff7ea23c547e9ec8 +0 -0
  10. package/.idea/sonarlint/issuestore/8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d +0 -0
  11. package/.idea/sonarlint/issuestore/d/9/d938938695d447dadda115e28781c6541f53fc4f +0 -0
  12. package/.idea/sonarlint/issuestore/index.pb +19 -0
  13. package/.idea/vcs.xml +6 -0
  14. package/README.md +274 -0
  15. package/build/asset-manifest.json +16 -0
  16. package/build/index.html +1 -0
  17. package/build/static/css/main.6f7c593d.css +2 -0
  18. package/build/static/css/main.6f7c593d.css.map +1 -0
  19. package/build/static/js/bundle.min.js +2 -0
  20. package/build/static/js/bundle.min.js.LICENSE.txt +124 -0
  21. package/build/static/js/main.cb252095.js +3 -0
  22. package/build/static/js/main.cb252095.js.LICENSE.txt +134 -0
  23. package/build/static/js/main.cb252095.js.map +1 -0
  24. package/build/static/media/aiavatar.74bafa995cce4c01b804.png +0 -0
  25. package/build/static/media/history-list-empty.1eb65b1550aef4e8c8a4.png +0 -0
  26. package/build/static/media/moreBg.9fc998472925cecd89f2.png +0 -0
  27. package/package.json +75 -0
  28. package/public/index.html +47 -0
  29. package/src/YtChatView/chatWidget/chatWindow/chatMessage/index.module.css +86 -0
  30. package/src/YtChatView/chatWidget/chatWindow/chatMessage/index.tsx +211 -0
  31. package/src/YtChatView/chatWidget/chatWindow/chatPlaceholder/index.module.css +9 -0
  32. package/src/YtChatView/chatWidget/chatWindow/chatPlaceholder/index.tsx +23 -0
  33. package/src/YtChatView/chatWidget/chatWindow/controllers/index.ts +236 -0
  34. package/src/YtChatView/chatWidget/chatWindow/index.module.css +197 -0
  35. package/src/YtChatView/chatWidget/chatWindow/index.tsx +791 -0
  36. package/src/YtChatView/chatWidget/chatWindow/types/chatWidget/index.ts +37 -0
  37. package/src/YtChatView/chatWidget/chatWindow/utils.ts +75 -0
  38. package/src/YtChatView/chatWidget/index.tsx +2289 -0
  39. package/src/YtChatView/logoBtn/index.css +4 -0
  40. package/src/YtChatView/logoBtn/index.jsx +65 -0
  41. package/src/YtChatView/logoSplitBtn/index.css +4 -0
  42. package/src/YtChatView/logoSplitBtn/index.jsx +67 -0
  43. package/src/YtChatView/previewDialog/index.jsx +431 -0
  44. package/src/YtChatView/previewDialog/index.module.css +144 -0
  45. package/src/assets/aicenter/add.png +0 -0
  46. package/src/assets/aicenter/aiavatar.png +0 -0
  47. package/src/assets/aicenter/aicenterbg.png +0 -0
  48. package/src/assets/aicenter/aicenterbgdark.png +0 -0
  49. package/src/assets/aicenter/close.png +0 -0
  50. package/src/assets/aicenter/closex.png +0 -0
  51. package/src/assets/aicenter/copy.png +0 -0
  52. package/src/assets/aicenter/file.png +0 -0
  53. package/src/assets/aicenter/fileupload.png +0 -0
  54. package/src/assets/aicenter/history-list-empty.png +0 -0
  55. package/src/assets/aicenter/history.png +0 -0
  56. package/src/assets/aicenter/luyin.png +0 -0
  57. package/src/assets/aicenter/moreAi.png +0 -0
  58. package/src/assets/aicenter/moreBg.png +0 -0
  59. package/src/assets/aicenter/play-run.gif +0 -0
  60. package/src/assets/aicenter/play.png +0 -0
  61. package/src/assets/aicenter/send-img.png +0 -0
  62. package/src/assets/aicenter/send-question-black.png +0 -0
  63. package/src/assets/aicenter/send-question.png +0 -0
  64. package/src/assets/aicenter/sendmessage.png +0 -0
  65. package/src/assets/aicenter/sound-wave.gif +0 -0
  66. package/src/assets/aicenter/toLeft.png +0 -0
  67. package/src/assets/aicenter/toRight.png +0 -0
  68. package/src/assets/aicenter/type-excel.png +0 -0
  69. package/src/assets/aicenter/type-markdown.png +0 -0
  70. package/src/assets/aicenter/type-mobi.png +0 -0
  71. package/src/assets/aicenter/type-pdf.png +0 -0
  72. package/src/assets/aicenter/type-rpub.png +0 -0
  73. package/src/assets/aicenter/type-text.png +0 -0
  74. package/src/assets/aicenter/type-word.png +0 -0
  75. package/src/assets/aicenter/upfile.png +0 -0
  76. package/src/chatPlaceholder/index.tsx +18 -0
  77. package/src/chatWidget/chatTrigger/index.tsx +15 -0
  78. package/src/chatWidget/chatWindow/chatMessage/index.tsx +42 -0
  79. package/src/chatWidget/chatWindow/index.tsx +426 -0
  80. package/src/chatWidget/index.tsx +2195 -0
  81. package/src/chatWidget/utils.ts +76 -0
  82. package/src/controllers/index.ts +205 -0
  83. package/src/index.tsx +60 -0
  84. package/src/react-app-env.d.ts +1 -0
  85. package/src/reportWebVitals.ts +15 -0
  86. package/src/setupTests.ts +5 -0
  87. package/src/types/chatWidget/index.ts +13 -0
  88. package/tsconfig.json +26 -0
  89. package/webpack.config.js +51 -0
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "yt-chat-components",
3
+ "version": "0.1.0",
4
+ "main": "build/static/js/bundle.min.js",
5
+ "module": "build/static/js/bundle.min.js",
6
+ "types": "build/static/js/index.d.ts",
7
+ "dependencies": {
8
+ "antd": "5.24.2",
9
+ "@ant-design/icons": "5.0.0",
10
+ "lodash": "^4.17.21",
11
+ "@r2wc/react-to-web-component": "^2.0.2",
12
+ "@testing-library/jest-dom": "^5.16.5",
13
+ "@testing-library/react": "^13.4.0",
14
+ "@testing-library/user-event": "^13.5.0",
15
+ "@types/jest": "^27.5.2",
16
+ "@types/node": "^16.18.37",
17
+ "@types/react": "^18.2.14",
18
+ "@types/react-dom": "^18.2.6",
19
+ "antd": "5.24.2",
20
+ "axios": "^1.4.0",
21
+ "lodash": "^4.17.21",
22
+ "lucide-react": "^0.256.0",
23
+ "react": "^18.3.1",
24
+ "react-dom": "^18.3.1",
25
+ "react-markdown": "^8.0.7",
26
+ "react-scripts": "5.0.1",
27
+ "react-shadow": "^20.3.0",
28
+ "rehype-mathjax": "^4.0.3",
29
+ "remark-gfm": "3.0.1",
30
+ "typescript": "^4.9.5",
31
+ "uglifyjs-webpack-plugin": "^2.2.0",
32
+ "uuid": "^10.0.0",
33
+ "web-vitals": "^2.1.4"
34
+ },
35
+ "scripts": {
36
+ "start": "react-scripts start",
37
+ "build": "npm install --legacy-peer-deps && npm run build:react && npm run build:bundle",
38
+ "build:react": "react-scripts build",
39
+ "build:bundle": "webpack --config webpack.config.js",
40
+ "test": "react-scripts test",
41
+ "eject": "react-scripts eject"
42
+ },
43
+ "eslintConfig": {
44
+ "extends": [
45
+ "react-app",
46
+ "react-app/jest"
47
+ ]
48
+ },
49
+ "browserslist": {
50
+ "production": [
51
+ ">0.2%",
52
+ "not dead",
53
+ "not op_mini all"
54
+ ],
55
+ "development": [
56
+ "last 1 chrome version",
57
+ "last 1 firefox version",
58
+ "last 1 safari version"
59
+ ]
60
+ },
61
+ "devDependencies": {
62
+ "@babel/core": "^7.26.10",
63
+ "@babel/preset-env": "^7.26.9",
64
+ "@babel/preset-react": "^7.26.3",
65
+ "@babel/preset-typescript": "^7.26.0",
66
+ "babel-loader": "^10.0.0",
67
+ "css-loader": "^7.1.2",
68
+ "file-loader": "^6.2.0",
69
+ "mini-css-extract-plugin": "^2.9.2",
70
+ "style-loader": "^4.0.0",
71
+ "url-loader": "^4.1.1",
72
+ "webpack": "^5.98.0",
73
+ "webpack-cli": "^5.1.4"
74
+ }
75
+ }
@@ -0,0 +1,47 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Document</title>
7
+ </head>
8
+ <!--<body style="width: 100vw; height: 100vh; position: relative; margin: unset ">-->
9
+ <!-- <yt-chat-->
10
+ <!-- right="100"-->
11
+ <!-- bottom="100"-->
12
+ <!-- width="50"-->
13
+ <!-- height="50"-->
14
+ <!-- title="菜鸟驿站"-->
15
+ <!-- icon-url="https://trans-from-yuntu-resourse.oss-cn-beijing.aliyuncs.com/smartSchool/appCreator/school/ccit/user/xc//image/ebfaf4da-c1d9-46fb-a0b1-f159e95cffc2_AI招生咨询小助手.png"-->
16
+ <!-- host-url="https://ai-api.yuntu.cn"-->
17
+ <!-- user-info='{"id": "123", "name": "John Doe", "code":"1606451129" }'-->
18
+ <!-- flow-id="a8e7ebd8-9bf7-499b-9e4a-c235078a0910"-->
19
+ <!-- />-->
20
+ <!--</body>-->
21
+
22
+ <!--<body style="width: 100vw; height: 100vh; margin:0 ">-->
23
+ <!--<yt-page-chat-->
24
+ <!-- title="菜鸟驿站"-->
25
+ <!-- host-url="https://ai-api.yuntu.cn"-->
26
+ <!-- flow-id="a8e7ebd8-9bf7-499b-9e4a-c235078a0910"-->
27
+ <!-- user-info='{"id": "123", "name": "John Doe", "code":"1606451129" }'-->
28
+ <!-- tags='["tag1", "tag2", "tag3"]'-->
29
+ <!-- box-style='{"height":"100%"}'-->
30
+ <!--/>-->
31
+ <!--</body>-->
32
+
33
+ <body style="width: 100vw; height: 100vh; position: relative; margin: unset ">
34
+ <yt-split-modal-chat
35
+ right="100"
36
+ bottom="100"
37
+ width="50"
38
+ height="50"
39
+ title="菜鸟驿站"
40
+ icon-url="https://trans-from-yuntu-resourse.oss-cn-beijing.aliyuncs.com/smartSchool/appCreator/school/ccit/user/xc//image/ebfaf4da-c1d9-46fb-a0b1-f159e95cffc2_AI招生咨询小助手.png"
41
+ host-url="https://ai-api.yuntu.cn"
42
+ user-info='{"id": "123", "name": "John Doe", "code":"1606451129" }'
43
+ flow-id="a8e7ebd8-9bf7-499b-9e4a-c235078a0910"
44
+ scene-id="e6fb45ea-3415-44e1-a1c8-5e98963bf512"
45
+ />
46
+ </body>
47
+ </html>
@@ -0,0 +1,86 @@
1
+ .msg_userMessageBox {
2
+ width: fit-content;
3
+ max-width: 90%;
4
+ display: flex;
5
+ flex-direction: column;
6
+ align-items: flex-end;
7
+ }
8
+
9
+ .msg_userMessageBox .msg_messageImgBox {
10
+ width: 850px;
11
+ display: flex;
12
+ flex-wrap: wrap;
13
+ justify-content: flex-end;
14
+ }
15
+
16
+ .msg_userMessageBox .msg_messageImgBox .msg_messageImg {
17
+ border-radius: 5px;
18
+ }
19
+
20
+ .msg_userMessageBox .msg_messageImgBox .msg_fileBox {
21
+ flex-shrink: 0;
22
+ pointer-events: auto;
23
+ position: relative;
24
+ width: 137px;
25
+ background-color: #f0f0f0;
26
+ border-radius: 10px;
27
+ margin: 3px 5px;
28
+ padding: 5px;
29
+ display: flex;
30
+ }
31
+
32
+ .msg_userMessageBox .msg_messageImgBox .msg_fileBox:hover .msg_fileRemove {
33
+ display: block;
34
+ }
35
+
36
+ .msg_userMessageBox .msg_messageImgBox .msg_fileBox .msg_fileInfoBox {
37
+ margin-left: 10px;
38
+ overflow: hidden;
39
+ display: flex;
40
+ align-items: center;
41
+ }
42
+
43
+ .msg_userMessageBox .msg_messageImgBox .msg_fileBox .msg_fileInfoBox .msg_fileInfoFileName {
44
+ overflow: hidden;
45
+ text-overflow: ellipsis;
46
+ white-space: nowrap;
47
+ width: 72px;
48
+ color: #242424;
49
+ user-select: none;
50
+ }
51
+
52
+ .msg_userMessageBox .msg_messageImgBox .msg_fileBox .msg_fileInfoBox .msg_fileInfoMeta {
53
+ user-select: none;
54
+ color: #a8a8a8;
55
+ }
56
+
57
+ .msg_userMessageBox .msg_messageImgBox .msg_fileBox .msg_fileRemove {
58
+ display: none;
59
+ position: absolute;
60
+ cursor: pointer;
61
+ top: -2px;
62
+ right: 0;
63
+ z-index: 99999;
64
+ }
65
+
66
+ .msg_userMessageBox .msg_messageImgBox .msg_fileBox .msg_fileRemove img {
67
+ width: 20px;
68
+ }
69
+
70
+ .msg_userMessageBox .msg_messageImgBox .msg_fileBox .msg_upImg {
71
+ border-radius: 3px;
72
+ z-index: 510;
73
+ }
74
+
75
+ .msg_operateBox {
76
+ display: flex;
77
+ margin-top: 5px;
78
+ user-select: none;
79
+ }
80
+
81
+ .msg_operateBox img {
82
+ -webkit-user-drag: none;
83
+ margin-right: 3px;
84
+ cursor: pointer;
85
+ width: 16px;
86
+ }
@@ -0,0 +1,211 @@
1
+ // @ts-nocheck
2
+ import Markdown from 'react-markdown';
3
+ import { ChatMessageType } 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 { Image, message as messageTip } from 'antd';
9
+ import React, {useState} 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
+ message,
26
+ isSend,
27
+ error,
28
+ host_url,
29
+ user_message_style,
30
+ bot_message_style,
31
+ error_message_style,
32
+ }: ChatMessageType) {
33
+ const parseFileName = (
34
+ text: string,
35
+ ): { fileName: string | null; fileType: string | null; isImg: boolean } => {
36
+ // 检查输入是否有效
37
+ if (!text || typeof text !== 'string') {
38
+ return { fileName: null, fileType: null };
39
+ }
40
+
41
+ // 找到最后一个反斜杠的位置
42
+ const lastBackslashIndex = text.lastIndexOf('\\');
43
+ if (lastBackslashIndex === -1) {
44
+ return { fileName: null, fileType: null }; // 如果没有找到反斜杠,返回空结果
45
+ }
46
+
47
+ // 截取反斜杠后面的部分
48
+ const fileNameWithExtension = text.substring(lastBackslashIndex + 1);
49
+
50
+ // 分割文件名和文件类型
51
+ const parts = fileNameWithExtension.split('.');
52
+ const fileName = parts.slice(0, -1).join('.'); // 文件名(不含扩展名)
53
+ const fileType = parts.length > 1 ? parts.pop() : null; // 文件类型
54
+
55
+ const isImg = fileType && ['jpg', 'jpeg', 'png', 'gif'].includes(fileType.toLowerCase());
56
+ // 返回结果对象
57
+ return {
58
+ fileName: fileName,
59
+ fileType: fileType,
60
+ isImg,
61
+ };
62
+ };
63
+
64
+ const [isPlay, setIsPlay] = useState(false) // 是否正在播放文字
65
+ /**
66
+ * 根据文件URL获取文件类型
67
+ * @param url
68
+ */
69
+ const getFileTypeByUrl = (url) => {
70
+ if (!url) {
71
+ return 'file';
72
+ }
73
+ switch (url.split('.').pop().toLowerCase()) {
74
+ case 'jpg':
75
+ case 'jpeg':
76
+ case 'png':
77
+ case 'gif':
78
+ return 'image';
79
+ case 'pdf':
80
+ return 'pdf';
81
+ case 'doc':
82
+ case 'docx':
83
+ return 'word';
84
+ case 'xls':
85
+ case 'xlsx':
86
+ return 'excel';
87
+ case 'md':
88
+ return 'markdown';
89
+ case 'txt':
90
+ return 'txt';
91
+ case 'mobi':
92
+ return 'mobi';
93
+ case 'rpub':
94
+ return 'rpub';
95
+ default:
96
+ return 'file';
97
+ }
98
+ };
99
+
100
+ /**
101
+ * 播放文字
102
+ * @param text 文字
103
+ */
104
+ const playVoice = (text)=>{
105
+ if (isPlay) {
106
+ speechSynth.cancel();
107
+ setIsPlay(false)
108
+ }else{
109
+ if (text) {
110
+ utterance = new SpeechSynthesisUtterance(text);
111
+ const voices = speechSynth.getVoices();
112
+ utterance.voice = voices[61];
113
+ utterance.rate = 1;
114
+ utterance.pitch = 1;
115
+ speechSynth.speak(utterance);
116
+ setIsPlay(true)
117
+ }
118
+ }
119
+ }
120
+
121
+ /**
122
+ * 复制文字
123
+ * @param text 文字
124
+ */
125
+ const copyText = async (text) => {
126
+ if(text){
127
+ await navigator.clipboard.writeText(text);
128
+ messageTip.info('复制成功')
129
+ }else{
130
+ messageTip.error("消息框没有文字")
131
+ }
132
+ }
133
+ return (
134
+ <div className={'cl-chat-message ' + (isSend ? ' cl-justify-end' : ' cl-justify-start')}>
135
+ {isSend ? (
136
+ <div className="msg_userMessageBox">
137
+ <div style={user_message_style} className="cl-user_message">
138
+ {message.message}
139
+ </div>
140
+ <div className="msg_messageImgBox">
141
+ {message.rawInfo?.files.map((item, index) => {
142
+ return (
143
+ <div key={item} className="msg_fileBox">
144
+ {parseFileName(item).isImg && (
145
+ <Image
146
+ height={40}
147
+ width={40}
148
+ className="msg_messageImg"
149
+ src={`${host_url}/api/v1/files/images/${item}`}
150
+ alt={item}
151
+ preview={{
152
+ mask: <span className="custom-mask"></span>,
153
+ }}
154
+ />
155
+ )}
156
+ {getFileTypeByUrl(item) == 'pdf' && (
157
+ <img style={{ width: 40, height: 40 }} src={typePdfPng} />
158
+ )}
159
+ {getFileTypeByUrl(item) == 'excel' && (
160
+ <img style={{ width: 40, height: 40 }} src={typeExcelPng} />
161
+ )}
162
+ {getFileTypeByUrl(item) == 'markdown' && (
163
+ <img style={{ width: 40, height: 40 }} src={typeMarkdownPng} />
164
+ )}
165
+ {getFileTypeByUrl(item) == 'txt' && (
166
+ <img style={{ width: 40, height: 40 }} src={typeTextPng} />
167
+ )}
168
+ {getFileTypeByUrl(item) == 'word' && (
169
+ <img style={{ width: 40, height: 40 }} src={typeWordPng} />
170
+ )}
171
+ {getFileTypeByUrl(item) == 'mobi' && (
172
+ <img style={{ width: 40, height: 40 }} src={typeMobiPng} />
173
+ )}
174
+ {getFileTypeByUrl(item) == 'rpub' && (
175
+ <img style={{ width: 40, height: 40 }} src={typeRPubPng} />
176
+ )}
177
+ {getFileTypeByUrl(item) == 'file' && (
178
+ <img style={{ width: 40, height: 40 }} src={upFilePng} />
179
+ )}
180
+ <div className="msg_fileInfoBox">
181
+ <div className="msg_fileInfoFileName">{parseFileName(item).fileName}</div>
182
+ </div>
183
+ </div>
184
+ );
185
+ })}
186
+ </div>
187
+ </div>
188
+ ) : error ? (
189
+ <div style={error_message_style} className={'cl-error_message'}>
190
+ {message.message}
191
+ </div>
192
+ ) : (
193
+ <div>
194
+ <div style={bot_message_style} className={'cl-bot_message'}>
195
+ <Markdown
196
+ className={'markdown-body prose flex flex-col word-break-break-word'}
197
+ remarkPlugins={[remarkGfm]}
198
+ rehypePlugins={[rehypeMathjax]}
199
+ >
200
+ {message.message}
201
+ </Markdown>
202
+ </div>
203
+ <div className="msg_operateBox">
204
+ <img src={isPlay?playRunGif:playPng} onClick={()=>playVoice(message.message)} />
205
+ <img src={copyPng} onClick={()=>copyText(message.message)} />
206
+ </div>
207
+ </div>
208
+ )}
209
+ </div>
210
+ );
211
+ }
@@ -0,0 +1,9 @@
1
+ .plh_textBox {
2
+ display: flex;
3
+ justify-content: center;
4
+ align-content: center;
5
+ }
6
+
7
+ .plh_textBox .plh_text {
8
+ font-weight: bold;
9
+ }
@@ -0,0 +1,23 @@
1
+ // @ts-nocheck
2
+ import { MoreHorizontal } from "lucide-react";
3
+ import { ChatMessagePlaceholderType } from "../types/chatWidget";
4
+ // import './index.module.css'
5
+
6
+ export default function ChatMessagePlaceholder({
7
+ bot_message_style,
8
+ }: ChatMessagePlaceholderType) {
9
+ return (
10
+ <div
11
+ className="cl-chat-message cl-justify-start"
12
+ >
13
+ <div style={bot_message_style} className={"cl-bot_message"}>
14
+ <div className="cl-animate-pulse">
15
+ <div className="plh_textBox">
16
+ <div className="plh_text">深度思考中</div>
17
+ <MoreHorizontal />
18
+ </div>
19
+ </div>
20
+ </div>
21
+ </div>
22
+ );
23
+ }
@@ -0,0 +1,236 @@
1
+ // @ts-nocheck
2
+ import axios from "axios";
3
+
4
+
5
+ /**
6
+ * 发送消息
7
+ * @param embed_app_extend 扩展参数
8
+ * @param isStream 是否启用流式传输
9
+ * @param handleMessageContent 处理消息内容的回调函数
10
+ * @param signal 用于主动关闭请求流
11
+ * @param baseUrl 接口地址
12
+ * @param flowId AI应用ID
13
+ * @param message 消息内容
14
+ * @param input_type 输入类型
15
+ * @param output_type 输出类型
16
+ * @param sessionId 对话ID
17
+ * @param output_component
18
+ * @param tweaks
19
+ * @param api_key 接口密钥
20
+ * @param additional_headers
21
+ */
22
+ export async function sendMessage(
23
+ embed_app_extend: object,
24
+ isStream: boolean,
25
+ handleMessageContent: Function,
26
+ signal,
27
+ baseUrl: string,
28
+ flowId: string,
29
+ message: string,
30
+ input_type: string,
31
+ output_type: string,
32
+ sessionId: React.MutableRefObject<string>,
33
+ output_component?: string,
34
+ tweaks?: Object,
35
+ api_key?: string,
36
+ additional_headers?: {
37
+ [key: string]: string;
38
+ },
39
+ ) {
40
+ let data: any;
41
+ data = {input_type, input_value: message, output_type};
42
+ if (tweaks) {
43
+ data['tweaks'] = tweaks;
44
+ }
45
+ if (output_component) {
46
+ data['output_component'] = output_component;
47
+ }
48
+ if (embed_app_extend) {
49
+ data['embed_app_extend'] = embed_app_extend;
50
+ }
51
+ let headers: { [key: string]: string } = {'Content-Type': 'application/json'};
52
+ if (api_key) {
53
+ headers['x-api-key'] = api_key;
54
+ }
55
+ if (additional_headers) {
56
+ headers = Object.assign(headers, additional_headers);
57
+ // headers = {...headers, ...additional_headers};
58
+ }
59
+ // @ts-ignore
60
+ if (sessionId && sessionId != '') {
61
+ data.session_id = sessionId;
62
+ }
63
+
64
+ if (isStream) {
65
+ return await fetchCustomStream(
66
+ `${baseUrl}/api/v1/run/${flowId}?stream=true`,
67
+ data,
68
+ headers,
69
+ handleMessageContent,
70
+ signal,
71
+ );
72
+ } else {
73
+ let response = axios.post(`${baseUrl}/api/v1/run/${flowId}`, data, { headers });
74
+ return response;
75
+ }
76
+ }
77
+
78
+ // 缓冲区用于累积未完成的 JSON 数据
79
+ // let buffer = '';
80
+
81
+ async function fetchCustomStream(url, body, headers, handleMessageContent, signal) {
82
+ const response = await fetch(url, {
83
+ method: 'POST',
84
+ headers: headers,
85
+ body: JSON.stringify(body),
86
+ signal: signal,
87
+ });
88
+
89
+ if (!response.ok) {
90
+ throw new Error('请求失败');
91
+ }
92
+
93
+ // 手动读取响应体作为可读流
94
+ const reader = response.body.getReader();
95
+ const decoder = new TextDecoder();
96
+ let buffer = '';
97
+
98
+ while (true) {
99
+ const {done, value} = await reader.read();
100
+ if (done) break;
101
+
102
+ // 将二进制数据解码为字符串,并累积到缓冲区
103
+ const chunk = decoder.decode(value, {stream: true});
104
+ buffer += chunk;
105
+
106
+ // 提取并解析 JSON
107
+ const {parsedData, remainingBuffer} = extractAndParseJSON(buffer, handleMessageContent);
108
+
109
+ // 更新缓冲区为剩余未处理的数据
110
+ buffer = remainingBuffer;
111
+ }
112
+
113
+ // 如果缓冲区中还有剩余数据,可能是不完整的 JSON
114
+ if (buffer.trim() !== '') {
115
+ console.warn('缓冲区中存在未完成的 JSON 数据:', buffer);
116
+ }
117
+ }
118
+
119
+ // 辅助函数:从缓冲区中提取并解析所有完整的 JSON 对象
120
+ function extractAndParseJSON(buffer, handleMessageContent) {
121
+ let remainingBuffer = buffer; // 剩余未处理的数据
122
+ const parsedData = []; // 存储解析成功的 JSON 对象
123
+ let bracketCount = 0; // 记录大括号的嵌套层次
124
+ let jsonStartIndex = -1; // 当前 JSON 对象的起始索引
125
+ let inString = false; // 是否处于字符串内部
126
+ let escapeChar = false; // 是否遇到转义字符
127
+
128
+ for (let i = 0; i < remainingBuffer.length; i++) {
129
+ const char = remainingBuffer[i];
130
+
131
+ if (escapeChar) {
132
+ escapeChar = false; // 跳过转义字符后的下一个字符
133
+ continue;
134
+ }
135
+
136
+ if (char === '\\') {
137
+ escapeChar = true; // 标记转义字符
138
+ continue;
139
+ }
140
+
141
+ if (char === '"' && !escapeChar) {
142
+ inString = !inString; // 切换字符串状态
143
+ continue;
144
+ }
145
+
146
+ if (!inString) {
147
+ if (char === '{') {
148
+ if (bracketCount === 0) {
149
+ jsonStartIndex = i; // 标记 JSON 开始位置
150
+ }
151
+ bracketCount++;
152
+ } else if (char === '}') {
153
+ bracketCount--;
154
+ if (bracketCount === 0 && jsonStartIndex !== -1) {
155
+ // 找到一个完整的 JSON 对象
156
+ const jsonString = remainingBuffer.slice(jsonStartIndex, i + 1);
157
+
158
+ try {
159
+ const parsedJson = JSON.parse(jsonString); // 解析 JSON
160
+ handleMessageContent(parsedJson['event'], parsedJson['data']);
161
+ parsedData.push(parsedJson); // 添加到解析结果中
162
+ } catch (error) {
163
+ console.warn('无法解析 JSON 数据:', error);
164
+ }
165
+
166
+ // 更新剩余缓冲区的起始位置
167
+ remainingBuffer = remainingBuffer.slice(i + 1);
168
+ i = -1; // 重置索引以重新开始扫描
169
+ jsonStartIndex = -1; // 重置 JSON 起始索引
170
+ }
171
+ }
172
+ }
173
+ }
174
+
175
+ return {parsedData, remainingBuffer}; // 返回解析结果和剩余缓冲区
176
+ }
177
+
178
+ /**
179
+ * 获取对话历史列表
180
+ * @param baseUrl
181
+ * @param flowId
182
+ * @param operatorId
183
+ */
184
+ export async function getHistoryList(baseUrl: string, flowId: string, operatorId: string) {
185
+ return axios.get(`${baseUrl}/api/v1/embed/chat_history_list/${flowId}/${operatorId}`);
186
+ }
187
+
188
+ /**
189
+ * 获取对话历史记录
190
+ * @param baseUrl
191
+ * @param flowI
192
+ * @param sessionId
193
+ * @param operatorId
194
+ */
195
+ export async function getChatHistory(
196
+ baseUrl: string,
197
+ flowId: string,
198
+ sessionId: string,
199
+ operatorId: string,
200
+ ) {
201
+ return axios.get(`${baseUrl}/api/v1/embed/chat_history/${flowId}/${sessionId}/${operatorId}`);
202
+ }
203
+
204
+
205
+ /**
206
+ * 上传文件
207
+ * @param baseUrl
208
+ * @param file
209
+ * @param flowId
210
+ * @param api_key
211
+ */
212
+ export async function fetchUploadFile(baseUrl: string, file: File, flowId: string, api_key: string) {
213
+ const headers = {
214
+ 'Content-Type': 'multipart/form-data',
215
+ 'x-api-key': api_key
216
+ };
217
+ const formData = new FormData();
218
+ formData.append('file', file);
219
+ formData.append('folder', flowId);
220
+
221
+ return axios.post(`${baseUrl}/api/t1/file/upload`, formData, {headers});
222
+ }
223
+
224
+ /**
225
+ * 获取场景信息
226
+ * @param baseUrl
227
+ * @param sceneId
228
+ * @param api_key
229
+ */
230
+ export async function getSceneInfo(baseUrl: string, sceneId: string, api_key: string) {
231
+ const headers = {
232
+ 'Content-Type': 'multipart/form-data',
233
+ 'x-api-key': api_key
234
+ };
235
+ return axios.get(`${baseUrl}/api/t1/scene/detail/${sceneId}`,{headers});
236
+ }