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

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 +236 -0
  8. package/apps/desktop/src/main/utils/next-electron-rsc.ts +7 -5
  9. package/changelog/v1.json +73 -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 +15 -6
  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 +13 -19
  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
@@ -1,3 +1,4 @@
1
+ import { INBOX_SESSION_ID } from '@lobechat/const';
1
2
  import {
2
3
  ChatFileItem,
3
4
  ChatImageItem,
@@ -14,6 +15,7 @@ import {
14
15
  UIChatMessage,
15
16
  UpdateMessageParams,
16
17
  UpdateMessageRAGParams,
18
+ UpdateMessageResult,
17
19
  } from '@lobechat/types';
18
20
  import type { HeatmapsProps } from '@lobehub/charts';
19
21
  import dayjs from 'dayjs';
@@ -39,6 +41,7 @@ import {
39
41
  } from '../schemas';
40
42
  import { LobeChatDatabase } from '../type';
41
43
  import { genEndDateWhere, genRangeWhere, genStartDateWhere, genWhere } from '../utils/genWhere';
44
+ import { groupAssistantMessages } from '../utils/groupMessages';
42
45
  import { idGenerator } from '../utils/idGenerator';
43
46
 
44
47
  export class MessageModel {
@@ -54,6 +57,7 @@ export class MessageModel {
54
57
  query = async (
55
58
  { current = 0, pageSize = 1000, sessionId, topicId, groupId }: QueryMessageParams = {},
56
59
  options: {
60
+ groupAssistantMessages?: boolean;
57
61
  postProcessUrl?: (path: string | null, file: { fileType: string }) => Promise<string>;
58
62
  } = {},
59
63
  ) => {
@@ -211,7 +215,7 @@ export class MessageModel {
211
215
  .from(messageQueries)
212
216
  .where(inArray(messageQueries.messageId, messageIds));
213
217
 
214
- return result.map(
218
+ const mappedMessages = result.map(
215
219
  ({ model, provider, translate, ttsId, ttsFile, ttsContentMd5, ttsVoice, ...item }) => {
216
220
  const messageQuery = messageQueriesList.find((relation) => relation.messageId === item.id);
217
221
  return {
@@ -246,13 +250,15 @@ export class MessageModel {
246
250
  size: size!,
247
251
  url,
248
252
  })),
249
-
250
253
  imageList: imageList
251
254
  .filter((relation) => relation.messageId === item.id)
252
255
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
253
256
  .map<ChatImageItem>(({ id, url, name }) => ({ alt: name!, id, url })),
254
-
255
257
  meta: {},
258
+
259
+ model,
260
+
261
+ provider,
256
262
  ragQuery: messageQuery?.rewriteQuery,
257
263
  ragQueryId: messageQuery?.id,
258
264
  ragRawQuery: messageQuery?.userQuery,
@@ -263,6 +269,10 @@ export class MessageModel {
263
269
  } as unknown as UIChatMessage;
264
270
  },
265
271
  );
272
+
273
+ // Group assistant messages with their tool results
274
+ const { groupAssistantMessages: useGroup = false } = options;
275
+ return useGroup ? groupAssistantMessages(mappedMessages) : mappedMessages;
266
276
  };
267
277
 
268
278
  findById = async (id: string) => {
@@ -560,7 +570,7 @@ export class MessageModel {
560
570
  sessionId: params.sessionId,
561
571
  topicId: params.topicId, // Get all messages
562
572
  },
563
- options,
573
+ { ...options, groupAssistantMessages: true },
564
574
  );
565
575
 
566
576
  // 3. Return the result
@@ -589,27 +599,52 @@ export class MessageModel {
589
599
  };
590
600
  // **************** Update *************** //
591
601
 
592
- update = async (id: string, { imageList, ...message }: Partial<UpdateMessageParams>) => {
593
- return this.db.transaction(async (trx) => {
594
- // 1. insert message files
595
- if (imageList && imageList.length > 0) {
602
+ update = async (
603
+ id: string,
604
+ { imageList, ...message }: Partial<UpdateMessageParams>,
605
+ options?: {
606
+ postProcessUrl?: (path: string | null, file: { fileType: string }) => Promise<string>;
607
+ sessionId?: string | null;
608
+ topicId?: string | null;
609
+ },
610
+ ): Promise<UpdateMessageResult> => {
611
+ try {
612
+ await this.db.transaction(async (trx) => {
613
+ // 1. insert message files
614
+ if (imageList && imageList.length > 0) {
615
+ await trx
616
+ .insert(messagesFiles)
617
+ .values(
618
+ imageList.map((file) => ({ fileId: file.id, messageId: id, userId: this.userId })),
619
+ );
620
+ }
621
+
596
622
  await trx
597
- .insert(messagesFiles)
598
- .values(
599
- imageList.map((file) => ({ fileId: file.id, messageId: id, userId: this.userId })),
600
- );
623
+ .update(messages)
624
+ .set({ ...message })
625
+ .where(and(eq(messages.id, id), eq(messages.userId, this.userId)));
626
+ });
627
+
628
+ // if sessionId or topicId provided, return the updated message list
629
+ if (options?.sessionId !== undefined || options?.topicId !== undefined) {
630
+ const messageList = await this.query(
631
+ {
632
+ sessionId: options.sessionId,
633
+ topicId: options.topicId,
634
+ },
635
+ {
636
+ postProcessUrl: options.postProcessUrl,
637
+ },
638
+ );
639
+
640
+ return { messages: messageList, success: true };
601
641
  }
602
642
 
603
- return trx
604
- .update(messages)
605
- .set({
606
- ...message,
607
- // TODO: need a better way to handle this
608
- // TODO: but I forget why 🤡
609
- role: message.role as any,
610
- })
611
- .where(and(eq(messages.id, id), eq(messages.userId, this.userId)));
612
- });
643
+ return { success: true };
644
+ } catch (error) {
645
+ console.error('Update message error:', error);
646
+ return { success: false };
647
+ }
613
648
  };
614
649
 
615
650
  updateMetadata = async (id: string, metadata: Record<string, any>) => {
@@ -778,8 +813,11 @@ export class MessageModel {
778
813
 
779
814
  private genId = () => idGenerator('messages', 14);
780
815
 
781
- private matchSession = (sessionId?: string | null) =>
782
- sessionId ? eq(messages.sessionId, sessionId) : isNull(messages.sessionId);
816
+ private matchSession = (sessionId?: string | null) => {
817
+ if (sessionId === INBOX_SESSION_ID) return isNull(messages.sessionId);
818
+
819
+ return sessionId ? eq(messages.sessionId, sessionId) : isNull(messages.sessionId);
820
+ };
783
821
 
784
822
  private matchTopic = (topicId?: string | null) =>
785
823
  topicId ? eq(messages.topicId, topicId) : isNull(messages.topicId);
@@ -53,13 +53,44 @@ export class SessionModel {
53
53
  query = async ({ current = 0, pageSize = 9999 } = {}) => {
54
54
  const offset = current * pageSize;
55
55
 
56
- return this.db.query.sessions.findMany({
57
- limit: pageSize,
58
- offset,
59
- orderBy: [desc(sessions.updatedAt)],
60
- where: and(eq(sessions.userId, this.userId), not(eq(sessions.slug, INBOX_SESSION_ID))),
61
- with: { agentsToSessions: { columns: {}, with: { agent: true } }, group: true },
62
- });
56
+ // Use leftJoin instead of nested with for better performance
57
+ const result = await this.db
58
+ .select({
59
+ // Agent fields (from agentsToSessions join)
60
+ agent: agents,
61
+ // Group fields
62
+ group: sessionGroups,
63
+ // Session fields
64
+ session: sessions,
65
+ })
66
+ .from(sessions)
67
+ .leftJoin(agentsToSessions, eq(sessions.id, agentsToSessions.sessionId))
68
+ .leftJoin(agents, eq(agentsToSessions.agentId, agents.id))
69
+ .leftJoin(sessionGroups, eq(sessions.groupId, sessionGroups.id))
70
+ .where(and(eq(sessions.userId, this.userId), not(eq(sessions.slug, INBOX_SESSION_ID))))
71
+ .orderBy(desc(sessions.updatedAt))
72
+ .limit(pageSize)
73
+ .offset(offset);
74
+
75
+ // Group results by session (since leftJoin can create multiple rows per session)
76
+ // Use Map to preserve order
77
+ const groupedResults = new Map<string, any>();
78
+
79
+ for (const row of result) {
80
+ const sessionId = row.session.id;
81
+ if (!groupedResults.has(sessionId)) {
82
+ groupedResults.set(sessionId, {
83
+ ...row.session,
84
+ agentsToSessions: [],
85
+ group: row.group,
86
+ });
87
+ }
88
+ if (row.agent) {
89
+ groupedResults.get(sessionId)!.agentsToSessions.push({ agent: row.agent });
90
+ }
91
+ }
92
+
93
+ return Array.from(groupedResults.values());
63
94
  };
64
95
 
65
96
  queryWithGroups = async (): Promise<ChatSessionList> => {
@@ -71,9 +102,11 @@ export class SessionModel {
71
102
  where: eq(sessions.userId, this.userId),
72
103
  });
73
104
 
105
+ const mappedSessions = result.map((item) => this.mapSessionItem(item as any));
106
+
74
107
  return {
75
108
  sessionGroups: groups as unknown as ChatSessionList['sessionGroups'],
76
- sessions: result.map((item) => this.mapSessionItem(item as any)),
109
+ sessions: mappedSessions,
77
110
  };
78
111
  };
79
112
 
@@ -332,7 +365,7 @@ export class SessionModel {
332
365
  .delete(agentsToSessions)
333
366
  .where(and(eq(agentsToSessions.sessionId, id), eq(agentsToSessions.userId, this.userId)));
334
367
 
335
- // Delete the session
368
+ // Delete the session (this will cascade delete messages, topics, etc.)
336
369
  const result = await trx
337
370
  .delete(sessions)
338
371
  .where(and(eq(sessions.id, id), eq(sessions.userId, this.userId)));
@@ -397,17 +430,25 @@ export class SessionModel {
397
430
  };
398
431
 
399
432
  clearOrphanAgent = async (agentIds: string[], trx: any) => {
400
- // Delete orphaned agents (those not linked to any other sessions)
401
- for (const agentId of agentIds) {
402
- const remaining = await trx
403
- .select()
404
- .from(agentsToSessions)
405
- .where(eq(agentsToSessions.agentId, agentId))
406
- .limit(1);
433
+ if (agentIds.length === 0) return;
407
434
 
408
- if (remaining.length === 0) {
409
- await trx.delete(agents).where(and(eq(agents.id, agentId), eq(agents.userId, this.userId)));
410
- }
435
+ // Batch query to find which agents still have sessions
436
+ const remainingLinks = (await trx
437
+ .select({ agentId: agentsToSessions.agentId })
438
+ .from(agentsToSessions)
439
+ .where(inArray(agentsToSessions.agentId, agentIds))) as { agentId: string }[];
440
+
441
+ const linkedAgentIds = new Set(remainingLinks.map((link) => link.agentId));
442
+
443
+ // Find orphaned agents (those not in the linked set)
444
+ const orphanedAgentIds = agentIds.filter((id) => !linkedAgentIds.has(id));
445
+
446
+ // Batch delete orphaned agents (this will cascade to agentsFiles, agentsKnowledgeBases, etc.)
447
+ // and SET NULL on messages.agentId
448
+ if (orphanedAgentIds.length > 0) {
449
+ await trx
450
+ .delete(agents)
451
+ .where(and(inArray(agents.id, orphanedAgentIds), eq(agents.userId, this.userId)));
411
452
  }
412
453
  };
413
454
 
@@ -62,11 +62,11 @@ export const agents = pgTable(
62
62
 
63
63
  ...timestamps,
64
64
  },
65
- (t) => ({
66
- clientIdUnique: uniqueIndex('client_id_user_id_unique').on(t.clientId, t.userId),
67
- titleIndex: index('agents_title_idx').on(t.title),
68
- descriptionIndex: index('agents_description_idx').on(t.description),
69
- }),
65
+ (t) => [
66
+ uniqueIndex('client_id_user_id_unique').on(t.clientId, t.userId),
67
+ index('agents_title_idx').on(t.title),
68
+ index('agents_description_idx').on(t.description),
69
+ ],
70
70
  );
71
71
 
72
72
  export const insertAgentSchema = createInsertSchema(agents);
@@ -90,9 +90,7 @@ export const agentsKnowledgeBases = pgTable(
90
90
 
91
91
  ...timestamps,
92
92
  },
93
- (t) => ({
94
- pk: primaryKey({ columns: [t.agentId, t.knowledgeBaseId] }),
95
- }),
93
+ (t) => [primaryKey({ columns: [t.agentId, t.knowledgeBaseId] })],
96
94
  );
97
95
 
98
96
  export const agentsFiles = pgTable(
@@ -111,7 +109,8 @@ export const agentsFiles = pgTable(
111
109
 
112
110
  ...timestamps,
113
111
  },
114
- (t) => ({
115
- pk: primaryKey({ columns: [t.fileId, t.agentId, t.userId] }),
116
- }),
112
+ (t) => [
113
+ primaryKey({ columns: [t.fileId, t.agentId, t.userId] }),
114
+ index('agents_files_agent_id_idx').on(t.agentId),
115
+ ],
117
116
  );
@@ -62,7 +62,10 @@ export const messageGroups = pgTable(
62
62
 
63
63
  ...timestamps,
64
64
  },
65
- (t) => [uniqueIndex('message_groups_client_id_user_id_unique').on(t.clientId, t.userId)],
65
+ (t) => [
66
+ uniqueIndex('message_groups_client_id_user_id_unique').on(t.clientId, t.userId),
67
+ index('message_groups_topic_id_idx').on(t.topicId),
68
+ ],
66
69
  );
67
70
 
68
71
  export const insertMessageGroupSchema = createInsertSchema(messageGroups);
@@ -130,6 +133,7 @@ export const messages = pgTable(
130
133
  index('messages_user_id_idx').on(table.userId),
131
134
  index('messages_session_id_idx').on(table.sessionId),
132
135
  index('messages_thread_id_idx').on(table.threadId),
136
+ index('messages_agent_id_idx').on(table.agentId),
133
137
  ],
134
138
  );
135
139
 
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable sort-keys-fix/sort-keys-fix */
2
2
  import { relations } from 'drizzle-orm';
3
- import { pgTable, primaryKey, text, uuid, varchar } from 'drizzle-orm/pg-core';
3
+ import { index, pgTable, primaryKey, text, uuid, varchar } from 'drizzle-orm/pg-core';
4
4
 
5
5
  import { createdAt } from './_helpers';
6
6
  import { agents, agentsFiles, agentsKnowledgeBases } from './agent';
@@ -28,9 +28,11 @@ export const agentsToSessions = pgTable(
28
28
  .references(() => users.id, { onDelete: 'cascade' })
29
29
  .notNull(),
30
30
  },
31
- (t) => ({
32
- pk: primaryKey({ columns: [t.agentId, t.sessionId] }),
33
- }),
31
+ (t) => [
32
+ primaryKey({ columns: [t.agentId, t.sessionId] }),
33
+ index('agents_to_sessions_session_id_idx').on(t.sessionId),
34
+ index('agents_to_sessions_agent_id_idx').on(t.agentId),
35
+ ],
34
36
  );
35
37
 
36
38
  export const filesToSessions = pgTable(
@@ -70,6 +70,8 @@ export const sessions = pgTable(
70
70
 
71
71
  index('sessions_user_id_idx').on(t.userId),
72
72
  index('sessions_id_user_id_idx').on(t.id, t.userId),
73
+ index('sessions_user_id_updated_at_idx').on(t.userId, t.updatedAt),
74
+ index('sessions_group_id_idx').on(t.groupId),
73
75
  ],
74
76
  );
75
77
 
@@ -33,6 +33,8 @@ export const topics = pgTable(
33
33
  uniqueIndex('topics_client_id_user_id_unique').on(t.clientId, t.userId),
34
34
  index('topics_user_id_idx').on(t.userId),
35
35
  index('topics_id_user_id_idx').on(t.id, t.userId),
36
+ index('topics_session_id_idx').on(t.sessionId),
37
+ index('topics_group_id_idx').on(t.groupId),
36
38
  ],
37
39
  );
38
40
 
@@ -65,7 +67,10 @@ export const threads = pgTable(
65
67
  lastActiveAt: timestamptz('last_active_at').defaultNow(),
66
68
  ...timestamps,
67
69
  },
68
- (t) => [uniqueIndex('threads_client_id_user_id_unique').on(t.clientId, t.userId)],
70
+ (t) => [
71
+ uniqueIndex('threads_client_id_user_id_unique').on(t.clientId, t.userId),
72
+ index('threads_topic_id_idx').on(t.topicId),
73
+ ],
69
74
  );
70
75
 
71
76
  export type NewThread = typeof threads.$inferInsert;
@@ -54,6 +54,7 @@ describe('groupAssistantMessages', () => {
54
54
  content: 'Beijing: Sunny, 25°C',
55
55
  state: { cached: true },
56
56
  },
57
+ result_msg_id: 'msg-2',
57
58
  });
58
59
  });
59
60
 
@@ -112,7 +113,9 @@ describe('groupAssistantMessages', () => {
112
113
  const block = result[0].children![0];
113
114
  expect(block.tools).toHaveLength(2);
114
115
  expect(block.tools![0].result?.content).toBe('Beijing: Sunny, 25°C');
116
+ expect(block.tools![0].result_msg_id).toBe('msg-2');
115
117
  expect(block.tools![1].result?.content).toBe('Latest tech news: AI breakthrough');
118
+ expect(block.tools![1].result_msg_id).toBe('msg-3');
116
119
  });
117
120
 
118
121
  it('should handle assistant message without tools', () => {
@@ -165,6 +168,7 @@ describe('groupAssistantMessages', () => {
165
168
  const block = result[0].children![0];
166
169
  expect(block.tools).toHaveLength(1);
167
170
  expect(block.tools![0].result).toBeUndefined();
171
+ expect(block.tools![0].result_msg_id).toBeUndefined();
168
172
  });
169
173
  });
170
174
 
@@ -366,11 +370,13 @@ describe('groupAssistantMessages', () => {
366
370
  // First child: original assistant with tool result
367
371
  expect(result[0].children![0].id).toBe('msg-1');
368
372
  expect(result[0].children![0].tools![0].result?.content).toBe('Sunny, 25°C');
373
+ expect(result[0].children![0].tools![0].result_msg_id).toBe('msg-2');
369
374
 
370
375
  // Second child: follow-up assistant with its own tool result
371
376
  expect(result[0].children![1].id).toBe('msg-3');
372
377
  expect(result[0].children![1].tools).toHaveLength(1);
373
378
  expect(result[0].children![1].tools![0].result?.content).toBe('Breaking news');
379
+ expect(result[0].children![1].tools![0].result_msg_id).toBe('msg-4');
374
380
  });
375
381
 
376
382
  it('should group multiple follow-up assistants in chain (3+ assistants)', () => {
@@ -448,9 +454,11 @@ describe('groupAssistantMessages', () => {
448
454
 
449
455
  expect(result[0].children![0].id).toBe('msg-1');
450
456
  expect(result[0].children![0].tools![0].result?.content).toBe('Result 1');
457
+ expect(result[0].children![0].tools![0].result_msg_id).toBe('msg-2');
451
458
 
452
459
  expect(result[0].children![1].id).toBe('msg-3');
453
460
  expect(result[0].children![1].tools![0].result?.content).toBe('Result 2');
461
+ expect(result[0].children![1].tools![0].result_msg_id).toBe('msg-4');
454
462
 
455
463
  expect(result[0].children![2].id).toBe('msg-5');
456
464
  expect(result[0].children![2].content).toBe('Step 3 final');
@@ -677,7 +685,6 @@ describe('groupAssistantMessages', () => {
677
685
  expect(block.content).toBe('Test');
678
686
  expect(block.tools).toHaveLength(1);
679
687
  expect(block.imageList).toHaveLength(1);
680
- expect(block.fileList).toHaveLength(1);
681
688
  });
682
689
 
683
690
  it('should preserve all tool result fields', () => {
@@ -720,6 +727,7 @@ describe('groupAssistantMessages', () => {
720
727
  state: { step: 1 },
721
728
  error: null,
722
729
  });
730
+ expect(block.tools![0].result_msg_id).toBe('msg-2');
723
731
  });
724
732
  });
725
733
 
@@ -928,6 +936,142 @@ describe('groupAssistantMessages', () => {
928
936
  expect(result[0].performance).toBeUndefined();
929
937
  expect(result[0].metadata).toBeUndefined();
930
938
  });
939
+
940
+ it('should map reasoning field to reasoning in children blocks', () => {
941
+ const input: UIChatMessage[] = [
942
+ {
943
+ id: 'msg-1',
944
+ role: 'assistant',
945
+ content: 'Test response',
946
+ reasoning: {
947
+ content: 'This is my reasoning process',
948
+ duration: 1500,
949
+ },
950
+ tools: [
951
+ {
952
+ id: 'tool-1',
953
+ identifier: 'test',
954
+ apiName: 'test',
955
+ arguments: '{}',
956
+ type: 'default',
957
+ },
958
+ ],
959
+ createdAt: Date.now(),
960
+ updatedAt: Date.now(),
961
+ meta: {},
962
+ } as UIChatMessage,
963
+ {
964
+ id: 'msg-2',
965
+ role: 'tool',
966
+ tool_call_id: 'tool-1',
967
+ content: 'Tool result',
968
+ createdAt: Date.now(),
969
+ updatedAt: Date.now(),
970
+ meta: {},
971
+ } as UIChatMessage,
972
+ {
973
+ id: 'msg-3',
974
+ role: 'assistant',
975
+ content: 'Follow-up response',
976
+ parentId: 'msg-2',
977
+ reasoning: {
978
+ content: 'Follow-up reasoning',
979
+ duration: 2000,
980
+ },
981
+ createdAt: Date.now(),
982
+ updatedAt: Date.now(),
983
+ meta: {},
984
+ } as UIChatMessage,
985
+ ];
986
+
987
+ const result = groupAssistantMessages(input);
988
+
989
+ // First block should have reasoning
990
+ expect(result[0].children![0].reasoning).toEqual({
991
+ content: 'This is my reasoning process',
992
+ duration: 1500,
993
+ });
994
+
995
+ // Second block (follow-up) should also have reasoning
996
+ expect(result[0].children![1].reasoning).toEqual({
997
+ content: 'Follow-up reasoning',
998
+ duration: 2000,
999
+ });
1000
+
1001
+ // Group message should not have reasoning (moved to children)
1002
+ expect(result[0].reasoning).toBeUndefined();
1003
+ });
1004
+
1005
+ it('should preserve error field in children blocks', () => {
1006
+ const input = [
1007
+ {
1008
+ id: 'msg-1',
1009
+ role: 'assistant',
1010
+ content: 'Failed to process',
1011
+ error: {
1012
+ type: 'InvalidAPIKey',
1013
+ message: 'API key is invalid',
1014
+ },
1015
+ tools: [
1016
+ {
1017
+ id: 'tool-1',
1018
+ identifier: 'test',
1019
+ apiName: 'test',
1020
+ arguments: '{}',
1021
+ type: 'default',
1022
+ },
1023
+ ],
1024
+ createdAt: Date.now(),
1025
+ updatedAt: Date.now(),
1026
+ meta: {},
1027
+ } as unknown as UIChatMessage,
1028
+ ] as UIChatMessage[];
1029
+
1030
+ const result = groupAssistantMessages(input);
1031
+
1032
+ // Child block should have error
1033
+ expect(result[0].children![0].error).toEqual({
1034
+ type: 'InvalidAPIKey',
1035
+ message: 'API key is invalid',
1036
+ });
1037
+ });
1038
+
1039
+ it('should preserve imageList in children blocks', () => {
1040
+ const input: UIChatMessage[] = [
1041
+ {
1042
+ id: 'msg-1',
1043
+ role: 'assistant',
1044
+ content: 'Here are the images',
1045
+ imageList: [
1046
+ { id: 'img-1', url: 'https://example.com/img1.jpg', alt: 'Image 1' },
1047
+ { id: 'img-2', url: 'https://example.com/img2.jpg', alt: 'Image 2' },
1048
+ ],
1049
+ tools: [
1050
+ {
1051
+ id: 'tool-1',
1052
+ identifier: 'test',
1053
+ apiName: 'test',
1054
+ arguments: '{}',
1055
+ type: 'default',
1056
+ },
1057
+ ],
1058
+ createdAt: Date.now(),
1059
+ updatedAt: Date.now(),
1060
+ meta: {},
1061
+ } as UIChatMessage,
1062
+ ];
1063
+
1064
+ const result = groupAssistantMessages(input);
1065
+
1066
+ // Child block should have imageList
1067
+ expect(result[0].children![0].imageList).toEqual([
1068
+ { id: 'img-1', url: 'https://example.com/img1.jpg', alt: 'Image 1' },
1069
+ { id: 'img-2', url: 'https://example.com/img2.jpg', alt: 'Image 2' },
1070
+ ]);
1071
+
1072
+ // Parent should not have imageList (moved to children)
1073
+ expect(result[0].imageList).toBeUndefined();
1074
+ });
931
1075
  });
932
1076
 
933
1077
  describe('Empty and Null Cases', () => {
@@ -958,7 +1102,6 @@ describe('groupAssistantMessages', () => {
958
1102
 
959
1103
  // Empty arrays should become undefined
960
1104
  expect(result[0].children![0].imageList).toBeUndefined();
961
- expect(result[0].children![0].fileList).toBeUndefined();
962
1105
  });
963
1106
 
964
1107
  it('should handle empty message list', () => {
@@ -243,6 +243,7 @@ export function groupAssistantMessages(messages: UIChatMessage[]): UIChatMessage
243
243
  id: toolMsg.id,
244
244
  state: toolMsg.pluginState,
245
245
  },
246
+ result_msg_id: toolMsg.id,
246
247
  };
247
248
  }
248
249
 
@@ -253,10 +254,11 @@ export function groupAssistantMessages(messages: UIChatMessage[]): UIChatMessage
253
254
  const { usage: msgUsage, performance: msgPerformance } = splitMetadata(msg.metadata);
254
255
  children.push({
255
256
  content: msg.content || '',
256
- fileList: msg.fileList && msg.fileList.length > 0 ? msg.fileList : undefined,
257
+ error: msg.error,
257
258
  id: msg.id,
258
259
  imageList: msg.imageList && msg.imageList.length > 0 ? msg.imageList : undefined,
259
260
  performance: msgPerformance,
261
+ reasoning: msg.reasoning || undefined,
260
262
  tools: toolsWithResults,
261
263
  usage: msgUsage,
262
264
  });
@@ -299,6 +301,7 @@ export function groupAssistantMessages(messages: UIChatMessage[]): UIChatMessage
299
301
  id: followUpToolMsg.id,
300
302
  state: followUpToolMsg.pluginState,
301
303
  },
304
+ result_msg_id: followUpToolMsg.id,
302
305
  };
303
306
  }
304
307
 
@@ -311,16 +314,14 @@ export function groupAssistantMessages(messages: UIChatMessage[]): UIChatMessage
311
314
  );
312
315
  children.push({
313
316
  content: followUpMsg.content || '',
314
- fileList:
315
- followUpMsg.fileList && followUpMsg.fileList.length > 0
316
- ? followUpMsg.fileList
317
- : undefined,
317
+ error: followUpMsg.error,
318
318
  id: followUpMsg.id,
319
319
  imageList:
320
320
  followUpMsg.imageList && followUpMsg.imageList.length > 0
321
321
  ? followUpMsg.imageList
322
322
  : undefined,
323
323
  performance: followUpPerformance,
324
+ reasoning: followUpMsg.reasoning || undefined,
324
325
  tools: followUpToolsWithResults,
325
326
  usage: followUpUsage,
326
327
  });
@@ -347,6 +348,7 @@ export function groupAssistantMessages(messages: UIChatMessage[]): UIChatMessage
347
348
  assistantMsg.performance = aggregated.performance;
348
349
  }
349
350
  delete assistantMsg.metadata; // Clear individual metadata
351
+ delete assistantMsg.reasoning; // Reasoning moved to children blocks
350
352
  delete assistantMsg.tools;
351
353
  delete assistantMsg.imageList;
352
354
  delete assistantMsg.fileList;
@@ -3,5 +3,8 @@
3
3
  "version": "1.0.0",
4
4
  "private": true,
5
5
  "main": "src/index.ts",
6
- "types": "src/index.ts"
6
+ "types": "src/index.ts",
7
+ "peerDependencies": {
8
+ "react": "^19"
9
+ }
7
10
  }
@@ -28,6 +28,7 @@
28
28
  "@napi-rs/canvas": "^0.1.70",
29
29
  "@xmldom/xmldom": "^0.9.8",
30
30
  "concat-stream": "^2.0.0",
31
+ "debug": "^4.3.4",
31
32
  "mammoth": "^1.8.0",
32
33
  "officeparser": "5.1.1",
33
34
  "pdfjs-dist": "4.10.38",