@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,2054 @@
1
+ import React, { useEffect, useState, useMemo, useRef } from 'react';
2
+ import axios from 'axios';
3
+ import { AiChat, type AppStatusManager } from '@mxmweb/aichat';
4
+ import { ChatMessageAdapter } from '@mxmweb/rtext';
5
+ import { message } from 'antd';
6
+ import { uid } from 'uid';
7
+ import { History } from 'lucide-react';
8
+
9
+ import { defaultTheme, deepMergeTheme, type Styles } from '@mxmweb/zui';
10
+ import { DefaultSenderConfig } from './constants';
11
+ import GientechConversationPanel from './Conversations';
12
+ import {
13
+ convertQueryReplyPairListToMessages,
14
+ getFileTypeByName,
15
+ toCopy,
16
+ } from '../../utils/commonFn';
17
+ import {
18
+ getFeedbackList,
19
+ handleCancelFeedBack,
20
+ handleDeleteConversation,
21
+ handleFeedBack,
22
+ handleRename,
23
+ handleCreateConversation,
24
+ } from '../../utils/gientechCommon/utils/serverFn';
25
+
26
+ import GientechNewChatWelcome from '../../utils/gientechCommon/components/Messages/GientechNewChatWelcome';
27
+ import AichatUseController from '../../utils/gientechCommon/hooks/AichatUseController';
28
+ import { useFileManager } from '../../utils/gientechCommon/hooks/useFileManager';
29
+ import { useFileDisplayTools } from '../../utils/gientechCommon/hooks/useFileDisplayTools';
30
+ import FeedBackModal from '../../utils/gientechCommon/components/FeedBackModal';
31
+ import FileManager from '../../utils/gientechCommon/components/FileManager';
32
+ import AppLoading from '../../utils/gientechCommon/components/AppLoading';
33
+ import { Icon } from '@mxmweb/zui';
34
+ import DisplayLoading from '../../utils/gientechCommon/components/DisplayLoading';
35
+ import AppError from '../../utils/gientechCommon/components/AppError';
36
+ import DisplayError from '../../utils/gientechCommon/components/DisplayError';
37
+ import { maxPollCount, maxPollInterval } from '../../utils/gientechCommon/configs/commonConfig';
38
+ import { mergeFiles } from '../../utils/gientechCommon/utils/fileUtils';
39
+ import { RenameModal } from '../../utils/gientechCommon/components/RenameModal';
40
+ import { DeleteModal } from '../../utils/gientechCommon/components/DeleteModal';
41
+ import { ReferenceBar } from './ReferenceBar';
42
+ import '@mxmweb/rtext/style.css';
43
+ import '@mxmweb/aichat/style.css';
44
+ import '@mxmweb/zui/style.css';
45
+ export enum DrawerType {
46
+ REFERENCELIST = 'referencelist',
47
+ }
48
+
49
+ interface SenderConfig {
50
+ actions?: Array<{
51
+ name: string;
52
+ icon?: React.ReactNode;
53
+ badgeCount?: number;
54
+ enabled?: boolean;
55
+ [key: string]: any;
56
+ }>;
57
+ switchs?: Array<{
58
+ name: string;
59
+ label: string;
60
+ enabled?: boolean;
61
+ [key: string]: any;
62
+ }>;
63
+ [key: string]: any;
64
+ }
65
+
66
+ // 自定义组件接口
67
+ interface CustomComponents {
68
+ LogoBox?: React.ComponentType<any> | null;
69
+ AiChatBox?: React.ComponentType<any>;
70
+ UserChatBox?: React.ComponentType<any>;
71
+ WelcomeComponent?: React.ComponentType<any>;
72
+ DisplayLoading?: React.ComponentType<any>;
73
+ DisplayError?: React.ComponentType<any>;
74
+ AppError?: React.ComponentType<any>;
75
+ AppLoading?: React.ComponentType<any>;
76
+ [key: string]: React.ComponentType<any> | null | undefined;
77
+ }
78
+
79
+ interface GientechChatAdopterProps {
80
+ token: string;
81
+ url?: string;
82
+ CSRFToken?: string;
83
+ styles?: Styles;
84
+ eventsEmit?: (eventName: string, data: any) => void;
85
+ CustomComponents?: CustomComponents;
86
+ senderConfig?: SenderConfig;
87
+ appLoadingConfig?: {
88
+ title?: string;
89
+ subtitle?: string;
90
+ };
91
+ [key: string]: any;
92
+ }
93
+
94
+ export interface SidebarTabConfig {
95
+ key: string;
96
+ type: 'custom' | 'form';
97
+ label: string;
98
+ icon: React.ReactNode;
99
+ enabled?: boolean;
100
+ component?: React.ReactNode; // type=custom
101
+ formConfig?: any[]; // type=form
102
+ initialValues?: Record<string, any>;
103
+ onChange?: (values: any) => void;
104
+ [key: string]: any;
105
+ }
106
+
107
+ export default function withGientechChatAdopter(WrappedComponent = AiChat) {
108
+ /**
109
+ * GientechChatAdopter 高阶组件
110
+ * 封装了与Gientech后端服务的接口对接、状态管理、事件处理等核心业务逻辑
111
+ * @param {React.ComponentType} WrappedComponent - 需要包裹的基础聊天组件,默认为 AiChat
112
+ */
113
+ return function GientechChatAdopter({
114
+ token,
115
+ url = 'http://localhost:8888',
116
+ styles,
117
+ CSRFToken,
118
+ eventsEmit,
119
+ scrollOld,
120
+ ...rest
121
+ }: GientechChatAdopterProps) {
122
+ // 统一归一化样式:使用 UI 库的 deepMergeTheme
123
+ const normalizedStyles: Styles = useMemo(() => {
124
+ const baseStyles: Styles = { theme: defaultTheme, mode: 'light' };
125
+ return deepMergeTheme(baseStyles, styles);
126
+ }, [styles]);
127
+ const [isRenameModalOpen, setisRenameModalOpen] = useState(false);
128
+ const [isRemoveModalOpen, setisRemoveModalOpen] = useState(false);
129
+ const [curEditData, setCurEditData] = useState<any>(null);
130
+ // =================================================================
131
+ // State Management - 状态管理
132
+ // =================================================================
133
+ const [conversationList, setConversationList] = useState<any[]>([]); // 对话列表
134
+ // 会话列表分页状态
135
+ const [convPageNo, setConvPageNo] = useState<number>(1);
136
+ const [convPageSize] = useState<number>(16);
137
+ const [convTotal, setConvTotal] = useState<number>(0);
138
+ const [convLoading, setConvLoading] = useState<boolean>(false);
139
+ const [chatData, setChatData] = useState<any[]>([]); // 所有对话的聊天记录
140
+ const [activeSessionId, setActiveSessionId] = useState<string | undefined>(undefined); // 当前激活的对话ID
141
+ const [recommandQuestions, setRecommandQuestions] = useState<any[]>([]); // 推荐问题列表
142
+ const [assistantList, setAssistantList] = useState<any[]>([]); // 智能体(助手)列表
143
+ const [newAssistantId, setNewAssistantId] = useState<string | undefined>(undefined); // 新建对话时选择的智能体ID
144
+ const [appStatus, setAppStatus] = useState<AppStatusManager>({
145
+ display: 'ready', // 主聊天区域状态: ready, loading, processing, error, uploading
146
+ sender: 'ready', // 发送器状态: ready, processing, error, uploading
147
+ app: 'initializing', // 应用整体状态: initializing, ready, error
148
+ });
149
+ // 发送器配置支持外部传入
150
+ // 深度合并工具,customConfig 有的字段覆盖 defaultConfig
151
+ function deepMerge(defaultConfig: any, customConfig: any): any {
152
+ if (!customConfig) return defaultConfig;
153
+ const result: any = Array.isArray(defaultConfig) ? [...defaultConfig] : { ...defaultConfig };
154
+ for (const key in customConfig) {
155
+ if (
156
+ customConfig[key] !== undefined &&
157
+ customConfig[key] !== null &&
158
+ Object.prototype.hasOwnProperty.call(customConfig, key)
159
+ ) {
160
+ // 特殊处理 actions 数组:合并而不是替换
161
+ if (key === 'actions' && Array.isArray(defaultConfig[key]) && Array.isArray(customConfig[key])) {
162
+ // 创建一个以 name 为 key 的 Map,优先使用 customConfig 中的 action
163
+ const actionsMap = new Map();
164
+ // 先添加 defaultConfig 中的 actions
165
+ defaultConfig[key].forEach((action: any) => {
166
+ if (action.name) {
167
+ actionsMap.set(action.name, action);
168
+ }
169
+ });
170
+ // 然后用 customConfig 中的 actions 覆盖或添加
171
+ customConfig[key].forEach((action: any) => {
172
+ if (action.name) {
173
+ actionsMap.set(action.name, action);
174
+ }
175
+ });
176
+ result[key] = Array.from(actionsMap.values());
177
+ } else if (
178
+ typeof defaultConfig[key] === 'object' &&
179
+ defaultConfig[key] !== null &&
180
+ !Array.isArray(defaultConfig[key]) &&
181
+ typeof customConfig[key] === 'object' &&
182
+ customConfig[key] !== null &&
183
+ !Array.isArray(customConfig[key])
184
+ ) {
185
+ result[key] = deepMerge(defaultConfig[key], customConfig[key]);
186
+ } else {
187
+ result[key] = customConfig[key];
188
+ }
189
+ }
190
+ }
191
+ return result;
192
+ }
193
+
194
+ // 发送器配置支持外部传入,merge 默认和外部
195
+ const [dynamicSenderConfig, setDynamicSenderConfig] = useState(() => {
196
+ const merged = deepMerge(DefaultSenderConfig, rest.senderConfig);
197
+ console.log('[DEBUG] 初始化 senderConfig:', {
198
+ DefaultSenderConfig,
199
+ restSenderConfig: rest.senderConfig,
200
+ merged,
201
+ 'merged.actions': merged.actions,
202
+ 'merged.actions[0]': merged.actions?.[0],
203
+ 'merged.actions[0].icon': merged.actions?.[0]?.icon,
204
+ });
205
+ return merged;
206
+ });
207
+
208
+ // 监听外部 senderConfig 变化自动 merge
209
+ useEffect(() => {
210
+ if (rest.senderConfig) {
211
+ const merged = deepMerge(DefaultSenderConfig, rest.senderConfig);
212
+ console.log('[DEBUG] senderConfig 更新:', {
213
+ DefaultSenderConfig,
214
+ restSenderConfig: rest.senderConfig,
215
+ merged,
216
+ 'merged.actions': merged.actions,
217
+ 'merged.actions[0]': merged.actions?.[0],
218
+ 'merged.actions[0].icon': merged.actions?.[0]?.icon,
219
+ });
220
+ setDynamicSenderConfig(merged);
221
+ }
222
+ }, [rest.senderConfig]);
223
+ // 使用统一的文件管理 Hook
224
+ const {
225
+ fileManagerData,
226
+ fileStatuses,
227
+ setFileManagerData,
228
+ setFileStatuses,
229
+ pollFileStatus: pollFileStatusHook,
230
+ handleRemoveFromTemp,
231
+ resetStopFlag,
232
+ setStopFlag,
233
+ stoppedRef,
234
+ } = useFileManager({
235
+ url,
236
+ token,
237
+ CSRFToken,
238
+ activeSessionId,
239
+ chatData,
240
+ });
241
+
242
+ // 新增:全局管理助手选中状态,彻底解决子组件重建跳回默认
243
+ const [selectedAssistantId, setSelectedAssistantId] = React.useState(() => {
244
+ if (!assistantList || assistantList.length === 0) return undefined;
245
+ const defaultItem = assistantList.find(item => item.isDefault);
246
+ return defaultItem ? defaultItem.id : assistantList[0]?.id;
247
+ });
248
+
249
+ // 监听 assistantList 变化,自动选中默认助手
250
+ React.useEffect(() => {
251
+ if (!assistantList || assistantList.length === 0) return;
252
+ const exists = assistantList.some(item => item.id === selectedAssistantId);
253
+ if (!selectedAssistantId || !exists) {
254
+ const defaultItem = assistantList.find(item => item.isDefault);
255
+ setSelectedAssistantId(defaultItem ? defaultItem.id : assistantList[0].id);
256
+ }
257
+ }, [assistantList]);
258
+
259
+ // 新增:记录 sender switchs 的选中状态
260
+ const [senderSwitchValues, setSenderSwitchValues] = useState<{ [key: string]: boolean }>({});
261
+
262
+ // =================================================================
263
+ // Refs - 引用
264
+ // =================================================================
265
+ const prevActiveSessionId = useRef<string | undefined>(undefined); // 记录上一个激活的对话ID,用于对比变化
266
+ const setUploadedFilesRef = useRef<(files: any[]) => void>(undefined); // 引用核心组件的文件上传方法
267
+ const removeFileRef = useRef<(fileObj: any, idx: number, _type: string) => void>(undefined); // 引用核心组件的文件移除方法
268
+ const fileInputRef = useRef<HTMLInputElement>(null); // 文件上传input引用
269
+ const deleteData = useRef({ sessionId: '' }); // 暂存待删除的对话数据
270
+ const confirmed = useRef<any>(null); // 暂存 antd Modal.confirm 的引用
271
+ const [feedParam, setFeedParam] = useState<{
272
+ queryId?: number | string | undefined;
273
+ restult?: number;
274
+ }>({});
275
+ const [openFeed, setOpenFeed] = useState(false);
276
+ const [feedBackList, setFeedBackList] = useState<any>([]);
277
+ // FileManager Modal 状态
278
+ const [fileManagerOpen, setFileManagerOpen] = useState(false);
279
+ // stoppedRef 已由 useFileManager hook 管理
280
+
281
+ // =================================================================
282
+ // Custom Hooks - 自定义钩子
283
+ // =================================================================
284
+ // 引入SSE控制器,封装了流式请求、消息管理等复杂逻辑
285
+ const {
286
+ api_startChat_re,
287
+ addEmptyMessage,
288
+ setLastEmptyMessage,
289
+ stopStream,
290
+ cancelStreamBySessionId,
291
+ } = AichatUseController({
292
+ baseUrl: url,
293
+ CSRFToken: CSRFToken,
294
+ token,
295
+ setChatData,
296
+ activeSessionId,
297
+ setAppStatus,
298
+ setConversationList,
299
+ });
300
+
301
+ // =================================================================
302
+ // Memoized Values - 缓存计算值
303
+ // =================================================================
304
+
305
+ /**
306
+ * 将智能体列表转换为以ID为键的Map,便于快速查找
307
+ */
308
+ const assistantMap = useMemo(() => {
309
+ const map: Record<string, any> = {};
310
+ (assistantList || []).forEach(item => {
311
+ map[item.id] = item;
312
+ });
313
+ return map;
314
+ }, [assistantList]);
315
+
316
+ /**
317
+ * 根据 activeSessionId 从 chatData 中筛选出当前对话的数据, 并合并智能体信息
318
+ */
319
+ const currentChatData = useMemo(() => {
320
+ const data = chatData.find((c: any) => c.id === activeSessionId) || {
321
+ id: activeSessionId,
322
+ messages: [],
323
+ };
324
+ const assistantInfo = data.configId ? assistantMap[data.configId] : null;
325
+ return {
326
+ ...data,
327
+ assistantInfo,
328
+ };
329
+ }, [chatData, activeSessionId, assistantMap]);
330
+
331
+ const is_download = useMemo(() => {
332
+ if (currentChatData.configId && assistantMap) {
333
+ const curAssist = assistantMap[currentChatData.configId];
334
+ if (curAssist?.configJson) {
335
+ const config = JSON.parse(curAssist.configJson);
336
+ return config.is_download;
337
+ }
338
+ return false;
339
+ } else {
340
+ return false;
341
+ }
342
+ }, [currentChatData.configId, assistantMap]);
343
+
344
+ const is_enableThinking = useMemo(() => {
345
+ //console.log('is_enableThinking',currentChatData.configId,assistantMap,newAssistantId)
346
+ if ((currentChatData.configId || newAssistantId) && assistantMap) {
347
+ const curAssist = assistantMap[currentChatData.configId];
348
+ if (curAssist?.configJson) {
349
+ const config = JSON.parse(curAssist.configJson);
350
+ console.log('is_enableThinking', currentChatData.configId, assistantMap, newAssistantId);
351
+ return config.thinking ? true : false;
352
+ } else {
353
+ // const curAssist = assistantMap[newAssistantId]
354
+ //console.log('newAssistantId',newAssistantId)
355
+ if (newAssistantId) {
356
+ const curAssist = assistantMap[newAssistantId];
357
+ if (curAssist?.configJson) {
358
+ const config = JSON.parse(curAssist.configJson);
359
+ return config.thinking ? true : false;
360
+ }
361
+ }
362
+ }
363
+ }
364
+ return false;
365
+ }, [currentChatData.configId, assistantMap, newAssistantId]);
366
+
367
+ // 文件工具函数已提取到 useFileManager hook 中
368
+
369
+ // =================================================================
370
+ // Side Effects (useEffect) - 副作用钩子
371
+ // =================================================================
372
+
373
+ /**
374
+ * 跟踪 activeSessionId 的变化:
375
+ * 1. 清理上一个无消息的临时会话
376
+ * 2. 更新推荐问题
377
+ */
378
+ useEffect(() => {
379
+ // 切换会话时,检查上一个会话是否为临时且无消息
380
+ if (prevActiveSessionId.current && prevActiveSessionId.current !== activeSessionId) {
381
+ const prevId = prevActiveSessionId.current;
382
+ const prevConv = conversationList.find(c => c.sessionId === prevId && c.isNew);
383
+ const prevChat = chatData.find(c => c.id === prevId && c.isNew);
384
+ const prevHasMsg = prevChat && prevChat.messages && prevChat.messages.length > 0;
385
+ // 如果是临时的且没有消息,则自动删除
386
+ if (prevConv && !prevHasMsg) {
387
+ setConversationList(list => list.filter(c => c.sessionId !== prevId));
388
+ setChatData(list => list.filter(c => c.id !== prevId));
389
+ }
390
+ }
391
+ prevActiveSessionId.current = activeSessionId;
392
+
393
+ // 更新推荐问题为当前对话的最后一个推荐问题
394
+ const historyRQ =
395
+ currentChatData.messages && currentChatData.messages.length > 0
396
+ ? currentChatData.messages[currentChatData.messages.length - 1].recommendQuestion
397
+ : [];
398
+ setRecommandQuestions(historyRQ);
399
+ }, [activeSessionId, conversationList, chatData, currentChatData.messages]);
400
+
401
+ /**
402
+ * 拉取会话列表分页
403
+ */
404
+ const fetchConversationPage = async (pageNo: number) => {
405
+ if (convLoading) return;
406
+ setConvLoading(true);
407
+ try {
408
+ const res = await axios.get(
409
+ `${url}/qa/dialogue/list?pageNo=${pageNo}&pageSize=${convPageSize}&sysType=INT_SESSION`,
410
+ { headers: { Authorization: token } }
411
+ );
412
+ const records = res?.data?.data?.records || [];
413
+ const total = res?.data?.data?.total || 0;
414
+ setConvTotal(total);
415
+ setConversationList(prev => {
416
+ // 去重合并(以 sessionId 为准)
417
+ const map = new Map<string, any>();
418
+ [...prev, ...records].forEach((it: any) => {
419
+ map.set(it.sessionId, it);
420
+ });
421
+ return Array.from(map.values());
422
+ });
423
+ setConvPageNo(pageNo);
424
+ } catch (_) {
425
+ // 忽略,错误态交由上层处理
426
+ } finally {
427
+ setConvLoading(false);
428
+ }
429
+ };
430
+
431
+ /**
432
+ * 首次加载:并发拉取助手列表与会话第一页
433
+ */
434
+ useEffect(() => {
435
+ setAppStatus(prev => ({ ...prev, app: 'initializing' }));
436
+ // 助手列表
437
+ axios
438
+ .get(`${url}/qa/search/config/helper/list?pageNo=1&pageSize=3000`, {
439
+ headers: { Authorization: token },
440
+ })
441
+ .then(assistantRes => {
442
+ setAssistantList(assistantRes.data.data || []);
443
+ })
444
+ .catch(() => {});
445
+
446
+ // 会话第一页
447
+ fetchConversationPage(1)
448
+ .then(() => {
449
+ if (!activeSessionId) {
450
+ handleConversationCreate({});
451
+ }
452
+ setAppStatus(prev => ({ ...prev, app: 'ready' }));
453
+ })
454
+ .catch(() => {
455
+ setAppStatus(prev => ({ ...prev, app: 'error' }));
456
+ });
457
+ }, []);
458
+
459
+ /**
460
+ * 当 activeSessionId 变化时,获取对应的聊天记录
461
+ */
462
+ useEffect(() => {
463
+ if (!activeSessionId) return;
464
+
465
+ setAppStatus(prev => ({ ...prev, display: 'loading' }));
466
+ axios
467
+ .get(`${url}/qa/dialogue/getDialogBySessionId?sessionId=${activeSessionId}`, {
468
+ headers: { Authorization: token },
469
+ })
470
+ .then(res => {
471
+ // const r = {
472
+ // data: {
473
+ // success: true,
474
+ // errorMsg: null,
475
+ // errorCode: null,
476
+ // data: {
477
+ // label: '控制器起不来(控制器右上角RDY灯不亮)',
478
+ // sessionId: '3d752532b3d4510867288e5ac5cb3833',
479
+ // gmtCreate: '2025-11-06 14:30:43',
480
+ // gmtModified: '2025-11-06 14:30:43',
481
+ // createBy: 'admin',
482
+ // updateBy: 'admin',
483
+ // filePath: 'http://10.160.33.221:9000',
484
+ // configId: 703,
485
+ // kbId: null,
486
+ // queryReplyPairList: [
487
+ // {
488
+ // query: {
489
+ // id: 27665,
490
+ // gmtCreate: '2025-11-06T06:31:05.000+00:00',
491
+ // content: '控制器起不来(控制器右上角RDY灯不亮)',
492
+ // type: 'text',
493
+ // queryId: 'query-2532b3d4510867288e5ac5cb38338870',
494
+ // audioUrl: '',
495
+ // filePaths: null,
496
+ // currentFiles: null,
497
+ // },
498
+ // reply: {
499
+ // id: 27638,
500
+ // gmtCreate: '2025-11-06T06:31:15.000+00:00',
501
+ // content:
502
+ // '<think>\n好的,我现在需要回答用户的问题:“控制器起不来(控制器右上角RDY灯不亮)”。首先,我要查看提供的参考资料,看看是否有相关信息。\n\n参考资料0提到了现象“控制器起不来(控制器右上角RDY灯不亮)”,并关联到“包含”和“产生”关系,但没有提供处理方法。参考资料1和2都详细描述了这个现象,并且都给出了处理方法:检查NT255模块是否报红,更换模块,并特别提到机舱检查FS211/N模块。这些处理方法的ID都是10001,说明是同一个解决方案。\n\n用户的问题没有指定版本,根据规则2,我应该使用最新的版本信息。不过,参考资料中没有明确的版本日期,所以只能假设它们都是最新的。因此,我应该综合所有参考资料的信息,提供处理方法。\n\n最后,我需要按照要求,用简洁的语言回答,并标注来源。由于处理方法在参考资料1和2中都有,我会引用这两个来源。\n\n\n</think>\n\n控制器起不来(控制器右上角RDY灯不亮)的处理方法如下:\n\n1. 检查NT255模块是否报红(ERR灯是否亮红)[retrive-tag id="2290607639633892359" image_urls="" fromType="2"][1][/retrive-tag][retrive-tag id="439368868506707299" image_urls="" fromType="2"][2][/retrive-tag]。\n2. 更换NT255模块(注:机舱检查FS211/N模块)[retrive-tag id="2290607639633892359" image_urls="" fromType="2"][1][/retrive-tag][retrive-tag id="439368868506707299" image_urls="" fromType="2"][2][/retrive-tag]。\n\n这些步骤可以帮助解决控制器无法启动的问题。',
503
+ // type: 'text',
504
+ // reference: '[]',
505
+ // webReference: '[]',
506
+ // graphReference:
507
+ // '[{"cyphers":["match (n:`组件`)-[e]-(m) where id(n) == \\"组件101\\" return n,e,m;","match (n:`现象`)-[e]-(m) where id(n) == \\"现象1001\\" return n,e,m;","match (n:`现象`)-[e]-(m) where id(n) == \\"现象1001\\" return n,e,m;"],"fetchUrl":"https://10.160.33.150:30001/jinxin_graph/proxy/search/graph/search/cypherSearch4Rag","graphName":"故障处理图谱","id":"1952195957278367755","traceNodes":[{"graphId":"1952195957278367755","id":"3484145002858895870","name":"控制器","nodeType":"组件","vid":"组件101"},{"graphId":"1952195957278367755","id":"2290607639633892359","name":"控制器起不来(控制器右上角RDY灯不亮)","nodeType":"现象","vid":"现象1001"},{"graphId":"1952195957278367755","id":"439368868506707299","name":"控制器起不来(控制器右上角RDY灯不亮)","nodeType":"现象","vid":"现象1001"}]}]',
508
+ // queryId: 'query-2532b3d4510867288e5ac5cb38338870',
509
+ // feedbackResult: null,
510
+ // feedbackId: null,
511
+ // parsedFilePaths: null,
512
+ // recommendQuestion:
513
+ // '["在检查NT255模块时,ERR灯亮红可能表示哪些具体问题?","更换NT255模块时需要注意哪些步骤或注意事项?","除了检查和更换NT255模块,还有哪些方法可以排查控制器无法启动的问题?"]',
514
+ // llmType: 1,
515
+ // resultType: 1,
516
+ // respTime: 32453,
517
+ // tokens: 824,
518
+ // },
519
+ // },
520
+ // ],
521
+ // searchConfigDTO: {
522
+ // id: 703,
523
+ // status: 1,
524
+ // isDefault: 0,
525
+ // configJson:
526
+ // '{"status":1,"defaultAnswer":"您好!我注意到您的消息中可能包含了一些敏感的内容。为了保证社区的友好氛围,我们无法继续就此话题进行讨论。如果您有其他问题或需要帮助,我很乐意继续为您服务!谢谢您的理解和支持。","dependOnKb":1,"isDel":1,"config_name":"图谱测试","model_name":"deepseek-distill-qwen32b","plan_model_name":"deepseek-distill-qwen32b","prologue":"你好","system_prompt":"","recommend_model_name":"deepseek-distill-qwen32b","temperature":0.3,"top_p":0.8,"top_k":10,"score_threshold":0.1,"max_tokens":4096,"max_round":5,"rerank_model_name":"bge-reranker-v2-m3_xinference","knowledge_base_name_source":"","graph_nb_name":[{"graph_name":"故障处理图谱","index_name":"1952195957278367755"}],"thinking":false,"knowledge_base_name":"","selectedKnowledgeBase":[],"enabled_graph":false,"enabled_nb_graph":true,"need_trace_source":true,"trace_by_llm":true,"is_download":false,"search_policy":1,"enabled_question_rewrite":false,"enabled_faq":true,"enabled_rerank":true,"rerank":false}',
527
+ // name: '图谱测试',
528
+ // des: null,
529
+ // createBy: 'admin',
530
+ // gmtCreate: '2025-11-05 19:37:54',
531
+ // permission: null,
532
+ // sensitiveWordIds: '',
533
+ // defaultAnswer:
534
+ // '您好!我注意到您的消息中可能包含了一些敏感的内容。为了保证社区的友好氛围,我们无法继续就此话题进行讨论。如果您有其他问题或需要帮助,我很乐意继续为您服务!谢谢您的理解和支持。',
535
+ // model: false,
536
+ // enabledFaq: 0,
537
+ // dependOnKb: 1,
538
+ // isDel: 1,
539
+ // },
540
+ // },
541
+ // },
542
+ // };
543
+ // 如果会话没有数据(例如新建的),直接返回
544
+
545
+ if (!res.data.data) {
546
+ setAppStatus(prev => ({ ...prev, display: 'ready' }));
547
+ return;
548
+ }
549
+ // 解析消息数据
550
+ const queryReplyPairList = res.data.data?.queryReplyPairList || [];
551
+ const messages = convertQueryReplyPairListToMessages(queryReplyPairList);
552
+ const {
553
+ sessionId,
554
+ searchConfigDTO,
555
+ filePath,
556
+ configId,
557
+ reference,
558
+ webReference,
559
+ uploadedFiles,
560
+ fileList,
561
+ currentFiles,
562
+ } = res.data.data;
563
+
564
+ // === 文件数据补全 ===
565
+ // 1. reference(AI消息)
566
+ if (messages.length > 0) {
567
+ // 找到最后一条 AI 消息的索引(从后往前找)
568
+ const lastAiIndex =
569
+ messages.length - 1 - [...messages].reverse().findIndex(m => m.istype === 'ai');
570
+
571
+ // 确保找到的是有效的 AI 消息
572
+ if (
573
+ lastAiIndex >= 0 &&
574
+ lastAiIndex < messages.length &&
575
+ messages[lastAiIndex].istype === 'ai'
576
+ ) {
577
+ if (reference) {
578
+ messages[lastAiIndex].reference = reference;
579
+ }
580
+ if (webReference) {
581
+ messages[lastAiIndex].webReference = webReference;
582
+ }
583
+ }
584
+ }
585
+ // 2. currentFiles/fileList/uploadedFiles(user消息)
586
+ const filesData = currentFiles || fileList || uploadedFiles;
587
+ if (filesData && messages.length > 0) {
588
+ const lastUserIdx = [...messages].reverse().findIndex(m => m.istype === 'user');
589
+ if (lastUserIdx !== -1) {
590
+ console.log(messages[messages.length - 1 - lastUserIdx].currentFiles, 'filesData');
591
+ messages[messages.length - 1 - lastUserIdx].currentFiles = filesData;
592
+ }
593
+ }
594
+
595
+ // 更新对话列表中的对应项的元数据
596
+ const item = conversationList.find(item => item.sessionId === sessionId);
597
+ if (item) {
598
+ // console.log(item,'你大爷')
599
+ setConversationList(list =>
600
+ list.map(item =>
601
+ item.sessionId === sessionId
602
+ ? { ...item, searchConfigDTO, filePath, configId }
603
+ : item
604
+ )
605
+ );
606
+ }
607
+
608
+ // 构造当前对话的完整数据
609
+ const conversationConfig = {
610
+ configId: res.data.data.configId,
611
+ filePath: res.data.data.filePath,
612
+ searchConfigDTO: res.data.data.searchConfigDTO || {},
613
+ sessionId: res.data.data.sessionId,
614
+ label: res.data.data.label,
615
+ kbId: res.data.data.kbId,
616
+ };
617
+
618
+ // 更新聊天数据
619
+ setChatData(prev => {
620
+ const others = prev.filter((c: any) => c.id !== activeSessionId);
621
+
622
+ const newChatData = [
623
+ { id: activeSessionId, messages, ...conversationConfig },
624
+ ...others,
625
+ ];
626
+ return newChatData;
627
+ });
628
+ setAppStatus(prev => ({ ...prev, display: 'ready' }));
629
+ })
630
+ .catch(err => {
631
+ console.error('获取聊天数据失败:', err);
632
+ if (err.response && err.response.status === 404) {
633
+ // 404 表示会话在后端不存在,按新建会话处理
634
+ handleConversationCreate({});
635
+ }
636
+ setAppStatus(prev => ({ ...prev, display: 'error' }));
637
+ });
638
+ }, [activeSessionId, url, token]);
639
+
640
+ /**
641
+ * 监听文件数据变化,更新Sender上的角标
642
+ */
643
+ useEffect(() => {
644
+ const totalFiles = fileManagerData.uploadedFiles.length;
645
+ setDynamicSenderConfig((prev: any) => {
646
+ const actions = Array.isArray(prev.actions) ? prev.actions : [];
647
+ const newConfig = {
648
+ ...prev,
649
+ actions: actions.map((action: any) =>
650
+ action.name === 'history' ? { ...action, badgeCount: totalFiles } : action
651
+ ),
652
+ };
653
+ return newConfig;
654
+ });
655
+ }, [fileManagerData.uploadedFiles]);
656
+ useEffect(() => {
657
+ getFeedbackList({ url, token }, data => {
658
+ setFeedBackList(data);
659
+ });
660
+ }, []);
661
+
662
+ /**
663
+ * 实时同步 fileStatuses 到当前消息的 currentFiles,便于消息区细致显示每个文件的处理状态
664
+ */
665
+ React.useEffect(() => {
666
+ if (!fileStatuses.length) return;
667
+ setChatData(chatList => {
668
+ if (!chatList[0]) return chatList;
669
+ const { messages } = chatList[0];
670
+ for (let i = messages.length - 1; i >= 0; i--) {
671
+ if (messages[i].istype === 'user') {
672
+ const oldFiles = messages[i].currentFiles || [];
673
+ messages[i].currentFiles = fileStatuses.map((f: any) => {
674
+ const old = oldFiles.find((of: any) => of.uid === f.uid);
675
+ return {
676
+ name: f.file?.name || f.name,
677
+ size: f.file?.size || f.size,
678
+ type: f.file?.type || f.type,
679
+ status: f.status,
680
+ uid: f.uid,
681
+ url: f.url || old?.url || '', // 优先用已有 url
682
+ };
683
+ });
684
+ break;
685
+ }
686
+ }
687
+ return [...chatList];
688
+ });
689
+ }, [fileStatuses]);
690
+
691
+ // =================================================================
692
+ // Core Functions - 核心功能函数
693
+ // =================================================================
694
+
695
+ // 1. 在组件作用域定义控制器和轮询停止标志
696
+ let uploadController: AbortController | null = null;
697
+ let stopPoll: (() => void) | null = null;
698
+
699
+ /**
700
+ * 轮询文件解析状态(使用统一的 hook)
701
+ */
702
+ const pollFileStatus = (
703
+ uids: string[],
704
+ onStatusUpdate: (serverStatuses: any[]) => void,
705
+ onComplete: () => void,
706
+ onFail: (errorMsg: string) => void
707
+ ) => {
708
+ stopPoll = pollFileStatusHook(uids, {
709
+ onStatusUpdate,
710
+ onComplete,
711
+ onFail,
712
+ });
713
+ };
714
+
715
+ /**
716
+ * 设置消息的反馈结果(赞/踩)
717
+ * @param {string} queryId - 消息ID
718
+ * @param {number} result - 反馈结果 (1: 赞, -1: 踩)
719
+ */
720
+ const setFeed = (queryId: any, restult: any) => {
721
+ setChatData(chatList => {
722
+ const newList = [...chatList];
723
+ if (!newList[0]) return newList;
724
+ newList[0] = {
725
+ ...newList[0],
726
+ messages: newList[0].messages.map((item: any) => {
727
+ if (item.queryId === queryId) {
728
+ return {
729
+ ...item,
730
+ feedbackResult: restult,
731
+ };
732
+ }
733
+ return item;
734
+ }),
735
+ };
736
+ return newList;
737
+ });
738
+ };
739
+
740
+ /**
741
+ * 获取指定queryId的推荐问题
742
+ * @param {string} queryId - 消息ID
743
+ */
744
+ const fetchRecommendQuestions = async (queryId: string) => {
745
+ if (!queryId) return;
746
+ try {
747
+ // 需要携带当前 configId
748
+ const configId =
749
+ currentChatData?.configId || assistantMap?.[currentChatData?.configId || '']?.id || '';
750
+ const res = await axios.get(
751
+ `${url}/qa/ai/recommendQuestion?queryId=${queryId}&configId=${configId || ''}`.replace(
752
+ /\?&/,
753
+ '?'
754
+ ),
755
+ {
756
+ headers: { Authorization: token },
757
+ }
758
+ );
759
+ setRecommandQuestions(res.data?.data || []);
760
+ } catch (e) {
761
+ setRecommandQuestions([]);
762
+ }
763
+ };
764
+
765
+ /**
766
+ * 处理重新发送消息逻辑
767
+ */
768
+ function handleReSenderSend(data: any) {
769
+ const { content, audioUrl, filePaths, queryId } = data;
770
+ if (!activeSessionId || !queryId) return;
771
+
772
+ // 从当前会话中按 queryId 定位本次 AI 回复
773
+ const msgs = currentChatData?.messages || [];
774
+ const aiIdx = msgs.findIndex(
775
+ (m: any) => m?.queryId === queryId && (m?.istype === 'ai' || m?.isUser === false)
776
+ );
777
+
778
+ // 回溯上一条用户消息
779
+ let userMsg: any | undefined;
780
+ if (aiIdx > 0) {
781
+ for (let i = aiIdx - 1; i >= 0; i -= 1) {
782
+ const m = msgs[i];
783
+ if (m?.istype === 'user' || m?.isUser === true) {
784
+ userMsg = m;
785
+ break;
786
+ }
787
+ }
788
+ }
789
+
790
+ const question = userMsg?.content ?? content;
791
+ if (!question) return;
792
+
793
+ const sourceFilePaths = userMsg?.filePaths ?? filePaths;
794
+ const _fileUids = (
795
+ typeof sourceFilePaths === 'string' && sourceFilePaths
796
+ ? JSON.parse(sourceFilePaths)
797
+ : sourceFilePaths || []
798
+ ).map((item: any) => item.uid);
799
+ const now = Date.now();
800
+ setLastEmptyMessage({ now });
801
+ setAppStatus(pre => {
802
+ return {
803
+ ...pre,
804
+ display: 'processing',
805
+ sender: 'processing',
806
+ };
807
+ });
808
+
809
+ data.clearFn && data.clearFn();
810
+ setConversationList(list =>
811
+ list.map(c => (c.sessionId === activeSessionId ? { ...c, gmtModified: now } : c))
812
+ );
813
+
814
+ api_startChat_re(
815
+ {
816
+ configId: currentChatData.configId,
817
+ content: question,
818
+ fileUids: _fileUids,
819
+ lastDate: now,
820
+ name: currentChatData.label || '',
821
+ queryId: queryId,
822
+ sessionId: String(activeSessionId || ''),
823
+ type: (userMsg?.audioUrl ?? audioUrl) ? 'audioUrl' : 'text',
824
+ audioUrl: (userMsg?.audioUrl ?? audioUrl) || '',
825
+ enableThinking: !!senderSwitchValues.reasoning,
826
+ enableWebsearch: !!senderSwitchValues.netSearch,
827
+ qaChannel: senderSwitchValues.qaChannel,
828
+ } as any,
829
+ gmtModified => {
830
+ // 只在AI回复完成后请求推荐问题
831
+ fetchRecommendQuestions(queryId);
832
+ }
833
+ );
834
+ }
835
+
836
+ // 提取发消息逻辑,确保只有有sessionId时才调用
837
+ async function handleSenderSend(data: any, clearFn: () => void) {
838
+ // 找到当前会话
839
+ if (!activeSessionId) return;
840
+ const openEvent = new CustomEvent('aichat:right_set', {
841
+ detail: { collapsed: true },
842
+ });
843
+ window.dispatchEvent(openEvent);
844
+ const conv = conversationList.find(c => c.sessionId === activeSessionId);
845
+ //console.log('zheli是啥',conv)
846
+ if (conv && conv.isNew) {
847
+ // 还没落库,先调 create
848
+ let createSuccess = false;
849
+ // 优先用用户选择的助手newAssistantId,否则用当前会话的configId
850
+ const configIdToUse = newAssistantId || conv.configId;
851
+ // 用用户输入内容截取20字作为label
852
+ const labelToUse =
853
+ data && data.content ? data.content.slice(0, 20) : conv.label || '未命名会话';
854
+ await new Promise<void>((resolve, reject) => {
855
+ handleCreateConversation(
856
+ { url, token, CSRFToken },
857
+ {
858
+ label: labelToUse,
859
+ sessionId: conv.sessionId,
860
+ sysType: 'INT_SESSION',
861
+ configId: configIdToUse,
862
+ },
863
+ (res: any) => {
864
+ if (res && res.data && res.data.sessionId) {
865
+ // create成功,标记已落库并更新label/configId
866
+ setConversationList(list =>
867
+ list.map(c =>
868
+ c.sessionId === conv.sessionId
869
+ ? { ...c, isNew: false, configId: configIdToUse, label: labelToUse }
870
+ : c
871
+ )
872
+ );
873
+ setChatData(list =>
874
+ list.map(c =>
875
+ c.id === conv.sessionId
876
+ ? { ...c, isNew: false, configId: configIdToUse, label: labelToUse }
877
+ : c
878
+ )
879
+ );
880
+ createSuccess = true;
881
+ resolve();
882
+ } else {
883
+ message.error('创建会话失败,请重试');
884
+ reject();
885
+ }
886
+ }
887
+ );
888
+ });
889
+ if (!createSuccess) return;
890
+ // 继续发消息
891
+ sendMessageWithSessionId(activeSessionId as string, data, clearFn, configIdToUse);
892
+ return;
893
+ }
894
+ // 已落库,直接发消息
895
+ sendMessageWithSessionId(activeSessionId as string, data, clearFn);
896
+ }
897
+
898
+ // 修改sendMessageWithSessionId,发消息时用本地会话的configId
899
+ function sendMessageWithSessionId(
900
+ sessionId: string,
901
+ data: any,
902
+ clearFn: () => void,
903
+ configIdOverride?: string
904
+ ) {
905
+ const queryId = 'query-' + uid(32);
906
+ // 优先用参数传递的 configId
907
+ const conv = conversationList.find(c => c.sessionId === sessionId);
908
+ const configIdToUse = configIdOverride || conv?.configId || currentChatData.configId;
909
+ console.log('助手配置详情', configIdToUse);
910
+
911
+ // === 数据流保障:明确保留内容格式,不进行任何 trim 或替换处理 ===
912
+ // 确保换行符、空格等格式完整保留,直接传递到后端
913
+ const rawContent = data.content || data.text || '';
914
+ const preservedContent = typeof rawContent === 'string' ? rawContent : String(rawContent);
915
+
916
+ const label = preservedContent ? preservedContent.slice(0, 20) : '未命名会话';
917
+ let localCurrentFiles = undefined;
918
+ if (data.files && data.files.length > 0) {
919
+ localCurrentFiles = data.files.map((fileItem: any) => ({
920
+ name: fileItem.file.name,
921
+ size: fileItem.file.size,
922
+ type: fileItem.file.type,
923
+ filePath: fileItem.filePath || '',
924
+ convertedFilePath: fileItem.convertedFilePath || '',
925
+ uid: fileItem.uid || '',
926
+ }));
927
+ }
928
+ console.log('本地文件上传', data.files);
929
+ addEmptyMessage({
930
+ content: preservedContent, // 使用保留格式的内容,确保换行和空格完整保留
931
+ queryId,
932
+ currentFiles: localCurrentFiles,
933
+ filePaths: localCurrentFiles ? JSON.stringify(localCurrentFiles) : undefined,
934
+ });
935
+ // === 新增:如果当前会话是未命名且isNew,首次提问后自动改名并去除isNew ===
936
+ setConversationList(list =>
937
+ list.map(c =>
938
+ c.sessionId === sessionId && c.label === '未命名会话' && c.isNew
939
+ ? { ...c, label, gmtModified: Date.now(), isNew: false }
940
+ : c
941
+ )
942
+ );
943
+ setChatData(list =>
944
+ list.map(c =>
945
+ c.id === sessionId && c.label === '未命名会话' && c.isNew
946
+ ? { ...c, label, isNew: false }
947
+ : c
948
+ )
949
+ );
950
+ // 2. 文件上传逻辑 ---
951
+ if (data.files && data.files.length > 0) {
952
+ setAppStatus(pre => ({ ...pre, sender: 'uploading', display: 'uploading' }));
953
+ uploadController = new AbortController();
954
+ resetStopFlag(); // 使用 hook 提供的重置函数
955
+ const clientFileUids = data.files.map(
956
+ () => `rc-upload-${Date.now()}-${Math.random().toString(36).slice(2)}`
957
+ );
958
+ const initialFileStatuses = data.files.map((fileItem: any, index: number) => ({
959
+ ...fileItem,
960
+ uid: clientFileUids[index],
961
+ status: 'uploading',
962
+ }));
963
+ setFileStatuses(initialFileStatuses);
964
+ const formData = new FormData();
965
+ data.files.forEach((fileItem: any) => {
966
+ formData.append('files', fileItem.file);
967
+ });
968
+ const sessionIdStr = String(sessionId || '');
969
+ formData.append('sessionId', sessionIdStr);
970
+ formData.append('uids', clientFileUids.join(','));
971
+ axios
972
+ .post(`${url}/index/knowledgeBase/file/uploadFiles`, formData, {
973
+ headers: {
974
+ Authorization: token,
975
+ 'Content-Type': 'multipart/form-data',
976
+ },
977
+ signal: uploadController.signal,
978
+ })
979
+ .then(response => {
980
+ console.log('文件上传的打印结果', response);
981
+ if (stoppedRef.current) return;
982
+ if (
983
+ response.data.success &&
984
+ Array.isArray(response.data.data) &&
985
+ response.data.data.length > 0
986
+ ) {
987
+ // === 新增:合并后端返回字段到 fileStatuses ===
988
+ setFileStatuses(prev => {
989
+ return prev.map(localFile => {
990
+ const serverFile = response.data.data.find((f: any) => f.uid === localFile.uid);
991
+ return serverFile ? { ...localFile, ...serverFile } : localFile;
992
+ });
993
+ });
994
+ setAppStatus(pre => ({ ...pre, sender: 'processing', display: 'analyzing' }));
995
+ setFileStatuses(prev => prev.map(f => ({ ...f, status: 'parsing' })));
996
+ const uploadedFilesData = response.data.data;
997
+ const serverUids = uploadedFilesData.map((f: any) => f.uid);
998
+ pollFileStatus(
999
+ serverUids,
1000
+ serverStatuses => {
1001
+ setFileStatuses(currentLocalStatuses => {
1002
+ return currentLocalStatuses.map(localFile => {
1003
+ const correspondingServerFile = serverStatuses.find(
1004
+ (sf: any) => sf.uid === localFile.uid
1005
+ );
1006
+ if (correspondingServerFile) {
1007
+ switch (correspondingServerFile.status) {
1008
+ case 3:
1009
+ return { ...localFile, status: 'done' };
1010
+ case 4:
1011
+ return { ...localFile, status: 'error' };
1012
+ case 1:
1013
+ case 2:
1014
+ default:
1015
+ return { ...localFile, status: 'parsing' };
1016
+ }
1017
+ }
1018
+ return localFile;
1019
+ });
1020
+ });
1021
+ // === 新增:全部完成时清空 sender 区 ===
1022
+ const allDone =
1023
+ serverStatuses.length > 0 &&
1024
+ serverStatuses.every((file: any) => file.status === 3);
1025
+ if (allDone) {
1026
+ setFileStatuses([]);
1027
+ // 上传完成后合并新文件到 uploadedFiles,保证结构统一
1028
+ const map = new Map();
1029
+ (serverStatuses || []).forEach((item: any) => {
1030
+ map.set(item.id, item);
1031
+ });
1032
+ setFileManagerData(prev => {
1033
+ const r = mergeFiles(prev.uploadedFiles, uploadedFilesData);
1034
+
1035
+ return {
1036
+ uploadedFiles: r.map(item => {
1037
+ return {
1038
+ ...item,
1039
+ pdfPages: map.get(item.id)?.pdfPages || 0, // 保留后端返回的 pdfPages 字段
1040
+ };
1041
+ }),
1042
+ };
1043
+ });
1044
+ }
1045
+ },
1046
+ // onComplete: 全部解析成功后才发起AI请求
1047
+ () => {
1048
+ clearFn?.();
1049
+ setAppStatus(pre => ({ ...pre, sender: 'processing', display: 'processing' }));
1050
+ // === 新增:回填 filePath 到 fileStatuses ===
1051
+ setFileStatuses(prev => {
1052
+ return prev.map(localFile => {
1053
+ const serverFile = uploadedFilesData.find(
1054
+ (f: any) => f.uid === localFile.uid
1055
+ );
1056
+
1057
+ if (serverFile) {
1058
+ return {
1059
+ ...localFile,
1060
+ ...serverFile, // 合并后端所有字段
1061
+ url: serverFile.filePath, // 兼容 url 字段
1062
+ filePath: serverFile.filePath,
1063
+ name: serverFile.fileName || localFile.name,
1064
+ convertedFilePath:
1065
+ serverFile.convertedFilePath || localFile.convertedFilePath,
1066
+ };
1067
+ }
1068
+ return localFile;
1069
+ });
1070
+ });
1071
+ // === 同步 currentFiles 字段到 chatData ===
1072
+ setChatData(chatList => {
1073
+ if (!chatList[0]) return chatList;
1074
+ const { messages } = chatList[0];
1075
+ for (let i = messages.length - 1; i >= 0; i--) {
1076
+ if (messages[i].istype === 'user') {
1077
+ const oldFiles = messages[i].currentFiles || [];
1078
+ messages[i].currentFiles = oldFiles.map((localFile: any) => {
1079
+ const serverFile = uploadedFilesData.find(
1080
+ (f: any) => f.uid === localFile.uid
1081
+ );
1082
+ return serverFile ? { ...localFile, ...serverFile } : localFile;
1083
+ });
1084
+ break;
1085
+ }
1086
+ }
1087
+ return [...chatList];
1088
+ });
1089
+ // === 发起AI请求 ===
1090
+ const now = Date.now();
1091
+ api_startChat_re(
1092
+ {
1093
+ configId: configIdToUse,
1094
+ content: preservedContent, // 使用保留格式的内容,确保换行和空格完整传递到后端
1095
+ fileUids: serverUids,
1096
+ lastDate: now,
1097
+ name: label,
1098
+ queryId: queryId,
1099
+ sessionId: String(sessionId || ''),
1100
+ type: 'text',
1101
+ audioUrl: '',
1102
+ enableThinking: !!senderSwitchValues.reasoning,
1103
+ enableWebsearch: !!senderSwitchValues.netSearch,
1104
+ qaChannel: senderSwitchValues.qaChannel,
1105
+ } as any,
1106
+ gmtModified => {
1107
+ fetchRecommendQuestions(queryId);
1108
+ }
1109
+ );
1110
+ },
1111
+ (errorMsg: string) => {
1112
+ setAppStatus(pre => ({ ...pre, display: 'ready', sender: 'ready' }));
1113
+ setFileStatuses(prev =>
1114
+ prev.map(f => ({ ...f, status: 'error', message: errorMsg }))
1115
+ );
1116
+ }
1117
+ );
1118
+ } else {
1119
+ const errorMsg = response.data.errorMsg || '文件上传失败';
1120
+ setAppStatus(pre => ({ ...pre, display: 'error', sender: 'ready' }));
1121
+ setFileStatuses(prev =>
1122
+ prev.map(f => ({ ...f, status: 'error', message: errorMsg }))
1123
+ );
1124
+ }
1125
+ })
1126
+ .catch(error => {
1127
+ // 检查是否为文件大小超限错误 (413)
1128
+ if (error?.response?.status === 413) {
1129
+ if (error?.response?.statusText.includes('Request Entity Too Large')) {
1130
+ message.error('文件超过最大可上传限制');
1131
+ }
1132
+ } else {
1133
+ // 其他错误显示通用网络错误提示
1134
+ message.error('网络错误');
1135
+ }
1136
+
1137
+ setAppStatus(pre => ({ ...pre, display: 'error', sender: 'ready' }));
1138
+ setFileStatuses(prev =>
1139
+ prev.map(f => ({ ...f, status: 'error', message: '网络错误' }))
1140
+ );
1141
+ });
1142
+ return;
1143
+ }
1144
+ // 3. 无文件,直接发起AI请求
1145
+ setAppStatus(pre => ({ ...pre, display: 'processing', sender: 'processing' }));
1146
+ const now = Date.now();
1147
+ setConversationList(list =>
1148
+ list.map(c => (c.sessionId === sessionId ? { ...c, gmtModified: now } : c))
1149
+ );
1150
+ clearFn?.();
1151
+ api_startChat_re(
1152
+ {
1153
+ configId: configIdToUse,
1154
+ content: preservedContent, // 使用保留格式的内容,确保换行和空格完整传递到后端
1155
+ fileUids: [],
1156
+ lastDate: now,
1157
+ name: label,
1158
+ queryId: queryId,
1159
+ sessionId: String(sessionId || ''),
1160
+ type: 'text',
1161
+ audioUrl: '',
1162
+ enableThinking: !!senderSwitchValues.reasoning,
1163
+ enableWebsearch: !!senderSwitchValues.netSearch,
1164
+ qaChannel: senderSwitchValues.qaChannel,
1165
+ } as any,
1166
+ gmtModified => {
1167
+ fetchRecommendQuestions(queryId);
1168
+ }
1169
+ );
1170
+ }
1171
+
1172
+ // =================================================================
1173
+ // Event Handlers - 事件处理器
1174
+ // =================================================================
1175
+
1176
+ /**
1177
+ * 处理点赞反馈
1178
+ */
1179
+ function handleThumbsup(data: any) {
1180
+ if (data.restult == 1) {
1181
+ handleCancelFeedBack({ url, token, CSRFToken }, { queryId: data.queryId }, () => {
1182
+ setFeed(data.queryId, null);
1183
+ });
1184
+ } else {
1185
+ handleFeedBack({ url, token, CSRFToken }, { restult: 1, queryId: data.queryId }, () => {
1186
+ setFeed(data.queryId, 1);
1187
+ });
1188
+ }
1189
+ }
1190
+
1191
+ /**
1192
+ * 处理新建一个临时会话
1193
+ */
1194
+ function handleConversationCreate(data: any) {
1195
+ const sessionId = uid(32);
1196
+ // 新会话对象,等待用户输入后再发起创建请求
1197
+ const newConversation = {
1198
+ sessionId,
1199
+ label: '未命名会话',
1200
+ gmtModified: Date.now(),
1201
+ searchConfigDTO: {},
1202
+ filePath: '',
1203
+ configId: '',
1204
+ isNew: true, // 标记为新会话
1205
+ };
1206
+ // 在列表顶部插入新会话
1207
+ setConversationList(list => [{ ...newConversation, messages: [] }, ...list]);
1208
+ setChatData(list => [{ id: sessionId, messages: [], ...newConversation }, ...list]);
1209
+ // 激活新会话
1210
+ setActiveSessionId(sessionId);
1211
+ }
1212
+
1213
+
1214
+ /**
1215
+ * 处理会话列表项点击
1216
+ */
1217
+ function handleItemClick(data: any) {
1218
+ if (data?.sessionId && data.sessionId !== activeSessionId) {
1219
+ // 如果切换会话时,有正在进行的流式请求(且请求属于当前会话),则取消它
1220
+ // 这样可以防止在流式输出开始前切换会话时,后续数据写入错误的会话
1221
+ if (activeSessionId) {
1222
+ cancelStreamBySessionId(activeSessionId);
1223
+ }
1224
+ setActiveSessionId(data.sessionId);
1225
+ setAppStatus(prev => ({ ...prev, sender: 'ready' }));
1226
+ // 点击会话时,使用其最后一条AI消息的推荐问题
1227
+ const conv = chatData.find(c => c.id === data.sessionId);
1228
+ if (conv && conv.messages && conv.messages.length > 0) {
1229
+ const lastAiMsg = [...conv.messages].reverse().find((m: any) => m.istype === 'ai');
1230
+ if (lastAiMsg && lastAiMsg.recommendQuestion) {
1231
+ setRecommandQuestions(lastAiMsg.recommendQuestion);
1232
+ } else {
1233
+ setRecommandQuestions([]);
1234
+ }
1235
+ } else {
1236
+ setRecommandQuestions([]);
1237
+ }
1238
+ }
1239
+ }
1240
+
1241
+ // =================================================================
1242
+ // Async Data Operations - 异步数据操作 (供事件发射器使用)
1243
+ // =================================================================
1244
+
1245
+ /**
1246
+ * 异步删除会话接口
1247
+ */
1248
+ const async_deleteConversation = async (sessionId: string) => {
1249
+ return new Promise((resolve, reject) => {
1250
+ handleDeleteConversation({ url, token, CSRFToken }, { sessionId }, (res: any) => {
1251
+ if (res) {
1252
+ message.success('删除成功!');
1253
+ resolve(res);
1254
+ } else {
1255
+ message.error('删除失败!');
1256
+ reject(res);
1257
+ }
1258
+ });
1259
+ });
1260
+ };
1261
+ /**
1262
+ * 异步刷新会话列表接口
1263
+ */
1264
+ const async_refreshConversationList = async () => {
1265
+ try {
1266
+ // 重置为第一页
1267
+ setConversationList([]);
1268
+ setConvTotal(0);
1269
+ await fetchConversationPage(1);
1270
+ return [];
1271
+ } catch (e) {
1272
+ message.error('刷新会话列表失败');
1273
+ throw e;
1274
+ }
1275
+ };
1276
+
1277
+ /**
1278
+ * 异步重命名会话接口
1279
+ */
1280
+ const async_renameConversation = async (sessionId: string, newLabel: string) => {
1281
+ return new Promise((resolve, reject) => {
1282
+ handleRename({ url, token, CSRFToken }, { label: newLabel, sessionId }, res => {
1283
+ if (res) {
1284
+ // 成功后立即更新本地状态
1285
+ const now = Date.now();
1286
+ setConversationList(list =>
1287
+ list.map(item =>
1288
+ item.sessionId === sessionId ? { ...item, label: newLabel, gmtModified: now } : item
1289
+ )
1290
+ );
1291
+ message.success('重命名成功!');
1292
+ resolve(res);
1293
+ } else {
1294
+ message.error('重命名失败');
1295
+ reject(res);
1296
+ }
1297
+ });
1298
+ });
1299
+ };
1300
+
1301
+ // =================================================================
1302
+ // Local State Updaters - 本地状态更新 (供事件发射器使用)
1303
+ // =================================================================
1304
+
1305
+ /**
1306
+ * 从本地状态中移除会话项
1307
+ */
1308
+ const fn_removeItemFromList = (sessionId: string) => {
1309
+ setConversationList(list => {
1310
+ const newList = list.filter(c => c.sessionId !== sessionId);
1311
+ // 如果删除的是当前会话,则激活列表中的第一个
1312
+ if (activeSessionId === sessionId) {
1313
+ if (newList.length > 0) {
1314
+ setActiveSessionId(newList[0].sessionId);
1315
+ } else {
1316
+ // 如果列表空了,则新建一个
1317
+ handleConversationCreate({});
1318
+ }
1319
+ }
1320
+ return newList;
1321
+ });
1322
+ };
1323
+
1324
+ /**
1325
+ * 在本地状态中重命名会话项
1326
+ */
1327
+ const fn_renameItemInList = (sessionId: string, newLabel: string) => {
1328
+ const now = Date.now();
1329
+ setConversationList(list =>
1330
+ list.map(item =>
1331
+ item.sessionId === sessionId ? { ...item, label: newLabel, gmtModified: now } : item
1332
+ )
1333
+ );
1334
+ };
1335
+
1336
+ const handleFeedDownConfirm = (data: any) => {
1337
+ handleFeedBack({ url, token, CSRFToken }, { ...data, ...feedParam, restult: 0 }, () => {
1338
+ setFeed(feedParam.queryId, 0);
1339
+ });
1340
+ };
1341
+
1342
+ const editDialog = (data: any) => {
1343
+ const newLabel = data.new_name;
1344
+ const oldLabel = curEditData.data.label;
1345
+ if (newLabel && newLabel !== oldLabel) {
1346
+ curEditData
1347
+ .async_renameConversation(newLabel)
1348
+ .then(() => {
1349
+ curEditData.fn_renameItemInList(newLabel);
1350
+ setisRenameModalOpen(false);
1351
+ })
1352
+ .catch(() => {
1353
+ // 重命名失败
1354
+ });
1355
+ } else {
1356
+ setisRenameModalOpen(false);
1357
+ }
1358
+ };
1359
+
1360
+ const editRemove = (data: any) => {
1361
+ curEditData
1362
+ .async_deleteConversation()
1363
+ .then(() => {
1364
+ curEditData.fn_removeItemFromList();
1365
+ setisRemoveModalOpen(false);
1366
+ })
1367
+ .catch(() => {
1368
+ // 删除失败
1369
+ });
1370
+ };
1371
+
1372
+ const handleEvent = (type: string, data?: any) => {
1373
+ console.log(type, data);
1374
+ switch (type) {
1375
+ case 'close_remove':
1376
+ setisRemoveModalOpen(false);
1377
+ break;
1378
+ case 'close_rename':
1379
+ setisRenameModalOpen(false);
1380
+ break;
1381
+ case 'ok_rename':
1382
+ editDialog(data);
1383
+ break;
1384
+ case 'ok_remove':
1385
+ editRemove(data);
1386
+ break;
1387
+ }
1388
+ };
1389
+
1390
+ // =================================================================
1391
+ // Event Emitter - 事件分发器
1392
+ // =================================================================
1393
+
1394
+ const getDataBaseData = async ({ url, params, replacePredataFunction }: any) => {
1395
+ try {
1396
+ const res = await axios.post(url, params, {
1397
+ headers: { Authorization: token },
1398
+ });
1399
+ const data = res?.data?.data?.data;
1400
+ if (data) {
1401
+ replacePredataFunction(data);
1402
+ }
1403
+ } catch (error) {
1404
+ console.log('error', error);
1405
+ }
1406
+ };
1407
+
1408
+ /**
1409
+ * 统一事件分发器,连接业务逻辑与所有子组件的事件
1410
+ * @param {string} eventName - 事件名称
1411
+ * @param {any} data - 事件数据
1412
+ */
1413
+ const mergedEventsEmit = (eventName: string, data: any) => {
1414
+ console.log(`[Event] ${eventName}`, data);
1415
+ switch (eventName) {
1416
+ case 'conversations:load_more': {
1417
+ const hasMore = conversationList.length < convTotal;
1418
+ if (hasMore && !convLoading) {
1419
+ fetchConversationPage(convPageNo + 1);
1420
+ }
1421
+ break;
1422
+ }
1423
+ case 'retrieve-sql-data:pagingation_click':
1424
+ const { page, pageSize, fetchUrl, source, replacePredataFunction } = data;
1425
+ getDataBaseData({
1426
+ url: `${url}${fetchUrl}`,
1427
+ replacePredataFunction,
1428
+ params: { pageNo: page, pageSize, convertedFilePath: source, stream: false },
1429
+ });
1430
+ break;
1431
+ case 'web_referenceFile_title:click':
1432
+ if (!data?.data?.link) return console.error('没有网页地址');
1433
+ window.open(data.data.link, '_blank');
1434
+ break;
1435
+ // --- 对话列表事件 ---
1436
+ case 'conversation:item_click':
1437
+ handleItemClick(data);
1438
+ break;
1439
+
1440
+ case 'files:finished_animation_complete':
1441
+ setFileStatuses([]);
1442
+ break;
1443
+
1444
+ case 'conversation:create':
1445
+ handleConversationCreate(data);
1446
+ break;
1447
+ case 'conversation:new_assistant_change':
1448
+ setNewAssistantId(data.assistantId);
1449
+ break;
1450
+ case 'conversations:rename_icon_clicked':
1451
+ setCurEditData({
1452
+ data,
1453
+ async_renameConversation: (newLabel: string) =>
1454
+ async_renameConversation(data.sessionId, newLabel),
1455
+ fn_renameItemInList: (newLabel: string) =>
1456
+ fn_renameItemInList(data.sessionId, newLabel),
1457
+ });
1458
+ setisRenameModalOpen(true);
1459
+ break;
1460
+ case 'conversations:delete_icon_clicked':
1461
+ setCurEditData({
1462
+ data,
1463
+ async_deleteConversation: () => async_deleteConversation(data.sessionId),
1464
+ async_refreshConversationList,
1465
+ fn_removeItemFromList: () => fn_removeItemFromList(data.sessionId),
1466
+ });
1467
+ setisRemoveModalOpen(true);
1468
+ break;
1469
+ case 'sender:send':
1470
+ handleSenderSend(data, data.clearFn);
1471
+ break;
1472
+ case 'sender:send_recommandQuestion': {
1473
+ // 旧链路:来自 Sender 的推荐问题,可能已经在 AiChat 内转发为 sender:send
1474
+ // 这里兜底处理,确保能直接触发发送
1475
+ const content = (data?.content ?? '').toString();
1476
+ if (!content) break;
1477
+ handleSenderSend({ content }, () => {});
1478
+ break;
1479
+ }
1480
+ case 'chatbox:follow_up_question_click': {
1481
+ // 新链路:来自 ChatMessageAdapter 的追问点击
1482
+ const content = (data?.question ?? data?.content ?? '').toString();
1483
+ if (!content) break;
1484
+ handleSenderSend({ content }, () => {});
1485
+ break;
1486
+ }
1487
+ case 'sender:stop':
1488
+ setStopFlag(); // 使用 hook 提供的停止函数
1489
+ if (uploadController) {
1490
+ uploadController.abort();
1491
+ uploadController = null;
1492
+ }
1493
+ if (stopPoll) {
1494
+ stopPoll();
1495
+ stopPoll = null;
1496
+ }
1497
+ setAppStatus(pre => ({ ...pre, display: 'ready', sender: 'ready' }));
1498
+ setFileStatuses([]);
1499
+ stopStream?.();
1500
+ break;
1501
+ case 'sender:clear':
1502
+ data.clearFn?.();
1503
+ break;
1504
+ case 'sender:configChange':
1505
+ // 配置项变化,记录最新 switch 状态
1506
+ if (data && data.all) {
1507
+ setSenderSwitchValues(data.all);
1508
+ }
1509
+ break;
1510
+ case 'sender:action_upload':
1511
+ // 触发文件上传按钮点击,打开文件选择对话框
1512
+ if (fileInputRef.current) {
1513
+ fileInputRef.current.click();
1514
+ }
1515
+ break;
1516
+ case 'sender:action_history':
1517
+ // 打开文件管理器 Modal
1518
+ setFileManagerOpen(true);
1519
+ break;
1520
+ case 'uploaded_file:item_click': {
1521
+ // 处理文件列表项点击,关闭 Popover(chat 模块使用 AiChat 内置右侧栏,不需要预览 Drawer)
1522
+ // 注意:setFileListPopoverOpen 由 useFileDisplayTools hook 管理,这里通过 eventsEmit 触发关闭
1523
+ // 可以在这里添加其他处理逻辑,比如在右侧栏显示文件详情
1524
+ break;
1525
+ }
1526
+
1527
+ // --- 消息卡片事件 ---
1528
+ case 'action_copy:click':
1529
+ if (data) {
1530
+ toCopy(data.content, data.event);
1531
+ }
1532
+ break;
1533
+ case 'chatbox:copy': {
1534
+ const text = data?.content || '';
1535
+ if (!text) break;
1536
+ // 直接使用 Clipboard API,避免依赖 event.currentTarget
1537
+ const copyByClipboardApi = async (t: string) => {
1538
+ try {
1539
+ await navigator.clipboard.writeText(t);
1540
+ message.success('已复制到剪贴板');
1541
+ return true;
1542
+ } catch (_) {
1543
+ return false;
1544
+ }
1545
+ };
1546
+ const fallbackCopy = (t: string) => {
1547
+ const textarea = document.createElement('textarea');
1548
+ textarea.value = t;
1549
+ textarea.style.position = 'fixed';
1550
+ textarea.style.opacity = '0';
1551
+ document.body.appendChild(textarea);
1552
+ textarea.focus();
1553
+ textarea.select();
1554
+ try {
1555
+ document.execCommand('copy');
1556
+ message.success('已复制到剪贴板');
1557
+ } catch (_) {
1558
+ message.error('复制失败');
1559
+ }
1560
+ document.body.removeChild(textarea);
1561
+ };
1562
+ copyByClipboardApi(text).then(ok => {
1563
+ if (!ok) fallbackCopy(text);
1564
+ });
1565
+ break;
1566
+ }
1567
+ case 'chatbox:repeat':
1568
+ console.log(data, 'sdkflskdjflksdjfklsdjlfkjsdklfjsdklfjlks');
1569
+ handleReSenderSend(data);
1570
+ break;
1571
+ case 'action_thumbsup':
1572
+ handleThumbsup(data);
1573
+ break;
1574
+ case 'chatbox:like':
1575
+ handleThumbsup({ queryId: data?.queryId, restult: data?.result });
1576
+ break;
1577
+ case 'action_thumbsdown':
1578
+ if (data.restult === 0) {
1579
+ handleCancelFeedBack({ url, token, CSRFToken }, { queryId: data.queryId }, () => {
1580
+ setFeed(data.queryId, null);
1581
+ });
1582
+ } else {
1583
+ setFeedParam(data);
1584
+ setOpenFeed(true);
1585
+ }
1586
+ break;
1587
+ case 'chatbox:dislike':
1588
+ if (data?.result === 0) {
1589
+ handleCancelFeedBack({ url, token, CSRFToken }, { queryId: data.queryId }, () => {
1590
+ setFeed(data.queryId, null);
1591
+ });
1592
+ } else {
1593
+ setFeedParam({ queryId: data?.queryId });
1594
+ setOpenFeed(true);
1595
+ }
1596
+ break;
1597
+ case 'action_retrive_tag':
1598
+ // 处理 retrive-tag 点击事件,透传给父组件
1599
+ eventsEmit?.('action_retrive_tag', data);
1600
+ break;
1601
+ case 'referenceFile_view':
1602
+ // 处理文件预览事件,透传给父组件
1603
+ eventsEmit?.('referenceFile_view', data);
1604
+ break;
1605
+ case 'referenceFile_question':
1606
+ // 处理单文档对话事件,透传给父组件
1607
+ console.log(data, 'zhezhhzhzh');
1608
+ eventsEmit?.('referenceFile_question', data);
1609
+ break;
1610
+ case 'referenceFile_download':
1611
+ // 处理文件下载事件,透传给父组件
1612
+ eventsEmit?.('referenceFile_download', data);
1613
+ break;
1614
+ case 'references:click': {
1615
+ // 使用 AiChat 内置的右侧栏事件
1616
+ const Content = () => {
1617
+ // const safe = (() => {
1618
+ // try {
1619
+ // return typeof data === 'string' ? data : JSON.stringify(data, null, 2);
1620
+ // } catch (e) {
1621
+ // return String(data);
1622
+ // }
1623
+ // })();
1624
+ return (
1625
+ <ReferenceBar
1626
+ type={DrawerType.REFERENCELIST}
1627
+ data={data}
1628
+ token={token}
1629
+ eventsEmit={eventsEmit}
1630
+ is_download={is_download}
1631
+ />
1632
+ );
1633
+ };
1634
+ const setContentEvent = new CustomEvent('aichat:right_set_content', {
1635
+ detail: { content: <Content /> },
1636
+ });
1637
+ window.dispatchEvent(setContentEvent);
1638
+ const openEvent = new CustomEvent('aichat:right_set', {
1639
+ detail: { collapsed: false },
1640
+ });
1641
+ window.dispatchEvent(openEvent);
1642
+ break;
1643
+ }
1644
+ case 'retrive_tag:click':
1645
+ // 处理 retrive-tag 点击事件,透传给父组件
1646
+ eventsEmit?.('retrive_tag:click', data);
1647
+ break;
1648
+ // --- 文件管理器事件 ---
1649
+ case 'fileManager:change': {
1650
+ // 保存核心组件传递的文件处理函数引用
1651
+ if (typeof data?.setUploadedFiles === 'function') {
1652
+ setUploadedFilesRef.current = data.setUploadedFiles;
1653
+ removeFileRef.current = data.removeFile;
1654
+ }
1655
+ break;
1656
+ }
1657
+
1658
+ // --- 文件管理器上传文件删除事件,透传到业务层 ---
1659
+ case 'uploaded_file:removeFromTemp': {
1660
+ console.log('[DEBUG] mergedEventsEmit uploaded_file:removeFromTemp 事件', data);
1661
+
1662
+ const uid = data?.file?.uid;
1663
+ if (!uid) {
1664
+ console.warn('[DEBUG] 缺少uid,无法删除临时文件', data);
1665
+ break;
1666
+ }
1667
+ // 获取当前对话最后一条用户消息的 queryId
1668
+ const lastUserMsg = [...(currentChatData?.messages || [])]
1669
+ .reverse()
1670
+ .find((msg: any) => msg.istype === 'user');
1671
+ const queryId = lastUserMsg?.queryId;
1672
+
1673
+ // 使用统一的文件删除处理函数
1674
+ handleRemoveFromTemp(uid, queryId);
1675
+ break;
1676
+ }
1677
+ case 'uploaded_file:remove_batch':
1678
+ break;
1679
+
1680
+ default:
1681
+ // 其他未处理事件可继续向上层抛出
1682
+ eventsEmit?.(eventName, data);
1683
+ }
1684
+ };
1685
+
1686
+ // =================================================================
1687
+ // Component & UI Configuration - 组件及UI配置
1688
+ // =================================================================
1689
+
1690
+ /**
1691
+ * Sidebar 配置
1692
+ */
1693
+ const sidebar: SidebarTabConfig[] = [
1694
+ {
1695
+ key: 'conversations',
1696
+ type: 'custom',
1697
+ label: '对话历史',
1698
+ icon: <History size={16} />,
1699
+ enabled: true,
1700
+ component: (
1701
+ <GientechConversationPanel
1702
+ data={conversationList}
1703
+ assistantList={assistantList}
1704
+ activedItem={activeSessionId || ''}
1705
+ hasMore={conversationList.length < convTotal}
1706
+ loadingMore={convLoading}
1707
+ sortRules={[
1708
+ { label: '当天', dayPeriod: 1 },
1709
+ { label: '3天', dayPeriod: 3 },
1710
+ { label: '7天', dayPeriod: 7 },
1711
+ { label: '30天', dayPeriod: 30 },
1712
+ ]}
1713
+ eventsEmit={mergedEventsEmit}
1714
+ styles={normalizedStyles.theme}
1715
+ />
1716
+ ),
1717
+ },
1718
+ ];
1719
+
1720
+ /**
1721
+ * 业务层自定义组件
1722
+ */
1723
+ const WelcomeComponent = React.useCallback(() => {
1724
+ return (
1725
+ <GientechNewChatWelcome
1726
+ eventsEmit={mergedEventsEmit}
1727
+ assistantList={assistantList}
1728
+ styles={normalizedStyles}
1729
+ selectedId={selectedAssistantId}
1730
+ onSelectAssistant={setSelectedAssistantId}
1731
+ productLogo={rest?.productLogo}
1732
+ />
1733
+ );
1734
+ }, [mergedEventsEmit, assistantList, normalizedStyles, selectedAssistantId, rest?.productLogo]);
1735
+
1736
+ const businessCustomComponents = React.useMemo(
1737
+ () => ({
1738
+ AiChatBox: (msg: any, idx: number) => {
1739
+ const isLastMessage = idx === (currentChatData?.messages?.length || 0) - 1;
1740
+
1741
+ // 根据消息的 status 和 appStatus 来确定 displayStatus
1742
+ let displayStatus = 'ready';
1743
+ if (isLastMessage) {
1744
+ // 优先使用消息的 status 字段
1745
+ if (msg.status !== undefined) {
1746
+ switch (msg.status) {
1747
+ case 0: // StatusType.Pending
1748
+ displayStatus = 'thinking';
1749
+ break;
1750
+ case 1: // StatusType.Process
1751
+ displayStatus = 'processing';
1752
+ break;
1753
+ case 2: // StatusType.Done
1754
+ displayStatus = 'ready';
1755
+ break;
1756
+ case 3: // StatusType.Error
1757
+ displayStatus = 'error';
1758
+ break;
1759
+ default:
1760
+ displayStatus = appStatus.display;
1761
+ }
1762
+ } else {
1763
+ // 如果没有 status 字段,使用 appStatus.display
1764
+ displayStatus = appStatus.display;
1765
+ }
1766
+ }
1767
+ // 将推荐问题强制转换为数组,避免切换会话时报 .map 错误
1768
+ let safeFollowUps: string[] = [];
1769
+ if (Array.isArray(recommandQuestions)) {
1770
+ safeFollowUps = recommandQuestions as string[];
1771
+ } else if (typeof recommandQuestions === 'string') {
1772
+ try {
1773
+ safeFollowUps = JSON.parse(recommandQuestions) || [];
1774
+ } catch {
1775
+ safeFollowUps = [];
1776
+ }
1777
+ }
1778
+ // 保证传入 ChatMessageAdapter 的 reference/webReference/graphReference 始终是有效的 JSON 字符串
1779
+ const safeReference = (() => {
1780
+ const r = (msg as any)?.reference;
1781
+ if (!r) return '[]';
1782
+ if (typeof r === 'string') {
1783
+ try {
1784
+ JSON.parse(r);
1785
+ return r;
1786
+ } catch {
1787
+ return '[]';
1788
+ }
1789
+ }
1790
+ try {
1791
+ return JSON.stringify(r);
1792
+ } catch {
1793
+ return '[]';
1794
+ }
1795
+ })();
1796
+ const safeWebReference = (() => {
1797
+ const r = (msg as any)?.webReference;
1798
+ if (!r) return '[]';
1799
+ if (typeof r === 'string') {
1800
+ try {
1801
+ JSON.parse(r);
1802
+ return r;
1803
+ } catch {
1804
+ return '[]';
1805
+ }
1806
+ }
1807
+ try {
1808
+ return JSON.stringify(r);
1809
+ } catch {
1810
+ return '[]';
1811
+ }
1812
+ })();
1813
+ const safeGraphReference = (() => {
1814
+ const r = (msg as any)?.graphReference;
1815
+ if (!r) return '[]';
1816
+ if (typeof r === 'string') {
1817
+ try {
1818
+ JSON.parse(r);
1819
+ return r;
1820
+ } catch {
1821
+ return '[]';
1822
+ }
1823
+ }
1824
+ try {
1825
+ return JSON.stringify(r);
1826
+ } catch {
1827
+ return '[]';
1828
+ }
1829
+ })();
1830
+ return (
1831
+ <ChatMessageAdapter
1832
+ key={`${msg?.queryId || msg?.id || idx}-${msg?.istype || 'unknown'}`}
1833
+ {...msg}
1834
+ referenceMode="button"
1835
+ reference={safeReference}
1836
+ webReference={safeWebReference}
1837
+ graphReference={safeGraphReference}
1838
+ contentType="stream" // 添加 contentType 确保 slateContent 能正确生成
1839
+ displayStatus={displayStatus}
1840
+ eventsEmit={mergedEventsEmit}
1841
+ // 仅最后一条 AI 消息展示追问
1842
+ isLast={isLastMessage && msg?.istype === 'ai'}
1843
+ followUpQuestions={isLastMessage && msg?.istype === 'ai' ? safeFollowUps : []}
1844
+ defaultAnswer={
1845
+ currentChatData?.searchConfigDTO?.defaultAnswer ||
1846
+ '不知道什么原因我们没能查到你要的问题*-*'
1847
+ }
1848
+ is_download={is_download}
1849
+ styles={normalizedStyles as any}
1850
+ />
1851
+ );
1852
+ },
1853
+ LogoBox: (props: any) => {
1854
+ if (props && props.LogoBox === null) return null;
1855
+ if (props && typeof props.LogoBox === 'function') return <props.LogoBox />;
1856
+ return <div className="text-2xl w-full text-center font-bold">小鲸智能助手</div>;
1857
+ },
1858
+ DisplayLoading: () => <DisplayLoading />,
1859
+ DisplayError: () => <DisplayError />,
1860
+ AppError: () => (
1861
+ <AppError
1862
+ msg="应用初始化失败"
1863
+ subMsg="无法完成应用初始化,这可能是由于网络连接问题或服务配置错误导致的。请检查您的网络连接并重试。"
1864
+ />
1865
+ ),
1866
+ AppLoading: () => (
1867
+ <AppLoading
1868
+ title={rest?.appLoadingConfig?.title}
1869
+ subtitle={rest?.appLoadingConfig?.subtitle || ''}
1870
+ />
1871
+ ),
1872
+ UserChatBox: (msg: any, idx: number) => {
1873
+ return (
1874
+ <ChatMessageAdapter
1875
+ key={`${msg?.queryId || msg?.id || idx}-user`}
1876
+ {...msg}
1877
+ referenceMode="button"
1878
+ contentType="plainText"
1879
+ isUser={true}
1880
+ displayStatus="ready"
1881
+ eventsEmit={mergedEventsEmit}
1882
+ styles={normalizedStyles as any}
1883
+ fileManagerData={fileManagerData}
1884
+ />
1885
+ );
1886
+ },
1887
+ WelcomeComponent,
1888
+ }),
1889
+ [
1890
+ WelcomeComponent,
1891
+ currentChatData,
1892
+ appStatus.display,
1893
+ mergedEventsEmit,
1894
+ normalizedStyles,
1895
+ is_download,
1896
+ fileManagerData,
1897
+ ]
1898
+ );
1899
+
1900
+ /**
1901
+ * 合并外部传入和业务默认的自定义组件,外部优先
1902
+ */
1903
+ const mergedCustomComponents: { [key: string]: any } = {
1904
+ ...businessCustomComponents,
1905
+ ...(rest.CustomComponents || {}),
1906
+ };
1907
+
1908
+ useEffect(() => {
1909
+ setDynamicSenderConfig((prev: any) => {
1910
+ const switchs = Array.isArray(prev.switchs)
1911
+ ? prev.switchs.map((item: any) =>
1912
+ item.name === 'reasoning' ? { ...item, enabled: is_enableThinking } : item
1913
+ )
1914
+ : [];
1915
+ return {
1916
+ ...prev,
1917
+ switchs,
1918
+ };
1919
+ });
1920
+ }, [is_enableThinking]);
1921
+ // =================================================================
1922
+ // Render - 渲染
1923
+ // =================================================================
1924
+
1925
+ // 使用统一的文件展示工具 Hook
1926
+ const { FileDisplayTools, setFileListPopoverOpen } = useFileDisplayTools({
1927
+ fileManagerData,
1928
+ styles: normalizedStyles,
1929
+ eventsEmit: mergedEventsEmit,
1930
+ });
1931
+
1932
+ // 处理文件选择
1933
+ const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
1934
+ const files = e.target.files;
1935
+ if (files && files.length > 0) {
1936
+ // 通过 setUploadedFilesRef 直接设置文件到 aichat 内部
1937
+ // 这个 ref 在 fileManager:change 事件中设置
1938
+ if (setUploadedFilesRef.current) {
1939
+ const fileList = Array.from(files).map(file => ({
1940
+ file,
1941
+ type: file.type.startsWith('image/') ? ('image' as const) : ('document' as const),
1942
+ }));
1943
+ setUploadedFilesRef.current(fileList);
1944
+ } else {
1945
+ // 如果 ref 还没有设置,通过 eventsEmit 发送事件给 aichat
1946
+ // 创建一个 FileList 对象来模拟文件选择
1947
+ const fileArray = Array.from(files);
1948
+ // 通过 eventsEmit 发送文件上传事件,aichat 内部会处理
1949
+ // 注意:这里需要等待 aichat 初始化完成,所以使用 setTimeout
1950
+ setTimeout(() => {
1951
+ if (setUploadedFilesRef.current) {
1952
+ const fileList = fileArray.map(file => ({
1953
+ file,
1954
+ type: file.type.startsWith('image/') ? ('image' as const) : ('document' as const),
1955
+ }));
1956
+ setUploadedFilesRef.current(fileList);
1957
+ }
1958
+ }, 100);
1959
+ }
1960
+ }
1961
+ // 清空 input,以便可以重复选择同一文件
1962
+ if (fileInputRef.current) {
1963
+ fileInputRef.current.value = '';
1964
+ }
1965
+ };
1966
+
1967
+ return (
1968
+ <div style={{ height: '100%', minWidth: '1116px', overflow: 'hidden' }}>
1969
+ {/* 隐藏的文件上传input */}
1970
+ <input
1971
+ ref={fileInputRef}
1972
+ type="file"
1973
+ multiple
1974
+ accept="image/*,.pdf,.doc,.docx,.ppt,.pptx,.xls,.xlsx,.txt,.md,.json,.csv,.zip,.rar,.7z"
1975
+ style={{ display: 'none' }}
1976
+ onChange={handleFileSelect}
1977
+ />
1978
+ {/* 右侧栏由 AiChat 内置事件控制,此处不再使用 antd Drawer */}
1979
+
1980
+ <WrappedComponent
1981
+ {...rest}
1982
+ status={appStatus}
1983
+ chatData={currentChatData}
1984
+ sidebar={sidebar}
1985
+ recommandQuestions={[]}
1986
+ eventsEmit={mergedEventsEmit}
1987
+ styles={normalizedStyles as any}
1988
+ senderConfig={dynamicSenderConfig}
1989
+ fileUploadStatus={fileStatuses}
1990
+ CustomComponents={mergedCustomComponents}
1991
+ activeSessionId={activeSessionId}
1992
+ scrollOld={scrollOld}
1993
+ rightbarWidth={'400px'}
1994
+ chatHeader={{
1995
+ title: currentChatData?.label || '新对话',
1996
+ tools: FileDisplayTools,
1997
+ }}
1998
+ />
1999
+ <FeedBackModal
2000
+ open={openFeed}
2001
+ feedBackList={feedBackList}
2002
+ setOpen={setOpenFeed}
2003
+ handleConfirm={handleFeedDownConfirm}
2004
+ />
2005
+ <RenameModal
2006
+ isModalOpen={isRenameModalOpen}
2007
+ handleOk={handleEvent}
2008
+ handleCancel={() => handleEvent('close_rename')}
2009
+ data={curEditData?.data || {}}
2010
+ />
2011
+ <DeleteModal
2012
+ isModalOpen={isRemoveModalOpen}
2013
+ handleOk={handleEvent}
2014
+ handleCancel={() => handleEvent('close_remove')}
2015
+ data={curEditData?.data || {}}
2016
+ />
2017
+ <FileManager
2018
+ open={fileManagerOpen}
2019
+ onCancel={() => setFileManagerOpen(false)}
2020
+ files={fileManagerData.uploadedFiles || []}
2021
+ currentFiles={[]}
2022
+ styles={normalizedStyles}
2023
+ eventsEmit={mergedEventsEmit}
2024
+ onUpload={(fileList) => {
2025
+ // 处理 FileManager 中的文件上传
2026
+ if (setUploadedFilesRef.current) {
2027
+ const fileArray = Array.from(fileList);
2028
+ const fileListWithType = fileArray.map(file => ({
2029
+ file,
2030
+ type: file.type.startsWith('image/') ? ('image' as const) : ('document' as const),
2031
+ }));
2032
+ setUploadedFilesRef.current(fileListWithType);
2033
+ } else {
2034
+ // 如果 ref 还没有设置,通过触发 fileInput 来处理
2035
+ if (fileInputRef.current) {
2036
+ // 创建一个 DataTransfer 对象来模拟文件选择
2037
+ const dataTransfer = new DataTransfer();
2038
+ Array.from(fileList).forEach(file => {
2039
+ dataTransfer.items.add(file);
2040
+ });
2041
+ fileInputRef.current.files = dataTransfer.files;
2042
+ const event = new Event('change', { bubbles: true });
2043
+ fileInputRef.current.dispatchEvent(event);
2044
+ }
2045
+ }
2046
+ }}
2047
+ />
2048
+ </div>
2049
+ );
2050
+ };
2051
+ }
2052
+
2053
+ // 重新导出 registerPDFWorker,方便用户从 chat 模块引入
2054
+ export { registerPDFWorker } from '@mxmweb/fviewer';