@lobehub/lobehub 2.0.0-next.1 → 2.0.0-next.11

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 (242) hide show
  1. package/.github/workflows/desktop-pr-build.yml +12 -10
  2. package/.github/workflows/docker.yml +25 -20
  3. package/.github/workflows/e2e.yml +3 -3
  4. package/.github/workflows/release-desktop-beta.yml +8 -8
  5. package/.github/workflows/release.yml +1 -1
  6. package/.github/workflows/test.yml +4 -4
  7. package/CHANGELOG.md +261 -0
  8. package/apps/desktop/src/main/utils/next-electron-rsc.ts +7 -5
  9. package/changelog/v1.json +82 -0
  10. package/docs/development/database-schema.dbml +11 -1
  11. package/docs/self-hosting/advanced/auth/next-auth/auth0.mdx +2 -2
  12. package/docs/self-hosting/advanced/auth/next-auth/auth0.zh-CN.mdx +2 -2
  13. package/docs/self-hosting/advanced/auth/next-auth/authelia.mdx +2 -2
  14. package/docs/self-hosting/advanced/auth/next-auth/authelia.zh-CN.mdx +2 -2
  15. package/docs/self-hosting/advanced/auth/next-auth/authentik.mdx +2 -2
  16. package/docs/self-hosting/advanced/auth/next-auth/authentik.zh-CN.mdx +2 -2
  17. package/docs/self-hosting/advanced/auth/next-auth/casdoor.mdx +2 -2
  18. package/docs/self-hosting/advanced/auth/next-auth/casdoor.zh-CN.mdx +2 -2
  19. package/docs/self-hosting/advanced/auth/next-auth/cloudflare-zero-trust.mdx +2 -2
  20. package/docs/self-hosting/advanced/auth/next-auth/cloudflare-zero-trust.zh-CN.mdx +2 -2
  21. package/docs/self-hosting/advanced/auth/next-auth/github.mdx +2 -2
  22. package/docs/self-hosting/advanced/auth/next-auth/github.zh-CN.mdx +2 -2
  23. package/docs/self-hosting/advanced/auth/next-auth/google.mdx +32 -29
  24. package/docs/self-hosting/advanced/auth/next-auth/keycloak.mdx +2 -2
  25. package/docs/self-hosting/advanced/auth/next-auth/keycloak.zh-CN.mdx +2 -2
  26. package/docs/self-hosting/advanced/auth/next-auth/logto.mdx +5 -3
  27. package/docs/self-hosting/advanced/auth/next-auth/logto.zh-CN.mdx +5 -3
  28. package/docs/self-hosting/advanced/auth/next-auth/microsoft-entra-id.mdx +2 -2
  29. package/docs/self-hosting/advanced/auth/next-auth/microsoft-entra-id.zh-CN.mdx +2 -2
  30. package/docs/self-hosting/advanced/auth/next-auth/okta.mdx +2 -2
  31. package/docs/self-hosting/advanced/auth/next-auth/okta.zh-CN.mdx +2 -2
  32. package/docs/self-hosting/advanced/auth/next-auth/wechat.mdx +2 -2
  33. package/docs/self-hosting/advanced/auth/next-auth/wechat.zh-CN.mdx +2 -2
  34. package/docs/self-hosting/advanced/auth/next-auth/zitadel.mdx +2 -2
  35. package/docs/self-hosting/advanced/auth/next-auth/zitadel.zh-CN.mdx +2 -2
  36. package/docs/self-hosting/advanced/auth.mdx +32 -21
  37. package/docs/self-hosting/advanced/auth.zh-CN.mdx +30 -19
  38. package/docs/self-hosting/advanced/online-search.mdx +30 -25
  39. package/docs/self-hosting/advanced/online-search.zh-CN.mdx +25 -23
  40. package/locales/ar/models.json +15 -6
  41. package/locales/bg-BG/models.json +15 -6
  42. package/locales/de-DE/models.json +15 -6
  43. package/locales/en-US/models.json +15 -6
  44. package/locales/es-ES/models.json +15 -6
  45. package/locales/fa-IR/models.json +15 -6
  46. package/locales/fr-FR/models.json +15 -6
  47. package/locales/it-IT/models.json +15 -6
  48. package/locales/ja-JP/models.json +15 -6
  49. package/locales/ko-KR/models.json +21 -12
  50. package/locales/nl-NL/models.json +15 -6
  51. package/locales/pl-PL/models.json +15 -6
  52. package/locales/pt-BR/models.json +15 -6
  53. package/locales/ru-RU/models.json +15 -6
  54. package/locales/tr-TR/models.json +15 -6
  55. package/locales/vi-VN/models.json +15 -6
  56. package/locales/zh-CN/models.json +15 -6
  57. package/locales/zh-TW/models.json +15 -6
  58. package/next.config.ts +2 -3
  59. package/package.json +17 -23
  60. package/packages/const/src/index.ts +0 -1
  61. package/packages/const/src/models.ts +13 -0
  62. package/packages/const/src/url.ts +1 -4
  63. package/packages/context-engine/src/index.ts +1 -6
  64. package/packages/context-engine/src/processors/GroupMessageFlatten.ts +12 -2
  65. package/packages/context-engine/src/processors/__tests__/GroupMessageFlatten.test.ts +73 -9
  66. package/packages/context-engine/src/providers/index.ts +0 -2
  67. package/packages/database/migrations/0041_improve_index.sql +10 -0
  68. package/packages/database/migrations/meta/0041_snapshot.json +7784 -0
  69. package/packages/database/migrations/meta/_journal.json +7 -0
  70. package/packages/database/package.json +1 -1
  71. package/packages/database/src/core/migrations.json +17 -0
  72. package/packages/database/src/models/__tests__/message.grouping.test.ts +812 -0
  73. package/packages/database/src/models/__tests__/message.test.ts +322 -170
  74. package/packages/database/src/models/message.ts +62 -24
  75. package/packages/database/src/models/session.ts +60 -19
  76. package/packages/database/src/schemas/agent.ts +10 -11
  77. package/packages/database/src/schemas/message.ts +5 -1
  78. package/packages/database/src/schemas/relations.ts +6 -4
  79. package/packages/database/src/schemas/session.ts +2 -0
  80. package/packages/database/src/schemas/topic.ts +6 -1
  81. package/packages/database/src/utils/__tests__/groupMessages.test.ts +145 -2
  82. package/packages/database/src/utils/groupMessages.ts +7 -5
  83. package/packages/electron-client-ipc/package.json +4 -1
  84. package/packages/file-loaders/package.json +1 -0
  85. package/packages/model-bank/src/aiModels/anthropic.ts +0 -63
  86. package/packages/model-bank/src/aiModels/azure.ts +155 -0
  87. package/packages/model-bank/src/aiModels/bedrock.ts +44 -0
  88. package/packages/model-bank/src/aiModels/higress.ts +0 -55
  89. package/packages/model-bank/src/aiModels/infiniai.ts +21 -0
  90. package/packages/model-bank/src/aiModels/ollamacloud.ts +13 -0
  91. package/packages/model-bank/src/aiModels/siliconcloud.ts +19 -0
  92. package/packages/model-runtime/src/core/openaiCompatibleFactory/createImage.ts +1 -1
  93. package/packages/model-runtime/src/core/openaiCompatibleFactory/index.test.ts +33 -3
  94. package/packages/model-runtime/src/core/parameterResolver.ts +3 -0
  95. package/packages/model-runtime/src/core/streams/openai/__snapshots__/responsesStream.test.ts.snap +0 -38
  96. package/packages/model-runtime/src/providers/azureOpenai/index.ts +2 -1
  97. package/packages/model-runtime/src/providers/minimax/index.ts +5 -5
  98. package/packages/model-runtime/src/providers/search1api/index.test.ts +2 -2
  99. package/packages/types/src/message/common/base.ts +13 -0
  100. package/packages/types/src/message/common/image.ts +8 -0
  101. package/packages/types/src/message/common/metadata.ts +39 -0
  102. package/packages/types/src/message/common/tools.ts +10 -0
  103. package/packages/types/src/message/db/params.ts +47 -1
  104. package/packages/types/src/message/ui/chat.ts +4 -1
  105. package/packages/types/src/search.ts +16 -0
  106. package/packages/web-crawler/src/crawImpl/firecrawl.ts +39 -12
  107. package/scripts/migrateServerDB/index.ts +2 -1
  108. package/src/app/(backend)/oidc/consent/route.ts +0 -1
  109. package/src/app/(backend)/webapi/revalidate/route.ts +1 -1
  110. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/V1Mobile/index.tsx +2 -2
  111. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/V1Mobile/useSend.ts +6 -4
  112. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/useSend.ts +15 -10
  113. package/src/app/[variants]/(main)/chat/@session/features/SessionListContent/List/Item/index.tsx +4 -2
  114. package/src/app/[variants]/(main)/settings/_layout/SettingsContent.tsx +0 -3
  115. package/src/app/[variants]/layout.tsx +1 -0
  116. package/src/app/sitemap.tsx +7 -1
  117. package/src/components/Thinking/index.tsx +4 -3
  118. package/src/config/modelProviders/anthropic.ts +0 -23
  119. package/src/config/modelProviders/higress.ts +0 -23
  120. package/src/config/modelProviders/minimax.ts +1 -1
  121. package/src/config/modelProviders/qiniu.ts +1 -1
  122. package/src/envs/auth.ts +0 -179
  123. package/src/features/AgentSetting/AgentPlugin/index.tsx +21 -13
  124. package/src/features/ChatInput/ActionBar/STT/browser.tsx +2 -2
  125. package/src/features/ChatInput/ActionBar/STT/openai.tsx +2 -2
  126. package/src/features/ChatInput/ActionBar/Tools/useControls.tsx +1 -3
  127. package/src/features/Conversation/Error/ErrorJsonViewer.tsx +4 -3
  128. package/src/features/Conversation/Error/OllamaBizError/index.tsx +7 -2
  129. package/src/features/Conversation/Error/index.tsx +15 -5
  130. package/src/features/Conversation/MarkdownElements/LobeArtifact/Render/index.tsx +2 -2
  131. package/src/features/Conversation/Messages/Assistant/Extra/index.tsx +2 -2
  132. package/src/features/Conversation/Messages/Assistant/MessageContent.tsx +5 -3
  133. package/src/features/Conversation/Messages/Assistant/Tool/Inspector/BuiltinPluginTitle.tsx +2 -2
  134. package/src/features/Conversation/Messages/Assistant/Tool/Inspector/ToolTitle.tsx +4 -2
  135. package/src/features/Conversation/Messages/Assistant/Tool/Render/CustomRender.tsx +2 -2
  136. package/src/features/Conversation/Messages/Assistant/Tool/Render/index.tsx +2 -2
  137. package/src/features/Conversation/Messages/Assistant/Tool/index.tsx +2 -2
  138. package/src/features/Conversation/Messages/Assistant/index.tsx +4 -4
  139. package/src/features/Conversation/Messages/Default.tsx +2 -2
  140. package/src/features/Conversation/Messages/User/Extra.tsx +2 -2
  141. package/src/features/Conversation/Messages/User/index.tsx +4 -4
  142. package/src/features/Conversation/Messages/index.tsx +3 -3
  143. package/src/features/Conversation/components/AutoScroll.tsx +2 -2
  144. package/src/features/Conversation/components/Extras/Usage/UsageDetail/index.tsx +9 -6
  145. package/src/features/PluginTag/index.tsx +1 -3
  146. package/src/features/PluginsUI/Render/BuiltinType/index.test.tsx +37 -28
  147. package/src/features/Portal/Artifacts/Body/index.tsx +2 -2
  148. package/src/libs/next-auth/auth.config.ts +1 -1
  149. package/src/libs/next-auth/sso-providers/auth0.ts +0 -7
  150. package/src/libs/next-auth/sso-providers/authelia.ts +3 -5
  151. package/src/libs/next-auth/sso-providers/authentik.ts +0 -7
  152. package/src/libs/next-auth/sso-providers/cloudflare-zero-trust.ts +3 -6
  153. package/src/libs/next-auth/sso-providers/cognito.ts +1 -5
  154. package/src/libs/next-auth/sso-providers/generic-oidc.ts +3 -5
  155. package/src/libs/next-auth/sso-providers/github.ts +0 -6
  156. package/src/libs/next-auth/sso-providers/google.ts +0 -2
  157. package/src/libs/next-auth/sso-providers/index.ts +0 -2
  158. package/src/libs/next-auth/sso-providers/keycloak.ts +0 -3
  159. package/src/libs/next-auth/sso-providers/logto.ts +3 -5
  160. package/src/libs/next-auth/sso-providers/okta.ts +0 -4
  161. package/src/libs/next-auth/sso-providers/zitadel.ts +0 -7
  162. package/src/libs/oidc-provider/provider.ts +1 -1
  163. package/src/server/modules/AssistantStore/index.ts +1 -1
  164. package/src/server/modules/ModelRuntime/trace.ts +11 -4
  165. package/src/server/routers/lambda/message.ts +14 -3
  166. package/src/server/routers/lambda/session.ts +8 -5
  167. package/src/server/services/search/impls/firecrawl/index.ts +51 -11
  168. package/src/server/services/search/impls/firecrawl/type.ts +60 -9
  169. package/src/services/chat/chat.test.ts +1 -40
  170. package/src/services/chat/contextEngineering.test.ts +0 -30
  171. package/src/services/chat/contextEngineering.ts +1 -12
  172. package/src/services/chat/index.ts +2 -7
  173. package/src/services/chat/types.ts +1 -1
  174. package/src/services/message/_deprecated.ts +1 -1
  175. package/src/services/message/client.ts +8 -2
  176. package/src/services/message/server.ts +7 -2
  177. package/src/services/message/type.ts +6 -1
  178. package/src/services/user/client.test.ts +4 -1
  179. package/src/store/chat/helpers.test.ts +99 -0
  180. package/src/store/chat/helpers.ts +21 -2
  181. package/src/store/chat/selectors.ts +1 -1
  182. package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +3 -3
  183. package/src/store/chat/slices/builtinTool/actions/index.ts +1 -4
  184. package/src/store/chat/slices/message/action.test.ts +5 -1
  185. package/src/store/chat/slices/message/action.ts +102 -14
  186. package/src/store/chat/slices/message/reducer.test.ts +363 -5
  187. package/src/store/chat/slices/message/reducer.ts +87 -3
  188. package/src/store/chat/slices/message/{selectors.test.ts → selectors/chat.test.ts} +266 -30
  189. package/src/store/chat/slices/message/{selectors.ts → selectors/chat.ts} +29 -79
  190. package/src/store/chat/slices/message/selectors/index.ts +2 -0
  191. package/src/store/chat/slices/message/selectors/messageState.test.ts +36 -0
  192. package/src/store/chat/slices/message/selectors/messageState.ts +80 -0
  193. package/src/store/chat/slices/plugin/action.test.ts +34 -132
  194. package/src/store/chat/slices/plugin/action.ts +1 -44
  195. package/src/store/tool/selectors/tool.test.ts +1 -1
  196. package/src/store/tool/selectors/tool.ts +6 -8
  197. package/src/store/tool/slices/builtin/action.test.ts +83 -35
  198. package/src/store/tool/slices/builtin/action.ts +0 -9
  199. package/src/store/tool/slices/builtin/selectors.test.ts +4 -30
  200. package/src/store/tool/slices/builtin/selectors.ts +15 -21
  201. package/src/tools/index.ts +0 -6
  202. package/src/tools/renders.ts +0 -3
  203. package/src/tools/web-browsing/Portal/Search/Footer.tsx +2 -2
  204. package/tsconfig.json +9 -2
  205. package/packages/const/src/guide.ts +0 -89
  206. package/packages/context-engine/src/providers/InboxGuide.ts +0 -102
  207. package/packages/context-engine/src/providers/__tests__/InboxGuideProvider.test.ts +0 -121
  208. package/src/app/[variants]/(main)/settings/llm/ProviderList/Azure/index.tsx +0 -93
  209. package/src/app/[variants]/(main)/settings/llm/ProviderList/Bedrock/index.tsx +0 -70
  210. package/src/app/[variants]/(main)/settings/llm/ProviderList/Cloudflare/index.tsx +0 -39
  211. package/src/app/[variants]/(main)/settings/llm/ProviderList/Github/index.tsx +0 -52
  212. package/src/app/[variants]/(main)/settings/llm/ProviderList/HuggingFace/index.tsx +0 -52
  213. package/src/app/[variants]/(main)/settings/llm/ProviderList/Ollama/index.tsx +0 -20
  214. package/src/app/[variants]/(main)/settings/llm/ProviderList/OpenAI/index.tsx +0 -17
  215. package/src/app/[variants]/(main)/settings/llm/ProviderList/providers.tsx +0 -132
  216. package/src/app/[variants]/(main)/settings/llm/components/Checker.tsx +0 -118
  217. package/src/app/[variants]/(main)/settings/llm/components/ProviderConfig/index.tsx +0 -303
  218. package/src/app/[variants]/(main)/settings/llm/components/ProviderModelList/CustomModelOption.tsx +0 -98
  219. package/src/app/[variants]/(main)/settings/llm/components/ProviderModelList/ModelConfigModal/Form.tsx +0 -104
  220. package/src/app/[variants]/(main)/settings/llm/components/ProviderModelList/ModelConfigModal/index.tsx +0 -77
  221. package/src/app/[variants]/(main)/settings/llm/components/ProviderModelList/ModelFetcher.tsx +0 -105
  222. package/src/app/[variants]/(main)/settings/llm/components/ProviderModelList/Option.tsx +0 -68
  223. package/src/app/[variants]/(main)/settings/llm/components/ProviderModelList/index.tsx +0 -146
  224. package/src/app/[variants]/(main)/settings/llm/const.ts +0 -20
  225. package/src/app/[variants]/(main)/settings/llm/features/Footer.tsx +0 -35
  226. package/src/app/[variants]/(main)/settings/llm/index.tsx +0 -30
  227. package/src/app/[variants]/(main)/settings/llm/type.ts +0 -5
  228. package/src/envs/__tests__/auth.test.ts +0 -200
  229. package/src/libs/next-auth/sso-providers/azure-ad.ts +0 -33
  230. package/src/services/chat/__snapshots__/chat.test.ts.snap +0 -110
  231. package/src/store/chat/slices/builtinTool/actions/__tests__/dalle.test.ts +0 -121
  232. package/src/store/chat/slices/builtinTool/actions/dalle.ts +0 -124
  233. package/src/tools/dalle/Render/GalleyGrid.tsx +0 -60
  234. package/src/tools/dalle/Render/Item/EditMode.tsx +0 -66
  235. package/src/tools/dalle/Render/Item/Error.tsx +0 -49
  236. package/src/tools/dalle/Render/Item/Image.tsx +0 -44
  237. package/src/tools/dalle/Render/Item/ImageFileItem.tsx +0 -57
  238. package/src/tools/dalle/Render/Item/index.tsx +0 -88
  239. package/src/tools/dalle/Render/ToolBar.tsx +0 -56
  240. package/src/tools/dalle/Render/index.tsx +0 -52
  241. package/src/tools/dalle/index.ts +0 -92
  242. /package/src/{middleware.ts → proxy.ts} +0 -0
@@ -471,7 +471,11 @@ describe('chatMessage actions', () => {
471
471
  await result.current.internal_updateMessageContent(messageId, newContent);
472
472
  });
473
473
 
474
- expect(spy).toHaveBeenCalledWith(messageId, { content: newContent });
474
+ expect(spy).toHaveBeenCalledWith(
475
+ messageId,
476
+ { content: newContent },
477
+ { sessionId: 'session-id', topicId: 'topic-id' },
478
+ );
475
479
  });
476
480
 
477
481
  it('should dispatch message update action', async () => {
@@ -6,6 +6,7 @@ import {
6
6
  ChatMessageError,
7
7
  ChatMessagePluginError,
8
8
  CreateMessageParams,
9
+ CreateNewMessageParams,
9
10
  GroundingSearch,
10
11
  MessageMetadata,
11
12
  MessageToolCall,
@@ -117,6 +118,14 @@ export interface ChatMessageAction {
117
118
  * otherwise the message will be too slow to show
118
119
  */
119
120
  internal_createTmpMessage: (params: CreateMessageParams) => string;
121
+ /**
122
+ * create a new message using createNewMessage API and return full message list
123
+ * used for group message scenarios to reduce network requests
124
+ */
125
+ internal_createNewMessage: (
126
+ params: CreateNewMessageParams,
127
+ context?: { tempMessageId?: string; groupMessageId?: string },
128
+ ) => Promise<{ id: string; messages: UIChatMessage[] } | undefined>;
120
129
  /**
121
130
  * delete the message content with optimistic update
122
131
  */
@@ -374,8 +383,16 @@ export const chatMessage: StateCreator<
374
383
 
375
384
  internal_updateMessageError: async (id, error) => {
376
385
  get().internal_dispatchMessage({ id, type: 'updateMessage', value: { error } });
377
- await messageService.updateMessage(id, { error });
378
- await get().refreshMessages();
386
+ const result = await messageService.updateMessage(
387
+ id,
388
+ { error },
389
+ { topicId: get().activeTopicId, sessionId: get().activeId },
390
+ );
391
+ if (result?.success && result.messages) {
392
+ get().replaceMessages(result.messages);
393
+ } else {
394
+ await get().refreshMessages();
395
+ }
379
396
  },
380
397
 
381
398
  internal_updateMessagePluginError: async (id, error) => {
@@ -384,7 +401,12 @@ export const chatMessage: StateCreator<
384
401
  },
385
402
 
386
403
  internal_updateMessageContent: async (id, content, extra) => {
387
- const { internal_dispatchMessage, refreshMessages, internal_transformToolCalls } = get();
404
+ const {
405
+ internal_dispatchMessage,
406
+ refreshMessages,
407
+ internal_transformToolCalls,
408
+ replaceMessages,
409
+ } = get();
388
410
 
389
411
  // Due to the async update method and refresh need about 100ms
390
412
  // we need to update the message content at the frontend to avoid the update flick
@@ -403,17 +425,26 @@ export const chatMessage: StateCreator<
403
425
  });
404
426
  }
405
427
 
406
- await messageService.updateMessage(id, {
407
- content,
408
- tools: extra?.toolCalls ? internal_transformToolCalls(extra?.toolCalls) : undefined,
409
- reasoning: extra?.reasoning,
410
- search: extra?.search,
411
- metadata: extra?.metadata,
412
- model: extra?.model,
413
- provider: extra?.provider,
414
- imageList: extra?.imageList,
415
- });
416
- await refreshMessages();
428
+ const result = await messageService.updateMessage(
429
+ id,
430
+ {
431
+ content,
432
+ tools: extra?.toolCalls ? internal_transformToolCalls(extra?.toolCalls) : undefined,
433
+ reasoning: extra?.reasoning,
434
+ search: extra?.search,
435
+ metadata: extra?.metadata,
436
+ model: extra?.model,
437
+ provider: extra?.provider,
438
+ imageList: extra?.imageList,
439
+ },
440
+ { topicId: get().activeTopicId, sessionId: get().activeId },
441
+ );
442
+
443
+ if (result && result.success && result.messages) {
444
+ replaceMessages(result.messages);
445
+ } else {
446
+ await refreshMessages();
447
+ }
417
448
  },
418
449
 
419
450
  internal_createMessage: async (message, context) => {
@@ -473,6 +504,63 @@ export const chatMessage: StateCreator<
473
504
 
474
505
  return tempId;
475
506
  },
507
+
508
+ internal_createNewMessage: async (message, context) => {
509
+ const {
510
+ internal_createTmpMessage,
511
+ internal_toggleMessageLoading,
512
+ internal_dispatchMessage,
513
+ replaceMessages,
514
+ } = get();
515
+
516
+ let tempId = context?.tempMessageId;
517
+ if (!tempId) {
518
+ tempId = 'tmp_' + nanoid();
519
+
520
+ // Check if should add as group block (explicitly controlled by caller)
521
+ if (context?.groupMessageId) {
522
+ internal_dispatchMessage({
523
+ type: 'addGroupBlock',
524
+ groupMessageId: context.groupMessageId,
525
+ blockId: tempId,
526
+ value: {
527
+ id: tempId,
528
+ content: message.content,
529
+ },
530
+ });
531
+ internal_toggleMessageLoading(true, tempId);
532
+ } else {
533
+ // Regular message creation at top level
534
+ tempId = internal_createTmpMessage(message as any);
535
+ internal_toggleMessageLoading(true, tempId);
536
+ }
537
+ }
538
+
539
+ try {
540
+ // 使用 createNewMessage API
541
+ const result = await messageService.createNewMessage(message);
542
+
543
+ // 直接用返回的 messages 更新 store(已包含 group 结构)
544
+ replaceMessages(result.messages);
545
+
546
+ internal_toggleMessageLoading(false, tempId);
547
+ return result;
548
+ } catch (e) {
549
+ internal_toggleMessageLoading(false, tempId);
550
+ internal_dispatchMessage({
551
+ id: tempId,
552
+ type: 'updateMessage',
553
+ value: {
554
+ error: {
555
+ type: ChatErrorType.CreateMessageError,
556
+ message: (e as Error).message,
557
+ body: e,
558
+ },
559
+ },
560
+ });
561
+ }
562
+ },
563
+
476
564
  internal_deleteMessage: async (id: string) => {
477
565
  get().internal_dispatchMessage({ type: 'deleteMessage', id });
478
566
  await messageService.removeMessage(id);
@@ -57,16 +57,131 @@ describe('messagesReducer', () => {
57
57
  expect(newState).toEqual(initialState);
58
58
  });
59
59
 
60
- it('should not modify the state if the specified message does not exist', () => {
60
+ it('should update a block in group message children when id matches a block', () => {
61
+ const stateWithGroup: UIChatMessage[] = [
62
+ ...initialState,
63
+ {
64
+ id: 'group1',
65
+ role: 'group',
66
+ content: '',
67
+ createdAt: 1629264000000,
68
+ updatedAt: 1629264000000,
69
+ meta: {},
70
+ children: [
71
+ {
72
+ id: 'block1',
73
+ content: 'Original block content',
74
+ tools: [
75
+ {
76
+ id: 'tool1',
77
+ identifier: 'search',
78
+ apiName: 'search',
79
+ type: 'builtin',
80
+ arguments: '{"query": "test"}',
81
+ },
82
+ ],
83
+ },
84
+ ],
85
+ } as UIChatMessage,
86
+ ];
87
+
61
88
  const payload: MessageDispatch = {
62
89
  type: 'updateMessage',
63
- id: 'nonexistentMessage',
64
- value: { content: 'Updated Message' },
90
+ id: 'block1',
91
+ value: { content: 'Updated block content' },
65
92
  };
66
93
 
67
- const newState = messagesReducer(initialState, payload);
94
+ const newState = messagesReducer(stateWithGroup, payload);
95
+ const groupMessage = newState.find((m) => m.id === 'group1');
96
+ const block = groupMessage?.children?.find((b) => b.id === 'block1');
68
97
 
69
- expect(newState).toEqual(initialState);
98
+ expect(block?.content).toBe('Updated block content');
99
+ expect(groupMessage?.updatedAt).toBeGreaterThan(1629264000000);
100
+ });
101
+
102
+ it('should update block tools in group message children', () => {
103
+ const stateWithGroup: UIChatMessage[] = [
104
+ ...initialState,
105
+ {
106
+ id: 'group1',
107
+ role: 'group',
108
+ content: '',
109
+ createdAt: 1629264000000,
110
+ updatedAt: 1629264000000,
111
+ meta: {},
112
+ children: [
113
+ {
114
+ id: 'block1',
115
+ content: 'Block content',
116
+ tools: [
117
+ {
118
+ id: 'tool1',
119
+ identifier: 'search',
120
+ apiName: 'search',
121
+ type: 'builtin',
122
+ arguments: '{"query": "test"}',
123
+ },
124
+ ],
125
+ },
126
+ ],
127
+ } as UIChatMessage,
128
+ ];
129
+
130
+ const newTools = [
131
+ {
132
+ id: 'tool1',
133
+ identifier: 'search',
134
+ apiName: 'search',
135
+ type: 'builtin',
136
+ arguments: '{"query": "updated"}',
137
+ result: {
138
+ id: 'result1',
139
+ content: 'Search result',
140
+ },
141
+ },
142
+ ];
143
+
144
+ const payload: MessageDispatch = {
145
+ type: 'updateMessage',
146
+ id: 'block1',
147
+ value: { tools: newTools as any },
148
+ };
149
+
150
+ const newState = messagesReducer(stateWithGroup, payload);
151
+ const groupMessage = newState.find((m) => m.id === 'group1');
152
+ const block = groupMessage?.children?.find((b) => b.id === 'block1');
153
+
154
+ expect(block?.tools).toEqual(newTools);
155
+ expect(groupMessage?.updatedAt).toBeGreaterThan(1629264000000);
156
+ });
157
+
158
+ it('should not modify state when updating non-existent block in group message', () => {
159
+ const stateWithGroup: UIChatMessage[] = [
160
+ ...initialState,
161
+ {
162
+ id: 'group1',
163
+ role: 'group',
164
+ content: '',
165
+ createdAt: 1629264000000,
166
+ updatedAt: 1629264000000,
167
+ meta: {},
168
+ children: [
169
+ {
170
+ id: 'block1',
171
+ content: 'Block content',
172
+ },
173
+ ],
174
+ } as UIChatMessage,
175
+ ];
176
+
177
+ const payload: MessageDispatch = {
178
+ type: 'updateMessage',
179
+ id: 'nonexistentBlock',
180
+ value: { content: 'Updated content' },
181
+ };
182
+
183
+ const newState = messagesReducer(stateWithGroup, payload);
184
+ expect(newState).toEqual(stateWithGroup);
70
185
  });
71
186
  });
72
187
 
@@ -502,4 +617,247 @@ describe('messagesReducer', () => {
502
617
  expect(newState).toEqual(initialState);
503
618
  });
504
619
  });
620
+
621
+ describe('updateGroupBlockToolResult', () => {
622
+ it('should update a tool result in a group message block', () => {
623
+ const stateWithGroup: UIChatMessage[] = [
624
+ ...initialState,
625
+ {
626
+ id: 'group1',
627
+ role: 'group',
628
+ content: '',
629
+ createdAt: 1629264000000,
630
+ updatedAt: 1629264000000,
631
+ meta: {},
632
+ children: [
633
+ {
634
+ id: 'block1',
635
+ content: 'Assistant response',
636
+ tools: [
637
+ {
638
+ id: 'tool1',
639
+ identifier: 'search',
640
+ apiName: 'search',
641
+ type: 'builtin',
642
+ arguments: '{"query": "test"}',
643
+ result: {
644
+ id: 'result1',
645
+ content: 'Initial result',
646
+ },
647
+ },
648
+ ],
649
+ },
650
+ ],
651
+ } as UIChatMessage,
652
+ ];
653
+
654
+ const payload: MessageDispatch = {
655
+ type: 'updateGroupBlockToolResult',
656
+ groupMessageId: 'group1',
657
+ blockId: 'block1',
658
+ toolId: 'tool1',
659
+ toolResult: {
660
+ id: 'result1',
661
+ content: 'Updated result content',
662
+ state: { foo: 'bar' },
663
+ },
664
+ };
665
+
666
+ const newState = messagesReducer(stateWithGroup, payload);
667
+ const groupMessage = newState.find((m) => m.id === 'group1');
668
+ const block = groupMessage?.children?.find((b) => b.id === 'block1');
669
+ const tool = block?.tools?.find((t) => t.id === 'tool1');
670
+
671
+ expect(tool?.result).toEqual({
672
+ id: 'result1',
673
+ content: 'Updated result content',
674
+ state: { foo: 'bar' },
675
+ });
676
+ expect(groupMessage?.updatedAt).toBeGreaterThan(1629264000000);
677
+ });
678
+
679
+ it('should not modify state if group message is not found', () => {
680
+ const payload: MessageDispatch = {
681
+ type: 'updateGroupBlockToolResult',
682
+ groupMessageId: 'nonexistent',
683
+ blockId: 'block1',
684
+ toolId: 'tool1',
685
+ toolResult: {
686
+ id: 'result1',
687
+ content: 'Updated result',
688
+ },
689
+ };
690
+
691
+ const newState = messagesReducer(initialState, payload);
692
+ expect(newState).toEqual(initialState);
693
+ });
694
+
695
+ it('should not modify state if block is not found', () => {
696
+ const stateWithGroup: UIChatMessage[] = [
697
+ ...initialState,
698
+ {
699
+ id: 'group1',
700
+ role: 'group',
701
+ content: '',
702
+ createdAt: 1629264000000,
703
+ updatedAt: 1629264000000,
704
+ meta: {},
705
+ children: [
706
+ {
707
+ id: 'block1',
708
+ content: 'Assistant response',
709
+ tools: [],
710
+ },
711
+ ],
712
+ } as UIChatMessage,
713
+ ];
714
+
715
+ const payload: MessageDispatch = {
716
+ type: 'updateGroupBlockToolResult',
717
+ groupMessageId: 'group1',
718
+ blockId: 'nonexistentBlock',
719
+ toolId: 'tool1',
720
+ toolResult: {
721
+ id: 'result1',
722
+ content: 'Updated result',
723
+ },
724
+ };
725
+
726
+ const newState = messagesReducer(stateWithGroup, payload);
727
+ expect(newState).toEqual(stateWithGroup);
728
+ });
729
+
730
+ it('should not modify state if tool is not found', () => {
731
+ const stateWithGroup: UIChatMessage[] = [
732
+ ...initialState,
733
+ {
734
+ id: 'group1',
735
+ role: 'group',
736
+ content: '',
737
+ createdAt: 1629264000000,
738
+ updatedAt: 1629264000000,
739
+ meta: {},
740
+ children: [
741
+ {
742
+ id: 'block1',
743
+ content: 'Assistant response',
744
+ tools: [
745
+ {
746
+ id: 'tool1',
747
+ identifier: 'search',
748
+ apiName: 'search',
749
+ type: 'builtin',
750
+ arguments: '{"query": "test"}',
751
+ },
752
+ ],
753
+ },
754
+ ],
755
+ } as UIChatMessage,
756
+ ];
757
+
758
+ const payload: MessageDispatch = {
759
+ type: 'updateGroupBlockToolResult',
760
+ groupMessageId: 'group1',
761
+ blockId: 'block1',
762
+ toolId: 'nonexistentTool',
763
+ toolResult: {
764
+ id: 'result1',
765
+ content: 'Updated result',
766
+ },
767
+ };
768
+
769
+ const newState = messagesReducer(stateWithGroup, payload);
770
+ expect(newState).toEqual(stateWithGroup);
771
+ });
772
+ });
773
+
774
+ describe('addGroupBlock', () => {
775
+ it('should add a new block to group message children', () => {
776
+ const stateWithGroup: UIChatMessage[] = [
777
+ ...initialState,
778
+ {
779
+ id: 'group1',
780
+ role: 'group',
781
+ content: '',
782
+ createdAt: 1629264000000,
783
+ updatedAt: 1629264000000,
784
+ meta: {},
785
+ children: [
786
+ {
787
+ id: 'block1',
788
+ content: 'First block',
789
+ },
790
+ ],
791
+ } as UIChatMessage,
792
+ ];
793
+
794
+ const payload: MessageDispatch = {
795
+ type: 'addGroupBlock',
796
+ groupMessageId: 'group1',
797
+ blockId: 'block2',
798
+ value: {
799
+ id: 'block2',
800
+ content: 'Second block',
801
+ },
802
+ };
803
+
804
+ const newState = messagesReducer(stateWithGroup, payload);
805
+ const groupMessage = newState.find((m) => m.id === 'group1');
806
+
807
+ expect(groupMessage?.children).toHaveLength(2);
808
+ expect(groupMessage?.children?.[1]).toEqual({
809
+ id: 'block2',
810
+ content: 'Second block',
811
+ });
812
+ expect(groupMessage?.updatedAt).toBeGreaterThan(1629264000000);
813
+ });
814
+
815
+ it('should not modify state if group message is not found', () => {
816
+ const stateWithGroup: UIChatMessage[] = [
817
+ ...initialState,
818
+ {
819
+ id: 'group1',
820
+ role: 'group',
821
+ content: '',
822
+ createdAt: 1629264000000,
823
+ updatedAt: 1629264000000,
824
+ meta: {},
825
+ children: [
826
+ {
827
+ id: 'block1',
828
+ content: 'First block',
829
+ },
830
+ ],
831
+ } as UIChatMessage,
832
+ ];
833
+
834
+ const payload: MessageDispatch = {
835
+ type: 'addGroupBlock',
836
+ groupMessageId: 'nonexistentGroup',
837
+ blockId: 'block2',
838
+ value: {
839
+ id: 'block2',
840
+ content: 'Second block',
841
+ },
842
+ };
843
+
844
+ const newState = messagesReducer(stateWithGroup, payload);
845
+ expect(newState).toEqual(stateWithGroup);
846
+ });
847
+
848
+ it('should not modify state if message is not a group message', () => {
849
+ const payload: MessageDispatch = {
850
+ type: 'addGroupBlock',
851
+ groupMessageId: 'message1',
852
+ blockId: 'block2',
853
+ value: {
854
+ id: 'block2',
855
+ content: 'Second block',
856
+ },
857
+ };
858
+
859
+ const newState = messagesReducer(initialState, payload);
860
+ expect(newState).toEqual(initialState);
861
+ });
862
+ });
505
863
  });
@@ -75,6 +75,29 @@ interface UpdateMessageExtra {
75
75
  value: any;
76
76
  }
77
77
 
78
+ interface UpdateGroupBlockToolResult {
79
+ blockId: string;
80
+ groupMessageId: string;
81
+ toolId: string;
82
+ toolResult: {
83
+ content: string;
84
+ error?: any;
85
+ id: string;
86
+ state?: any;
87
+ };
88
+ type: 'updateGroupBlockToolResult';
89
+ }
90
+
91
+ interface AddGroupBlock {
92
+ blockId: string;
93
+ groupMessageId: string;
94
+ type: 'addGroupBlock';
95
+ value: {
96
+ content: string;
97
+ id: string;
98
+ };
99
+ }
100
+
78
101
  export type MessageDispatch =
79
102
  | CreateMessage
80
103
  | UpdateMessage
@@ -86,7 +109,9 @@ export type MessageDispatch =
86
109
  | UpdateMessageTools
87
110
  | AddMessageTool
88
111
  | DeleteMessageTool
89
- | DeleteMessages;
112
+ | DeleteMessages
113
+ | UpdateGroupBlockToolResult
114
+ | AddGroupBlock;
90
115
 
91
116
  export const messagesReducer = (
92
117
  state: UIChatMessage[],
@@ -96,10 +121,27 @@ export const messagesReducer = (
96
121
  case 'updateMessage': {
97
122
  return produce(state, (draftState) => {
98
123
  const { id, value } = payload;
124
+
125
+ // First, try to find in top-level messages
99
126
  const index = draftState.findIndex((i) => i.id === id);
100
- if (index < 0) return;
127
+ if (index >= 0) {
128
+ draftState[index] = merge(draftState[index], { ...value, updatedAt: Date.now() });
129
+ return;
130
+ }
101
131
 
102
- draftState[index] = merge(draftState[index], { ...value, updatedAt: Date.now() });
132
+ // If not found, search in group message children (blocks)
133
+ for (const message of draftState) {
134
+ if (message.role === 'group' && message.children) {
135
+ const blockIndex = message.children.findIndex((block) => block.id === id);
136
+ if (blockIndex >= 0) {
137
+ message.children[blockIndex] = merge(message.children[blockIndex], {
138
+ ...value,
139
+ });
140
+ message.updatedAt = Date.now();
141
+ return;
142
+ }
143
+ }
144
+ }
103
145
  });
104
146
  }
105
147
 
@@ -226,6 +268,48 @@ export const messagesReducer = (
226
268
  });
227
269
  });
228
270
  }
271
+
272
+ case 'updateGroupBlockToolResult': {
273
+ return produce(state, (draftState) => {
274
+ const { groupMessageId, blockId, toolId, toolResult } = payload;
275
+
276
+ // Find the group message
277
+ const msg = draftState.find((m) => m.id === groupMessageId);
278
+ if (!msg || msg.role !== 'group' || !msg.children) return;
279
+
280
+ // Find the block within children
281
+ const block = msg.children.find((b) => b.id === blockId);
282
+ if (!block || !block.tools) return;
283
+
284
+ // Find the tool and update its result
285
+ const tool = block.tools.find((t) => t.id === toolId);
286
+ if (!tool) return;
287
+
288
+ // Update tool result (optimistic update)
289
+ tool.result = toolResult;
290
+
291
+ msg.updatedAt = Date.now();
292
+ });
293
+ }
294
+
295
+ case 'addGroupBlock': {
296
+ return produce(state, (draftState) => {
297
+ const { groupMessageId, blockId, value } = payload;
298
+
299
+ // Find the group message
300
+ const msg = draftState.find((m) => m.id === groupMessageId);
301
+ if (!msg || msg.role !== 'group' || !msg.children) return;
302
+
303
+ // Add new block to children
304
+ msg.children.push({
305
+ content: value.content,
306
+ id: blockId,
307
+ });
308
+
309
+ msg.updatedAt = Date.now();
310
+ });
311
+ }
312
+
229
313
  default: {
230
314
  throw new Error('暂未实现的 type,请检查 reducer');
231
315
  }