@gientech/modual 2.0.7 → 2.0.9

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