@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
@@ -0,0 +1,332 @@
1
+ import { act, renderHook } from '@testing-library/react';
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
+
4
+ import { useChatStore } from '../../../../store';
5
+ import { messageMapKey } from '../../../../utils/messageMapKey';
6
+ import { TEST_IDS } from './fixtures';
7
+ import { resetTestEnvironment } from './helpers';
8
+
9
+ // Keep zustand mock as it's needed globally
10
+ vi.mock('zustand/traditional');
11
+
12
+ beforeEach(() => {
13
+ resetTestEnvironment();
14
+ });
15
+
16
+ afterEach(() => {
17
+ vi.restoreAllMocks();
18
+ });
19
+
20
+ describe('ConversationControl actions', () => {
21
+ describe('stopGenerateMessage', () => {
22
+ it('should abort generation and clear loading state when controller exists', () => {
23
+ const abortController = new AbortController();
24
+
25
+ act(() => {
26
+ useChatStore.setState({ chatLoadingIdsAbortController: abortController });
27
+ });
28
+
29
+ const { result } = renderHook(() => useChatStore());
30
+ const toggleLoadingSpy = vi.spyOn(result.current, 'internal_toggleChatLoading');
31
+
32
+ act(() => {
33
+ result.current.stopGenerateMessage();
34
+ });
35
+
36
+ expect(abortController.signal.aborted).toBe(true);
37
+ expect(toggleLoadingSpy).toHaveBeenCalledWith(false, undefined, expect.any(String));
38
+ });
39
+
40
+ it('should do nothing when abort controller is not set', () => {
41
+ act(() => {
42
+ useChatStore.setState({ chatLoadingIdsAbortController: undefined });
43
+ });
44
+
45
+ const { result } = renderHook(() => useChatStore());
46
+ const toggleLoadingSpy = vi.spyOn(result.current, 'internal_toggleChatLoading');
47
+
48
+ act(() => {
49
+ result.current.stopGenerateMessage();
50
+ });
51
+
52
+ expect(toggleLoadingSpy).not.toHaveBeenCalled();
53
+ });
54
+ });
55
+
56
+ describe('cancelSendMessageInServer', () => {
57
+ it('should abort operation and restore editor state when cancelling', () => {
58
+ const { result } = renderHook(() => useChatStore());
59
+ const mockAbort = vi.fn();
60
+ const mockSetJSONState = vi.fn();
61
+
62
+ act(() => {
63
+ useChatStore.setState({
64
+ activeId: TEST_IDS.SESSION_ID,
65
+ activeTopicId: TEST_IDS.TOPIC_ID,
66
+ mainSendMessageOperations: {
67
+ [messageMapKey(TEST_IDS.SESSION_ID, TEST_IDS.TOPIC_ID)]: {
68
+ isLoading: true,
69
+ abortController: { abort: mockAbort, signal: {} as any },
70
+ inputEditorTempState: { content: 'saved content' },
71
+ },
72
+ },
73
+ mainInputEditor: { setJSONState: mockSetJSONState } as any,
74
+ });
75
+ });
76
+
77
+ act(() => {
78
+ result.current.cancelSendMessageInServer();
79
+ });
80
+
81
+ expect(mockAbort).toHaveBeenCalledWith('User cancelled sendMessage operation');
82
+ expect(
83
+ result.current.mainSendMessageOperations[
84
+ messageMapKey(TEST_IDS.SESSION_ID, TEST_IDS.TOPIC_ID)
85
+ ]?.isLoading,
86
+ ).toBe(false);
87
+ expect(mockSetJSONState).toHaveBeenCalledWith({ content: 'saved content' });
88
+ });
89
+
90
+ it('should cancel operation for specified topic ID', () => {
91
+ const { result } = renderHook(() => useChatStore());
92
+ const mockAbort = vi.fn();
93
+ const customTopicId = 'custom-topic-id';
94
+
95
+ act(() => {
96
+ useChatStore.setState({
97
+ activeId: TEST_IDS.SESSION_ID,
98
+ mainSendMessageOperations: {
99
+ [messageMapKey(TEST_IDS.SESSION_ID, customTopicId)]: {
100
+ isLoading: true,
101
+ abortController: { abort: mockAbort, signal: {} as any },
102
+ },
103
+ },
104
+ });
105
+ });
106
+
107
+ act(() => {
108
+ result.current.cancelSendMessageInServer(customTopicId);
109
+ });
110
+
111
+ expect(mockAbort).toHaveBeenCalledWith('User cancelled sendMessage operation');
112
+ });
113
+
114
+ it('should handle gracefully when operation does not exist', () => {
115
+ const { result } = renderHook(() => useChatStore());
116
+
117
+ act(() => {
118
+ useChatStore.setState({ mainSendMessageOperations: {} });
119
+ });
120
+
121
+ expect(() => {
122
+ act(() => {
123
+ result.current.cancelSendMessageInServer('non-existing-topic');
124
+ });
125
+ }).not.toThrow();
126
+ });
127
+ });
128
+
129
+ describe('clearSendMessageError', () => {
130
+ it('should clear error state for current topic', () => {
131
+ const { result } = renderHook(() => useChatStore());
132
+
133
+ act(() => {
134
+ useChatStore.setState({
135
+ activeId: TEST_IDS.SESSION_ID,
136
+ activeTopicId: TEST_IDS.TOPIC_ID,
137
+ mainSendMessageOperations: {
138
+ [messageMapKey(TEST_IDS.SESSION_ID, TEST_IDS.TOPIC_ID)]: {
139
+ isLoading: false,
140
+ inputSendErrorMsg: 'Some error',
141
+ },
142
+ },
143
+ });
144
+ });
145
+
146
+ act(() => {
147
+ result.current.clearSendMessageError();
148
+ });
149
+
150
+ expect(
151
+ result.current.mainSendMessageOperations[
152
+ messageMapKey(TEST_IDS.SESSION_ID, TEST_IDS.TOPIC_ID)
153
+ ],
154
+ ).toBeUndefined();
155
+ });
156
+
157
+ it('should handle gracefully when no error operation exists', () => {
158
+ const { result } = renderHook(() => useChatStore());
159
+
160
+ act(() => {
161
+ useChatStore.setState({ mainSendMessageOperations: {} });
162
+ });
163
+
164
+ expect(() => {
165
+ act(() => {
166
+ result.current.clearSendMessageError();
167
+ });
168
+ }).not.toThrow();
169
+ });
170
+ });
171
+
172
+ describe('internal_toggleSendMessageOperation', () => {
173
+ it('should create new send operation with abort controller', () => {
174
+ const { result } = renderHook(() => useChatStore());
175
+ let abortController: AbortController | undefined;
176
+
177
+ act(() => {
178
+ abortController = result.current.internal_toggleSendMessageOperation('test-key', true);
179
+ });
180
+
181
+ expect(abortController!).toBeInstanceOf(AbortController);
182
+ expect(result.current.mainSendMessageOperations['test-key']?.isLoading).toBe(true);
183
+ expect(result.current.mainSendMessageOperations['test-key']?.abortController).toBe(
184
+ abortController,
185
+ );
186
+ });
187
+
188
+ it('should stop send operation and clear abort controller', () => {
189
+ const { result } = renderHook(() => useChatStore());
190
+ const mockAbortController = { abort: vi.fn() } as any;
191
+
192
+ let abortController: AbortController | undefined;
193
+ act(() => {
194
+ result.current.internal_updateSendMessageOperation('test-key', {
195
+ isLoading: true,
196
+ abortController: mockAbortController,
197
+ });
198
+
199
+ abortController = result.current.internal_toggleSendMessageOperation('test-key', false);
200
+ });
201
+
202
+ expect(abortController).toBeUndefined();
203
+ expect(result.current.mainSendMessageOperations['test-key']?.isLoading).toBe(false);
204
+ expect(result.current.mainSendMessageOperations['test-key']?.abortController).toBeNull();
205
+ });
206
+
207
+ it('should call abort with cancel reason when stopping', () => {
208
+ const { result } = renderHook(() => useChatStore());
209
+ const mockAbortController = { abort: vi.fn() } as any;
210
+
211
+ act(() => {
212
+ result.current.internal_updateSendMessageOperation('test-key', {
213
+ isLoading: true,
214
+ abortController: mockAbortController,
215
+ });
216
+
217
+ result.current.internal_toggleSendMessageOperation('test-key', false, 'Test cancel reason');
218
+ });
219
+
220
+ expect(mockAbortController.abort).toHaveBeenCalledWith('Test cancel reason');
221
+ });
222
+
223
+ it('should support multiple parallel operations', () => {
224
+ const { result } = renderHook(() => useChatStore());
225
+
226
+ let abortController1, abortController2;
227
+ act(() => {
228
+ abortController1 = result.current.internal_toggleSendMessageOperation('key1', true);
229
+ abortController2 = result.current.internal_toggleSendMessageOperation('key2', true);
230
+ });
231
+
232
+ expect(result.current.mainSendMessageOperations['key1']?.isLoading).toBe(true);
233
+ expect(result.current.mainSendMessageOperations['key2']?.isLoading).toBe(true);
234
+ expect(abortController1).not.toBe(abortController2);
235
+ });
236
+ });
237
+
238
+ describe('internal_updateSendMessageOperation', () => {
239
+ it('should update operation state', () => {
240
+ const { result } = renderHook(() => useChatStore());
241
+ const mockAbortController = new AbortController();
242
+
243
+ act(() => {
244
+ result.current.internal_updateSendMessageOperation('test-key', {
245
+ isLoading: true,
246
+ abortController: mockAbortController,
247
+ inputSendErrorMsg: 'test error',
248
+ });
249
+ });
250
+
251
+ expect(result.current.mainSendMessageOperations['test-key']).toEqual({
252
+ isLoading: true,
253
+ abortController: mockAbortController,
254
+ inputSendErrorMsg: 'test error',
255
+ });
256
+ });
257
+
258
+ it('should support partial update of operation state', () => {
259
+ const { result } = renderHook(() => useChatStore());
260
+ const initialController = new AbortController();
261
+
262
+ act(() => {
263
+ result.current.internal_updateSendMessageOperation('test-key', {
264
+ isLoading: true,
265
+ abortController: initialController,
266
+ });
267
+
268
+ result.current.internal_updateSendMessageOperation('test-key', {
269
+ inputSendErrorMsg: 'new error',
270
+ });
271
+ });
272
+
273
+ expect(result.current.mainSendMessageOperations['test-key']).toEqual({
274
+ isLoading: true,
275
+ abortController: initialController,
276
+ inputSendErrorMsg: 'new error',
277
+ });
278
+ });
279
+ });
280
+
281
+ describe('switchMessageBranch', () => {
282
+ it('should switch to a different message branch', async () => {
283
+ const { result } = renderHook(() => useChatStore());
284
+ const messageId = TEST_IDS.MESSAGE_ID;
285
+ const branchIndex = 1;
286
+
287
+ const optimisticUpdateSpy = vi
288
+ .spyOn(result.current, 'optimisticUpdateMessageMetadata')
289
+ .mockResolvedValue(undefined);
290
+
291
+ await act(async () => {
292
+ await result.current.switchMessageBranch(messageId, branchIndex);
293
+ });
294
+
295
+ expect(optimisticUpdateSpy).toHaveBeenCalledWith(messageId, { activeBranchIndex: branchIndex });
296
+ });
297
+
298
+ it('should handle switching to branch 0', async () => {
299
+ const { result } = renderHook(() => useChatStore());
300
+ const messageId = TEST_IDS.MESSAGE_ID;
301
+ const branchIndex = 0;
302
+
303
+ const optimisticUpdateSpy = vi
304
+ .spyOn(result.current, 'optimisticUpdateMessageMetadata')
305
+ .mockResolvedValue(undefined);
306
+
307
+ await act(async () => {
308
+ await result.current.switchMessageBranch(messageId, branchIndex);
309
+ });
310
+
311
+ expect(optimisticUpdateSpy).toHaveBeenCalledWith(messageId, { activeBranchIndex: 0 });
312
+ });
313
+
314
+ it('should handle errors gracefully when optimistic update fails', async () => {
315
+ const { result } = renderHook(() => useChatStore());
316
+ const messageId = TEST_IDS.MESSAGE_ID;
317
+ const branchIndex = 2;
318
+
319
+ const optimisticUpdateSpy = vi
320
+ .spyOn(result.current, 'optimisticUpdateMessageMetadata')
321
+ .mockRejectedValue(new Error('Update failed'));
322
+
323
+ await expect(
324
+ act(async () => {
325
+ await result.current.switchMessageBranch(messageId, branchIndex);
326
+ }),
327
+ ).rejects.toThrow('Update failed');
328
+
329
+ expect(optimisticUpdateSpy).toHaveBeenCalledWith(messageId, { activeBranchIndex: branchIndex });
330
+ });
331
+ });
332
+ });
@@ -0,0 +1,257 @@
1
+ import { act, renderHook } from '@testing-library/react';
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
+
4
+ import { aiChatService } from '@/services/aiChat';
5
+
6
+ import { useChatStore } from '../../../../store';
7
+ import { TEST_CONTENT, TEST_IDS, createMockMessage } from './fixtures';
8
+ import {
9
+ resetTestEnvironment,
10
+ setupMockSelectors,
11
+ setupStoreWithMessages,
12
+ spyOnMessageService,
13
+ } from './helpers';
14
+
15
+ // Keep zustand mock as it's needed globally
16
+ vi.mock('zustand/traditional');
17
+
18
+ beforeEach(() => {
19
+ resetTestEnvironment();
20
+ setupMockSelectors();
21
+ spyOnMessageService();
22
+
23
+ act(() => {
24
+ useChatStore.setState({
25
+ refreshMessages: vi.fn(),
26
+ refreshTopic: vi.fn(),
27
+ internal_execAgentRuntime: vi.fn(),
28
+ });
29
+ });
30
+ });
31
+
32
+ afterEach(() => {
33
+ vi.restoreAllMocks();
34
+ });
35
+
36
+ describe('ConversationLifecycle actions', () => {
37
+ describe('sendMessage', () => {
38
+ describe('validation', () => {
39
+ it('should not send when there is no active session', async () => {
40
+ act(() => {
41
+ useChatStore.setState({ activeId: undefined });
42
+ });
43
+
44
+ const { result } = renderHook(() => useChatStore());
45
+
46
+ await act(async () => {
47
+ await result.current.sendMessage({ message: TEST_CONTENT.USER_MESSAGE });
48
+ });
49
+
50
+ expect(result.current.internal_execAgentRuntime).not.toHaveBeenCalled();
51
+ });
52
+
53
+ it('should not send when message is empty and no files are provided', async () => {
54
+ const { result } = renderHook(() => useChatStore());
55
+
56
+ await act(async () => {
57
+ await result.current.sendMessage({ message: TEST_CONTENT.EMPTY });
58
+ });
59
+
60
+ expect(result.current.internal_execAgentRuntime).not.toHaveBeenCalled();
61
+ });
62
+
63
+ it('should not send when message is empty with empty files array', async () => {
64
+ const { result } = renderHook(() => useChatStore());
65
+
66
+ await act(async () => {
67
+ await result.current.sendMessage({ message: TEST_CONTENT.EMPTY, files: [] });
68
+ });
69
+
70
+ expect(result.current.internal_execAgentRuntime).not.toHaveBeenCalled();
71
+ });
72
+ });
73
+
74
+ describe('message creation', () => {
75
+ it('should not process AI when onlyAddUserMessage is true', async () => {
76
+ const { result } = renderHook(() => useChatStore());
77
+
78
+ vi.spyOn(aiChatService, 'sendMessageInServer').mockResolvedValue({
79
+ messages: [],
80
+ topics: [],
81
+ assistantMessageId: TEST_IDS.ASSISTANT_MESSAGE_ID,
82
+ } as any);
83
+
84
+ await act(async () => {
85
+ await result.current.sendMessage({
86
+ message: TEST_CONTENT.USER_MESSAGE,
87
+ onlyAddUserMessage: true,
88
+ });
89
+ });
90
+
91
+ expect(result.current.internal_execAgentRuntime).not.toHaveBeenCalled();
92
+ });
93
+
94
+ it('should create user message and trigger AI processing', async () => {
95
+ const { result } = renderHook(() => useChatStore());
96
+
97
+ vi.spyOn(aiChatService, 'sendMessageInServer').mockResolvedValue({
98
+ messages: [
99
+ createMockMessage({ id: TEST_IDS.USER_MESSAGE_ID, role: 'user' }),
100
+ createMockMessage({ id: TEST_IDS.ASSISTANT_MESSAGE_ID, role: 'assistant' }),
101
+ ],
102
+ topics: [],
103
+ assistantMessageId: TEST_IDS.ASSISTANT_MESSAGE_ID,
104
+ } as any);
105
+
106
+ await act(async () => {
107
+ await result.current.sendMessage({ message: TEST_CONTENT.USER_MESSAGE });
108
+ });
109
+
110
+ expect(result.current.internal_execAgentRuntime).toHaveBeenCalled();
111
+ });
112
+ });
113
+ });
114
+
115
+ describe('regenerateUserMessage', () => {
116
+ it('should trigger user message regeneration', async () => {
117
+ const messages = [
118
+ createMockMessage({ id: TEST_IDS.USER_MESSAGE_ID, role: 'user', content: 'test' }),
119
+ createMockMessage({ id: TEST_IDS.MESSAGE_ID, role: 'assistant' }),
120
+ ];
121
+
122
+ setupStoreWithMessages(messages);
123
+
124
+ const switchMessageBranchSpy = vi.fn().mockResolvedValue(undefined);
125
+ const internalTraceSpy = vi.fn();
126
+
127
+ act(() => {
128
+ useChatStore.setState({
129
+ internal_traceMessage: internalTraceSpy,
130
+ switchMessageBranch: switchMessageBranchSpy,
131
+ internal_shouldUseRAG: vi.fn().mockReturnValue(false),
132
+ });
133
+ });
134
+
135
+ const { result } = renderHook(() => useChatStore());
136
+
137
+ await act(async () => {
138
+ await result.current.regenerateUserMessage(TEST_IDS.USER_MESSAGE_ID);
139
+ });
140
+
141
+ expect(switchMessageBranchSpy).toHaveBeenCalledWith(TEST_IDS.USER_MESSAGE_ID, 1);
142
+ expect(result.current.internal_execAgentRuntime).toHaveBeenCalledWith(
143
+ expect.objectContaining({
144
+ parentMessageId: TEST_IDS.USER_MESSAGE_ID,
145
+ parentMessageType: 'user',
146
+ }),
147
+ );
148
+ expect(internalTraceSpy).toHaveBeenCalled();
149
+ });
150
+
151
+ it('should not regenerate when already regenerating', async () => {
152
+ const { result } = renderHook(() => useChatStore());
153
+
154
+ act(() => {
155
+ useChatStore.setState({
156
+ regeneratingIds: [TEST_IDS.USER_MESSAGE_ID],
157
+ internal_execAgentRuntime: vi.fn(),
158
+ });
159
+ });
160
+
161
+ await act(async () => {
162
+ await result.current.regenerateUserMessage(TEST_IDS.USER_MESSAGE_ID);
163
+ });
164
+
165
+ expect(result.current.internal_execAgentRuntime).not.toHaveBeenCalled();
166
+ });
167
+ });
168
+
169
+ describe('regenerateAssistantMessage', () => {
170
+ it('should trigger assistant message regeneration', async () => {
171
+ const messages = [
172
+ createMockMessage({ id: TEST_IDS.USER_MESSAGE_ID, role: 'user' }),
173
+ createMockMessage({
174
+ id: TEST_IDS.MESSAGE_ID,
175
+ role: 'assistant',
176
+ parentId: TEST_IDS.USER_MESSAGE_ID,
177
+ }),
178
+ ];
179
+
180
+ setupStoreWithMessages(messages);
181
+
182
+ act(() => {
183
+ useChatStore.setState({
184
+ internal_traceMessage: vi.fn(),
185
+ switchMessageBranch: vi.fn(),
186
+ });
187
+ });
188
+
189
+ const { result } = renderHook(() => useChatStore());
190
+
191
+ await act(async () => {
192
+ await result.current.regenerateAssistantMessage(TEST_IDS.MESSAGE_ID);
193
+ });
194
+
195
+ expect(result.current.internal_execAgentRuntime).toHaveBeenCalledWith(
196
+ expect.objectContaining({
197
+ parentMessageId: TEST_IDS.USER_MESSAGE_ID,
198
+ parentMessageType: 'user',
199
+ }),
200
+ );
201
+ expect(result.current.internal_traceMessage).toHaveBeenCalled();
202
+ });
203
+
204
+ it('should not regenerate when already regenerating', async () => {
205
+ const { result } = renderHook(() => useChatStore());
206
+
207
+ act(() => {
208
+ useChatStore.setState({
209
+ regeneratingIds: [TEST_IDS.MESSAGE_ID],
210
+ internal_execAgentRuntime: vi.fn(),
211
+ });
212
+ });
213
+
214
+ await act(async () => {
215
+ await result.current.regenerateAssistantMessage(TEST_IDS.MESSAGE_ID);
216
+ });
217
+
218
+ expect(result.current.internal_execAgentRuntime).not.toHaveBeenCalled();
219
+ });
220
+ });
221
+
222
+ describe('delAndRegenerateMessage', () => {
223
+ it('should delete message then regenerate', async () => {
224
+ const messages = [
225
+ createMockMessage({ id: TEST_IDS.USER_MESSAGE_ID, role: 'user' }),
226
+ createMockMessage({
227
+ id: TEST_IDS.MESSAGE_ID,
228
+ role: 'assistant',
229
+ parentId: TEST_IDS.USER_MESSAGE_ID,
230
+ }),
231
+ ];
232
+
233
+ setupStoreWithMessages(messages);
234
+
235
+ act(() => {
236
+ useChatStore.setState({
237
+ regenerateAssistantMessage: vi.fn(),
238
+ deleteMessage: vi.fn(),
239
+ internal_traceMessage: vi.fn(),
240
+ });
241
+ });
242
+
243
+ const { result } = renderHook(() => useChatStore());
244
+
245
+ await act(async () => {
246
+ await result.current.delAndRegenerateMessage(TEST_IDS.MESSAGE_ID);
247
+ });
248
+
249
+ expect(result.current.regenerateAssistantMessage).toHaveBeenCalledWith(
250
+ TEST_IDS.MESSAGE_ID,
251
+ expect.objectContaining({ skipTrace: true }),
252
+ );
253
+ expect(result.current.deleteMessage).toHaveBeenCalledWith(TEST_IDS.MESSAGE_ID);
254
+ expect(result.current.internal_traceMessage).toHaveBeenCalled();
255
+ });
256
+ });
257
+ });
@@ -35,11 +35,16 @@ export const setupMockSelectors = (
35
35
  /**
36
36
  * Setup store state with messages
37
37
  */
38
- export const setupStoreWithMessages = (messages: any[], sessionId = TEST_IDS.SESSION_ID) => {
38
+ export const setupStoreWithMessages = (
39
+ messages: any[],
40
+ sessionId = TEST_IDS.SESSION_ID,
41
+ topicId: string | null | undefined = TEST_IDS.TOPIC_ID,
42
+ ) => {
39
43
  useChatStore.setState({
40
44
  activeId: sessionId,
45
+ activeTopicId: topicId ?? undefined,
41
46
  messagesMap: {
42
- [messageMapKey(sessionId)]: messages,
47
+ [messageMapKey(sessionId, topicId ?? undefined)]: messages,
43
48
  },
44
49
  });
45
50
  };
@@ -63,6 +68,9 @@ export const spyOnMessageService = () => {
63
68
  const updateMessageSpy = vi
64
69
  .spyOn(messageService, 'updateMessage')
65
70
  .mockResolvedValue({ messages: [], success: true });
71
+ const updateMessageMetadataSpy = vi
72
+ .spyOn(messageService, 'updateMessageMetadata')
73
+ .mockResolvedValue({ messages: [], success: true });
66
74
  const removeMessageSpy = vi
67
75
  .spyOn(messageService, 'removeMessage')
68
76
  .mockResolvedValue(undefined as any);
@@ -74,6 +82,7 @@ export const spyOnMessageService = () => {
74
82
  createMessageSpy,
75
83
  removeMessageSpy,
76
84
  updateMessageErrorSpy,
85
+ updateMessageMetadataSpy,
77
86
  updateMessageSpy,
78
87
  };
79
88
  };
@@ -6,7 +6,7 @@ import { chatService } from '@/services/chat';
6
6
  import { ragService } from '@/services/rag';
7
7
  import { useAgentStore } from '@/store/agent';
8
8
  import { agentSelectors } from '@/store/agent/selectors';
9
- import { chatSelectors } from '@/store/chat/selectors';
9
+ import { chatSelectors, dbMessageSelectors, displayMessageSelectors } from '@/store/chat/selectors';
10
10
  import { systemAgentSelectors } from '@/store/user/selectors';
11
11
  import { QueryRewriteSystemAgent } from '@/types/user/settings';
12
12
 
@@ -70,7 +70,7 @@ describe('chatRAG actions', () => {
70
70
  const userQuery = 'user-query';
71
71
 
72
72
  // Mock the message with existing ragQuery
73
- vi.spyOn(chatSelectors, 'getMessageById').mockReturnValue(
73
+ vi.spyOn(dbMessageSelectors, 'getDbMessageById').mockReturnValue(
74
74
  () =>
75
75
  ({
76
76
  id: messageId,
@@ -113,7 +113,7 @@ describe('chatRAG actions', () => {
113
113
  const rewrittenQuery = 'rewritten-query';
114
114
 
115
115
  // Mock the message without ragQuery
116
- vi.spyOn(chatSelectors, 'getMessageById').mockReturnValue(
116
+ vi.spyOn(dbMessageSelectors, 'getDbMessageById').mockReturnValue(
117
117
  () =>
118
118
  ({
119
119
  id: messageId,
@@ -220,7 +220,7 @@ describe('chatRAG actions', () => {
220
220
  it('should not rewrite if message not found', async () => {
221
221
  const { result } = renderHook(() => useChatStore());
222
222
 
223
- vi.spyOn(chatSelectors, 'getMessageById').mockReturnValue(() => undefined);
223
+ vi.spyOn(dbMessageSelectors, 'getDbMessageById').mockReturnValue(() => undefined);
224
224
  const rewriteSpy = vi.spyOn(result.current, 'internal_rewriteQuery');
225
225
 
226
226
  await act(async () => {
@@ -235,7 +235,7 @@ describe('chatRAG actions', () => {
235
235
  const messageId = 'message-id';
236
236
  const content = 'message content';
237
237
 
238
- vi.spyOn(chatSelectors, 'getMessageById').mockReturnValue(
238
+ vi.spyOn(dbMessageSelectors, 'getDbMessageById').mockReturnValue(
239
239
  () =>
240
240
  ({
241
241
  id: messageId,
@@ -243,7 +243,7 @@ describe('chatRAG actions', () => {
243
243
  }) as UIChatMessage,
244
244
  );
245
245
 
246
- vi.spyOn(chatSelectors, 'mainAIChatsWithHistoryConfig').mockReturnValue([
246
+ vi.spyOn(displayMessageSelectors, 'mainAIChatsWithHistoryConfig').mockReturnValue([
247
247
  { content: 'history' },
248
248
  ] as UIChatMessage[]);
249
249