@lobehub/lobehub 2.0.0-next.34 → 2.0.0-next.36

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 (282) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/next.config.ts +5 -6
  4. package/package.json +2 -2
  5. package/packages/agent-runtime/src/core/__tests__/runtime.test.ts +112 -77
  6. package/packages/agent-runtime/src/core/runtime.ts +63 -18
  7. package/packages/agent-runtime/src/types/generalAgent.ts +55 -0
  8. package/packages/agent-runtime/src/types/index.ts +1 -0
  9. package/packages/agent-runtime/src/types/instruction.ts +10 -3
  10. package/packages/const/src/user.ts +0 -1
  11. package/packages/context-engine/src/processors/GroupMessageFlatten.ts +8 -6
  12. package/packages/context-engine/src/processors/__tests__/GroupMessageFlatten.test.ts +12 -12
  13. package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/assistant-group-branches.json +249 -0
  14. package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/index.ts +4 -0
  15. package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/multi-assistant-group.json +260 -0
  16. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/active-index-1.json +4 -0
  17. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/assistant-group-branches.json +481 -0
  18. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/conversation.json +5 -1
  19. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/index.ts +4 -0
  20. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/multi-assistant-group.json +407 -0
  21. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/nested.json +18 -2
  22. package/packages/conversation-flow/src/__tests__/fixtures/outputs/complex-scenario.json +25 -3
  23. package/packages/conversation-flow/src/__tests__/parse.test.ts +12 -0
  24. package/packages/conversation-flow/src/index.ts +1 -1
  25. package/packages/conversation-flow/src/transformation/FlatListBuilder.ts +112 -34
  26. package/packages/conversation-flow/src/types/flatMessageList.ts +0 -12
  27. package/packages/conversation-flow/src/{types.ts → types/index.ts} +3 -14
  28. package/packages/database/src/models/message.ts +18 -19
  29. package/packages/types/src/aiChat.ts +2 -0
  30. package/packages/types/src/importer.ts +2 -2
  31. package/packages/types/src/message/ui/chat.ts +17 -1
  32. package/packages/types/src/message/ui/extra.ts +2 -2
  33. package/packages/types/src/message/ui/params.ts +2 -2
  34. package/packages/types/src/user/preference.ts +0 -4
  35. package/packages/utils/src/tokenizer/index.ts +3 -11
  36. package/src/app/[variants]/(main)/chat/ChatRouter.tsx +83 -0
  37. package/src/app/[variants]/(main)/chat/_layout/ChatLayout.tsx +22 -0
  38. package/src/app/[variants]/(main)/chat/_layout/Desktop/SessionPanel.tsx +12 -7
  39. package/src/app/[variants]/(main)/chat/_layout/Desktop/index.tsx +2 -2
  40. package/src/app/[variants]/(main)/chat/_layout/FeatureFlagsProvider.tsx +24 -0
  41. package/src/app/[variants]/(main)/chat/_layout/Mobile.tsx +3 -2
  42. package/src/app/[variants]/(main)/chat/_layout/type.ts +0 -1
  43. package/src/app/[variants]/(main)/chat/components/ConversationArea.tsx +29 -0
  44. package/src/app/[variants]/(main)/chat/components/MainChatPage.tsx +25 -0
  45. package/src/app/[variants]/(main)/chat/components/PortalPanel.tsx +28 -0
  46. package/src/app/[variants]/(main)/chat/components/SessionPanel.tsx +33 -0
  47. package/src/app/[variants]/(main)/chat/{settings/page.tsx → components/SettingsPage.tsx} +35 -3
  48. package/src/app/[variants]/(main)/chat/components/TopicSidebar.tsx +30 -0
  49. package/src/app/[variants]/(main)/chat/components/WorkspaceLayout.tsx +73 -0
  50. package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/Desktop/MessageFromUrl.tsx +3 -3
  51. package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/V1Mobile/index.tsx +1 -1
  52. package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/V1Mobile/useSend.ts +3 -3
  53. package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/useSend.ts +6 -6
  54. package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatList/ChatItem/index.tsx +1 -1
  55. package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatList/Content.tsx +5 -3
  56. package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatList/WelcomeChatItem/AgentWelcome/OpeningQuestions.tsx +2 -2
  57. package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatList/WelcomeChatItem/GroupWelcome/GroupUsageSuggest.tsx +2 -2
  58. package/src/app/[variants]/(main)/chat/{layout.ts → layout.tsx} +0 -1
  59. package/src/app/[variants]/(main)/chat/page.tsx +12 -0
  60. package/src/app/[variants]/(main)/labs/page.tsx +0 -9
  61. package/src/features/ChatInput/ActionBar/STT/browser.tsx +3 -3
  62. package/src/features/ChatInput/ActionBar/STT/openai.tsx +3 -3
  63. package/src/features/Conversation/Error/AccessCodeForm.tsx +1 -1
  64. package/src/features/Conversation/Error/ChatInvalidApiKey.tsx +1 -1
  65. package/src/features/Conversation/Error/ClerkLogin/index.tsx +1 -1
  66. package/src/features/Conversation/Error/OAuthForm.tsx +1 -1
  67. package/src/features/Conversation/Error/index.tsx +0 -5
  68. package/src/features/Conversation/Messages/Assistant/Actions/index.tsx +13 -10
  69. package/src/features/Conversation/Messages/Assistant/Extra/index.test.tsx +3 -8
  70. package/src/features/Conversation/Messages/Assistant/Extra/index.tsx +2 -6
  71. package/src/features/Conversation/Messages/Assistant/MessageContent.tsx +7 -9
  72. package/src/features/Conversation/Messages/Assistant/Tool/Inspector/PluginResult.tsx +2 -2
  73. package/src/features/Conversation/Messages/Assistant/Tool/Inspector/PluginState.tsx +2 -2
  74. package/src/features/Conversation/Messages/Assistant/Tool/Render/PluginSettings.tsx +4 -1
  75. package/src/features/Conversation/Messages/Assistant/Tool/Render/index.tsx +2 -3
  76. package/src/features/Conversation/Messages/Assistant/index.tsx +57 -60
  77. package/src/features/Conversation/Messages/Default.tsx +1 -0
  78. package/src/features/Conversation/Messages/Group/Actions/WithContentId.tsx +38 -10
  79. package/src/features/Conversation/Messages/Group/Actions/index.tsx +1 -1
  80. package/src/features/Conversation/Messages/Group/ContentBlock.tsx +1 -3
  81. package/src/features/Conversation/Messages/Group/GroupChildren.tsx +12 -12
  82. package/src/features/Conversation/Messages/Group/MessageContent.tsx +7 -1
  83. package/src/features/Conversation/Messages/Group/Tool/Render/PluginSettings.tsx +1 -1
  84. package/src/features/Conversation/Messages/Group/index.tsx +2 -1
  85. package/src/features/Conversation/Messages/Supervisor/index.tsx +2 -2
  86. package/src/features/Conversation/Messages/User/{Actions.tsx → Actions/ActionsBar.tsx} +26 -25
  87. package/src/features/Conversation/Messages/User/Actions/MessageBranch.tsx +107 -0
  88. package/src/features/Conversation/Messages/User/Actions/index.tsx +42 -0
  89. package/src/features/Conversation/Messages/User/index.tsx +43 -44
  90. package/src/features/Conversation/Messages/index.tsx +3 -3
  91. package/src/features/Conversation/components/AutoScroll.tsx +3 -3
  92. package/src/features/Conversation/components/Extras/Usage/UsageDetail/AnimatedNumber.tsx +55 -0
  93. package/src/features/Conversation/components/Extras/Usage/UsageDetail/index.tsx +5 -2
  94. package/src/features/Conversation/components/VirtualizedList/index.tsx +29 -20
  95. package/src/features/Conversation/hooks/useChatListActionsBar.tsx +8 -10
  96. package/src/features/Portal/GroupThread/Body/index.tsx +1 -1
  97. package/src/features/Portal/Thread/Chat/ChatInput/useSend.ts +3 -3
  98. package/src/hooks/useHotkeys/chatScope.ts +16 -8
  99. package/src/server/routers/lambda/__tests__/aiChat.test.ts +1 -1
  100. package/src/server/routers/lambda/__tests__/integration/message.integration.test.ts +0 -26
  101. package/src/server/routers/lambda/aiChat.ts +3 -2
  102. package/src/server/routers/lambda/message.ts +8 -16
  103. package/src/server/services/message/__tests__/index.test.ts +29 -39
  104. package/src/server/services/message/index.ts +41 -36
  105. package/src/services/electron/desktopNotification.ts +6 -6
  106. package/src/services/electron/file.ts +6 -6
  107. package/src/services/file/ClientS3/index.ts +8 -8
  108. package/src/services/message/__tests__/metadata-race-condition.test.ts +157 -0
  109. package/src/services/message/index.ts +21 -15
  110. package/src/services/upload.ts +11 -11
  111. package/src/services/utils/abortableRequest.test.ts +161 -0
  112. package/src/services/utils/abortableRequest.ts +67 -0
  113. package/src/store/chat/agents/GeneralChatAgent.ts +137 -0
  114. package/src/store/chat/agents/createAgentExecutors.ts +395 -0
  115. package/src/store/chat/helpers.test.ts +0 -99
  116. package/src/store/chat/helpers.ts +0 -11
  117. package/src/store/chat/slices/aiChat/actions/__tests__/conversationControl.test.ts +332 -0
  118. package/src/store/chat/slices/aiChat/actions/__tests__/conversationLifecycle.test.ts +257 -0
  119. package/src/store/chat/slices/aiChat/actions/__tests__/helpers.ts +11 -2
  120. package/src/store/chat/slices/aiChat/actions/__tests__/rag.test.ts +6 -6
  121. package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +391 -0
  122. package/src/store/chat/slices/aiChat/actions/__tests__/streamingStates.test.ts +179 -0
  123. package/src/store/chat/slices/aiChat/actions/conversationControl.ts +157 -0
  124. package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +329 -0
  125. package/src/store/chat/slices/aiChat/actions/generateAIGroupChat.ts +14 -14
  126. package/src/store/chat/slices/aiChat/actions/index.ts +12 -6
  127. package/src/store/chat/slices/aiChat/actions/rag.ts +9 -6
  128. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +604 -0
  129. package/src/store/chat/slices/aiChat/actions/streamingStates.ts +84 -0
  130. package/src/store/chat/slices/builtinTool/actions/__tests__/localSystem.test.ts +4 -4
  131. package/src/store/chat/slices/builtinTool/actions/__tests__/search.test.ts +11 -11
  132. package/src/store/chat/slices/builtinTool/actions/interpreter.ts +8 -8
  133. package/src/store/chat/slices/builtinTool/actions/localSystem.ts +2 -2
  134. package/src/store/chat/slices/builtinTool/actions/search.ts +8 -8
  135. package/src/store/chat/slices/message/action.test.ts +79 -68
  136. package/src/store/chat/slices/message/actions/index.ts +39 -0
  137. package/src/store/chat/slices/message/actions/internals.ts +77 -0
  138. package/src/store/chat/slices/message/actions/optimisticUpdate.ts +260 -0
  139. package/src/store/chat/slices/message/actions/publicApi.ts +224 -0
  140. package/src/store/chat/slices/message/actions/query.ts +120 -0
  141. package/src/store/chat/slices/message/actions/runtimeState.ts +108 -0
  142. package/src/store/chat/slices/message/initialState.ts +13 -0
  143. package/src/store/chat/slices/message/reducer.test.ts +48 -370
  144. package/src/store/chat/slices/message/reducer.ts +17 -81
  145. package/src/store/chat/slices/message/selectors/chat.test.ts +13 -50
  146. package/src/store/chat/slices/message/selectors/chat.ts +78 -242
  147. package/src/store/chat/slices/message/selectors/dbMessage.ts +140 -0
  148. package/src/store/chat/slices/message/selectors/displayMessage.ts +301 -0
  149. package/src/store/chat/slices/message/selectors/messageState.ts +5 -2
  150. package/src/store/chat/slices/plugin/action.test.ts +62 -64
  151. package/src/store/chat/slices/plugin/action.ts +34 -28
  152. package/src/store/chat/slices/thread/action.test.ts +28 -31
  153. package/src/store/chat/slices/thread/action.ts +13 -10
  154. package/src/store/chat/slices/thread/selectors/index.ts +8 -6
  155. package/src/store/chat/slices/topic/reducer.ts +11 -3
  156. package/src/store/chat/store.ts +1 -1
  157. package/src/store/user/slices/preference/selectors/labPrefer.ts +0 -3
  158. package/packages/database/src/models/__tests__/message.grouping.test.ts +0 -812
  159. package/packages/database/src/utils/__tests__/groupMessages.test.ts +0 -1132
  160. package/packages/database/src/utils/groupMessages.ts +0 -361
  161. package/packages/utils/src/tokenizer/client.ts +0 -35
  162. package/packages/utils/src/tokenizer/estimated.ts +0 -4
  163. package/packages/utils/src/tokenizer/server.ts +0 -11
  164. package/packages/utils/src/tokenizer/tokenizer.worker.ts +0 -12
  165. package/src/app/(backend)/webapi/tokenizer/index.test.ts +0 -32
  166. package/src/app/(backend)/webapi/tokenizer/route.ts +0 -8
  167. package/src/app/[variants]/(main)/chat/(workspace)/layout.ts +0 -11
  168. package/src/app/[variants]/(main)/chat/(workspace)/page.tsx +0 -53
  169. package/src/app/[variants]/(main)/chat/@session/default.tsx +0 -31
  170. package/src/app/[variants]/(main)/chat/settings/layout.tsx +0 -21
  171. package/src/features/Conversation/Error/InvalidAccessCode.tsx +0 -79
  172. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChat.test.ts +0 -975
  173. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChatV2.test.ts +0 -1050
  174. package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +0 -720
  175. package/src/store/chat/slices/aiChat/actions/generateAIChatV2.ts +0 -849
  176. package/src/store/chat/slices/message/action.ts +0 -629
  177. /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/default.tsx +0 -0
  178. /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatHydration/index.tsx +0 -0
  179. /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/Desktop/ClassicChat.tsx +0 -0
  180. /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/Desktop/GroupChat.tsx +0 -0
  181. /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/Desktop/index.tsx +0 -0
  182. /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/Desktop/useSendMenuItems.tsx +0 -0
  183. /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/Mobile/MentionedUsers/MentionedUserItem.tsx +0 -0
  184. /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/Mobile/MentionedUsers/index.tsx +0 -0
  185. /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/Mobile/index.tsx +0 -0
  186. /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/V1Mobile/ActionBar.tsx +0 -0
  187. /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/V1Mobile/Files/index.tsx +0 -0
  188. /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/V1Mobile/InputArea/Container.tsx +0 -0
  189. /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/V1Mobile/InputArea/index.tsx +0 -0
  190. /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/V1Mobile/Send.tsx +0 -0
  191. /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/index.tsx +0 -0
  192. /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatList/ChatItem/OrchestratorThinking.tsx +0 -0
  193. /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatList/ChatItem/Thread.tsx +0 -0
  194. /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatList/ChatItem/ThreadItem.tsx +0 -0
  195. /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatList/WelcomeChatItem/AgentWelcome/AddButton.tsx +0 -0
  196. /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatList/WelcomeChatItem/AgentWelcome/index.tsx +0 -0
  197. /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatList/WelcomeChatItem/GroupWelcome/index.tsx +0 -0
  198. /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatList/WelcomeChatItem/GroupWelcome/useTemplateMatching.ts +0 -0
  199. /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatList/WelcomeChatItem/index.tsx +0 -0
  200. /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatList/index.tsx +0 -0
  201. /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatMinimap/index.tsx +0 -0
  202. /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ThreadHydration.tsx +0 -0
  203. /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ZenModeToast/Toast.tsx +0 -0
  204. /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ZenModeToast/index.tsx +0 -0
  205. /package/src/app/[variants]/(main)/chat/{(workspace) → components}/features/AgentSettings/index.tsx +0 -0
  206. /package/src/app/[variants]/(main)/chat/{(workspace) → components}/features/AgentTeamSettings/index.tsx +0 -0
  207. /package/src/app/[variants]/(main)/chat/{(workspace) → components}/features/ChangelogModal.tsx +0 -0
  208. /package/src/app/[variants]/(main)/chat/{(workspace) → components}/features/SettingButton.tsx +0 -0
  209. /package/src/app/[variants]/(main)/chat/{(workspace) → components}/features/ShareButton/index.tsx +0 -0
  210. /package/src/app/[variants]/(main)/chat/{(workspace) → components}/features/TelemetryNotification.tsx +0 -0
  211. /package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/Desktop/ChatHeader/HeaderAction.tsx +0 -0
  212. /package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/Desktop/ChatHeader/Main.tsx +0 -0
  213. /package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/Desktop/ChatHeader/Tags/HistoryLimitTags.tsx +0 -0
  214. /package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/Desktop/ChatHeader/Tags/KnowledgeTag.tsx +0 -0
  215. /package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/Desktop/ChatHeader/Tags/MemberCountTag.tsx +0 -0
  216. /package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/Desktop/ChatHeader/Tags/SearchTags.tsx +0 -0
  217. /package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/Desktop/ChatHeader/Tags/index.tsx +0 -0
  218. /package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/Desktop/ChatHeader/index.tsx +0 -0
  219. /package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/Desktop/Portal.tsx +0 -0
  220. /package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/Desktop/TopicPanel.tsx +0 -0
  221. /package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/Desktop/index.tsx +0 -0
  222. /package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/Mobile/ChatHeader/ChatHeaderTitle.tsx +0 -0
  223. /package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/Mobile/ChatHeader/index.tsx +0 -0
  224. /package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/Mobile/TopicModal.tsx +0 -0
  225. /package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/Mobile/index.tsx +0 -0
  226. /package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/type.ts +0 -0
  227. /package/src/app/[variants]/(main)/chat/{(workspace)/@portal → components/portal}/_layout/Desktop.tsx +0 -0
  228. /package/src/app/[variants]/(main)/chat/{(workspace)/@portal → components/portal}/_layout/Mobile.tsx +0 -0
  229. /package/src/app/[variants]/(main)/chat/{(workspace)/@portal → components/portal}/default.tsx +0 -0
  230. /package/src/app/[variants]/(main)/chat/{(workspace)/@portal → components/portal}/error.tsx +0 -0
  231. /package/src/app/[variants]/(main)/chat/{(workspace)/@portal → components/portal}/features/Body.tsx +0 -0
  232. /package/src/app/[variants]/(main)/chat/{(workspace)/@portal → components/portal}/loading.tsx +0 -0
  233. /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/_layout/Desktop.tsx +0 -0
  234. /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/_layout/Mobile.tsx +0 -0
  235. /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/default.tsx +0 -0
  236. /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/AgentConfig/SystemRole.tsx +0 -0
  237. /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/AgentConfig/index.tsx +0 -0
  238. /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/ConfigLayout.tsx +0 -0
  239. /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/ConfigSwitcher.tsx +0 -0
  240. /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/GroupConfig/GroupMember.tsx +0 -0
  241. /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/GroupConfig/GroupMemberItem.tsx +0 -0
  242. /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/GroupConfig/GroupRole.tsx +0 -0
  243. /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/GroupConfig/index.tsx +0 -0
  244. /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/GroupConfig/style.ts +0 -0
  245. /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/SkeletonList.tsx +0 -0
  246. /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/Topic/Header.tsx +0 -0
  247. /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/Topic/TopicListContent/ByTimeMode/GroupItem.tsx +0 -0
  248. /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/Topic/TopicListContent/ByTimeMode/index.tsx +0 -0
  249. /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/Topic/TopicListContent/FlatMode/index.tsx +0 -0
  250. /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/Topic/TopicListContent/SearchResult/index.tsx +0 -0
  251. /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/Topic/TopicListContent/ThreadItem/Content.tsx +0 -0
  252. /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/Topic/TopicListContent/ThreadItem/index.tsx +0 -0
  253. /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/Topic/TopicListContent/ThreadList/index.tsx +0 -0
  254. /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/Topic/TopicListContent/TopicItem/DefaultContent.tsx +0 -0
  255. /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/Topic/TopicListContent/TopicItem/TopicContent.tsx +0 -0
  256. /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/Topic/TopicListContent/TopicItem/index.tsx +0 -0
  257. /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/Topic/TopicListContent/index.tsx +0 -0
  258. /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/Topic/TopicSearchBar/index.tsx +0 -0
  259. /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/Topic/index.tsx +0 -0
  260. /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionHydration.tsx +0 -0
  261. /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionListContent/CollapseGroup/Actions.tsx +0 -0
  262. /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionListContent/CollapseGroup/index.tsx +0 -0
  263. /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionListContent/DefaultMode.tsx +0 -0
  264. /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionListContent/Inbox/index.tsx +0 -0
  265. /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionListContent/List/AddButton.tsx +0 -0
  266. /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionListContent/List/Item/Actions.tsx +0 -0
  267. /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionListContent/List/Item/index.tsx +0 -0
  268. /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionListContent/List/index.tsx +0 -0
  269. /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionListContent/ListItem/index.tsx +0 -0
  270. /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionListContent/Modals/ConfigGroupModal/GroupItem.tsx +0 -0
  271. /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionListContent/Modals/ConfigGroupModal/index.tsx +0 -0
  272. /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionListContent/Modals/CreateGroupModal.tsx +0 -0
  273. /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionListContent/Modals/RenameGroupModal.tsx +0 -0
  274. /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionListContent/SearchMode.tsx +0 -0
  275. /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionListContent/index.tsx +0 -0
  276. /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionSearchBar.tsx +0 -0
  277. /package/src/app/[variants]/(main)/chat/{@session → session}/features/SkeletonList.tsx +0 -0
  278. /package/src/app/[variants]/(main)/chat/{@session/_layout → session/layout}/Desktop/PanelBody.tsx +0 -0
  279. /package/src/app/[variants]/(main)/chat/{@session/_layout → session/layout}/Desktop/SessionHeader.tsx +0 -0
  280. /package/src/app/[variants]/(main)/chat/{@session/_layout → session/layout}/Desktop/index.tsx +0 -0
  281. /package/src/app/[variants]/(main)/chat/{@session/_layout → session/layout}/Mobile/SessionHeader.tsx +0 -0
  282. /package/src/app/[variants]/(main)/chat/{@session/_layout → session/layout}/Mobile/index.tsx +0 -0
@@ -1,1050 +0,0 @@
1
- import { UIChatMessage } from '@lobechat/types';
2
- import { act, renderHook } from '@testing-library/react';
3
- import { TRPCClientError } from '@trpc/client';
4
- import { Mock, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
-
6
- import { LOADING_FLAT } from '@/const/message';
7
- import { DEFAULT_AGENT_CHAT_CONFIG, DEFAULT_MODEL, DEFAULT_PROVIDER } from '@/const/settings';
8
- import { aiChatService } from '@/services/aiChat';
9
- import { chatService } from '@/services/chat';
10
- import { messageService } from '@/services/message';
11
- import { agentChatConfigSelectors } from '@/store/agent/selectors';
12
- import { UploadFileItem } from '@/types/files/upload';
13
-
14
- import { useChatStore } from '../../../../store';
15
- import { messageMapKey } from '../../../../utils/messageMapKey';
16
- import { TEST_CONTENT, TEST_IDS, createMockStoreState } from './fixtures';
17
- import { resetTestEnvironment, setupMockSelectors, spyOnMessageService } from './helpers';
18
-
19
- // Keep zustand mock as it's needed globally
20
- vi.mock('zustand/traditional');
21
-
22
- // Mock AntdStaticMethods
23
- vi.mock('@/components/AntdStaticMethods', () => ({
24
- notification: {
25
- error: vi.fn(),
26
- success: vi.fn(),
27
- info: vi.fn(),
28
- warning: vi.fn(),
29
- },
30
- message: {
31
- error: vi.fn(),
32
- success: vi.fn(),
33
- info: vi.fn(),
34
- warning: vi.fn(),
35
- },
36
- }));
37
-
38
- // Mock sessionService to prevent TRPC requests
39
- vi.mock('@/services/session', () => ({
40
- sessionService: {
41
- updateSession: vi.fn(),
42
- updateSessionConfig: vi.fn(),
43
- updateSessionChatConfig: vi.fn(),
44
- },
45
- }));
46
-
47
- // Mock server mode for V2 tests
48
- vi.mock('@lobechat/const', async (importOriginal) => {
49
- const module = await importOriginal();
50
- return {
51
- ...(module as any),
52
- isServerMode: true,
53
- isDesktop: false,
54
- };
55
- });
56
-
57
- // Mock aiChatService for V2 server flow
58
- vi.mock('@/services/aiChat', () => ({
59
- aiChatService: {
60
- sendMessageInServer: vi.fn(async (params: any) => {
61
- const userId = TEST_IDS.USER_MESSAGE_ID;
62
- const assistantId = TEST_IDS.ASSISTANT_MESSAGE_ID;
63
- const topicId = params.topicId ?? TEST_IDS.TOPIC_ID;
64
- return {
65
- messages: [
66
- {
67
- id: userId,
68
- role: 'user',
69
- content: params.newUserMessage?.content ?? '',
70
- sessionId: params.sessionId ?? TEST_IDS.SESSION_ID,
71
- topicId,
72
- } as any,
73
- {
74
- id: assistantId,
75
- role: 'assistant',
76
- content: LOADING_FLAT,
77
- sessionId: params.sessionId ?? TEST_IDS.SESSION_ID,
78
- topicId,
79
- } as any,
80
- ],
81
- topics: [],
82
- topicId,
83
- userMessageId: userId,
84
- assistantMessageId: assistantId,
85
- isCreateNewTopic: !params.topicId,
86
- } as any;
87
- }),
88
- },
89
- }));
90
-
91
- const realExecAgentRuntime = useChatStore.getState().internal_execAgentRuntime;
92
-
93
- beforeEach(() => {
94
- resetTestEnvironment();
95
- setupMockSelectors();
96
-
97
- // Setup default spies that most tests need
98
- spyOnMessageService();
99
-
100
- // Setup common mock methods that most V2 tests need
101
- act(() => {
102
- useChatStore.setState({
103
- activeId: TEST_IDS.SESSION_ID,
104
- activeTopicId: TEST_IDS.TOPIC_ID,
105
- mainSendMessageOperations: {},
106
- refreshMessages: vi.fn(),
107
- refreshTopic: vi.fn(),
108
- internal_execAgentRuntime: vi.fn(),
109
- saveToTopic: vi.fn(),
110
- switchTopic: vi.fn(),
111
- });
112
- });
113
- });
114
-
115
- afterEach(() => {
116
- process.env.NEXT_PUBLIC_BASE_PATH = undefined;
117
- vi.restoreAllMocks();
118
- });
119
-
120
- describe('generateAIChatV2 actions', () => {
121
- describe('sendMessageInServer', () => {
122
- describe('validation', () => {
123
- it('should not send when there is no active session', async () => {
124
- act(() => {
125
- useChatStore.setState({ activeId: undefined });
126
- });
127
-
128
- const { result } = renderHook(() => useChatStore());
129
-
130
- await act(async () => {
131
- await result.current.sendMessage({ message: TEST_CONTENT.USER_MESSAGE });
132
- });
133
-
134
- expect(messageService.createMessage).not.toHaveBeenCalled();
135
- expect(result.current.internal_execAgentRuntime).not.toHaveBeenCalled();
136
- });
137
-
138
- it('should not send when message is empty and no files are provided', async () => {
139
- const { result } = renderHook(() => useChatStore());
140
-
141
- await act(async () => {
142
- await result.current.sendMessage({ message: TEST_CONTENT.EMPTY });
143
- });
144
-
145
- expect(messageService.createMessage).not.toHaveBeenCalled();
146
- });
147
-
148
- it('should not send when message is empty with empty files array', async () => {
149
- const { result } = renderHook(() => useChatStore());
150
-
151
- await act(async () => {
152
- await result.current.sendMessage({ message: TEST_CONTENT.EMPTY, files: [] });
153
- });
154
-
155
- expect(messageService.createMessage).not.toHaveBeenCalled();
156
- });
157
- });
158
-
159
- describe('message creation', () => {
160
- it('should create user message and trigger AI processing', async () => {
161
- const { result } = renderHook(() => useChatStore());
162
-
163
- await act(async () => {
164
- await result.current.sendMessage({ message: TEST_CONTENT.USER_MESSAGE });
165
- });
166
-
167
- expect(aiChatService.sendMessageInServer).toHaveBeenCalledWith(
168
- {
169
- newAssistantMessage: {
170
- model: DEFAULT_MODEL,
171
- provider: DEFAULT_PROVIDER,
172
- },
173
- newUserMessage: {
174
- content: TEST_CONTENT.USER_MESSAGE,
175
- files: undefined,
176
- },
177
- sessionId: TEST_IDS.SESSION_ID,
178
- topicId: TEST_IDS.TOPIC_ID,
179
- },
180
- expect.anything(),
181
- );
182
- expect(result.current.internal_execAgentRuntime).toHaveBeenCalled();
183
- });
184
-
185
- it('should skip creating new topic when auto-create topic is disabled', async () => {
186
- const { result } = renderHook(() => useChatStore());
187
-
188
- (agentChatConfigSelectors.currentChatConfig as Mock).mockReturnValue({
189
- ...DEFAULT_AGENT_CHAT_CONFIG,
190
- enableAutoCreateTopic: false,
191
- });
192
-
193
- await act(async () => {
194
- useChatStore.setState({
195
- ...createMockStoreState(),
196
- activeTopicId: undefined,
197
- messagesMap: {},
198
- });
199
-
200
- await result.current.sendMessage({ message: 'disable auto create' });
201
- });
202
-
203
- const callArgs = (aiChatService.sendMessageInServer as Mock).mock.calls[0][0];
204
- expect(callArgs.newTopic).toBeUndefined();
205
- });
206
-
207
- it('should include newTopic payload when auto-create topic is enabled and threshold is reached', async () => {
208
- const { result } = renderHook(() => useChatStore());
209
-
210
- (agentChatConfigSelectors.currentChatConfig as Mock).mockReturnValue({
211
- ...DEFAULT_AGENT_CHAT_CONFIG,
212
- enableAutoCreateTopic: true,
213
- autoCreateTopicThreshold: 1,
214
- });
215
-
216
- await act(async () => {
217
- useChatStore.setState({
218
- ...createMockStoreState(),
219
- activeTopicId: undefined,
220
- messagesMap: {},
221
- });
222
-
223
- await result.current.sendMessage({ message: 'auto create topic' });
224
- });
225
-
226
- const callArgs = (aiChatService.sendMessageInServer as Mock).mock.calls[0][0];
227
- expect(callArgs.newTopic).toMatchObject({
228
- topicMessageIds: [],
229
- });
230
- });
231
-
232
- it('should not create new topic when threshold is not reached', async () => {
233
- const { result } = renderHook(() => useChatStore());
234
-
235
- (agentChatConfigSelectors.currentChatConfig as Mock).mockReturnValue({
236
- ...DEFAULT_AGENT_CHAT_CONFIG,
237
- enableAutoCreateTopic: true,
238
- autoCreateTopicThreshold: 10,
239
- });
240
-
241
- await act(async () => {
242
- useChatStore.setState({
243
- ...createMockStoreState(),
244
- activeTopicId: undefined,
245
- messagesMap: {},
246
- });
247
-
248
- await result.current.sendMessage({ message: 'threshold not met' });
249
- });
250
-
251
- const callArgs = (aiChatService.sendMessageInServer as Mock).mock.calls[0][0];
252
- expect(callArgs.newTopic).toBeUndefined();
253
- });
254
-
255
- it('should send message with files attached', async () => {
256
- const { result } = renderHook(() => useChatStore());
257
- const files = [{ id: TEST_IDS.FILE_ID } as UploadFileItem];
258
-
259
- await act(async () => {
260
- await result.current.sendMessage({ message: TEST_CONTENT.USER_MESSAGE, files });
261
- });
262
-
263
- expect(aiChatService.sendMessageInServer).toHaveBeenCalledWith(
264
- {
265
- newAssistantMessage: {
266
- model: DEFAULT_MODEL,
267
- provider: DEFAULT_PROVIDER,
268
- },
269
- newUserMessage: {
270
- content: TEST_CONTENT.USER_MESSAGE,
271
- files: [TEST_IDS.FILE_ID],
272
- },
273
- sessionId: TEST_IDS.SESSION_ID,
274
- topicId: TEST_IDS.TOPIC_ID,
275
- },
276
- expect.anything(),
277
- );
278
- });
279
-
280
- it('should send files without message content', async () => {
281
- const { result } = renderHook(() => useChatStore());
282
- const files = [{ id: TEST_IDS.FILE_ID } as UploadFileItem];
283
-
284
- await act(async () => {
285
- await result.current.sendMessage({ message: TEST_CONTENT.EMPTY, files });
286
- });
287
-
288
- expect(aiChatService.sendMessageInServer).toHaveBeenCalledWith(
289
- {
290
- newAssistantMessage: {
291
- model: DEFAULT_MODEL,
292
- provider: DEFAULT_PROVIDER,
293
- },
294
- newUserMessage: {
295
- content: TEST_CONTENT.EMPTY,
296
- files: [TEST_IDS.FILE_ID],
297
- },
298
- sessionId: TEST_IDS.SESSION_ID,
299
- topicId: TEST_IDS.TOPIC_ID,
300
- },
301
- expect.anything(),
302
- );
303
- });
304
-
305
- it('should not process AI when onlyAddUserMessage is true', async () => {
306
- const { result } = renderHook(() => useChatStore());
307
-
308
- await act(async () => {
309
- await result.current.sendMessage({
310
- message: TEST_CONTENT.USER_MESSAGE,
311
- onlyAddUserMessage: true,
312
- });
313
- });
314
-
315
- expect(messageService.createMessage).toHaveBeenCalled();
316
- expect(result.current.internal_execAgentRuntime).not.toHaveBeenCalled();
317
- });
318
-
319
- it('should handle message creation errors gracefully', async () => {
320
- const { result } = renderHook(() => useChatStore());
321
- vi.spyOn(aiChatService, 'sendMessageInServer').mockRejectedValue(
322
- new Error('create message error'),
323
- );
324
-
325
- await act(async () => {
326
- try {
327
- await result.current.sendMessage({ message: TEST_CONTENT.USER_MESSAGE });
328
- } catch {
329
- // Expected to throw
330
- }
331
- });
332
-
333
- expect(result.current.internal_execAgentRuntime).not.toHaveBeenCalled();
334
- });
335
- });
336
-
337
- describe('RAG integration', () => {
338
- it('should include RAG query when RAG is enabled', async () => {
339
- const { result } = renderHook(() => useChatStore());
340
- vi.spyOn(result.current, 'internal_shouldUseRAG').mockReturnValue(true);
341
-
342
- await act(async () => {
343
- await result.current.sendMessage({ message: TEST_CONTENT.RAG_QUERY });
344
- });
345
-
346
- expect(result.current.internal_execAgentRuntime).toHaveBeenCalledWith(
347
- expect.objectContaining({
348
- ragQuery: TEST_CONTENT.RAG_QUERY,
349
- }),
350
- );
351
- });
352
-
353
- it('should not use RAG when feature is disabled', async () => {
354
- const { result } = renderHook(() => useChatStore());
355
- vi.spyOn(result.current, 'internal_shouldUseRAG').mockReturnValue(false);
356
- const retrieveChunksSpy = vi.spyOn(result.current, 'internal_retrieveChunks');
357
-
358
- await act(async () => {
359
- await result.current.sendMessage({ message: TEST_CONTENT.USER_MESSAGE });
360
- });
361
-
362
- expect(retrieveChunksSpy).not.toHaveBeenCalled();
363
- expect(result.current.internal_execAgentRuntime).toHaveBeenCalledWith(
364
- expect.objectContaining({
365
- ragQuery: undefined,
366
- }),
367
- );
368
- });
369
- });
370
-
371
- describe('special flags', () => {
372
- it('should pass isWelcomeQuestion flag to processing', async () => {
373
- const { result } = renderHook(() => useChatStore());
374
-
375
- await act(async () => {
376
- await result.current.sendMessage({
377
- message: TEST_CONTENT.USER_MESSAGE,
378
- isWelcomeQuestion: true,
379
- });
380
- });
381
-
382
- expect(result.current.internal_execAgentRuntime).toHaveBeenCalledWith(
383
- expect.objectContaining({
384
- isWelcomeQuestion: true,
385
- }),
386
- );
387
- });
388
- });
389
- });
390
-
391
- describe('internal_execAgentRuntime', () => {
392
- it('should handle the core AI message processing', async () => {
393
- act(() => {
394
- useChatStore.setState({ internal_execAgentRuntime: realExecAgentRuntime });
395
- });
396
-
397
- const { result } = renderHook(() => useChatStore());
398
- const userMessage = {
399
- id: TEST_IDS.USER_MESSAGE_ID,
400
- role: 'user',
401
- content: TEST_CONTENT.USER_MESSAGE,
402
- sessionId: TEST_IDS.SESSION_ID,
403
- topicId: TEST_IDS.TOPIC_ID,
404
- } as UIChatMessage;
405
- const messages = [userMessage];
406
-
407
- const streamSpy = vi.spyOn(chatService, 'createAssistantMessageStream');
408
-
409
- await act(async () => {
410
- await result.current.internal_execAgentRuntime({
411
- messages,
412
- userMessageId: userMessage.id,
413
- assistantMessageId: TEST_IDS.ASSISTANT_MESSAGE_ID,
414
- });
415
- });
416
-
417
- expect(streamSpy).toHaveBeenCalled();
418
- expect(result.current.refreshMessages).toHaveBeenCalled();
419
- });
420
- });
421
-
422
- describe('error handling', () => {
423
- it('should set error message when sendMessageInServer throws a regular error', async () => {
424
- const { result } = renderHook(() => useChatStore());
425
- const errorMessage = 'Network error';
426
- const mockError = new TRPCClientError(errorMessage);
427
- (mockError as any).data = { code: 'BAD_REQUEST' };
428
-
429
- vi.spyOn(aiChatService, 'sendMessageInServer').mockRejectedValue(mockError);
430
-
431
- await act(async () => {
432
- await result.current.sendMessage({ message: TEST_CONTENT.USER_MESSAGE });
433
- });
434
-
435
- const operationKey = messageMapKey(TEST_IDS.SESSION_ID, TEST_IDS.TOPIC_ID);
436
- expect(result.current.mainSendMessageOperations[operationKey]?.inputSendErrorMsg).toBe(
437
- errorMessage,
438
- );
439
- });
440
-
441
- it('should not set error message when receiving a cancel signal', async () => {
442
- const { result } = renderHook(() => useChatStore());
443
- const abortError = new Error('AbortError');
444
- abortError.name = 'AbortError';
445
-
446
- vi.spyOn(aiChatService, 'sendMessageInServer').mockRejectedValue(abortError);
447
-
448
- await act(async () => {
449
- await result.current.sendMessage({ message: TEST_CONTENT.USER_MESSAGE });
450
- });
451
-
452
- const operationKey = messageMapKey(TEST_IDS.SESSION_ID, TEST_IDS.TOPIC_ID);
453
- expect(
454
- result.current.mainSendMessageOperations[operationKey]?.inputSendErrorMsg,
455
- ).toBeUndefined();
456
- });
457
- });
458
-
459
- describe('topic creation and switching', () => {
460
- it('should remove temporary message when creating new topic in default state', async () => {
461
- const { result } = renderHook(() => useChatStore());
462
-
463
- vi.spyOn(aiChatService, 'sendMessageInServer').mockResolvedValueOnce({
464
- isCreateNewTopic: true,
465
- topicId: TEST_IDS.TOPIC_ID,
466
- messages: [{}, {}] as any,
467
- topics: [{}] as any,
468
- assistantMessageId: TEST_IDS.ASSISTANT_MESSAGE_ID,
469
- userMessageId: TEST_IDS.USER_MESSAGE_ID,
470
- });
471
-
472
- await act(async () => {
473
- useChatStore.setState({
474
- activeId: TEST_IDS.SESSION_ID,
475
- activeTopicId: undefined,
476
- messagesMap: {},
477
- switchTopic: vi.fn(),
478
- });
479
-
480
- await result.current.sendMessage({ message: TEST_CONTENT.USER_MESSAGE });
481
- });
482
-
483
- expect(useChatStore.getState().messagesMap[`${TEST_IDS.SESSION_ID}_null`]).toEqual([]);
484
- });
485
-
486
- it('should automatically switch to newly created topic when no active topic exists', async () => {
487
- const { result } = renderHook(() => useChatStore());
488
- const mockSwitchTopic = vi.fn();
489
-
490
- await act(async () => {
491
- useChatStore.setState({
492
- activeId: TEST_IDS.SESSION_ID,
493
- activeTopicId: undefined,
494
- switchTopic: mockSwitchTopic,
495
- });
496
- await result.current.sendMessage({ message: TEST_CONTENT.USER_MESSAGE });
497
- });
498
-
499
- expect(mockSwitchTopic).toHaveBeenCalledWith(TEST_IDS.TOPIC_ID, true);
500
- });
501
-
502
- it('should not switch topic when active topic already exists', async () => {
503
- const { result } = renderHook(() => useChatStore());
504
- const mockSwitchTopic = vi.fn();
505
-
506
- await act(async () => {
507
- useChatStore.setState({
508
- activeId: TEST_IDS.SESSION_ID,
509
- activeTopicId: TEST_IDS.TOPIC_ID,
510
- switchTopic: mockSwitchTopic,
511
- });
512
- await result.current.sendMessage({ message: TEST_CONTENT.USER_MESSAGE });
513
- });
514
-
515
- expect(mockSwitchTopic).not.toHaveBeenCalled();
516
- });
517
- });
518
-
519
- describe('cancelSendMessageInServer', () => {
520
- it('should abort operation and restore editor state when cancelling', () => {
521
- const { result } = renderHook(() => useChatStore());
522
- const mockAbort = vi.fn();
523
- const mockSetJSONState = vi.fn();
524
-
525
- act(() => {
526
- useChatStore.setState({
527
- activeId: TEST_IDS.SESSION_ID,
528
- activeTopicId: TEST_IDS.TOPIC_ID,
529
- mainSendMessageOperations: {
530
- [messageMapKey(TEST_IDS.SESSION_ID, TEST_IDS.TOPIC_ID)]: {
531
- isLoading: true,
532
- abortController: { abort: mockAbort, signal: {} as any },
533
- inputEditorTempState: { content: 'saved content' },
534
- },
535
- },
536
- mainInputEditor: { setJSONState: mockSetJSONState } as any,
537
- });
538
- });
539
-
540
- act(() => {
541
- result.current.cancelSendMessageInServer();
542
- });
543
-
544
- expect(mockAbort).toHaveBeenCalledWith('User cancelled sendMessage operation');
545
- expect(
546
- result.current.mainSendMessageOperations[
547
- messageMapKey(TEST_IDS.SESSION_ID, TEST_IDS.TOPIC_ID)
548
- ]?.isLoading,
549
- ).toBe(false);
550
- expect(mockSetJSONState).toHaveBeenCalledWith({ content: 'saved content' });
551
- });
552
-
553
- it('should cancel operation for specified topic ID', () => {
554
- const { result } = renderHook(() => useChatStore());
555
- const mockAbort = vi.fn();
556
- const customTopicId = 'custom-topic-id';
557
-
558
- act(() => {
559
- useChatStore.setState({
560
- activeId: TEST_IDS.SESSION_ID,
561
- mainSendMessageOperations: {
562
- [messageMapKey(TEST_IDS.SESSION_ID, customTopicId)]: {
563
- isLoading: true,
564
- abortController: { abort: mockAbort, signal: {} as any },
565
- },
566
- },
567
- });
568
- });
569
-
570
- act(() => {
571
- result.current.cancelSendMessageInServer(customTopicId);
572
- });
573
-
574
- expect(mockAbort).toHaveBeenCalledWith('User cancelled sendMessage operation');
575
- });
576
-
577
- it('should handle gracefully when operation does not exist', () => {
578
- const { result } = renderHook(() => useChatStore());
579
-
580
- act(() => {
581
- useChatStore.setState({ mainSendMessageOperations: {} });
582
- });
583
-
584
- expect(() => {
585
- act(() => {
586
- result.current.cancelSendMessageInServer('non-existing-topic');
587
- });
588
- }).not.toThrow();
589
- });
590
- });
591
-
592
- describe('clearSendMessageError', () => {
593
- it('should clear error state for current topic', () => {
594
- const { result } = renderHook(() => useChatStore());
595
-
596
- act(() => {
597
- useChatStore.setState({
598
- activeId: TEST_IDS.SESSION_ID,
599
- activeTopicId: TEST_IDS.TOPIC_ID,
600
- mainSendMessageOperations: {
601
- [messageMapKey(TEST_IDS.SESSION_ID, TEST_IDS.TOPIC_ID)]: {
602
- isLoading: false,
603
- inputSendErrorMsg: 'Some error',
604
- },
605
- },
606
- });
607
- });
608
-
609
- act(() => {
610
- result.current.clearSendMessageError();
611
- });
612
-
613
- expect(
614
- result.current.mainSendMessageOperations[
615
- messageMapKey(TEST_IDS.SESSION_ID, TEST_IDS.TOPIC_ID)
616
- ],
617
- ).toBeUndefined();
618
- });
619
-
620
- it('should handle gracefully when no error operation exists', () => {
621
- const { result } = renderHook(() => useChatStore());
622
-
623
- act(() => {
624
- useChatStore.setState({ mainSendMessageOperations: {} });
625
- });
626
-
627
- expect(() => {
628
- act(() => {
629
- result.current.clearSendMessageError();
630
- });
631
- }).not.toThrow();
632
- });
633
- });
634
-
635
- describe('internal_toggleSendMessageOperation', () => {
636
- it('should create new send operation with abort controller', () => {
637
- const { result } = renderHook(() => useChatStore());
638
- let abortController: AbortController | undefined;
639
-
640
- act(() => {
641
- abortController = result.current.internal_toggleSendMessageOperation('test-key', true);
642
- });
643
-
644
- expect(abortController!).toBeInstanceOf(AbortController);
645
- expect(result.current.mainSendMessageOperations['test-key']?.isLoading).toBe(true);
646
- expect(result.current.mainSendMessageOperations['test-key']?.abortController).toBe(
647
- abortController,
648
- );
649
- });
650
-
651
- it('should stop send operation and clear abort controller', () => {
652
- const { result } = renderHook(() => useChatStore());
653
- const mockAbortController = { abort: vi.fn() } as any;
654
-
655
- let abortController: AbortController | undefined;
656
- act(() => {
657
- result.current.internal_updateSendMessageOperation('test-key', {
658
- isLoading: true,
659
- abortController: mockAbortController,
660
- });
661
-
662
- abortController = result.current.internal_toggleSendMessageOperation('test-key', false);
663
- });
664
-
665
- expect(abortController).toBeUndefined();
666
- expect(result.current.mainSendMessageOperations['test-key']?.isLoading).toBe(false);
667
- expect(result.current.mainSendMessageOperations['test-key']?.abortController).toBeNull();
668
- });
669
-
670
- it('should call abort with cancel reason when stopping', () => {
671
- const { result } = renderHook(() => useChatStore());
672
- const mockAbortController = { abort: vi.fn() } as any;
673
-
674
- act(() => {
675
- result.current.internal_updateSendMessageOperation('test-key', {
676
- isLoading: true,
677
- abortController: mockAbortController,
678
- });
679
-
680
- result.current.internal_toggleSendMessageOperation('test-key', false, 'Test cancel reason');
681
- });
682
-
683
- expect(mockAbortController.abort).toHaveBeenCalledWith('Test cancel reason');
684
- });
685
-
686
- it('should support multiple parallel operations', () => {
687
- const { result } = renderHook(() => useChatStore());
688
-
689
- let abortController1, abortController2;
690
- act(() => {
691
- abortController1 = result.current.internal_toggleSendMessageOperation('key1', true);
692
- abortController2 = result.current.internal_toggleSendMessageOperation('key2', true);
693
- });
694
-
695
- expect(result.current.mainSendMessageOperations['key1']?.isLoading).toBe(true);
696
- expect(result.current.mainSendMessageOperations['key2']?.isLoading).toBe(true);
697
- expect(abortController1).not.toBe(abortController2);
698
- });
699
- });
700
-
701
- describe('internal_updateSendMessageOperation', () => {
702
- it('should update operation state', () => {
703
- const { result } = renderHook(() => useChatStore());
704
- const mockAbortController = new AbortController();
705
-
706
- act(() => {
707
- result.current.internal_updateSendMessageOperation('test-key', {
708
- isLoading: true,
709
- abortController: mockAbortController,
710
- inputSendErrorMsg: 'test error',
711
- });
712
- });
713
-
714
- expect(result.current.mainSendMessageOperations['test-key']).toEqual({
715
- isLoading: true,
716
- abortController: mockAbortController,
717
- inputSendErrorMsg: 'test error',
718
- });
719
- });
720
-
721
- it('should support partial update of operation state', () => {
722
- const { result } = renderHook(() => useChatStore());
723
- const initialController = new AbortController();
724
-
725
- act(() => {
726
- result.current.internal_updateSendMessageOperation('test-key', {
727
- isLoading: true,
728
- abortController: initialController,
729
- });
730
-
731
- result.current.internal_updateSendMessageOperation('test-key', {
732
- inputSendErrorMsg: 'new error',
733
- });
734
- });
735
-
736
- expect(result.current.mainSendMessageOperations['test-key']).toEqual({
737
- isLoading: true,
738
- abortController: initialController,
739
- inputSendErrorMsg: 'new error',
740
- });
741
- });
742
- });
743
-
744
- describe('callToolFollowAssistantMessage', () => {
745
- const TOOL_RESULT_MSG_ID = 'tool-result-msg-id';
746
- const ASSISTANT_BLOCK_ID = 'assistant-block-id';
747
- const GROUP_MESSAGE_ID = 'group-message-id';
748
- const TOOL_CALL_ID = 'tool-call-id';
749
-
750
- beforeEach(() => {
751
- // Reset mocks
752
- vi.spyOn(messageService, 'createMessage').mockResolvedValue({
753
- id: 'new-assistant-block-id',
754
- messages: [] as any,
755
- });
756
- });
757
-
758
- it('should find group message from tool result_msg_id in tools array', async () => {
759
- const { result } = renderHook(() => useChatStore());
760
- const dispatchSpy = vi.fn();
761
-
762
- // Create a group message structure with tool results
763
- const groupMessage: UIChatMessage = {
764
- id: GROUP_MESSAGE_ID,
765
- role: 'group',
766
- content: '',
767
- sessionId: TEST_IDS.SESSION_ID,
768
- topicId: TEST_IDS.TOPIC_ID,
769
- children: [
770
- {
771
- id: ASSISTANT_BLOCK_ID,
772
- content: 'Assistant response',
773
- tools: [
774
- {
775
- id: TOOL_CALL_ID,
776
- type: 'builtin',
777
- apiName: 'testTool',
778
- identifier: 'test-tool',
779
- arguments: '{}',
780
- result: {
781
- id: TOOL_RESULT_MSG_ID,
782
- content: 'Tool result',
783
- },
784
- result_msg_id: TOOL_RESULT_MSG_ID,
785
- },
786
- ],
787
- },
788
- ],
789
- } as any;
790
-
791
- act(() => {
792
- useChatStore.setState({
793
- activeId: TEST_IDS.SESSION_ID,
794
- activeTopicId: TEST_IDS.TOPIC_ID,
795
- messagesMap: {
796
- [messageMapKey(TEST_IDS.SESSION_ID, TEST_IDS.TOPIC_ID)]: [groupMessage],
797
- },
798
- internal_execAgentRuntime: vi.fn(),
799
- internal_dispatchMessage: dispatchSpy,
800
- });
801
- });
802
-
803
- await act(async () => {
804
- await result.current.callToolFollowAssistantMessage({
805
- parentId: TOOL_RESULT_MSG_ID,
806
- });
807
- });
808
-
809
- // Verify that addGroupBlock was called with the correct groupMessageId
810
- expect(dispatchSpy).toHaveBeenCalledWith(
811
- expect.objectContaining({
812
- type: 'addGroupBlock',
813
- groupMessageId: GROUP_MESSAGE_ID,
814
- }),
815
- );
816
-
817
- // Verify that createMessage was called with message params
818
- expect(messageService.createMessage).toHaveBeenCalledWith(
819
- expect.objectContaining({
820
- role: 'assistant',
821
- parentId: TOOL_RESULT_MSG_ID,
822
- }),
823
- );
824
- });
825
-
826
- it('should handle case when tool result is not found in any group message', async () => {
827
- const { result } = renderHook(() => useChatStore());
828
- const dispatchSpy = vi.fn();
829
-
830
- const groupMessage: UIChatMessage = {
831
- id: GROUP_MESSAGE_ID,
832
- role: 'group',
833
- content: '',
834
- sessionId: TEST_IDS.SESSION_ID,
835
- children: [
836
- {
837
- id: ASSISTANT_BLOCK_ID,
838
- content: 'Assistant response',
839
- tools: [], // No tools
840
- },
841
- ],
842
- } as any;
843
-
844
- act(() => {
845
- useChatStore.setState({
846
- activeId: TEST_IDS.SESSION_ID,
847
- activeTopicId: TEST_IDS.TOPIC_ID,
848
- messagesMap: {
849
- [messageMapKey(TEST_IDS.SESSION_ID, TEST_IDS.TOPIC_ID)]: [groupMessage],
850
- },
851
- internal_execAgentRuntime: vi.fn(),
852
- internal_dispatchMessage: dispatchSpy,
853
- });
854
- });
855
-
856
- await act(async () => {
857
- await result.current.callToolFollowAssistantMessage({
858
- parentId: 'non-existent-tool-result-id',
859
- });
860
- });
861
-
862
- // Should create message as regular top-level message (createMessage, not addGroupBlock)
863
- expect(dispatchSpy).toHaveBeenCalledWith(
864
- expect.objectContaining({
865
- type: 'createMessage',
866
- }),
867
- );
868
-
869
- expect(messageService.createMessage).toHaveBeenCalledWith(
870
- expect.objectContaining({
871
- role: 'assistant',
872
- parentId: 'non-existent-tool-result-id',
873
- }),
874
- );
875
- });
876
-
877
- it('should find group message from nested tool results in multiple children', async () => {
878
- const { result } = renderHook(() => useChatStore());
879
- const dispatchSpy = vi.fn();
880
-
881
- const groupMessage: UIChatMessage = {
882
- id: GROUP_MESSAGE_ID,
883
- role: 'group',
884
- content: '',
885
- sessionId: TEST_IDS.SESSION_ID,
886
- children: [
887
- {
888
- id: 'first-block',
889
- content: 'First assistant response',
890
- tools: [
891
- {
892
- id: 'tool-1',
893
- type: 'builtin',
894
- apiName: 'tool1',
895
- identifier: 'tool-1',
896
- arguments: '{}',
897
- result_msg_id: 'other-result-id',
898
- },
899
- ],
900
- },
901
- {
902
- id: 'second-block',
903
- content: 'Second assistant response',
904
- tools: [
905
- {
906
- id: 'tool-2',
907
- type: 'builtin',
908
- apiName: 'tool2',
909
- identifier: 'tool-2',
910
- arguments: '{}',
911
- result_msg_id: TOOL_RESULT_MSG_ID, // Target tool result
912
- },
913
- ],
914
- },
915
- ],
916
- } as any;
917
-
918
- act(() => {
919
- useChatStore.setState({
920
- activeId: TEST_IDS.SESSION_ID,
921
- messagesMap: {
922
- [messageMapKey(TEST_IDS.SESSION_ID, TEST_IDS.TOPIC_ID)]: [groupMessage],
923
- },
924
- internal_execAgentRuntime: vi.fn(),
925
- internal_dispatchMessage: dispatchSpy,
926
- });
927
- });
928
-
929
- await act(async () => {
930
- await result.current.callToolFollowAssistantMessage({
931
- parentId: TOOL_RESULT_MSG_ID,
932
- });
933
- });
934
-
935
- // Should find the correct group message even with multiple children
936
- expect(dispatchSpy).toHaveBeenCalledWith(
937
- expect.objectContaining({
938
- type: 'addGroupBlock',
939
- groupMessageId: GROUP_MESSAGE_ID,
940
- }),
941
- );
942
- });
943
-
944
- it('should call internal_execAgentRuntime after creating assistant message', async () => {
945
- const { result } = renderHook(() => useChatStore());
946
- const mockExecAgentRuntime = vi.fn();
947
-
948
- const groupMessage: UIChatMessage = {
949
- id: GROUP_MESSAGE_ID,
950
- role: 'group',
951
- content: '',
952
- sessionId: TEST_IDS.SESSION_ID,
953
- children: [
954
- {
955
- id: ASSISTANT_BLOCK_ID,
956
- content: 'Response',
957
- tools: [
958
- {
959
- id: TOOL_CALL_ID,
960
- type: 'builtin',
961
- apiName: 'test',
962
- identifier: 'test',
963
- arguments: '{}',
964
- result_msg_id: TOOL_RESULT_MSG_ID,
965
- },
966
- ],
967
- },
968
- ],
969
- } as any;
970
-
971
- act(() => {
972
- useChatStore.setState({
973
- activeId: TEST_IDS.SESSION_ID,
974
- messagesMap: {
975
- [messageMapKey(TEST_IDS.SESSION_ID, TEST_IDS.TOPIC_ID)]: [groupMessage],
976
- },
977
- internal_execAgentRuntime: mockExecAgentRuntime,
978
- });
979
- });
980
-
981
- await act(async () => {
982
- await result.current.callToolFollowAssistantMessage({
983
- parentId: TOOL_RESULT_MSG_ID,
984
- traceId: 'test-trace-id',
985
- threadId: 'test-thread-id',
986
- });
987
- });
988
-
989
- expect(mockExecAgentRuntime).toHaveBeenCalledWith(
990
- expect.objectContaining({
991
- assistantMessageId: 'new-assistant-block-id',
992
- traceId: 'test-trace-id',
993
- threadId: 'test-thread-id',
994
- }),
995
- );
996
- });
997
-
998
- it('should handle missing result_msg_id field gracefully', async () => {
999
- const { result } = renderHook(() => useChatStore());
1000
- const dispatchSpy = vi.fn();
1001
-
1002
- const groupMessage: UIChatMessage = {
1003
- id: GROUP_MESSAGE_ID,
1004
- role: 'group',
1005
- content: '',
1006
- sessionId: TEST_IDS.SESSION_ID,
1007
- children: [
1008
- {
1009
- id: ASSISTANT_BLOCK_ID,
1010
- content: 'Response',
1011
- tools: [
1012
- {
1013
- id: TOOL_CALL_ID,
1014
- type: 'builtin',
1015
- apiName: 'test',
1016
- identifier: 'test',
1017
- arguments: '{}',
1018
- // Missing result_msg_id
1019
- },
1020
- ],
1021
- },
1022
- ],
1023
- } as any;
1024
-
1025
- act(() => {
1026
- useChatStore.setState({
1027
- activeId: TEST_IDS.SESSION_ID,
1028
- messagesMap: {
1029
- [messageMapKey(TEST_IDS.SESSION_ID, TEST_IDS.TOPIC_ID)]: [groupMessage],
1030
- },
1031
- internal_execAgentRuntime: vi.fn(),
1032
- internal_dispatchMessage: dispatchSpy,
1033
- });
1034
- });
1035
-
1036
- await act(async () => {
1037
- await result.current.callToolFollowAssistantMessage({
1038
- parentId: TOOL_RESULT_MSG_ID,
1039
- });
1040
- });
1041
-
1042
- // Should create as regular message since no groupMessageId found
1043
- expect(dispatchSpy).toHaveBeenCalledWith(
1044
- expect.objectContaining({
1045
- type: 'createMessage',
1046
- }),
1047
- );
1048
- });
1049
- });
1050
- });