@gientech/modual 2.0.3 → 2.0.5

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 (419) hide show
  1. package/.editorconfig +38 -0
  2. package/.prettierignore +16 -0
  3. package/.prettierrc +17 -0
  4. package/README.md +643 -643
  5. package/USAGE.md +247 -0
  6. package/bash.exe.stackdump +40 -0
  7. package/components.json +21 -0
  8. package/dist/README.md +643 -0
  9. package/{assets/GientechStreamReader-CzB5AuYo.js → dist/assets/GientechStreamReader-DUCq-O5X.js} +1 -1
  10. package/{assets → dist/assets}/database.svg +11 -11
  11. package/{assets → dist/assets}/database_add.svg +53 -53
  12. package/{assets → dist/assets}/database_connect.svg +66 -66
  13. package/{assets → dist/assets}/database_upload.svg +29 -29
  14. package/{assets → dist/assets}/databse.svg +6 -6
  15. package/{assets → dist/assets}/defaultWeLogo.svg +14 -14
  16. package/{assets → dist/assets}/graph.svg +4 -4
  17. package/{assets/index-CN2AYX8Q.js → dist/assets/index-B2yNvzjy.js} +1 -1
  18. package/{assets/index-D9lYd8TZ.js → dist/assets/index-BKe5FgcC.js} +1 -1
  19. package/{assets/index-CBTA5S-5.js → dist/assets/index-D72cKELw.js} +1 -1
  20. package/{assets/index-s0cmC4Ks.js → dist/assets/index-DdVFXD_y.js} +1 -1
  21. package/{assets/index-B1ZDDbC1.js → dist/assets/index-DrkSoKz6.js} +1 -1
  22. package/{assets/index-B2rFliXg.js → dist/assets/index-KCfkkJKb.js} +1 -1
  23. package/{assets/index-DnBY0UXI.js → dist/assets/index-QI9biQrR.js} +2 -2
  24. package/{assets/index-DYhXjvk4.js → dist/assets/index-ZopkeZtI.js} +1 -1
  25. package/{assets/index-LDFwTu47.js → dist/assets/index-g-SUxfJH.js} +1 -1
  26. package/{assets/index-NFQIKNFn.js → dist/assets/index-j0kQJd0a.js} +1 -1
  27. package/{assets → dist/assets}/knowledge.svg +4 -4
  28. package/{assets/MySQL.svg → dist/assets/mysql.svg} +14 -14
  29. package/{assets/plus-G7zGXf6S.js → dist/assets/plus-V9zUoSq6.js} +1 -1
  30. package/{assets → dist/assets}/sensitive.svg +5 -5
  31. package/{assets/style-Bn7b5XHU.js → dist/assets/style-CGmZ5osp.js} +2 -2
  32. package/{assets/x-vfKYzKkK.js → dist/assets/x-CnaaLGJF.js} +1 -1
  33. package/dist/assistantConfig.js +1 -0
  34. package/dist/chat.js +824 -0
  35. package/dist/database.js +20 -0
  36. package/dist/databaseId.js +1 -0
  37. package/{databaseTable.js → dist/databaseTable.js} +1 -1
  38. package/dist/index.js +1 -0
  39. package/{modelManage.js → dist/modelManage.js} +1 -1
  40. package/dist/package.json +68 -0
  41. package/{sensitive.js → dist/sensitive.js} +1 -1
  42. package/dist/streamFilesReader.js +1 -0
  43. package/{worker → dist/worker}/pdf.worker.min.js +21 -21
  44. package/doc_assets/2.png +0 -0
  45. package/doc_assets/demo.md +27 -0
  46. package/doc_assets/demos/dist-app/assets/index.Dh-ZAS9Z.css +2 -0
  47. package/doc_assets/demos/dist-app/assets/index.Dv8KVW18.js +23699 -0
  48. package/doc_assets/demos/dist-app/assets/index.Dv8KVW18.js.map +1 -0
  49. package/doc_assets/demos/dist-app/index.html +14 -0
  50. package/doc_assets/demos/dist-app/vite.svg +1 -0
  51. package/doc_assets/images/1.png +0 -0
  52. package/doc_assets/images/3.png +0 -0
  53. package/doc_assets/images/component-screenshot.png +1 -0
  54. package/doc_assets/install.md +5 -0
  55. package/doc_assets//346/226/271/346/241/210//344/274/230/345/214/226/346/226/271/346/241/210-/345/244/232/344/274/232/350/257/235SSE/350/277/236/346/216/245/347/256/241/347/220/206.md +504 -0
  56. package/eslint.config.js +92 -0
  57. package/index.html +13 -0
  58. package/package.json +106 -44
  59. package/package.json.demo-backup +109 -0
  60. package/postcss.config.cjs +19 -0
  61. package/public/icons/answerAwartar.png +0 -0
  62. package/public/icons/docx-file.png +0 -0
  63. package/public/icons/folder.png +0 -0
  64. package/public/icons/html.png +0 -0
  65. package/public/icons/image.png +0 -0
  66. package/public/icons/jpg-file.png +0 -0
  67. package/public/icons/json.png +0 -0
  68. package/public/icons/md.png +0 -0
  69. package/public/icons/pdf.png +0 -0
  70. package/public/icons/pptx.png +0 -0
  71. package/public/icons/questionAwartar.png +0 -0
  72. package/public/icons/sheets.png +0 -0
  73. package/public/icons/txt.png +0 -0
  74. package/public/icons/xlsx.png +0 -0
  75. package/public/vite.svg +1 -0
  76. package/public/worker/pdf.worker.min.js +22 -0
  77. package/scripts/README.md +133 -0
  78. package/scripts/build-demo.js +88 -0
  79. package/scripts/decrypt-api-key.js +95 -0
  80. package/scripts/demo-selector.js +216 -0
  81. package/scripts/dev-demo.js +76 -0
  82. package/scripts/preview-demo.js +130 -0
  83. package/scripts/run-demo.bat +34 -0
  84. package/src/assets/img/close.png +0 -0
  85. package/src/assets/img/database.png +0 -0
  86. package/src/assets/img/downArrow.png +0 -0
  87. package/src/assets/img/downLoad.png +0 -0
  88. package/src/assets/img/excel.png +0 -0
  89. package/src/assets/img/graphIcon.png +0 -0
  90. package/src/assets/img/img.png +0 -0
  91. package/src/assets/img/pdf.png +0 -0
  92. package/src/assets/img/ppt.png +0 -0
  93. package/src/assets/img/singleQa.png +0 -0
  94. package/src/assets/img/txt.png +0 -0
  95. package/src/assets/img/webSearch.png +0 -0
  96. package/src/assets/img/word.png +0 -0
  97. package/src/assets/login/homeBg.png +0 -0
  98. package/src/assets/login/left.jpg +0 -0
  99. package/src/assets/login/logoImg.png +0 -0
  100. package/src/examples/ConversationAssistantPage/index.tsx +41 -0
  101. package/src/examples/Demo/index.tsx +12 -0
  102. package/src/examples/LoginPage/index.tsx +18 -0
  103. package/src/examples/chat/components/DrawerGraphPreview.tsx +78 -0
  104. package/src/examples/chat/index.tsx +154 -0
  105. package/src/examples/chat/logo03.png +0 -0
  106. package/src/examples/gientechStreamFilesReader/index.tsx +951 -0
  107. package/src/examples/headlessChat/index.tsx +234 -0
  108. package/src/examples/ragDatabaseDataPage/index.tsx +40 -0
  109. package/src/examples/ragDatabaseIdPage/index.tsx +47 -0
  110. package/src/examples/ragDatabasePage/index.tsx +36 -0
  111. package/src/examples/ragModelManagePage/index.tsx +38 -0
  112. package/src/examples/ragSearchPage/index.tsx +0 -0
  113. package/src/examples/ragSensitiveWordsPage/index.tsx +32 -0
  114. package/src/examples/streamFiles/index.tsx +417 -0
  115. package/src/lib_enter.ts +43 -0
  116. package/src/main.tsx +5 -0
  117. package/src/main.tsx.backup +5 -0
  118. package/src/modules/CHAT_UNIFICATION_PLAN.md +324 -0
  119. package/src/modules/assistantConfig/assets/databse.svg +6 -0
  120. package/src/modules/assistantConfig/assets/empty.png +0 -0
  121. package/src/modules/assistantConfig/assets/graph.svg +4 -0
  122. package/src/modules/assistantConfig/assets/knowledge.svg +4 -0
  123. package/src/modules/assistantConfig/assets/sensitive.svg +5 -0
  124. package/src/modules/assistantConfig/components/Database.tsx +171 -0
  125. package/src/modules/assistantConfig/components/Graph.tsx +177 -0
  126. package/src/modules/assistantConfig/components/Knowledge.tsx +276 -0
  127. package/src/modules/assistantConfig/components/NotFoundContent.tsx +21 -0
  128. package/src/modules/assistantConfig/components/Paragraph.tsx +51 -0
  129. package/src/modules/assistantConfig/components/ParamsItem.tsx +39 -0
  130. package/src/modules/assistantConfig/components/ResourceBinderItem.tsx +133 -0
  131. package/src/modules/assistantConfig/components/SearchableSelector.tsx +500 -0
  132. package/src/modules/assistantConfig/components/Sensitive.tsx +221 -0
  133. package/src/modules/assistantConfig/components/SliderInput.tsx +65 -0
  134. package/src/modules/assistantConfig/constants.tsx +75 -0
  135. package/src/modules/assistantConfig/index.tsx +710 -0
  136. package/src/modules/assistantConfig/server.ts +262 -0
  137. package/src/modules/chat/Conversations/Item.tsx +167 -0
  138. package/src/modules/chat/Conversations/List.tsx +210 -0
  139. package/src/modules/chat/Conversations/groupByTime.ts +39 -0
  140. package/src/modules/chat/Conversations/index.tsx +252 -0
  141. package/src/modules/chat/ReferenceBar.tsx +622 -0
  142. package/src/modules/chat/constants.tsx +52 -0
  143. package/src/modules/chat/data.txt +82 -0
  144. package/src/modules/chat/index.tsx +2054 -0
  145. package/src/modules/chat/referenceCom/DeleteModal.tsx +75 -0
  146. package/src/modules/chat/referenceCom/DrawerContent.tsx +136 -0
  147. package/src/modules/chat/referenceCom/DrawerDatabase.tsx +102 -0
  148. package/src/modules/chat/referenceCom/DrawerGraphPreview.tsx +86 -0
  149. package/src/modules/chat/referenceCom/DrawerPreview.tsx +73 -0
  150. package/src/modules/chat/referenceCom/DrawerTitle.tsx +26 -0
  151. package/src/modules/chat/referenceCom/RenameModal.tsx +86 -0
  152. package/src/modules/chat/referenceCom/TagCom.tsx +30 -0
  153. package/src/modules/chat/style.less +3 -0
  154. package/src/modules/chat/types.ts +17 -0
  155. package/src/modules/chat/utils/index.ts +348 -0
  156. package/src/modules/database/CreateModal.tsx +403 -0
  157. package/src/modules/database/assets/Doris.png +0 -0
  158. package/src/modules/database/assets/PostgreSQL.png +0 -0
  159. package/src/modules/database/assets/SQLServer.png +0 -0
  160. package/src/modules/database/assets/database.svg +11 -0
  161. package/src/modules/database/assets/database_add.svg +53 -0
  162. package/src/modules/database/assets/database_connect.svg +66 -0
  163. package/src/modules/database/assets/database_upload.svg +29 -0
  164. package/src/modules/database/assets/empty.png +0 -0
  165. package/src/modules/database/assets/mysql.svg +14 -0
  166. package/src/modules/database/index.tsx +477 -0
  167. package/src/modules/database/server.ts +196 -0
  168. package/src/modules/databaseId/CustomCom.tsx +156 -0
  169. package/src/modules/databaseId/EditConfig.tsx +280 -0
  170. package/src/modules/databaseId/UploadDrawer.tsx +535 -0
  171. package/src/modules/databaseId/assets/aiOptimize.svg +10 -0
  172. package/src/modules/databaseId/assets/empty.png +0 -0
  173. package/src/modules/databaseId/assets/template.svg +6 -0
  174. package/src/modules/databaseId/assets/upload.svg +9 -0
  175. package/src/modules/databaseId/assets/useTemp.svg +6 -0
  176. package/src/modules/databaseId/index.tsx +769 -0
  177. package/src/modules/databaseId/server.ts +286 -0
  178. package/src/modules/databaseId/style.css +5 -0
  179. package/src/modules/databaseTable/EditRowDrawer.tsx +124 -0
  180. package/src/modules/databaseTable/index.tsx +359 -0
  181. package/src/modules/databaseTable/server.ts +180 -0
  182. package/src/modules/headlessChat/ReferenceBar.tsx +783 -0
  183. package/src/modules/headlessChat/constants.tsx +54 -0
  184. package/src/modules/headlessChat/index.tsx +1706 -0
  185. package/src/modules/headlessChat/referenceCom/DeleteModal.tsx +75 -0
  186. package/src/modules/headlessChat/referenceCom/DrawerContent.tsx +136 -0
  187. package/src/modules/headlessChat/referenceCom/DrawerDatabase.tsx +102 -0
  188. package/src/modules/headlessChat/referenceCom/DrawerGraphPreview.tsx +86 -0
  189. package/src/modules/headlessChat/referenceCom/DrawerPreview.tsx +73 -0
  190. package/src/modules/headlessChat/referenceCom/DrawerTitle.tsx +26 -0
  191. package/src/modules/headlessChat/referenceCom/RenameModal.tsx +86 -0
  192. package/src/modules/headlessChat/referenceCom/TagCom.tsx +30 -0
  193. package/src/modules/headlessChat/style.less +3 -0
  194. package/src/modules/headlessChat/types.ts +23 -0
  195. package/src/modules/headlessChat/utils/index.ts +348 -0
  196. package/src/modules/login/components/Login/LoginBox/index.tsx +102 -0
  197. package/src/modules/login/components/Login/RegisterBox/index.tsx +180 -0
  198. package/src/modules/login/components/Login/index.tsx +100 -0
  199. package/src/modules/login/index.tsx +106 -0
  200. package/src/modules/login/style.css +3 -0
  201. package/src/modules/login/useServices.ts +53 -0
  202. package/src/modules/login/utils.ts +42 -0
  203. package/src/modules/modelManage/ConfigDrawer.tsx +249 -0
  204. package/src/modules/modelManage/ReplaceModal.tsx +124 -0
  205. package/src/modules/modelManage/assets/empty.png +0 -0
  206. package/src/modules/modelManage/const.ts +51 -0
  207. package/src/modules/modelManage/index.tsx +606 -0
  208. package/src/modules/modelManage/server.ts +223 -0
  209. package/src/modules/nodegraph/index.tsx +1 -0
  210. package/src/modules/search/assets/Icon-history.svg +8 -0
  211. package/src/modules/search/assets/answerAwartar.png +0 -0
  212. package/src/modules/search/assets/doc.png +0 -0
  213. package/src/modules/search/assets/genera.gif +0 -0
  214. package/src/modules/search/assets/icon-robot.svg +9 -0
  215. package/src/modules/search/assets/icon-search-bar.svg +14 -0
  216. package/src/modules/search/assets/icon-sub-title.svg +3 -0
  217. package/src/modules/search/assets/icon-title.svg +9 -0
  218. package/src/modules/search/assets/icon-zoomOut.svg +9 -0
  219. package/src/modules/search/assets/iconAi.svg +9 -0
  220. package/src/modules/search/assets/pdf.png +0 -0
  221. package/src/modules/search/assets/ppt.png +0 -0
  222. package/src/modules/search/assets/search.svg +3 -0
  223. package/src/modules/search/assets/selected.svg +4 -0
  224. package/src/modules/search/assets/txt.png +0 -0
  225. package/src/modules/search/assets/xls.png +0 -0
  226. package/src/modules/search/components/AssisSelect.tsx +137 -0
  227. package/src/modules/search/components/Editor/ChatViewEditor.tsx +261 -0
  228. package/src/modules/search/components/Editor/aichat.css +1 -0
  229. package/src/modules/search/components/Editor/constant.ts +13 -0
  230. package/src/modules/search/components/Editor/index.tsx +113 -0
  231. package/src/modules/search/components/Editor/plugins/autofomatRules.ts +332 -0
  232. package/src/modules/search/components/Editor/plugins/convertImgPlugins.tsx +20 -0
  233. package/src/modules/search/components/Editor/plugins/createIndexes.tsx +38 -0
  234. package/src/modules/search/components/Editor/plugins/displayer.ts +298 -0
  235. package/src/modules/search/components/Editor/plugins/imageClick.tsx +32 -0
  236. package/src/modules/search/components/Editor/plugins/myplugin.tsx +98 -0
  237. package/src/modules/search/components/Editor/ui/avatar.tsx +19 -0
  238. package/src/modules/search/components/Editor/ui/blockquote-element.tsx +21 -0
  239. package/src/modules/search/components/Editor/ui/button.tsx +58 -0
  240. package/src/modules/search/components/Editor/ui/calendar.tsx +68 -0
  241. package/src/modules/search/components/Editor/ui/caption.tsx +46 -0
  242. package/src/modules/search/components/Editor/ui/checkbox.tsx +27 -0
  243. package/src/modules/search/components/Editor/ui/code-block-combobox.tsx +188 -0
  244. package/src/modules/search/components/Editor/ui/code-block-element.css +434 -0
  245. package/src/modules/search/components/Editor/ui/code-block-element.tsx +39 -0
  246. package/src/modules/search/components/Editor/ui/code-leaf.tsx +24 -0
  247. package/src/modules/search/components/Editor/ui/code-line-element.tsx +10 -0
  248. package/src/modules/search/components/Editor/ui/code-syntax-leaf.tsx +21 -0
  249. package/src/modules/search/components/Editor/ui/column-element.tsx +30 -0
  250. package/src/modules/search/components/Editor/ui/column-group-element.tsx +94 -0
  251. package/src/modules/search/components/Editor/ui/command.tsx +75 -0
  252. package/src/modules/search/components/Editor/ui/comment-avatar.tsx +22 -0
  253. package/src/modules/search/components/Editor/ui/comment-create-form.tsx +37 -0
  254. package/src/modules/search/components/Editor/ui/comment-item.tsx +74 -0
  255. package/src/modules/search/components/Editor/ui/comment-leaf.tsx +49 -0
  256. package/src/modules/search/components/Editor/ui/comment-more-dropdown.tsx +42 -0
  257. package/src/modules/search/components/Editor/ui/comment-reply-items.tsx +22 -0
  258. package/src/modules/search/components/Editor/ui/comment-resolve-button.tsx +32 -0
  259. package/src/modules/search/components/Editor/ui/comment-value.tsx +34 -0
  260. package/src/modules/search/components/Editor/ui/comments-popover.tsx +63 -0
  261. package/src/modules/search/components/Editor/ui/date-element.tsx +83 -0
  262. package/src/modules/search/components/Editor/ui/dialog.tsx +63 -0
  263. package/src/modules/search/components/Editor/ui/draggable.tsx +177 -0
  264. package/src/modules/search/components/Editor/ui/dropdown-menu.tsx +180 -0
  265. package/src/modules/search/components/Editor/ui/emoji-input-element.tsx +85 -0
  266. package/src/modules/search/components/Editor/ui/excalidraw-element.tsx +28 -0
  267. package/src/modules/search/components/Editor/ui/fixed-toolbar-buttons.tsx +76 -0
  268. package/src/modules/search/components/Editor/ui/fixed-toolbar.tsx +8 -0
  269. package/src/modules/search/components/Editor/ui/floating-toolbar-buttons.tsx +51 -0
  270. package/src/modules/search/components/Editor/ui/floating-toolbar.tsx +77 -0
  271. package/src/modules/search/components/Editor/ui/heading-element.tsx +48 -0
  272. package/src/modules/search/components/Editor/ui/highlight-leaf.tsx +17 -0
  273. package/src/modules/search/components/Editor/ui/hr-element.tsx +30 -0
  274. package/src/modules/search/components/Editor/ui/icons.tsx +267 -0
  275. package/src/modules/search/components/Editor/ui/image-element.tsx +74 -0
  276. package/src/modules/search/components/Editor/ui/inline-combobox.tsx +368 -0
  277. package/src/modules/search/components/Editor/ui/input.tsx +25 -0
  278. package/src/modules/search/components/Editor/ui/insert-dropdown-menu.tsx +218 -0
  279. package/src/modules/search/components/Editor/ui/kbd-leaf.tsx +20 -0
  280. package/src/modules/search/components/Editor/ui/link-element.tsx +29 -0
  281. package/src/modules/search/components/Editor/ui/link-floating-toolbar.tsx +161 -0
  282. package/src/modules/search/components/Editor/ui/list-element.tsx +30 -0
  283. package/src/modules/search/components/Editor/ui/mark-toolbar-button.tsx +24 -0
  284. package/src/modules/search/components/Editor/ui/media-embed-element.tsx +133 -0
  285. package/src/modules/search/components/Editor/ui/media-popover.tsx +97 -0
  286. package/src/modules/search/components/Editor/ui/mention-element.tsx +43 -0
  287. package/src/modules/search/components/Editor/ui/mention-input-element.tsx +141 -0
  288. package/src/modules/search/components/Editor/ui/mode-dropdown-menu.tsx +93 -0
  289. package/src/modules/search/components/Editor/ui/more-dropdown-menu.tsx +67 -0
  290. package/src/modules/search/components/Editor/ui/paragraph-element.tsx +4 -0
  291. package/src/modules/search/components/Editor/ui/placeholder.tsx +52 -0
  292. package/src/modules/search/components/Editor/ui/popover.tsx +32 -0
  293. package/src/modules/search/components/Editor/ui/resizable.tsx +66 -0
  294. package/src/modules/search/components/Editor/ui/separator.tsx +25 -0
  295. package/src/modules/search/components/Editor/ui/style.less +12 -0
  296. package/src/modules/search/components/Editor/ui/table-cell-element.tsx +143 -0
  297. package/src/modules/search/components/Editor/ui/table-element.tsx +243 -0
  298. package/src/modules/search/components/Editor/ui/table-row-element.tsx +22 -0
  299. package/src/modules/search/components/Editor/ui/tableValue.tsx +135 -0
  300. package/src/modules/search/components/Editor/ui/todo-list-element.tsx +43 -0
  301. package/src/modules/search/components/Editor/ui/toggle-element.tsx +31 -0
  302. package/src/modules/search/components/Editor/ui/toolbar.tsx +157 -0
  303. package/src/modules/search/components/Editor/ui/tooltip.tsx +65 -0
  304. package/src/modules/search/components/Editor/ui/turn-into-dropdown-menu.tsx +160 -0
  305. package/src/modules/search/components/Editor/ui/with-draggables.tsx +175 -0
  306. package/src/modules/search/components/FileList.tsx +287 -0
  307. package/src/modules/search/components/ImageGroupView/index.tsx +85 -0
  308. package/src/modules/search/components/ResultContent.tsx +232 -0
  309. package/src/modules/search/components/SearchInput.tsx +232 -0
  310. package/src/modules/search/components/SearchLanding.tsx +74 -0
  311. package/src/modules/search/components/SearchView.tsx +563 -0
  312. package/src/modules/search/components/SimpleEditor.tsx +158 -0
  313. package/src/modules/search/components/SimpleFileList.tsx +215 -0
  314. package/src/modules/search/index.tsx +10 -0
  315. package/src/modules/search/reademe.md +1 -0
  316. package/src/modules/search/servers/apis.tsx +19 -0
  317. package/src/modules/search/servers/index.ts +184 -0
  318. package/src/modules/search/style.less +503 -0
  319. package/src/modules/search/type.ts +22 -0
  320. package/src/modules/search/utils.ts +34 -0
  321. package/src/modules/sensitive/index.tsx +313 -0
  322. package/src/modules/sensitive/server.ts +122 -0
  323. package/src/modules/streamFilesReader/GientechStreamReader.tsx +1625 -0
  324. package/src/modules/streamFilesReader/components/Header/Toolbar.tsx +0 -0
  325. package/src/modules/streamFilesReader/components/Header/index.tsx +297 -0
  326. package/src/modules/streamFilesReader/index.tsx +3 -0
  327. package/src/style.css +6 -0
  328. package/src/type.d.ts +0 -0
  329. package/src/utils/commonFn.tsx +111 -0
  330. package/src/utils/decryptApiKey.ts +40 -0
  331. package/src/utils/gientechCommon/components/AppError.tsx +32 -0
  332. package/src/utils/gientechCommon/components/AppLoading.tsx +75 -0
  333. package/src/utils/gientechCommon/components/DeleteModal.tsx +75 -0
  334. package/src/utils/gientechCommon/components/DisplayError.tsx +33 -0
  335. package/src/utils/gientechCommon/components/DisplayLoading.tsx +38 -0
  336. package/src/utils/gientechCommon/components/FeedBackModal.tsx +319 -0
  337. package/src/utils/gientechCommon/components/FileCardCommon.tsx +82 -0
  338. package/src/utils/gientechCommon/components/FileManager/index.tsx +400 -0
  339. package/src/utils/gientechCommon/components/FileManager/style.css +5 -0
  340. package/src/utils/gientechCommon/components/Messages/GientechNewChatWelcome.tsx +581 -0
  341. package/src/utils/gientechCommon/components/Messages/ReferenceCard.tsx +359 -0
  342. package/src/utils/gientechCommon/components/Messages/RetriveItem.tsx +245 -0
  343. package/src/utils/gientechCommon/components/Messages/WebRetriveItem.tsx +209 -0
  344. package/src/utils/gientechCommon/components/Messages/defaultBot.png +0 -0
  345. package/src/utils/gientechCommon/components/Messages/defaultStyleSet.tsx +148 -0
  346. package/src/utils/gientechCommon/components/Messages/defaultWeLogo.svg +14 -0
  347. package/src/utils/gientechCommon/components/RenameModal.tsx +86 -0
  348. package/src/utils/gientechCommon/components/style.less +11 -0
  349. package/src/utils/gientechCommon/configs/commonConfig.ts +2 -0
  350. package/src/utils/gientechCommon/configs/senderConfig.ts +0 -0
  351. package/src/utils/gientechCommon/configs/stylesConfig.ts +142 -0
  352. package/src/utils/gientechCommon/hooks/AichatUseController.tsx +417 -0
  353. package/src/utils/gientechCommon/hooks/useFileDisplayTools.tsx +204 -0
  354. package/src/utils/gientechCommon/hooks/useFileManager.ts +193 -0
  355. package/src/utils/gientechCommon/slate/converters/deserializers.ts +763 -0
  356. package/src/utils/gientechCommon/slate/converters/mockData.ts +232 -0
  357. package/src/utils/gientechCommon/slate/converters/slateConverters.ts +258 -0
  358. package/src/utils/gientechCommon/slate/richElements/index.tsx +499 -0
  359. package/src/utils/gientechCommon/utils/fileUtils.ts +86 -0
  360. package/src/utils/gientechCommon/utils/request.ts +37 -0
  361. package/src/utils/gientechCommon/utils/serverFn.ts +172 -0
  362. package/src/utils/index.tsx +142 -0
  363. package/src/utils/testconfigs/demologin/index.tsx +32 -0
  364. package/src/utils/testconfigs/index.ts +59 -0
  365. package/src/vite-env.d.ts +42 -0
  366. package/stats.html +4949 -0
  367. package/tailwind.config.js +170 -0
  368. package/tsconfig.app.json +30 -0
  369. package/tsconfig.app.tsbuildinfo +11 -0
  370. package/tsconfig.json +13 -0
  371. package/tsconfig.node.json +22 -0
  372. package/tsconfig.node.tsbuildinfo +1 -0
  373. package/vite.config.app.ts +93 -0
  374. package/vite.config.ts +232 -0
  375. package/workflows/release.yml +60 -0
  376. package//346/215/242/350/241/214/346/240/274/345/274/217/344/277/235/346/214/201/344/274/230/345/214/226/346/226/271/346/241/210.md +359 -0
  377. package/assistantConfig.js +0 -1
  378. package/chat.js +0 -786
  379. package/database.js +0 -20
  380. package/databaseId.js +0 -1
  381. package/index.js +0 -1
  382. package/streamFilesReader.js +0 -1
  383. /package/{assets → dist/assets}/Doris.png +0 -0
  384. /package/{assets → dist/assets}/PostgreSQL.png +0 -0
  385. /package/{assets → dist/assets}/SQLServer.png +0 -0
  386. /package/{assets → dist/assets}/empty.png +0 -0
  387. /package/{assets → dist/assets}/homeBg.png +0 -0
  388. /package/{assets → dist/assets}/index-CpW6Dhpp.js +0 -0
  389. /package/{assets → dist/assets}/left.jpg +0 -0
  390. /package/{assets → dist/assets}/logoImg.png +0 -0
  391. /package/{assets → dist/assets}/style.css +0 -0
  392. /package/{assets → dist/assets}/style2.css +0 -0
  393. /package/{assets → dist/assets}/style3.css +0 -0
  394. /package/{assets → dist/assets}/style4.css +0 -0
  395. /package/{assets → dist/assets}/worker-BbpylX7l.js +0 -0
  396. /package/{assistantConfig.d.ts → dist/assistantConfig.d.ts} +0 -0
  397. /package/{chat.d.ts → dist/chat.d.ts} +0 -0
  398. /package/{database.d.ts → dist/database.d.ts} +0 -0
  399. /package/{databaseId.d.ts → dist/databaseId.d.ts} +0 -0
  400. /package/{databaseTable.d.ts → dist/databaseTable.d.ts} +0 -0
  401. /package/{icons → dist/icons}/answerAwartar.png +0 -0
  402. /package/{icons → dist/icons}/docx-file.png +0 -0
  403. /package/{icons → dist/icons}/folder.png +0 -0
  404. /package/{icons → dist/icons}/html.png +0 -0
  405. /package/{icons → dist/icons}/image.png +0 -0
  406. /package/{icons → dist/icons}/jpg-file.png +0 -0
  407. /package/{icons → dist/icons}/json.png +0 -0
  408. /package/{icons → dist/icons}/md.png +0 -0
  409. /package/{icons → dist/icons}/pdf.png +0 -0
  410. /package/{icons → dist/icons}/pptx.png +0 -0
  411. /package/{icons → dist/icons}/questionAwartar.png +0 -0
  412. /package/{icons → dist/icons}/sheets.png +0 -0
  413. /package/{icons → dist/icons}/txt.png +0 -0
  414. /package/{icons → dist/icons}/xlsx.png +0 -0
  415. /package/{index.d.ts → dist/index.d.ts} +0 -0
  416. /package/{modelManage.d.ts → dist/modelManage.d.ts} +0 -0
  417. /package/{sensitive.d.ts → dist/sensitive.d.ts} +0 -0
  418. /package/{streamFilesReader.d.ts → dist/streamFilesReader.d.ts} +0 -0
  419. /package/{vite.svg → dist/vite.svg} +0 -0
@@ -0,0 +1,1706 @@
1
+ import React, { useEffect, useState, useMemo, useRef } from 'react';
2
+ import { AiChat, AppStatusManager } from '@mxmweb/aichat';
3
+ import { ChatMessageAdapter } from '@mxmweb/rtext';
4
+ import request from '../../utils/gientechCommon/utils/request';
5
+ import { registerPDFWorker } from '../streamFilesReader';
6
+ registerPDFWorker('/worker/pdf.worker.min.js');
7
+ import {
8
+ getFeedbackList,
9
+ handleCancelFeedBack,
10
+ handleFeedBack,
11
+ } from '../../utils/gientechCommon/utils/serverFn';
12
+ import { toCopy } from '../../utils/commonFn';
13
+ import { uid } from 'uid';
14
+ import GientechNewChatWelcome from '../../utils/gientechCommon/components/Messages/GientechNewChatWelcome';
15
+ import AichatUseController from '../../utils/gientechCommon/hooks/AichatUseController';
16
+ import { useFileManager } from '../../utils/gientechCommon/hooks/useFileManager';
17
+ import { useFileDisplayTools } from '../../utils/gientechCommon/hooks/useFileDisplayTools';
18
+ import FeedBackModal from '../../utils/gientechCommon/components/FeedBackModal';
19
+ import { ConfigProvider, Drawer, message } from 'antd';
20
+ import AppLoading from '../../utils/gientechCommon/components/AppLoading';
21
+ import DisplayLoading from '../../utils/gientechCommon/components/DisplayLoading';
22
+ import AppError from '../../utils/gientechCommon/components/AppError';
23
+ import DisplayError from '../../utils/gientechCommon/components/DisplayError';
24
+ import { DefaultSenderConfig } from './constants';
25
+ import { mergeFiles } from '../../utils/gientechCommon/utils/fileUtils';
26
+ import '@mxmweb/rtext/style.css';
27
+ import '@mxmweb/aichat/style.css';
28
+ import '@mxmweb/zui/style.css';
29
+ import { ReferenceBar } from './ReferenceBar';
30
+ import { DrawerType } from '../chat';
31
+ import { fileViewTypes, getMarkExcludeType, getUrlPrefix, TempBaseUrl } from './utils';
32
+ import { DrawerContent } from './referenceCom/DrawerContent';
33
+ import axios from 'axios';
34
+ import DrawerPreview from './referenceCom/DrawerPreview';
35
+
36
+ interface SenderConfig {
37
+ actions?: Array<{
38
+ name: string;
39
+ icon?: React.ReactNode;
40
+ badgeCount?: number;
41
+ tip?: string;
42
+ }>;
43
+ switchs?: Array<{
44
+ label: string;
45
+ name: string;
46
+ type: 'switch' | 'checkbox';
47
+ defaultValue: boolean;
48
+ enabled: boolean;
49
+ icon?: React.ReactNode;
50
+ }>;
51
+ }
52
+
53
+ interface HeadlessChatAdopterProps {
54
+ token: string;
55
+ url?: string;
56
+ initialMessage?: string;
57
+ title?: string;
58
+ configId?: string;
59
+ useLogin?: boolean;
60
+ styles?: any;
61
+ welcomeContent?: string;
62
+ senderConfig?: SenderConfig;
63
+ onError?: (ems: any) => void;
64
+ eventsEmit?: (eventName: string, data: any) => void;
65
+ [key: string]: any;
66
+ }
67
+
68
+ export default function withHeadLessChatAdopter(
69
+ WrappedComponent: React.ComponentType<any> = AiChat as any
70
+ ) {
71
+ /**
72
+ * GientechChatAdopter 高阶组件
73
+ * 封装了与Gientech后端服务的接口对接、状态管理、事件处理等核心业务逻辑
74
+ * @param {React.ComponentType} WrappedComponent - 需要包裹的基础聊天组件,默认为 AiChat
75
+ */
76
+ return function GientechChatAdopter({
77
+ token,
78
+ url = 'http://localhost:8888',
79
+ welcomeContent = '欢迎来到小鲸智能助手,有什么能帮你的么?',
80
+ styles,
81
+ eventsEmit,
82
+ onError,
83
+ configId,
84
+ senderConfig,
85
+ initialMessage,
86
+ ...rest
87
+ }: HeadlessChatAdopterProps) {
88
+ console.log('[HeadlessChat] 组件初始化 - 接收到的 props:', {
89
+ configId,
90
+ url,
91
+ hasToken: !!token,
92
+ });
93
+ const [open, setOpen] = useState(false);
94
+ const [content, setContent] = useState<
95
+ | {
96
+ title: '';
97
+ file_type: '';
98
+ metadata: { offset: [] };
99
+ }
100
+ | any
101
+ >(null);
102
+ const [drawerType, setDrawerType] = useState<string>('mark');
103
+ const [curFileInfo, setCurFileInfo] = useState({
104
+ url: '',
105
+ parse_url: '',
106
+ file_type: '',
107
+ file_name: '',
108
+ });
109
+ // =================================================================
110
+ // State Management - 状态管理
111
+ // =================================================================
112
+ const [conversationList, setConversationList] = useState<any[]>([]); // 对话列表
113
+ const [chatData, setChatData] = useState<any[]>([]); // 所有对话的聊天记录
114
+ const [activeSessionId, setActiveSessionId] = useState<string | undefined>(undefined); // 当前激活的对话ID
115
+ const [recommandQuestions, setRecommandQuestions] = useState<any[]>([]); // 推荐问题列表
116
+ const [assistantList] = useState<any[]>([]); // 智能体(助手)列表
117
+ const [appStatus, setAppStatus] = useState<AppStatusManager>({
118
+ display: 'ready', // 主聊天区域状态: ready, loading, processing, error, uploading
119
+ sender: 'ready', // 发送器状态: ready, processing, error, uploading
120
+ app: 'initializing', // 应用整体状态: initializing, ready, error
121
+ });
122
+ // 深合并函数,用于合并默认配置和外部配置
123
+ function deepMerge(defaultConfig: any, customConfig: any): any {
124
+ if (!customConfig) return defaultConfig;
125
+ const result = { ...defaultConfig };
126
+ for (const key in customConfig) {
127
+ if (
128
+ Object.prototype.hasOwnProperty.call(customConfig, key)
129
+ ) {
130
+ // 特殊处理 actions 数组:合并而不是替换
131
+ if (key === 'actions' && Array.isArray(defaultConfig[key]) && Array.isArray(customConfig[key])) {
132
+ // 创建一个以 name 为 key 的 Map,优先使用 customConfig 中的 action
133
+ const actionsMap = new Map();
134
+ // 先添加 defaultConfig 中的 actions
135
+ (defaultConfig[key] as any[]).forEach((action: any) => {
136
+ if (action.name) {
137
+ actionsMap.set(action.name, action);
138
+ }
139
+ });
140
+ // 然后用 customConfig 中的 actions 覆盖或添加
141
+ (customConfig[key] as any[]).forEach((action: any) => {
142
+ if (action.name) {
143
+ actionsMap.set(action.name, action);
144
+ }
145
+ });
146
+ result[key] = Array.from(actionsMap.values());
147
+ } else if (
148
+ typeof defaultConfig[key] === 'object' &&
149
+ defaultConfig[key] !== null &&
150
+ !Array.isArray(defaultConfig[key]) &&
151
+ typeof customConfig[key] === 'object' &&
152
+ customConfig[key] !== null &&
153
+ !Array.isArray(customConfig[key])
154
+ ) {
155
+ result[key] = deepMerge(defaultConfig[key], customConfig[key]);
156
+ } else {
157
+ result[key] = customConfig[key];
158
+ }
159
+ }
160
+ }
161
+ return result;
162
+ }
163
+
164
+ // 发送器配置支持外部传入,merge 默认和外部
165
+ const [dynamicSenderConfig, setDynamicSenderConfig] = useState(() => {
166
+ const merged = deepMerge(DefaultSenderConfig, senderConfig);
167
+ return merged;
168
+ });
169
+
170
+ // 监听外部 senderConfig 变化自动 merge
171
+ useEffect(() => {
172
+ if (senderConfig) {
173
+ const merged = deepMerge(DefaultSenderConfig, senderConfig);
174
+ setDynamicSenderConfig(merged);
175
+ }
176
+ }, [senderConfig]);
177
+
178
+ // 使用统一的文件管理 Hook
179
+ const {
180
+ fileManagerData,
181
+ fileStatuses,
182
+ setFileManagerData,
183
+ setFileStatuses,
184
+ pollFileStatus: pollFileStatusHook,
185
+ handleRemoveFromTemp,
186
+ resetStopFlag,
187
+ setStopFlag,
188
+ stoppedRef,
189
+ } = useFileManager({
190
+ url,
191
+ token,
192
+ activeSessionId,
193
+ chatData,
194
+ onError,
195
+ });
196
+
197
+ // 新增:记录 sender switchs 的选中状态
198
+ const [senderSwitchValues, setSenderSwitchValues] = useState<{ [key: string]: boolean }>({});
199
+
200
+ // =================================================================
201
+ // Refs - 引用
202
+ // =================================================================
203
+ const prevActiveSessionId = useRef<string | undefined>(undefined); // 记录上一个激活的对话ID,用于对比变化
204
+ const setUploadedFilesRef = useRef<(files: any[]) => void>(undefined); // 引用核心组件的文件上传方法
205
+ const removeFileRef = useRef<(fileObj: any, idx: number, _type: string) => void>(undefined); // 引用核心组件的文件移除方法
206
+ const fileInputRef = useRef<HTMLInputElement>(null); // 文件上传input引用
207
+ const setFileListPopoverOpenRef = useRef<((open: boolean) => void) | null>(null); // 文件列表 Popover 控制函数引用
208
+
209
+ const [feedParam, setFeedParam] = useState<{
210
+ queryId?: number | string | undefined;
211
+ restult?: number;
212
+ }>({});
213
+ const [openFeed, setOpenFeed] = useState(false);
214
+ const [feedBackList, setFeedBackList] = useState<any>([]);
215
+ // stoppedRef 已由 useFileManager hook 管理
216
+
217
+ // =================================================================
218
+ // Custom Hooks - 自定义钩子
219
+ // =================================================================
220
+ // 引入SSE控制器,封装了流式请求、消息管理等复杂逻辑
221
+ const { api_startChat_re, addEmptyMessage, setLastEmptyMessage, stopStream } =
222
+ AichatUseController({
223
+ baseUrl: url,
224
+ token,
225
+ setChatData,
226
+ activeSessionId,
227
+ setAppStatus,
228
+ setConversationList,
229
+ });
230
+
231
+ // =================================================================
232
+ // Memoized Values - 缓存计算值
233
+ // =================================================================
234
+
235
+ // welcomeContent 伪消息唯一 id/time/queryId
236
+ const welcomeMsgId = useRef(uid(32));
237
+ const welcomeMsgQueryId = useRef(uid(32));
238
+ const welcomeMsgTime = useRef(Date.now());
239
+
240
+ /**
241
+ * 将智能体列表转换为以ID为键的Map,便于快速查找
242
+ */
243
+ const assistantMap = useMemo(() => {
244
+ const map: Record<string, any> = {};
245
+ (assistantList || []).forEach(item => {
246
+ map[item.id] = item;
247
+ });
248
+ return map;
249
+ }, [assistantList]);
250
+
251
+ /**
252
+ * 根据 activeSessionId 从 chatData 中筛选出当前对话的数据, 并合并智能体信息
253
+ */
254
+ const currentChatData = useMemo(() => {
255
+ let data = chatData.find((c: any) => c.id === activeSessionId) || {
256
+ id: activeSessionId,
257
+ messages: [],
258
+ };
259
+ let messages = data.messages || [];
260
+ // 检查第一条是否已经是 welcome 消息,如果不是则插入
261
+ if (welcomeContent && (messages.length === 0 || !messages[0]?.isDefault)) {
262
+ messages = [
263
+ {
264
+ istype: 'ai',
265
+ content: welcomeContent,
266
+ time: welcomeMsgTime.current,
267
+ clientSideId: welcomeMsgId.current,
268
+ isWelcome: true,
269
+ isDefault: true,
270
+ queryId: welcomeMsgQueryId.current,
271
+ },
272
+ ...messages,
273
+ ];
274
+ }
275
+ // 如果第一条已经是 isDefault: true 的欢迎消息,确保它的 isWelcome 字段存在
276
+ if (messages[0]?.isDefault && !messages[0]?.isWelcome) {
277
+ messages[0].isWelcome = true;
278
+ }
279
+ const assistantInfo = data.configId ? assistantMap[data.configId] : null;
280
+ return {
281
+ ...data,
282
+ messages,
283
+ assistantInfo,
284
+ };
285
+ }, [chatData, activeSessionId, assistantMap, welcomeContent]);
286
+
287
+ // 文件工具函数已提取到 useFileManager hook 中
288
+
289
+ // 文件同步逻辑已由 useFileManager hook 处理
290
+
291
+ // =================================================================
292
+ // Side Effects (useEffect) - 副作用钩子
293
+ // =================================================================
294
+
295
+ /**
296
+ * 跟踪 activeSessionId 的变化:
297
+ * 1. 清理上一个无消息的临时会话
298
+ * 2. 更新推荐问题
299
+ */
300
+ useEffect(() => {
301
+ // 切换会话时,检查上一个会话是否为临时且无消息
302
+ if (prevActiveSessionId.current && prevActiveSessionId.current !== activeSessionId) {
303
+ const prevId = prevActiveSessionId.current;
304
+ const prevConv = conversationList.find(c => c.sessionId === prevId && c.isNew);
305
+ const prevChat = chatData.find(c => c.id === prevId && c.isNew);
306
+ const prevHasMsg = prevChat && prevChat.messages && prevChat.messages.length > 0;
307
+ // 如果是临时的且没有消息,则自动删除
308
+ if (prevConv && !prevHasMsg) {
309
+ setConversationList(list => list.filter(c => c.sessionId !== prevId));
310
+ setChatData(list => list.filter(c => c.id !== prevId));
311
+ }
312
+ }
313
+ prevActiveSessionId.current = activeSessionId;
314
+
315
+ // 更新推荐问题为当前对话的最后一个推荐问题
316
+ const historyRQ =
317
+ currentChatData.messages && currentChatData.messages.length > 0
318
+ ? currentChatData.messages[currentChatData.messages.length - 1].recommendQuestion
319
+ : [];
320
+ setRecommandQuestions(historyRQ);
321
+ }, [activeSessionId, conversationList, chatData, currentChatData.messages]);
322
+
323
+ /**
324
+ * 1. useEffect 只拉助手列表,自动新建会话
325
+ */
326
+ useEffect(() => {
327
+ setAppStatus(prev => ({ ...prev, app: 'initializing' }));
328
+
329
+ if (token) {
330
+ handleConversationCreate({ configId });
331
+ }
332
+ setAppStatus(prev => ({ ...prev, app: 'ready' }));
333
+ }, [url, token, configId]);
334
+
335
+ /**
336
+ * 当 activeSessionId 变化时,获取对应的聊天记录
337
+ */
338
+ useEffect(() => {
339
+ if (!activeSessionId) return;
340
+ setAppStatus(prev => ({ ...prev, display: 'loading' }));
341
+
342
+ setAppStatus(prev => ({ ...prev, display: 'ready' }));
343
+ }, [activeSessionId, url, token]);
344
+
345
+ /**
346
+ * 自动发送初始消息
347
+ * 当 activeSessionId 设置且组件准备好后,如果有 initialMessage,就自动发送
348
+ */
349
+ const hasSentInitialMessage = useRef(false);
350
+ const initialMessageRef = useRef<string | undefined>(initialMessage);
351
+
352
+ // 更新 ref,确保始终使用最新的 initialMessage
353
+ useEffect(() => {
354
+ initialMessageRef.current = initialMessage;
355
+ }, [initialMessage]);
356
+
357
+ useEffect(() => {
358
+ // 确保只在组件初始化时发送一次,且 activeSessionId 已设置,且应用状态为 ready
359
+ // 适配两种模式:
360
+ // 1. 直接有token:组件挂载时 token 已存在,立即创建会话并发送
361
+ // 2. 需要登录:登录成功后组件才开始渲染,此时 token 已有值,创建会话并发送
362
+ if (
363
+ initialMessageRef.current &&
364
+ activeSessionId &&
365
+ appStatus.app === 'ready' &&
366
+ appStatus.display === 'ready' &&
367
+ !hasSentInitialMessage.current
368
+ ) {
369
+ hasSentInitialMessage.current = true;
370
+ // 延迟一下确保组件完全准备好
371
+ setTimeout(() => {
372
+ handleSenderSend({ content: initialMessageRef.current }, () => {});
373
+ }, 100);
374
+ }
375
+ }, [activeSessionId, appStatus.app, appStatus.display, initialMessage]);
376
+
377
+ // 文件角标逻辑已移除,upload action 不需要角标
378
+ useEffect(() => {
379
+ getFeedbackList({ url, token }, data => {
380
+ setFeedBackList(data);
381
+ });
382
+ }, []);
383
+
384
+ /**
385
+ * 实时同步 fileStatuses 到当前消息的 currentFiles,便于消息区细致显示每个文件的处理状态
386
+ */
387
+ React.useEffect(() => {
388
+ if (!fileStatuses.length) return;
389
+ setChatData(chatList => {
390
+ if (!chatList[0]) return chatList;
391
+ const { messages } = chatList[0];
392
+ for (let i = messages.length - 1; i >= 0; i--) {
393
+ if (messages[i].istype === 'user') {
394
+ const oldFiles = messages[i].currentFiles || [];
395
+ messages[i].currentFiles = fileStatuses.map((f: any) => {
396
+ const old = oldFiles.find((of: any) => of.uid === f.uid);
397
+ return {
398
+ name: f.file?.name || f.name,
399
+ size: f.file?.size || f.size,
400
+ type: f.file?.type || f.type,
401
+ status: f.status,
402
+ uid: f.uid,
403
+ url: f.url || old?.url || '', // 优先用已有 url
404
+ };
405
+ });
406
+ break;
407
+ }
408
+ }
409
+ return [...chatList];
410
+ });
411
+ }, [fileStatuses]);
412
+
413
+ // =================================================================
414
+ // Core Functions - 核心功能函数
415
+ // =================================================================
416
+
417
+ // 1. 在组件作用域定义控制器和轮询停止标志
418
+ let uploadController: AbortController | null = null;
419
+ let stopPoll: (() => void) | null = null;
420
+
421
+ /**
422
+ * 轮询文件解析状态(使用统一的 hook)
423
+ */
424
+ const pollFileStatus = (
425
+ uids: string[],
426
+ onStatusUpdate: (serverStatuses: any[]) => void,
427
+ onComplete: () => void,
428
+ onFail: (errorMsg: string) => void
429
+ ) => {
430
+ stopPoll = pollFileStatusHook(uids, {
431
+ onStatusUpdate,
432
+ onComplete,
433
+ onFail,
434
+ });
435
+ };
436
+
437
+ /**
438
+ * 设置消息的反馈结果(赞/踩)
439
+ * @param {string} queryId - 消息ID
440
+ * @param {number} result - 反馈结果 (1: 赞, -1: 踩)
441
+ */
442
+ const setFeed = (queryId: any, restult: any) => {
443
+ setChatData(chatList => {
444
+ const newList = [...chatList];
445
+ if (!newList[0]) return newList;
446
+ newList[0] = {
447
+ ...newList[0],
448
+ messages: newList[0].messages.map((item: any) => {
449
+ if (item.queryId === queryId) {
450
+ return {
451
+ ...item,
452
+ feedbackResult: restult,
453
+ };
454
+ }
455
+ return item;
456
+ }),
457
+ };
458
+ return newList;
459
+ });
460
+ };
461
+
462
+ /**
463
+ * 获取指定queryId的推荐问题
464
+ * @param {string} queryId - 消息ID
465
+ */
466
+ const fetchRecommendQuestions = async (queryId: string) => {
467
+ if (!queryId) return;
468
+ try {
469
+ const res = await request.get(`${url}/qa/ai/recommendQuestion?queryId=${queryId}`, {
470
+ headers: { Authorization: token },
471
+ });
472
+ setRecommandQuestions(res.data?.data || []);
473
+ } catch (e) {
474
+ onError?.(e);
475
+ setRecommandQuestions([]);
476
+ }
477
+ };
478
+
479
+ /**
480
+ * 处理重新发送消息逻辑
481
+ * 重发时应该使用上一次用户的提问内容,而不是AI的回复内容
482
+ */
483
+ function handleReSenderSend(data: any) {
484
+ const { audioUrl, filePaths, queryId } = data;
485
+ if (!queryId) return;
486
+
487
+ // 从当前对话的消息列表中找到对应的用户消息
488
+ const messages = currentChatData.messages || [];
489
+ let userMessage: any = null;
490
+
491
+ // 首先尝试通过 queryId 找到对应的消息
492
+ const targetMessageIndex = messages.findIndex((msg: any) => msg.queryId === queryId);
493
+
494
+ if (targetMessageIndex !== -1) {
495
+ const targetMessage = messages[targetMessageIndex];
496
+ // 如果找到的是AI消息,向前查找对应的用户消息
497
+ if (targetMessage.istype === 'ai') {
498
+ // 从当前位置向前查找用户消息
499
+ for (let i = targetMessageIndex - 1; i >= 0; i--) {
500
+ if (messages[i].istype === 'user') {
501
+ userMessage = messages[i];
502
+ break;
503
+ }
504
+ }
505
+ } else if (targetMessage.istype === 'user') {
506
+ // 如果找到的就是用户消息,直接使用
507
+ userMessage = targetMessage;
508
+ }
509
+ } else {
510
+ // 如果没找到对应queryId的消息,尝试找到最后一条用户消息
511
+ for (let i = messages.length - 1; i >= 0; i--) {
512
+ if (messages[i].istype === 'user') {
513
+ userMessage = messages[i];
514
+ break;
515
+ }
516
+ }
517
+ }
518
+
519
+ // 如果找不到用户消息,则使用传入的content作为兜底
520
+ const content = userMessage?.content || data.content;
521
+ if (!content) return;
522
+
523
+ // 优先使用用户消息中的文件信息
524
+ const userFilePaths = userMessage?.filePaths || filePaths;
525
+ const _fileUids = (
526
+ typeof userFilePaths === 'string' && userFilePaths
527
+ ? JSON.parse(userFilePaths)
528
+ : userFilePaths || []
529
+ ).map((item: any) => item.uid);
530
+
531
+ const now = Date.now();
532
+ setLastEmptyMessage({ now });
533
+ if (!activeSessionId) return;
534
+ setAppStatus(pre => {
535
+ return {
536
+ ...pre,
537
+ display: 'processing',
538
+ sender: 'processing',
539
+ };
540
+ });
541
+
542
+ data.clearFn && data.clearFn();
543
+ setConversationList(list =>
544
+ list.map(c => (c.sessionId === activeSessionId ? { ...c, gmtModified: now } : c))
545
+ );
546
+
547
+ // 使用用户消息的queryId,如果没有则使用传入的queryId
548
+ const userQueryId = userMessage?.queryId || queryId;
549
+
550
+ api_startChat_re(
551
+ {
552
+ configId: configId ?? currentChatData.configId ?? null,
553
+ content,
554
+ fileUids: _fileUids,
555
+ lastDate: now,
556
+ name: currentChatData.label || '',
557
+ queryId: userQueryId,
558
+ sessionId: activeSessionId,
559
+ type: audioUrl ? 'audioUrl' : 'text',
560
+ audioUrl: userMessage?.audioUrl || audioUrl,
561
+ sysType: 'TEMPORARY',
562
+ enableThinking: !!senderSwitchValues.reasoning,
563
+ enableWebsearch: !!senderSwitchValues.netSearch,
564
+ qaChannel: senderSwitchValues.qaChannel,
565
+ } as any,
566
+ () => {
567
+ fetchRecommendQuestions(userQueryId);
568
+ }
569
+ );
570
+ }
571
+
572
+ /**
573
+ * 处理消息发送(核心逻辑)
574
+ * 1. 包含文件:先上传文件,成功后再发消息
575
+ * 2. 新建会话:先调用创建会话接口,成功后再发消息
576
+ * 3. 普通消息:直接发送
577
+ * @param {object} data - 消息数据
578
+ */
579
+ async function handleSenderSend(data: any, clearFn: () => void) {
580
+ const queryId = 'query-' + uid(32);
581
+ // 1. 立即插入空壳消息
582
+ let localCurrentFiles = undefined;
583
+ if (data.files && data.files.length > 0) {
584
+ localCurrentFiles = data.files.map((fileItem: any) => ({
585
+ name: fileItem.file.name,
586
+ size: fileItem.file.size,
587
+ type: fileItem.file.type,
588
+ filePath: fileItem.filePath || '',
589
+ convertedFilePath: fileItem.convertedFilePath || '',
590
+ uid: fileItem.uid || '',
591
+ }));
592
+ }
593
+ addEmptyMessage({
594
+ content: data.content || data.text || '',
595
+ queryId,
596
+ currentFiles: localCurrentFiles,
597
+ filePaths: localCurrentFiles ? JSON.stringify(localCurrentFiles) : undefined,
598
+ });
599
+ setConversationList(list => {
600
+ const newList = list
601
+ .map(c =>
602
+ c.sessionId === activeSessionId ? { ...c, isNew: false, gmtModified: Date.now() } : c
603
+ )
604
+ .sort((a, b) => b.gmtModified - a.gmtModified);
605
+ setTimeout(() => {
606
+ setActiveSessionId(activeSessionId);
607
+ }, 300);
608
+ return newList;
609
+ });
610
+ // 2. 有文件时,先上传并轮询,全部解析成功后再发起AI请求
611
+ if (data.files && data.files.length > 0) {
612
+ setAppStatus(pre => ({ ...pre, sender: 'uploading', display: 'uploading' }));
613
+ uploadController = new AbortController();
614
+ resetStopFlag(); // 使用 hook 提供的重置函数
615
+ const clientFileUids = data.files.map(
616
+ () => `rc-upload-${Date.now()}-${Math.random().toString(36).slice(2)}`
617
+ );
618
+ const initialFileStatuses = data.files.map((fileItem: any, index: number) => ({
619
+ ...fileItem,
620
+ uid: clientFileUids[index],
621
+ status: 'uploading',
622
+ }));
623
+ setFileStatuses(initialFileStatuses);
624
+ const formData = new FormData();
625
+ data.files.forEach((fileItem: any) => {
626
+ formData.append('files', fileItem.file);
627
+ });
628
+ const sessionIdStr = activeSessionId ? String(activeSessionId) : '';
629
+ formData.append('sessionId', sessionIdStr);
630
+ formData.append('uids', clientFileUids.join(','));
631
+ try {
632
+ const response = await request.post(
633
+ `${url}/index/knowledgeBase/file/uploadFiles`,
634
+ formData,
635
+ {
636
+ headers: {
637
+ Authorization: token,
638
+ 'Content-Type': 'multipart/form-data',
639
+ },
640
+ signal: uploadController.signal,
641
+ }
642
+ );
643
+ if (stoppedRef.current) return;
644
+ if (
645
+ response.data.success &&
646
+ Array.isArray(response.data.data) &&
647
+ response.data.data.length > 0
648
+ ) {
649
+ // === 新增:合并后端返回字段到 fileStatuses ===
650
+ setFileStatuses(prev => {
651
+ return prev.map(localFile => {
652
+ const serverFile = response.data.data.find((f: any) => f.uid === localFile.uid);
653
+ return serverFile ? { ...localFile, ...serverFile } : localFile;
654
+ });
655
+ });
656
+ setAppStatus(pre => ({ ...pre, sender: 'processing', display: 'analyzing' }));
657
+ setFileStatuses(prev => prev.map(f => ({ ...f, status: 'parsing' })));
658
+ const uploadedFilesData = response.data.data;
659
+ const serverUids = uploadedFilesData.map((f: any) => f.uid);
660
+ pollFileStatus(
661
+ serverUids,
662
+ serverStatuses => {
663
+ setFileStatuses(currentLocalStatuses => {
664
+ return currentLocalStatuses.map(localFile => {
665
+ const correspondingServerFile = serverStatuses.find(
666
+ (sf: any) => sf.uid === localFile.uid
667
+ );
668
+ if (correspondingServerFile) {
669
+ switch (correspondingServerFile.status) {
670
+ case 3:
671
+ return { ...localFile, status: 'done' };
672
+ case 4:
673
+ return { ...localFile, status: 'error' };
674
+ case 1:
675
+ case 2:
676
+ default:
677
+ return { ...localFile, status: 'parsing' };
678
+ }
679
+ }
680
+ return localFile;
681
+ });
682
+ });
683
+ // === 新增:全部完成时清空 sender 区并合并文件 ===
684
+ const allDone =
685
+ serverStatuses.length > 0 &&
686
+ serverStatuses.every((file: any) => file.status === 3);
687
+ if (allDone) {
688
+ setFileStatuses([]);
689
+ // 上传完成后合并新文件到 uploadedFiles,保证结构统一
690
+ const map = new Map();
691
+ (uploadedFilesData || []).forEach((item: any) => {
692
+ map.set(item.id, item);
693
+ });
694
+ setFileManagerData(prev => {
695
+ const r = mergeFiles(prev.uploadedFiles, uploadedFilesData);
696
+
697
+ return {
698
+ uploadedFiles: r.map(item => {
699
+ return {
700
+ ...item,
701
+ pdfPages: map.get(item.id)?.pdfPages || 0, // 保留后端返回的 pdfPages 字段
702
+ };
703
+ }),
704
+ };
705
+ });
706
+ }
707
+ },
708
+ // onComplete: 全部解析成功后才发起AI请求
709
+ () => {
710
+ clearFn?.();
711
+ setAppStatus(pre => ({ ...pre, sender: 'processing', display: 'processing' }));
712
+ // === 新增:回填 filePath 到 fileStatuses ===
713
+ setFileStatuses(prev => {
714
+ return prev.map(localFile => {
715
+ const serverFile = uploadedFilesData.find((f: any) => f.uid === localFile.uid);
716
+
717
+ if (serverFile) {
718
+ return {
719
+ ...localFile,
720
+ ...serverFile, // 合并后端所有字段
721
+ url: serverFile.filePath, // 兼容 url 字段
722
+ filePath: serverFile.filePath,
723
+ name: serverFile.fileName || localFile.name,
724
+ convertedFilePath:
725
+ serverFile.convertedFilePath || localFile.convertedFilePath,
726
+ };
727
+ }
728
+ return localFile;
729
+ });
730
+ });
731
+ // === 同步 currentFiles 字段到 chatData ===
732
+ setChatData(chatList => {
733
+ if (!chatList[0]) return chatList;
734
+ const { messages } = chatList[0];
735
+ for (let i = messages.length - 1; i >= 0; i--) {
736
+ if (messages[i].istype === 'user') {
737
+ const oldFiles = messages[i].currentFiles || [];
738
+ messages[i].currentFiles = oldFiles.map((localFile: any) => {
739
+ const serverFile = uploadedFilesData.find(
740
+ (f: any) => f.uid === localFile.uid
741
+ );
742
+ return serverFile ? { ...localFile, ...serverFile } : localFile;
743
+ });
744
+ break;
745
+ }
746
+ }
747
+ return [...chatList];
748
+ });
749
+ // === 全部完成时清空 sender 区并合并文件 ===
750
+ setFileStatuses([]);
751
+ // 上传完成后合并新文件到 uploadedFiles,保证结构统一
752
+ const map = new Map();
753
+ (uploadedFilesData || []).forEach((item: any) => {
754
+ map.set(item.id, item);
755
+ });
756
+ setFileManagerData(prev => {
757
+ const r = mergeFiles(prev.uploadedFiles, uploadedFilesData);
758
+
759
+ return {
760
+ uploadedFiles: r.map(item => {
761
+ return {
762
+ ...item,
763
+ pdfPages: map.get(item.id)?.pdfPages || 0, // 保留后端返回的 pdfPages 字段
764
+ };
765
+ }),
766
+ };
767
+ });
768
+ const now = Date.now();
769
+ api_startChat_re(
770
+ {
771
+ configId: configId || currentChatData.configId,
772
+ content: data.content || data.text || '',
773
+ fileUids: serverUids,
774
+ lastDate: now,
775
+ name: currentChatData.label || '',
776
+ queryId: queryId,
777
+ sessionId: String(activeSessionId || ''),
778
+ type: 'text',
779
+ audioUrl: '',
780
+ sysType: 'TEMPORARY',
781
+ enableThinking: !!senderSwitchValues.reasoning,
782
+ enableWebsearch: !!senderSwitchValues.netSearch,
783
+ qaChannel: senderSwitchValues.qaChannel,
784
+ } as any,
785
+ () => {
786
+ fetchRecommendQuestions(queryId);
787
+ }
788
+ );
789
+ },
790
+ (errorMsg: string) => {
791
+ setAppStatus(pre => ({ ...pre, display: 'ready', sender: 'ready' }));
792
+ setFileStatuses(prev =>
793
+ prev.map(f => ({ ...f, status: 'error', message: errorMsg }))
794
+ );
795
+ }
796
+ );
797
+ } else {
798
+ const errorMsg = response.data.errorMsg || '文件上传失败';
799
+ setAppStatus(pre => ({ ...pre, display: 'error', sender: 'ready' }));
800
+ setFileStatuses(prev => prev.map(f => ({ ...f, status: 'error', message: errorMsg })));
801
+ }
802
+ } catch (error) {
803
+ setAppStatus(pre => ({ ...pre, display: 'error', sender: 'ready' }));
804
+ setFileStatuses(prev => prev.map(f => ({ ...f, status: 'error', message: '网络错误' })));
805
+ }
806
+ return;
807
+ }
808
+ // 3. 无文件,直接发起AI请求
809
+ if (!activeSessionId) return;
810
+ setAppStatus(pre => ({ ...pre, display: 'processing', sender: 'processing' }));
811
+ const now = Date.now();
812
+ setConversationList(list =>
813
+ list.map(c => (c.sessionId === activeSessionId ? { ...c, gmtModified: now } : c))
814
+ );
815
+ clearFn?.();
816
+ const requestConfigId = configId ?? currentChatData.configId ?? null;
817
+ console.log('[HeadlessChat] handleSenderSend - 发送请求 configId:', {
818
+ propsConfigId: configId,
819
+ currentChatDataConfigId: currentChatData.configId,
820
+ requestConfigId,
821
+ });
822
+ api_startChat_re(
823
+ {
824
+ configId: requestConfigId,
825
+ content: data.content || data.text || '',
826
+ fileUids: [],
827
+ lastDate: now,
828
+ name: currentChatData.label || '',
829
+ queryId: queryId,
830
+ sessionId: activeSessionId,
831
+ type: 'text',
832
+ audioUrl: '',
833
+ sysType: 'TEMPORARY',
834
+ enableThinking: !!senderSwitchValues.reasoning,
835
+ enableWebsearch: !!senderSwitchValues.netSearch,
836
+ qaChannel: senderSwitchValues.qaChannel,
837
+ } as any,
838
+ () => {
839
+ fetchRecommendQuestions(queryId);
840
+ }
841
+ );
842
+ }
843
+
844
+ // =================================================================
845
+ // Event Handlers - 事件处理器
846
+ // =================================================================
847
+
848
+ /**
849
+ * 处理点赞反馈
850
+ */
851
+ function handleThumbsup(data: any) {
852
+ if (data.restult == 1) {
853
+ handleCancelFeedBack({ url, token }, { queryId: data.queryId }, () => {
854
+ setFeed(data.queryId, null);
855
+ });
856
+ } else {
857
+ handleFeedBack({ url, token }, { restult: 1, queryId: data.queryId }, () => {
858
+ setFeed(data.queryId, 1);
859
+ });
860
+ }
861
+ }
862
+
863
+ /**
864
+ * 处理新建一个临时会话
865
+ */
866
+ function handleConversationCreate(data: any) {
867
+ const sessionId = uid(32);
868
+ // 优先用 props.configId,使用 ?? 确保空字符串也能正确传递
869
+ const effectiveConfigId = configId ?? data?.configId ?? '';
870
+ console.log('[HeadlessChat] handleConversationCreate - configId 解析:', {
871
+ propsConfigId: configId,
872
+ dataConfigId: data?.configId,
873
+ effectiveConfigId,
874
+ });
875
+ const newConversation = {
876
+ sessionId,
877
+ label: '未命名会话',
878
+ gmtModified: Date.now(),
879
+ searchConfigDTO: {},
880
+ filePath: '',
881
+ configId: effectiveConfigId,
882
+ isNew: true,
883
+ };
884
+ setConversationList(list => [{ ...newConversation, messages: [] }, ...list]);
885
+ setChatData(list => [{ id: sessionId, messages: [], ...newConversation }, ...list]);
886
+ setActiveSessionId(sessionId);
887
+ // 自动触发新建会话接口
888
+
889
+ if (token && url) {
890
+ request
891
+ .post(
892
+ `${url}/qa/dialogue/create`,
893
+ {
894
+ configId: effectiveConfigId,
895
+ label: `来自内嵌应用${uid(8)}`,
896
+ sessionId,
897
+ sysType: 'TEMPORARY',
898
+ },
899
+ {
900
+ headers: { Authorization: token },
901
+ }
902
+ )
903
+ .then(res => {
904
+ const newSession = res.data.data;
905
+ if (newSession?.sessionId) {
906
+ setConversationList(list => [
907
+ { ...newSession, messages: [] },
908
+ ...list.filter(c => c.sessionId !== sessionId),
909
+ ]);
910
+ setChatData(list => [
911
+ { id: newSession.sessionId, messages: [], ...newSession },
912
+ ...list.filter(c => c.id !== sessionId),
913
+ ]);
914
+ setActiveSessionId(newSession.sessionId);
915
+ }
916
+ })
917
+ .catch(err => {
918
+ onError?.(err);
919
+ });
920
+ }
921
+ }
922
+
923
+ /**
924
+ * 处理文件选择
925
+ */
926
+ const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
927
+ const files = e.target.files;
928
+ if (files && files.length > 0) {
929
+ // 收集所有已经存在的文件名
930
+ const existingFileNames = new Set<string>(
931
+ fileManagerData.uploadedFiles.map((f: any) => (f.name || f.file?.name || '') as string)
932
+ );
933
+
934
+ const newFiles: { file: File; type: string }[] = [];
935
+ const duplicateFileNames = new Set<string>();
936
+
937
+ // 遍历新选择的文件,筛选出重复项和有效项
938
+ Array.from(files).forEach(file => {
939
+ if (existingFileNames.has(file.name)) {
940
+ duplicateFileNames.add(file.name);
941
+ } else {
942
+ newFiles.push({
943
+ file,
944
+ type: file.type.startsWith('image/') ? ('image' as const) : ('document' as const),
945
+ });
946
+ existingFileNames.add(file.name);
947
+ }
948
+ });
949
+
950
+ // 如果有重复,发出警告
951
+ if (duplicateFileNames.size > 0) {
952
+ message.warning(`不能上传同名文件: ${Array.from(duplicateFileNames).join(', ')}`);
953
+ }
954
+
955
+ // 将有效的文件通过 setUploadedFilesRef 设置到 aichat 内部
956
+ if (newFiles.length > 0) {
957
+ if (setUploadedFilesRef.current) {
958
+ setUploadedFilesRef.current(newFiles);
959
+ } else {
960
+ // 如果 ref 还没有设置,等待一下再试
961
+ setTimeout(() => {
962
+ if (setUploadedFilesRef.current) {
963
+ setUploadedFilesRef.current(newFiles);
964
+ }
965
+ }, 100);
966
+ }
967
+ }
968
+ }
969
+ // 清空 input,以便可以重复选择同一文件
970
+ if (fileInputRef.current) {
971
+ fileInputRef.current.value = '';
972
+ }
973
+ };
974
+
975
+ const [is_download, setIs_download] = useState(false);
976
+
977
+ const getConfigById = async (params: any) => {
978
+ try {
979
+ const res: any = await axios.get(
980
+ `${getUrlPrefix()}/qa/search/config/getById?${new URLSearchParams(params).toString()}`,
981
+ { headers: { Authorization: token } }
982
+ );
983
+ console.log('resslkdjgalsg', res);
984
+ if (res?.data?.success && res?.data?.data?.configJson) {
985
+ const config = JSON.parse(res.data.data.configJson);
986
+ setIs_download(config.is_download);
987
+ }
988
+ } catch (error) {}
989
+ };
990
+
991
+ useEffect(() => {
992
+ if (!configId) return;
993
+ getConfigById({ configId });
994
+ }, [configId]);
995
+
996
+ /**
997
+ * 处理会话列表项点击
998
+ */
999
+ function handleItemClick(data: any) {
1000
+ if (data?.sessionId && data.sessionId !== activeSessionId) {
1001
+ setActiveSessionId(data.sessionId);
1002
+ setAppStatus(prev => ({ ...prev, sender: 'ready' }));
1003
+ // 点击会话时,使用其最后一条AI消息的推荐问题
1004
+ const conv = chatData.find(c => c.id === data.sessionId);
1005
+ if (conv && conv.messages && conv.messages.length > 0) {
1006
+ const lastAiMsg = [...conv.messages].reverse().find((m: any) => m.istype === 'ai');
1007
+ if (lastAiMsg && lastAiMsg.recommendQuestion) {
1008
+ setRecommandQuestions(lastAiMsg.recommendQuestion);
1009
+ } else {
1010
+ setRecommandQuestions([]);
1011
+ }
1012
+ } else {
1013
+ setRecommandQuestions([]);
1014
+ }
1015
+ }
1016
+ }
1017
+
1018
+ // =================================================================
1019
+ // Local State Updaters - 本地状态更新 (供事件发射器使用)
1020
+ // =================================================================
1021
+
1022
+ const handleFeedDownConfirm = (data: any) => {
1023
+ handleFeedBack({ url, token }, { ...data, ...feedParam, restult: 0 }, () => {
1024
+ setFeed(feedParam.queryId, 0);
1025
+ });
1026
+ };
1027
+
1028
+ // =================================================================
1029
+ // Event Emitter - 事件分发器
1030
+ // =================================================================
1031
+ /**
1032
+ * 统一事件分发器,连接业务逻辑与所有子组件的事件
1033
+ * @param {string} eventName - 事件名称
1034
+ * @param {any} data - 事件数据
1035
+ */
1036
+ const api_getFileStatusByUid = async (data: any) => {
1037
+ try {
1038
+ const res: any = await axios.get(
1039
+ `${getUrlPrefix()}/index/knowledgeBase/file/getFileStatusByUids?uids=${data.data.uids}`,
1040
+ { headers: { Authorization: token } }
1041
+ );
1042
+ console.log('resslkdjgalsg', res);
1043
+ return res?.data?.data;
1044
+ } catch (error) {}
1045
+ };
1046
+ const referenceFile_question_upload = async (data: any) => {
1047
+ if (fileViewTypes.includes(data.file_type)) {
1048
+ setCurFileInfo({
1049
+ ...data,
1050
+ parse_url:
1051
+ data.url || data.metadata.source ? `${TempBaseUrl}${data.metadata.source}` : data.url,
1052
+ });
1053
+
1054
+ setDrawerType('preview');
1055
+ setOpen(true);
1056
+ return;
1057
+ }
1058
+ try {
1059
+ const res: any = await api_getFileStatusByUid({
1060
+ data: { uids: data.uid },
1061
+ config: {
1062
+ url: getUrlPrefix(),
1063
+ token: configId,
1064
+ enableMock: false,
1065
+ },
1066
+ });
1067
+ if (res?.length) {
1068
+ const d = res[0];
1069
+ setCurFileInfo({
1070
+ ...data,
1071
+ parse_url: d.convertedFilePath,
1072
+ });
1073
+
1074
+ setDrawerType('preview');
1075
+ setOpen(true);
1076
+ }
1077
+ } catch (error) {}
1078
+ };
1079
+ const mergedEventsEmit = (eventName: string, data: any) => {
1080
+ console.log(`[Event] ${eventName}`, data);
1081
+ switch (eventName) {
1082
+ case 'reference_file:click':
1083
+ if (data.file_type === 'image') return;
1084
+
1085
+ if (!data.parse_url) {
1086
+ if (!data.uid) {
1087
+ if (data.references) return;
1088
+ return message.warning('文件还未解析完成,请稍后预览');
1089
+ }
1090
+ referenceFile_question_upload(data);
1091
+ return;
1092
+ }
1093
+ setCurFileInfo({
1094
+ ...data,
1095
+ file_type: data.type,
1096
+ file_name: data.file,
1097
+ });
1098
+ setDrawerType('preview');
1099
+ setOpen(true);
1100
+
1101
+ break;
1102
+ case 'referenceFile_question':
1103
+ referenceFile_question_upload(data.data);
1104
+ break;
1105
+ case 'retrive_tag:click':
1106
+ if (data?.graph?.id) {
1107
+ //图谱溯源
1108
+ setDrawerType('graphPreview');
1109
+ setCurFileInfo({ ...data.graph, traceNode: data.traceNode });
1110
+ setOpen(true);
1111
+ return;
1112
+ }
1113
+ if (!data?.tagInfo && !data?.file && !data?.referenceDoc) return;
1114
+ const curFile = data?.referenceDoc || data?.file || data?.tagInfo;
1115
+ const { pdfPages, parsedFilePath, order_num, metadata, tags } = curFile;
1116
+ if (curFile.type == 'video') {
1117
+ setCurFileInfo({
1118
+ ...metadata,
1119
+ displayType: 'retrive',
1120
+ });
1121
+ setDrawerType('video');
1122
+ setOpen(true);
1123
+ return;
1124
+ }
1125
+
1126
+ const id = curFile.id || order_num || 'Unknown ID';
1127
+ // 解析 source 字段
1128
+ const sourceParts = metadata.source.split('/');
1129
+ const fileName = sourceParts[sourceParts.length - 1] || '';
1130
+ const fileParts = fileName.split('.');
1131
+ const label = fileParts.slice(0, -1).join('.') || 'Unknown Label';
1132
+ const file_type = fileParts[fileParts.length - 1] || 'Unknown Type';
1133
+
1134
+ setContent({
1135
+ id,
1136
+ title: label,
1137
+ file_name: fileName,
1138
+ file_type: file_type,
1139
+ file_source: getMarkExcludeType(file_type)
1140
+ ? `${TempBaseUrl}${metadata.source}`
1141
+ : parsedFilePath,
1142
+ metadata: metadata,
1143
+ pdfPages: pdfPages,
1144
+ });
1145
+ setDrawerType('mark');
1146
+ setOpen(true);
1147
+ break;
1148
+ // --- 对话列表事件 ---
1149
+ case 'conversation:item_click':
1150
+ handleItemClick(data);
1151
+ break;
1152
+
1153
+ case 'files:finished_animation_complete':
1154
+ setFileStatuses([]);
1155
+ break;
1156
+
1157
+ case 'conversation:create':
1158
+ handleConversationCreate(data);
1159
+ break;
1160
+ case 'conversation:new_assistant_change':
1161
+ // headlessChat 不需要处理助手切换
1162
+ break;
1163
+
1164
+ // --- 发送器事件 ---
1165
+ case 'sender:send':
1166
+ handleSenderSend(data, data.clearFn);
1167
+ break;
1168
+ case 'sender:send_recommandQuestion':
1169
+ // 点击推荐问题,视作一次普通发送
1170
+ handleSenderSend(data, data.clearFn);
1171
+ break;
1172
+ case 'sender:action_upload':
1173
+ // 触发文件上传按钮点击,打开文件选择对话框
1174
+ if (fileInputRef.current) {
1175
+ fileInputRef.current.click();
1176
+ }
1177
+ break;
1178
+ case 'fileManager:change':
1179
+ // 接收核心组件提供的文件管理方法引用
1180
+ if (data) {
1181
+ setUploadedFilesRef.current = data.setUploadedFiles;
1182
+ removeFileRef.current = data.removeFile;
1183
+ }
1184
+ break;
1185
+ case 'sender:stop':
1186
+ setStopFlag(); // 使用 hook 提供的停止函数
1187
+ if (uploadController) {
1188
+ uploadController.abort();
1189
+ uploadController = null;
1190
+ }
1191
+ if (stopPoll) {
1192
+ stopPoll();
1193
+ stopPoll = null;
1194
+ }
1195
+ setAppStatus(pre => ({ ...pre, display: 'ready', sender: 'ready' }));
1196
+ setFileStatuses([]);
1197
+ stopStream?.();
1198
+ break;
1199
+ case 'sender:clear':
1200
+ data.clearFn?.();
1201
+ break;
1202
+ case 'sender:configChange':
1203
+ // 配置项变化,记录最新 switch 状态
1204
+ if (data && data.all) {
1205
+ setSenderSwitchValues(data.all);
1206
+ }
1207
+ break;
1208
+ case 'chatbox:follow_up_question_click': {
1209
+ // 新链路:来自 ChatMessageAdapter 的追问点击
1210
+ const content = (data?.question ?? data?.content ?? '').toString();
1211
+ if (!content) break;
1212
+ handleSenderSend({ content }, () => {});
1213
+ break;
1214
+ }
1215
+ case 'chatbox:copy': {
1216
+ const text = data?.content || '';
1217
+ if (!text) break;
1218
+ // 直接使用 Clipboard API,避免依赖 event.currentTarget
1219
+ const copyByClipboardApi = async (t: string) => {
1220
+ try {
1221
+ await navigator.clipboard.writeText(t);
1222
+ message.success('已复制到剪贴板');
1223
+ return true;
1224
+ } catch (_) {
1225
+ return false;
1226
+ }
1227
+ };
1228
+ const fallbackCopy = (t: string) => {
1229
+ const textarea = document.createElement('textarea');
1230
+ textarea.value = t;
1231
+ textarea.style.position = 'fixed';
1232
+ textarea.style.opacity = '0';
1233
+ document.body.appendChild(textarea);
1234
+ textarea.focus();
1235
+ textarea.select();
1236
+ try {
1237
+ document.execCommand('copy');
1238
+ message.success('已复制到剪贴板');
1239
+ } catch (_) {
1240
+ message.error('复制失败');
1241
+ }
1242
+ document.body.removeChild(textarea);
1243
+ };
1244
+ copyByClipboardApi(text).then(ok => {
1245
+ if (!ok) fallbackCopy(text);
1246
+ });
1247
+ break;
1248
+ }
1249
+ case 'chatbox:repeat':
1250
+ handleReSenderSend(data);
1251
+ break;
1252
+ case 'chatbox:like':
1253
+ handleThumbsup({ queryId: data?.queryId, restult: data?.result });
1254
+ break;
1255
+ case 'chatbox:dislike':
1256
+ if (data?.result === 0) {
1257
+ handleCancelFeedBack({ url, token }, { queryId: data.queryId }, () => {
1258
+ setFeed(data.queryId, null);
1259
+ });
1260
+ } else {
1261
+ setFeedParam({ queryId: data?.queryId });
1262
+ setOpenFeed(true);
1263
+ }
1264
+ break;
1265
+
1266
+ // --- 消息卡片事件 ---
1267
+ case 'action_copy:click':
1268
+ if (data) {
1269
+ toCopy(data.content, data.event);
1270
+ }
1271
+ break;
1272
+ case 'action_retry':
1273
+ handleReSenderSend(data);
1274
+ break;
1275
+ case 'action_thumbsup':
1276
+ handleThumbsup(data);
1277
+ break;
1278
+ case 'references:click': {
1279
+ // 使用 AiChat 内置的右侧栏事件
1280
+ const Content = () => {
1281
+ // const safe = (() => {
1282
+ // try {
1283
+ // return typeof data === 'string' ? data : JSON.stringify(data, null, 2);
1284
+ // } catch (e) {
1285
+ // return String(data);
1286
+ // }
1287
+ // })();
1288
+ return (
1289
+ <ReferenceBar
1290
+ type={DrawerType.REFERENCELIST}
1291
+ data={data}
1292
+ token={token}
1293
+ is_download={is_download}
1294
+ />
1295
+ );
1296
+ };
1297
+ const setContentEvent = new CustomEvent('aichat:right_set_content', {
1298
+ detail: { content: <Content /> },
1299
+ });
1300
+ window.dispatchEvent(setContentEvent);
1301
+ const openEvent = new CustomEvent('aichat:right_set', {
1302
+ detail: { collapsed: false },
1303
+ });
1304
+ window.dispatchEvent(openEvent);
1305
+ break;
1306
+ }
1307
+ case 'action_thumbsdown':
1308
+ if (data.restult === 0) {
1309
+ handleCancelFeedBack({ url, token }, { queryId: data.queryId }, () => {
1310
+ setFeed(data.queryId, null);
1311
+ });
1312
+ } else {
1313
+ setFeedParam(data);
1314
+ setOpenFeed(true);
1315
+ }
1316
+ break;
1317
+ // --- 文件管理器上传文件删除事件,透传到业务层 ---
1318
+ case 'uploaded_file:removeFromTemp': {
1319
+ console.log('[DEBUG] mergedEventsEmit uploaded_file:removeFromTemp 事件', data);
1320
+
1321
+ const uid = data?.file?.uid;
1322
+ if (!uid) {
1323
+ console.warn('[DEBUG] 缺少uid,无法删除临时文件', data);
1324
+ break;
1325
+ }
1326
+ // 获取当前对话最后一条用户消息的 queryId
1327
+ const lastUserMsg = [...(currentChatData?.messages || [])]
1328
+ .reverse()
1329
+ .find((msg: any) => msg.istype === 'user');
1330
+ const queryId = lastUserMsg?.queryId;
1331
+ console.log('[DEBUG] mergedEventsEmit 获取到 queryId', queryId, lastUserMsg);
1332
+
1333
+ // 使用统一的文件删除处理函数
1334
+ handleRemoveFromTemp(uid, queryId);
1335
+ break;
1336
+ }
1337
+ case 'uploaded_file:item_click': {
1338
+ // 处理文件列表项点击,触发预览
1339
+ const file = data?.file;
1340
+ if (!file) break;
1341
+
1342
+ // 获取文件信息
1343
+ const fileName = file.name || file.file?.name || '';
1344
+ const fileExt = fileName.split('.').pop()?.toLowerCase() || '';
1345
+ const fileType = file.type || fileExt;
1346
+
1347
+ // 构建预览数据,参考 referenceFile_question_upload 的逻辑
1348
+ const previewData = {
1349
+ ...file,
1350
+ file_type: fileType,
1351
+ file_name: fileName,
1352
+ uid: file.uid,
1353
+ url: file.url || file.preview,
1354
+ convertedFilePath: file.convertedFilePath,
1355
+ metadata: file.metadata || { source: file.url || file.preview },
1356
+ };
1357
+
1358
+ // 如果是图片等可直接预览的类型,直接打开预览
1359
+ if (fileViewTypes.includes(fileExt)) {
1360
+ // 关闭文件列表 Popover,避免与预览 Drawer 重叠
1361
+ setFileListPopoverOpenRef.current?.(false);
1362
+ setCurFileInfo({
1363
+ ...previewData,
1364
+ parse_url: previewData.url || previewData.metadata?.source
1365
+ ? `${TempBaseUrl}${previewData.metadata.source}`
1366
+ : previewData.url || previewData.convertedFilePath || '',
1367
+ });
1368
+ setDrawerType('preview');
1369
+ setOpen(true);
1370
+ break;
1371
+ }
1372
+
1373
+ // 如果有转换后的文件路径,直接使用
1374
+ if (previewData.convertedFilePath) {
1375
+ // 关闭文件列表 Popover,避免与预览 Drawer 重叠
1376
+ setFileListPopoverOpenRef.current?.(false);
1377
+ setCurFileInfo({
1378
+ ...previewData,
1379
+ parse_url: previewData.convertedFilePath,
1380
+ });
1381
+ setDrawerType('preview');
1382
+ setOpen(true);
1383
+ break;
1384
+ }
1385
+
1386
+ // 如果有 uid,尝试获取文件状态(参考 referenceFile_question_upload)
1387
+ if (previewData.uid) {
1388
+ api_getFileStatusByUid({
1389
+ data: { uids: previewData.uid },
1390
+ config: {
1391
+ url: getUrlPrefix(),
1392
+ token: configId || token,
1393
+ enableMock: false,
1394
+ },
1395
+ })
1396
+ .then((res: any) => {
1397
+ // 关闭文件列表 Popover,避免与预览 Drawer 重叠
1398
+ setFileListPopoverOpenRef.current?.(false);
1399
+ if (res?.length) {
1400
+ const fileStatus = res[0];
1401
+ setCurFileInfo({
1402
+ ...previewData,
1403
+ parse_url: fileStatus.convertedFilePath || previewData.url || '',
1404
+ });
1405
+ setDrawerType('preview');
1406
+ setOpen(true);
1407
+ } else {
1408
+ // 如果没有获取到状态,尝试直接使用 url
1409
+ if (previewData.url) {
1410
+ setCurFileInfo({
1411
+ ...previewData,
1412
+ parse_url: previewData.url,
1413
+ });
1414
+ setDrawerType('preview');
1415
+ setOpen(true);
1416
+ }
1417
+ }
1418
+ })
1419
+ .catch((error: any) => {
1420
+ console.error('获取文件状态失败:', error);
1421
+ // 关闭文件列表 Popover
1422
+ setFileListPopoverOpenRef.current?.(false);
1423
+ // 失败时尝试直接使用 url
1424
+ if (previewData.url) {
1425
+ setCurFileInfo({
1426
+ ...previewData,
1427
+ parse_url: previewData.url,
1428
+ });
1429
+ setDrawerType('preview');
1430
+ setOpen(true);
1431
+ }
1432
+ });
1433
+ } else if (previewData.url) {
1434
+ // 没有 uid 但有 url,直接使用
1435
+ // 关闭文件列表 Popover,避免与预览 Drawer 重叠
1436
+ setFileListPopoverOpenRef.current?.(false);
1437
+ setCurFileInfo({
1438
+ ...previewData,
1439
+ parse_url: previewData.url,
1440
+ });
1441
+ setDrawerType('preview');
1442
+ setOpen(true);
1443
+ }
1444
+ break;
1445
+ }
1446
+
1447
+ default:
1448
+ // 其他未处理事件可继续向上层抛出
1449
+ eventsEmit?.(eventName, data);
1450
+ }
1451
+ };
1452
+
1453
+ // =================================================================
1454
+ // Component & UI Configuration - 组件及UI配置
1455
+ // =================================================================
1456
+
1457
+ /**
1458
+ * Sidebar 配置 - headlessChat 不需要左侧历史对话栏
1459
+ */
1460
+ const sidebar = undefined;
1461
+
1462
+ /**
1463
+ * 业务层自定义组件
1464
+ */
1465
+ const businessCustomComponents: { [key: string]: any } = {
1466
+ AiChatBox: (msg: any, idx: number) => {
1467
+ const isLastMessage = idx === (currentChatData?.messages?.length || 0) - 1;
1468
+
1469
+ // 根据消息的 status 和 appStatus 来确定 displayStatus
1470
+ let displayStatus = 'ready';
1471
+ if (isLastMessage) {
1472
+ // 优先使用消息的 status 字段
1473
+ if (msg.status !== undefined) {
1474
+ switch (msg.status) {
1475
+ case 0: // StatusType.Pending
1476
+ displayStatus = 'thinking';
1477
+ break;
1478
+ case 1: // StatusType.Process
1479
+ displayStatus = 'processing';
1480
+ break;
1481
+ case 2: // StatusType.Done
1482
+ displayStatus = 'ready';
1483
+ break;
1484
+ case 3: // StatusType.Error
1485
+ displayStatus = 'error';
1486
+ break;
1487
+ default:
1488
+ displayStatus = appStatus.display;
1489
+ }
1490
+ } else {
1491
+ // 如果没有 status 字段,使用 appStatus.display
1492
+ displayStatus = appStatus.display;
1493
+ }
1494
+ }
1495
+ // 将推荐问题强制转换为数组,避免切换会话时报 .map 错误
1496
+ let safeFollowUps: string[] = [];
1497
+ if (Array.isArray(recommandQuestions)) {
1498
+ safeFollowUps = recommandQuestions as string[];
1499
+ } else if (typeof recommandQuestions === 'string') {
1500
+ try {
1501
+ safeFollowUps = JSON.parse(recommandQuestions) || [];
1502
+ } catch {
1503
+ safeFollowUps = [];
1504
+ }
1505
+ }
1506
+ // 保证传入 ChatMessageAdapter 的 reference/webReference/graphReference 始终是有效的 JSON 字符串
1507
+ const safeReference = (() => {
1508
+ const r = (msg as any)?.reference;
1509
+ if (!r) return '[]';
1510
+ if (typeof r === 'string') {
1511
+ try {
1512
+ JSON.parse(r);
1513
+ return r;
1514
+ } catch {
1515
+ return '[]';
1516
+ }
1517
+ }
1518
+ try {
1519
+ return JSON.stringify(r);
1520
+ } catch {
1521
+ return '[]';
1522
+ }
1523
+ })();
1524
+ const safeWebReference = (() => {
1525
+ const r = (msg as any)?.webReference;
1526
+ if (!r) return '[]';
1527
+ if (typeof r === 'string') {
1528
+ try {
1529
+ JSON.parse(r);
1530
+ return r;
1531
+ } catch {
1532
+ return '[]';
1533
+ }
1534
+ }
1535
+ try {
1536
+ return JSON.stringify(r);
1537
+ } catch {
1538
+ return '[]';
1539
+ }
1540
+ })();
1541
+ const safeGraphReference = (() => {
1542
+ const r = (msg as any)?.graphReference;
1543
+ if (!r) return '[]';
1544
+ if (typeof r === 'string') {
1545
+ try {
1546
+ JSON.parse(r);
1547
+ return r;
1548
+ } catch {
1549
+ return '[]';
1550
+ }
1551
+ }
1552
+ try {
1553
+ return JSON.stringify(r);
1554
+ } catch {
1555
+ return '[]';
1556
+ }
1557
+ })();
1558
+ return (
1559
+ <ChatMessageAdapter
1560
+ key={`${msg?.queryId || msg?.id || idx}-${msg?.istype || 'unknown'}`}
1561
+ {...msg}
1562
+ referenceMode="button"
1563
+ reference={safeReference}
1564
+ webReference={safeWebReference}
1565
+ graphReference={safeGraphReference}
1566
+ contentType="stream"
1567
+ displayStatus={displayStatus}
1568
+ eventsEmit={mergedEventsEmit}
1569
+ // 仅最后一条 AI 消息展示追问
1570
+ isLast={isLastMessage && msg?.istype === 'ai'}
1571
+ followUpQuestions={isLastMessage && msg?.istype === 'ai' ? safeFollowUps : []}
1572
+ defaultAnswer={
1573
+ currentChatData?.searchConfigDTO?.defaultAnswer ||
1574
+ '不知道什么原因我们没能查到你要的问题*-*'
1575
+ }
1576
+ styles={styles}
1577
+ />
1578
+ );
1579
+ },
1580
+ LogoBox: () => <div className="text-2xl w-full text-center font-bold">小鲸智能助手</div>,
1581
+ DisplayLoading: () => <DisplayLoading />,
1582
+ DisplayError: () => <DisplayError />,
1583
+ AppError: () => <AppError />,
1584
+ AppLoading: () => <AppLoading />,
1585
+ UserChatBox: (msg: any, idx: number) => {
1586
+ return (
1587
+ <ChatMessageAdapter
1588
+ key={`${msg?.queryId || msg?.id || idx}-user`}
1589
+ {...msg}
1590
+ referenceMode="button"
1591
+ contentType="plainText"
1592
+ isUser={true}
1593
+ displayStatus="ready"
1594
+ eventsEmit={mergedEventsEmit}
1595
+ styles={styles}
1596
+ fileManagerData={fileManagerData}
1597
+ />
1598
+ );
1599
+ },
1600
+ WelcomeComponent: () => (
1601
+ <GientechNewChatWelcome
1602
+ eventsEmit={mergedEventsEmit}
1603
+ assistantList={assistantList}
1604
+ styles={styles}
1605
+ // defaultSelect={newAssistantId || assistantList[0]?.id}
1606
+ />
1607
+ ),
1608
+ };
1609
+
1610
+ /**
1611
+ * 合并外部传入和业务默认的自定义组件,外部优先
1612
+ */
1613
+ const mergedCustomComponents: { [key: string]: any } = { ...businessCustomComponents };
1614
+ if (rest.CustomComponents) {
1615
+ Object.keys(rest.CustomComponents).forEach(key => {
1616
+ const val = rest.CustomComponents?.[key];
1617
+ if (val !== undefined) {
1618
+ mergedCustomComponents[key] = val;
1619
+ }
1620
+ });
1621
+ }
1622
+
1623
+ // 使用统一的文件展示工具 Hook
1624
+ const { FileDisplayTools, setFileListPopoverOpen } = useFileDisplayTools({
1625
+ fileManagerData,
1626
+ styles,
1627
+ eventsEmit: mergedEventsEmit,
1628
+ });
1629
+ // 保存 setFileListPopoverOpen 到 ref,以便在 mergedEventsEmit 中访问
1630
+ setFileListPopoverOpenRef.current = setFileListPopoverOpen;
1631
+
1632
+ // =================================================================
1633
+ // Render - 渲染
1634
+ // =================================================================
1635
+ return (
1636
+ <>
1637
+ {/* 隐藏的文件上传input */}
1638
+ <input
1639
+ ref={fileInputRef}
1640
+ type="file"
1641
+ multiple
1642
+ accept="image/*,.pdf,.doc,.docx,.ppt,.pptx,.xls,.xlsx,.txt,.md,.json,.csv,.zip,.rar,.7z"
1643
+ style={{ display: 'none' }}
1644
+ onChange={handleFileSelect}
1645
+ />
1646
+
1647
+ <WrappedComponent
1648
+ activeSessionId={activeSessionId}
1649
+ status={appStatus}
1650
+ chatData={currentChatData}
1651
+ sidebar={sidebar}
1652
+ recommandQuestions={[]}
1653
+ eventsEmit={mergedEventsEmit}
1654
+ styles={styles}
1655
+ senderConfig={dynamicSenderConfig}
1656
+ fileUploadStatus={fileStatuses}
1657
+ CustomComponents={mergedCustomComponents}
1658
+ rightbarWidth={'400px'}
1659
+ chatHeader={{
1660
+ title: rest.title || '智能助手',
1661
+ tools: FileDisplayTools,
1662
+ }}
1663
+ />
1664
+ <FeedBackModal
1665
+ open={openFeed}
1666
+ feedBackList={feedBackList}
1667
+ setOpen={setOpenFeed}
1668
+ handleConfirm={handleFeedDownConfirm}
1669
+ />
1670
+ <ConfigProvider
1671
+ drawer={{
1672
+ styles: {
1673
+ body: { padding: 0 },
1674
+ header: { display: 'none' },
1675
+ },
1676
+ }}
1677
+ >
1678
+ <Drawer
1679
+ autoFocus={false}
1680
+ onClose={() => setOpen(false)}
1681
+ open={open}
1682
+ width={drawerType === 'graphPreview' ? '100%' : 720}
1683
+ className="overflow-x-hidden"
1684
+ destroyOnClose
1685
+ afterOpenChange={open => {
1686
+ if (!open) {
1687
+ setContent(null);
1688
+ }
1689
+ }}
1690
+ >
1691
+ {drawerType === 'preview' && (
1692
+ <DrawerPreview curFileInfo={curFileInfo} onClose={setOpen} token={token} />
1693
+ )}
1694
+ {drawerType === 'mark' && (
1695
+ <DrawerContent
1696
+ content={content as any}
1697
+ onClose={() => setOpen(false)}
1698
+ token={token}
1699
+ />
1700
+ )}
1701
+ </Drawer>
1702
+ </ConfigProvider>
1703
+ </>
1704
+ );
1705
+ };
1706
+ }