@lobehub/lobehub 2.0.0-next.7 → 2.0.0-next.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/.github/workflows/desktop-pr-build.yml +8 -8
  2. package/.github/workflows/docker.yml +17 -16
  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 +50 -0
  8. package/changelog/v1.json +18 -0
  9. package/locales/ar/models.json +6 -6
  10. package/locales/bg-BG/models.json +6 -6
  11. package/locales/de-DE/models.json +6 -6
  12. package/locales/en-US/models.json +6 -6
  13. package/locales/es-ES/models.json +6 -6
  14. package/locales/fa-IR/models.json +6 -6
  15. package/locales/fr-FR/models.json +6 -6
  16. package/locales/it-IT/models.json +6 -6
  17. package/locales/ja-JP/models.json +6 -6
  18. package/locales/ko-KR/models.json +6 -6
  19. package/locales/nl-NL/models.json +6 -6
  20. package/locales/pl-PL/models.json +6 -6
  21. package/locales/pt-BR/models.json +6 -6
  22. package/locales/ru-RU/models.json +6 -6
  23. package/locales/tr-TR/models.json +6 -6
  24. package/locales/vi-VN/models.json +6 -6
  25. package/locales/zh-CN/models.json +6 -6
  26. package/locales/zh-TW/models.json +6 -6
  27. package/package.json +1 -1
  28. package/packages/const/src/index.ts +0 -1
  29. package/packages/const/src/url.ts +1 -4
  30. package/packages/context-engine/src/index.ts +1 -6
  31. package/packages/context-engine/src/processors/GroupMessageFlatten.ts +12 -2
  32. package/packages/context-engine/src/processors/__tests__/GroupMessageFlatten.test.ts +73 -9
  33. package/packages/context-engine/src/providers/index.ts +0 -2
  34. package/packages/database/package.json +1 -1
  35. package/packages/database/src/models/__tests__/message.grouping.test.ts +812 -0
  36. package/packages/database/src/models/__tests__/message.test.ts +322 -170
  37. package/packages/database/src/models/message.ts +62 -24
  38. package/packages/database/src/utils/__tests__/groupMessages.test.ts +145 -2
  39. package/packages/database/src/utils/groupMessages.ts +7 -5
  40. package/packages/types/src/message/common/base.ts +13 -0
  41. package/packages/types/src/message/common/image.ts +8 -0
  42. package/packages/types/src/message/common/metadata.ts +39 -0
  43. package/packages/types/src/message/common/tools.ts +10 -0
  44. package/packages/types/src/message/db/params.ts +47 -1
  45. package/packages/types/src/message/ui/chat.ts +4 -1
  46. package/packages/types/src/search.ts +16 -0
  47. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/V1Mobile/index.tsx +2 -2
  48. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/V1Mobile/useSend.ts +6 -4
  49. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/useSend.ts +15 -10
  50. package/src/app/[variants]/(main)/chat/@session/features/SessionListContent/List/Item/index.tsx +4 -2
  51. package/src/components/Thinking/index.tsx +4 -3
  52. package/src/features/AgentSetting/AgentPlugin/index.tsx +2 -2
  53. package/src/features/ChatInput/ActionBar/STT/browser.tsx +2 -2
  54. package/src/features/ChatInput/ActionBar/STT/openai.tsx +2 -2
  55. package/src/features/ChatInput/ActionBar/Tools/useControls.tsx +1 -3
  56. package/src/features/Conversation/Error/ErrorJsonViewer.tsx +4 -3
  57. package/src/features/Conversation/Error/OllamaBizError/index.tsx +7 -2
  58. package/src/features/Conversation/Error/index.tsx +15 -5
  59. package/src/features/Conversation/MarkdownElements/LobeArtifact/Render/index.tsx +2 -2
  60. package/src/features/Conversation/Messages/Assistant/Extra/index.tsx +2 -2
  61. package/src/features/Conversation/Messages/Assistant/MessageContent.tsx +5 -3
  62. package/src/features/Conversation/Messages/Assistant/Tool/Inspector/BuiltinPluginTitle.tsx +2 -2
  63. package/src/features/Conversation/Messages/Assistant/Tool/Inspector/ToolTitle.tsx +4 -2
  64. package/src/features/Conversation/Messages/Assistant/Tool/Render/CustomRender.tsx +2 -2
  65. package/src/features/Conversation/Messages/Assistant/Tool/Render/index.tsx +2 -2
  66. package/src/features/Conversation/Messages/Assistant/Tool/index.tsx +2 -2
  67. package/src/features/Conversation/Messages/Assistant/index.tsx +4 -4
  68. package/src/features/Conversation/Messages/Default.tsx +2 -2
  69. package/src/features/Conversation/Messages/User/Extra.tsx +2 -2
  70. package/src/features/Conversation/Messages/User/index.tsx +4 -4
  71. package/src/features/Conversation/Messages/index.tsx +3 -3
  72. package/src/features/Conversation/components/AutoScroll.tsx +2 -2
  73. package/src/features/Conversation/components/Extras/Usage/UsageDetail/index.tsx +9 -6
  74. package/src/features/PluginTag/index.tsx +1 -3
  75. package/src/features/PluginsUI/Render/BuiltinType/index.test.tsx +37 -28
  76. package/src/features/Portal/Artifacts/Body/index.tsx +2 -2
  77. package/src/server/modules/ModelRuntime/trace.ts +11 -4
  78. package/src/server/routers/lambda/message.ts +14 -3
  79. package/src/services/chat/chat.test.ts +1 -40
  80. package/src/services/chat/contextEngineering.test.ts +0 -30
  81. package/src/services/chat/contextEngineering.ts +1 -12
  82. package/src/services/chat/index.ts +2 -7
  83. package/src/services/chat/types.ts +1 -1
  84. package/src/services/message/_deprecated.ts +1 -1
  85. package/src/services/message/client.ts +8 -2
  86. package/src/services/message/server.ts +7 -2
  87. package/src/services/message/type.ts +6 -1
  88. package/src/store/chat/helpers.test.ts +99 -0
  89. package/src/store/chat/helpers.ts +21 -2
  90. package/src/store/chat/selectors.ts +1 -1
  91. package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +3 -3
  92. package/src/store/chat/slices/builtinTool/actions/index.ts +1 -4
  93. package/src/store/chat/slices/message/action.test.ts +5 -1
  94. package/src/store/chat/slices/message/action.ts +102 -14
  95. package/src/store/chat/slices/message/reducer.test.ts +363 -5
  96. package/src/store/chat/slices/message/reducer.ts +87 -3
  97. package/src/store/chat/slices/message/{selectors.test.ts → selectors/chat.test.ts} +266 -30
  98. package/src/store/chat/slices/message/{selectors.ts → selectors/chat.ts} +29 -79
  99. package/src/store/chat/slices/message/selectors/index.ts +2 -0
  100. package/src/store/chat/slices/message/selectors/messageState.test.ts +36 -0
  101. package/src/store/chat/slices/message/selectors/messageState.ts +80 -0
  102. package/src/store/chat/slices/plugin/action.test.ts +34 -132
  103. package/src/store/chat/slices/plugin/action.ts +1 -44
  104. package/src/store/tool/selectors/tool.test.ts +1 -1
  105. package/src/store/tool/selectors/tool.ts +6 -8
  106. package/src/store/tool/slices/builtin/action.test.ts +83 -35
  107. package/src/store/tool/slices/builtin/action.ts +0 -9
  108. package/src/store/tool/slices/builtin/selectors.test.ts +4 -30
  109. package/src/store/tool/slices/builtin/selectors.ts +15 -21
  110. package/src/tools/index.ts +0 -6
  111. package/src/tools/renders.ts +0 -3
  112. package/src/tools/web-browsing/Portal/Search/Footer.tsx +2 -2
  113. package/packages/const/src/guide.ts +0 -89
  114. package/packages/context-engine/src/providers/InboxGuide.ts +0 -102
  115. package/packages/context-engine/src/providers/__tests__/InboxGuideProvider.test.ts +0 -121
  116. package/src/services/chat/__snapshots__/chat.test.ts.snap +0 -110
  117. package/src/store/chat/slices/builtinTool/actions/__tests__/dalle.test.ts +0 -121
  118. package/src/store/chat/slices/builtinTool/actions/dalle.ts +0 -124
  119. package/src/tools/dalle/Render/GalleyGrid.tsx +0 -60
  120. package/src/tools/dalle/Render/Item/EditMode.tsx +0 -66
  121. package/src/tools/dalle/Render/Item/Error.tsx +0 -49
  122. package/src/tools/dalle/Render/Item/Image.tsx +0 -44
  123. package/src/tools/dalle/Render/Item/ImageFileItem.tsx +0 -57
  124. package/src/tools/dalle/Render/Item/index.tsx +0 -88
  125. package/src/tools/dalle/Render/ToolBar.tsx +0 -56
  126. package/src/tools/dalle/Render/index.tsx +0 -52
  127. package/src/tools/dalle/index.ts +0 -92
@@ -11,7 +11,7 @@ import InfoTooltip from '@/components/InfoTooltip';
11
11
  import { aiModelSelectors, useAiInfraStore } from '@/store/aiInfra';
12
12
  import { useGlobalStore } from '@/store/global';
13
13
  import { systemStatusSelectors } from '@/store/global/selectors';
14
- import { formatNumber } from '@/utils/format';
14
+ import { formatNumber, formatShortenNumber } from '@/utils/format';
15
15
 
16
16
  import ModelCard from './ModelCard';
17
17
  import TokenProgress, { TokenProgressItem } from './TokenProgress';
@@ -111,10 +111,13 @@ const TokenDetail = memo<TokenDetailProps>(({ meta, model, provider }) => {
111
111
  },
112
112
  ].filter(Boolean) as TokenProgressItem[];
113
113
 
114
- const displayTotal =
114
+ const totalCount =
115
115
  isShowCredit && !!detailTokens.totalTokens
116
- ? formatNumber(detailTokens.totalTokens.credit)
117
- : formatNumber(detailTokens.totalTokens!.token);
116
+ ? detailTokens.totalTokens.credit
117
+ : detailTokens.totalTokens!.token;
118
+
119
+ const shortTotal = (formatShortenNumber(totalCount) as string).toLowerCase?.();
120
+ const detailTotal = formatNumber(totalCount);
118
121
 
119
122
  const averagePricing = formatNumber(
120
123
  detailTokens.totalTokens!.credit / detailTokens.totalTokens!.token,
@@ -171,7 +174,7 @@ const TokenDetail = memo<TokenDetailProps>(({ meta, model, provider }) => {
171
174
  <div style={{ color: theme.colorTextSecondary }}>
172
175
  {t('messages.tokenDetails.total')}
173
176
  </div>
174
- <div style={{ fontWeight: 500 }}>{displayTotal}</div>
177
+ <div style={{ fontWeight: 500 }}>{detailTotal}</div>
175
178
  </Flexbox>
176
179
  {isShowCredit && (
177
180
  <Flexbox align={'center'} gap={4} horizontal justify={'space-between'}>
@@ -212,7 +215,7 @@ const TokenDetail = memo<TokenDetailProps>(({ meta, model, provider }) => {
212
215
  >
213
216
  <Center gap={2} horizontal style={{ cursor: 'default' }}>
214
217
  <Icon icon={isShowCredit ? BadgeCent : CoinsIcon} />
215
- {displayTotal}
218
+ {shortTotal}
216
219
  </Center>
217
220
  </Popover>
218
221
  );
@@ -7,7 +7,6 @@ import { memo } from 'react';
7
7
  import { Center } from 'react-layout-kit';
8
8
 
9
9
  import Avatar from '@/components/Plugins/PluginAvatar';
10
- import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
11
10
  import { pluginHelpers, useToolStore } from '@/store/tool';
12
11
  import { toolSelectors } from '@/store/tool/selectors';
13
12
 
@@ -18,8 +17,7 @@ export interface PluginTagProps {
18
17
  }
19
18
 
20
19
  const PluginTag = memo<PluginTagProps>(({ plugins }) => {
21
- const { showDalle } = useServerConfigStore(featureFlagsSelectors);
22
- const list = useToolStore(toolSelectors.metaList(showDalle), isEqual);
20
+ const list = useToolStore(toolSelectors.metaList, isEqual);
23
21
 
24
22
  const displayPlugin = useToolStore(toolSelectors.getMetaById(plugins[0]), isEqual);
25
23
 
@@ -1,22 +1,19 @@
1
1
  import { render, screen } from '@testing-library/react';
2
2
  import { describe, expect, it, vi } from 'vitest';
3
3
 
4
- import { DalleManifest } from '@/tools/dalle';
5
- import { BuiltinToolsRenders } from '@/tools/renders';
6
-
7
4
  import BuiltinType from './index';
8
5
 
9
- // Mock Render component and useParseContent hook
6
+ // Mock renders module
10
7
  vi.mock('@/tools/renders', () => ({
11
8
  BuiltinToolsRenders: {
12
- dalle3: vi.fn(() => <div>Test Renderer</div>),
13
- [DalleManifest.identifier]: vi.fn(() => <div>{DalleManifest.identifier}</div>),
9
+ 'lobe-web-browsing': vi.fn(({ content }) => <div>WebBrowsingRender: {content}</div>),
10
+ 'lobe-code-interpreter': vi.fn(({ content }) => <div>CodeInterpreterRender: {content}</div>),
14
11
  },
15
12
  }));
16
13
 
17
- // Mock Loading component
18
- vi.mock('../Loading', () => ({
19
- default: vi.fn(() => <div>Loading...</div>),
14
+ // Mock useParseContent hook
15
+ vi.mock('../useParseContent', () => ({
16
+ useParseContent: vi.fn((content) => ({ data: content })),
20
17
  }));
21
18
 
22
19
  describe('BuiltinType', () => {
@@ -25,29 +22,41 @@ describe('BuiltinType', () => {
25
22
  expect(container).toBeEmptyDOMElement();
26
23
  });
27
24
 
28
- it('should not render anything if content is not JSON and no identifier', () => {
29
- const { container } = render(<BuiltinType content="..." id="123" />);
30
- expect(container).toBeEmptyDOMElement();
31
- });
32
-
33
25
  it('should not render anything if identifier is unknown', () => {
34
26
  const { container } = render(<BuiltinType content="{}" id="123" identifier="unknown" />);
35
27
  expect(container).toBeEmptyDOMElement();
36
28
  });
37
29
 
38
- describe('DALL·E', () => {
39
- it('should render the correct renderer if identifier is dalle3', () => {
40
- render(<BuiltinType content='{"some":"data"}' id="123" identifier="dalle3" />);
41
- expect(BuiltinToolsRenders.dalle3).toHaveBeenCalled();
42
- expect(screen.getByText('Test Renderer')).toBeInTheDocument();
43
- });
44
-
45
- it('should render the correct renderer if is DALL·E ', () => {
46
- render(
47
- <BuiltinType content='{"some":"data"}' id="123" identifier={DalleManifest.identifier} />,
48
- );
49
- expect(BuiltinToolsRenders.dalle3).toHaveBeenCalled();
50
- expect(screen.getByText(DalleManifest.identifier)).toBeInTheDocument();
51
- });
30
+ it('should render the correct renderer for web browsing', () => {
31
+ const content = '{"query":"test"}';
32
+ render(<BuiltinType content={content} id="123" identifier="lobe-web-browsing" />);
33
+ expect(screen.getByText(`WebBrowsingRender: ${content}`)).toBeInTheDocument();
34
+ });
35
+
36
+ it('should render the correct renderer for code interpreter', () => {
37
+ const content = '{"code":"print(1)"}';
38
+ render(<BuiltinType content={content} id="123" identifier="lobe-code-interpreter" />);
39
+ expect(screen.getByText(`CodeInterpreterRender: ${content}`)).toBeInTheDocument();
40
+ });
41
+
42
+ it('should pass correct props to renderer', () => {
43
+ const content = '{"test":"data"}';
44
+ const args = '{"arg":"value"}';
45
+ const pluginState = { state: 'value' };
46
+ const pluginError = { error: 'test' };
47
+
48
+ render(
49
+ <BuiltinType
50
+ content={content}
51
+ id="msg-123"
52
+ identifier="lobe-web-browsing"
53
+ arguments={args}
54
+ pluginState={pluginState}
55
+ pluginError={pluginError}
56
+ apiName="testApi"
57
+ />,
58
+ );
59
+
60
+ expect(screen.getByText(`WebBrowsingRender: ${content}`)).toBeInTheDocument();
52
61
  });
53
62
  });
@@ -3,7 +3,7 @@ import { memo, useEffect, useMemo } from 'react';
3
3
  import { Flexbox } from 'react-layout-kit';
4
4
 
5
5
  import { useChatStore } from '@/store/chat';
6
- import { chatPortalSelectors, chatSelectors } from '@/store/chat/selectors';
6
+ import { chatPortalSelectors, messageStateSelectors } from '@/store/chat/selectors';
7
7
  import { ArtifactDisplayMode } from '@/store/chat/slices/portal/initialState';
8
8
  import { ArtifactType } from '@/types/artifact';
9
9
 
@@ -24,7 +24,7 @@ const ArtifactsUI = memo(() => {
24
24
  return [
25
25
  messageId,
26
26
  s.portalArtifactDisplayMode,
27
- chatSelectors.isMessageGenerating(messageId)(s),
27
+ messageStateSelectors.isMessageGenerating(messageId)(s),
28
28
  chatPortalSelectors.artifactType(s),
29
29
  chatPortalSelectors.artifactCode(messageId)(s),
30
30
  chatPortalSelectors.artifactCodeLanguage(s),
@@ -42,6 +42,16 @@ export const createTraceOptions = (
42
42
  startTime: new Date(),
43
43
  });
44
44
 
45
+ const headers = new Headers();
46
+
47
+ if (trace?.id) {
48
+ headers.set(LOBE_CHAT_TRACE_ID, trace.id);
49
+ }
50
+
51
+ if (generation?.id) {
52
+ headers.set(LOBE_CHAT_OBSERVATION_ID, generation.id);
53
+ }
54
+
45
55
  return {
46
56
  callback: {
47
57
  onCompletion: async ({ text, thinking, usage, grounding, toolsCalling }) => {
@@ -94,9 +104,6 @@ export const createTraceOptions = (
94
104
  });
95
105
  },
96
106
  } as ChatStreamCallbacks,
97
- headers: {
98
- [LOBE_CHAT_OBSERVATION_ID]: generation?.id,
99
- [LOBE_CHAT_TRACE_ID]: trace?.id,
100
- },
107
+ headers: headers,
101
108
  };
102
109
  };
@@ -1,4 +1,9 @@
1
- import { BatchTaskResult, UIChatMessage, UpdateMessageRAGParamsSchema } from '@lobechat/types';
1
+ import {
2
+ BatchTaskResult,
3
+ UIChatMessage,
4
+ UpdateMessageParamsSchema,
5
+ UpdateMessageRAGParamsSchema,
6
+ } from '@lobechat/types';
2
7
  import { z } from 'zod';
3
8
 
4
9
  import { MessageModel } from '@/database/models/message';
@@ -180,11 +185,17 @@ export const messageRouter = router({
180
185
  .input(
181
186
  z.object({
182
187
  id: z.string(),
183
- value: z.object({}).passthrough().partial(),
188
+ sessionId: z.string().nullable().optional(),
189
+ topicId: z.string().nullable().optional(),
190
+ value: UpdateMessageParamsSchema,
184
191
  }),
185
192
  )
186
193
  .mutation(async ({ input, ctx }) => {
187
- return ctx.messageModel.update(input.id, input.value);
194
+ return ctx.messageModel.update(input.id, input.value as any, {
195
+ postProcessUrl: (path) => ctx.fileService.getFullFileUrl(path),
196
+ sessionId: input.sessionId,
197
+ topicId: input.topicId,
198
+ });
188
199
  }),
189
200
 
190
201
  updateMessagePlugin: messageProcedure
@@ -8,18 +8,12 @@ import { type Mock, afterEach, beforeEach, describe, expect, it, vi } from 'vite
8
8
 
9
9
  import { DEFAULT_USER_AVATAR } from '@/const/meta';
10
10
  import { DEFAULT_AGENT_CONFIG } from '@/const/settings';
11
- import * as isCanUseFCModule from '@/helpers/isCanUseFC';
12
11
  import * as toolEngineeringModule from '@/helpers/toolEngineering';
13
- import { agentChatConfigSelectors, agentSelectors } from '@/store/agent/selectors';
12
+ import { agentChatConfigSelectors } from '@/store/agent/selectors';
14
13
  import { aiModelSelectors } from '@/store/aiInfra';
15
14
  import { useToolStore } from '@/store/tool';
16
- import { toolSelectors } from '@/store/tool/selectors';
17
- import { modelProviderSelectors } from '@/store/user/selectors';
18
- import { DalleManifest } from '@/tools/dalle';
19
15
  import { WebBrowsingManifest } from '@/tools/web-browsing';
20
16
 
21
- import { API_ENDPOINTS } from '../_url';
22
- import * as helpers from './helper';
23
17
  import { chatService } from './index';
24
18
 
25
19
  // Mocking external dependencies
@@ -831,39 +825,6 @@ describe('ChatService', () => {
831
825
  undefined,
832
826
  );
833
827
  });
834
-
835
- it('work with dalle3', async () => {
836
- const getChatCompletionSpy = vi.spyOn(chatService, 'getChatCompletion');
837
- const messages = [
838
- {
839
- role: 'user',
840
- content: 'https://vercel.com/ 请分析 chatGPT 关键词\n\n',
841
- sessionId: 'inbox',
842
- createdAt: 1702723964330,
843
- id: 'vyQvEw6V',
844
- updatedAt: 1702723964330,
845
- extra: {},
846
- meta: {
847
- avatar: DEFAULT_USER_AVATAR,
848
- },
849
- },
850
- ] as UIChatMessage[];
851
-
852
- await chatService.createAssistantMessage({
853
- messages,
854
- model: 'gpt-3.5-turbo-1106',
855
- top_p: 1,
856
- plugins: [DalleManifest.identifier],
857
- });
858
-
859
- // Assert that getChatCompletionSpy was called with the expected arguments
860
- expect(getChatCompletionSpy).toHaveBeenCalled();
861
-
862
- const calls = getChatCompletionSpy.mock.lastCall;
863
- // Take a snapshot of the first call's first argument
864
- expect(calls![0]).toMatchSnapshot();
865
- expect(calls![1]).toBeUndefined();
866
- });
867
828
  });
868
829
 
869
830
  describe('search functionality', () => {
@@ -248,36 +248,6 @@ describe('contextEngineering', () => {
248
248
  ]);
249
249
  });
250
250
 
251
- it('should inject INBOX_GUIDE_SYSTEM_ROLE for welcome questions in inbox session', async () => {
252
- // Don't mock INBOX_GUIDE_SYSTEMROLE, use the real one
253
- const messages: UIChatMessage[] = [
254
- {
255
- role: 'user',
256
- content: 'Hello, this is my first question',
257
- createdAt: Date.now(),
258
- id: 'test-welcome',
259
- meta: {},
260
- updatedAt: Date.now(),
261
- },
262
- ];
263
-
264
- const result = await contextEngineering({
265
- messages,
266
- model: 'gpt-4',
267
- provider: 'openai',
268
- isWelcomeQuestion: true,
269
- sessionId: 'inbox',
270
- });
271
-
272
- // Should have system message with inbox guide content
273
- const systemMessage = result.find((msg) => msg.role === 'system');
274
- expect(systemMessage).toBeDefined();
275
- // Check for characteristic content of the actual INBOX_GUIDE_SYSTEMROLE
276
- expect(systemMessage!.content).toContain('LobeChat Support Assistant');
277
- expect(systemMessage!.content).toContain('LobeHub');
278
- expect(Object.keys(systemMessage!).length).toEqual(2);
279
- });
280
-
281
251
  it('should inject historySummary into system message when provided', async () => {
282
252
  const historySummary = 'Previous conversation summary: User discussed AI topics.';
283
253
 
@@ -1,9 +1,8 @@
1
- import { INBOX_GUIDE_SYSTEMROLE, INBOX_SESSION_ID, isDesktop, isServerMode } from '@lobechat/const';
1
+ import { isDesktop, isServerMode } from '@lobechat/const';
2
2
  import {
3
3
  ContextEngine,
4
4
  HistorySummaryProvider,
5
5
  HistoryTruncateProcessor,
6
- InboxGuideProvider,
7
6
  InputTemplateProcessor,
8
7
  MessageCleanupProcessor,
9
8
  MessageContentProcessor,
@@ -48,8 +47,6 @@ export const contextEngineering = async ({
48
47
  enableHistoryCount,
49
48
  historyCount,
50
49
  historySummary,
51
- sessionId,
52
- isWelcomeQuestion,
53
50
  }: ContextEngineeringContext): Promise<OpenAIChatMessage[]> => {
54
51
  const toolNameResolver = new ToolNameResolver();
55
52
 
@@ -63,14 +60,6 @@ export const contextEngineering = async ({
63
60
  // 2. System role injection (agent's system role)
64
61
  new SystemRoleInjector({ systemRole }),
65
62
 
66
- // 3. Inbox guide system role injection
67
- new InboxGuideProvider({
68
- inboxGuideSystemRole: INBOX_GUIDE_SYSTEMROLE,
69
- inboxSessionId: INBOX_SESSION_ID,
70
- isWelcomeQuestion: isWelcomeQuestion,
71
- sessionId: sessionId,
72
- }),
73
-
74
63
  // 4. Tool system role injection
75
64
  new ToolSystemRoleProvider({
76
65
  getToolSystemRoles: (tools) => toolSelectors.enabledSystemRoles(tools)(getToolStoreState()),
@@ -66,7 +66,6 @@ interface FetchAITaskResultParams extends FetchSSEOptions {
66
66
  interface CreateAssistantMessageStream extends FetchSSEOptions {
67
67
  abortController?: AbortController;
68
68
  historySummary?: string;
69
- isWelcomeQuestion?: boolean;
70
69
  params: GetChatCompletionPayload;
71
70
  trace?: TracePayload;
72
71
  }
@@ -113,9 +112,7 @@ class ChatService {
113
112
  enableHistoryCount: agentChatConfigSelectors.enableHistoryCount(agentStoreState),
114
113
  // include user messages
115
114
  historyCount: agentChatConfigSelectors.historyCount(agentStoreState) + 2,
116
- historySummary: options?.historySummary,
117
115
  inputTemplate: chatConfig.inputTemplate,
118
- isWelcomeQuestion: options?.isWelcomeQuestion,
119
116
  messages,
120
117
  model: payload.model,
121
118
  provider: payload.provider!,
@@ -217,12 +214,10 @@ class ChatService {
217
214
  onErrorHandle,
218
215
  onFinish,
219
216
  trace,
220
- isWelcomeQuestion,
221
217
  historySummary,
222
218
  }: CreateAssistantMessageStream) => {
223
219
  await this.createAssistantMessage(params, {
224
220
  historySummary,
225
- isWelcomeQuestion,
226
221
  onAbort,
227
222
  onErrorHandle,
228
223
  onFinish,
@@ -404,7 +399,7 @@ class ChatService {
404
399
  onLoadingChange?.(true);
405
400
 
406
401
  try {
407
- const oaiMessages = await contextEngineering({
402
+ const llmMessages = await contextEngineering({
408
403
  messages: params.messages as any,
409
404
  model: params.model!,
410
405
  provider: params.provider!,
@@ -421,7 +416,7 @@ class ChatService {
421
416
  // remove plugins
422
417
  delete params.plugins;
423
418
  await this.getChatCompletion(
424
- { ...params, messages: oaiMessages, tools },
419
+ { ...params, messages: llmMessages, tools },
425
420
  {
426
421
  onErrorHandle: (error) => {
427
422
  errorHandle(new Error(error.message), error);
@@ -1,9 +1,9 @@
1
1
  import { TracePayload } from '@lobechat/types';
2
+
2
3
  import { FetchSSEOptions } from '@/utils/fetch';
3
4
 
4
5
  export interface FetchOptions extends FetchSSEOptions {
5
6
  historySummary?: string;
6
- isWelcomeQuestion?: boolean;
7
7
  signal?: AbortSignal | undefined;
8
8
  trace?: TracePayload;
9
9
  }
@@ -102,7 +102,7 @@ export class ClientService implements IMessageService {
102
102
  }
103
103
 
104
104
  // @ts-ignore
105
- async updateMessage(id: string, message: Partial<DB_Message>) {
105
+ async updateMessage(id: string, message: Partial<DB_Message>): Promise<any> {
106
106
  return MessageModel.update(id, message);
107
107
  }
108
108
 
@@ -45,6 +45,7 @@ export class ClientService extends BaseClientService implements IMessageService
45
45
  topicId,
46
46
  },
47
47
  {
48
+ groupAssistantMessages: false,
48
49
  postProcessUrl: this.postProcessUrl,
49
50
  },
50
51
  );
@@ -60,6 +61,7 @@ export class ClientService extends BaseClientService implements IMessageService
60
61
  topicId,
61
62
  },
62
63
  {
64
+ groupAssistantMessages: false,
63
65
  postProcessUrl: this.postProcessUrl,
64
66
  },
65
67
  );
@@ -99,8 +101,12 @@ export class ClientService extends BaseClientService implements IMessageService
99
101
  return this.messageModel.update(id, { error });
100
102
  };
101
103
 
102
- updateMessage: IMessageService['updateMessage'] = async (id, message) => {
103
- return this.messageModel.update(id, message);
104
+ updateMessage: IMessageService['updateMessage'] = async (id, message, options) => {
105
+ return this.messageModel.update(id, message, {
106
+ postProcessUrl: this.postProcessUrl,
107
+ sessionId: options?.sessionId,
108
+ topicId: options?.topicId,
109
+ });
104
110
  };
105
111
 
106
112
  updateMessageTTS: IMessageService['updateMessageTTS'] = async (id, tts) => {
@@ -81,8 +81,13 @@ export class ServerService implements IMessageService {
81
81
  return lambdaClient.message.updateMessagePlugin.mutate({ id, value: { arguments: args } });
82
82
  };
83
83
 
84
- updateMessage: IMessageService['updateMessage'] = async (id, value) => {
85
- return lambdaClient.message.update.mutate({ id, value });
84
+ updateMessage: IMessageService['updateMessage'] = async (id, value, options) => {
85
+ return lambdaClient.message.update.mutate({
86
+ id,
87
+ sessionId: options?.sessionId,
88
+ topicId: options?.topicId,
89
+ value,
90
+ });
86
91
  };
87
92
 
88
93
  updateMessageTranslate: IMessageService['updateMessageTranslate'] = async (id, translate) => {
@@ -10,6 +10,7 @@ import {
10
10
  UIChatMessage,
11
11
  UpdateMessageParams,
12
12
  UpdateMessageRAGParams,
13
+ UpdateMessageResult,
13
14
  } from '@lobechat/types';
14
15
  import type { HeatmapsProps } from '@lobehub/charts';
15
16
 
@@ -37,7 +38,11 @@ export interface IMessageService {
37
38
  rankModels(): Promise<ModelRankItem[]>;
38
39
  getHeatmaps(): Promise<HeatmapsProps['data']>;
39
40
  updateMessageError(id: string, error: ChatMessageError): Promise<any>;
40
- updateMessage(id: string, message: Partial<UpdateMessageParams>): Promise<any>;
41
+ updateMessage(
42
+ id: string,
43
+ message: Partial<UpdateMessageParams>,
44
+ options?: { sessionId?: string | null; topicId?: string | null },
45
+ ): Promise<UpdateMessageResult>;
41
46
  updateMessageTTS(id: string, tts: Partial<ChatTTS> | false): Promise<any>;
42
47
  updateMessageTranslate(id: string, translate: Partial<ChatTranslate> | false): Promise<any>;
43
48
  updateMessagePluginState(id: string, value: Record<string, any>): Promise<any>;
@@ -64,6 +64,105 @@ describe('chatHelpers', () => {
64
64
  const message = chatHelpers.getMessageById([], '1');
65
65
  expect(message).toBeUndefined();
66
66
  });
67
+
68
+ it('finds a block within a group message', () => {
69
+ const messagesWithGroup = [
70
+ { id: '1', content: 'Hello' },
71
+ {
72
+ id: 'group1',
73
+ role: 'group',
74
+ content: '',
75
+ children: [
76
+ { id: 'block1', content: 'First block' },
77
+ { id: 'block2', content: 'Second block' },
78
+ ],
79
+ },
80
+ ] as UIChatMessage[];
81
+
82
+ const block = chatHelpers.getMessageById(messagesWithGroup, 'block1');
83
+ expect(block).toBeDefined();
84
+ expect(block?.id).toBe('block1');
85
+ expect(block?.content).toBe('First block');
86
+ });
87
+
88
+ it('returns block with parentId set to group message id', () => {
89
+ const messagesWithGroup = [
90
+ {
91
+ id: 'group1',
92
+ role: 'group',
93
+ content: '',
94
+ children: [{ id: 'block1', content: 'Block content' }],
95
+ },
96
+ ] as UIChatMessage[];
97
+
98
+ const block = chatHelpers.getMessageById(messagesWithGroup, 'block1');
99
+ expect(block).toBeDefined();
100
+ expect(block?.parentId).toBe('group1');
101
+ });
102
+
103
+ it('searches across multiple group messages', () => {
104
+ const messagesWithGroups = [
105
+ {
106
+ id: 'group1',
107
+ role: 'group',
108
+ content: '',
109
+ children: [{ id: 'block1', content: 'First group block' }],
110
+ },
111
+ {
112
+ id: 'group2',
113
+ role: 'group',
114
+ content: '',
115
+ children: [{ id: 'block2', content: 'Second group block' }],
116
+ },
117
+ ] as UIChatMessage[];
118
+
119
+ const block = chatHelpers.getMessageById(messagesWithGroups, 'block2');
120
+ expect(block).toBeDefined();
121
+ expect(block?.id).toBe('block2');
122
+ expect(block?.parentId).toBe('group2');
123
+ expect(block?.content).toBe('Second group block');
124
+ });
125
+
126
+ it('prioritizes top-level message over block with same id', () => {
127
+ const messagesWithConflict = [
128
+ { id: 'duplicate', content: 'Top-level message', role: 'user' },
129
+ {
130
+ id: 'group1',
131
+ role: 'group',
132
+ content: '',
133
+ children: [{ id: 'duplicate', content: 'Block message' }],
134
+ },
135
+ ] as UIChatMessage[];
136
+
137
+ const message = chatHelpers.getMessageById(messagesWithConflict, 'duplicate');
138
+ expect(message).toBeDefined();
139
+ expect(message?.content).toBe('Top-level message');
140
+ expect(message?.role).toBe('user');
141
+ expect(message?.parentId).toBeUndefined();
142
+ });
143
+
144
+ it('returns undefined when block is not found in any group', () => {
145
+ const messagesWithGroup = [
146
+ {
147
+ id: 'group1',
148
+ role: 'group',
149
+ content: '',
150
+ children: [{ id: 'block1', content: 'Block content' }],
151
+ },
152
+ ] as UIChatMessage[];
153
+
154
+ const block = chatHelpers.getMessageById(messagesWithGroup, 'nonexistent');
155
+ expect(block).toBeUndefined();
156
+ });
157
+
158
+ it('handles group message without children', () => {
159
+ const messagesWithEmptyGroup = [
160
+ { id: 'group1', role: 'group', content: '' },
161
+ ] as UIChatMessage[];
162
+
163
+ const block = chatHelpers.getMessageById(messagesWithEmptyGroup, 'block1');
164
+ expect(block).toBeUndefined();
165
+ });
67
166
  });
68
167
 
69
168
  describe('getSlicedMessages', () => {
@@ -5,8 +5,27 @@ import { encodeAsync } from '@/utils/tokenizer';
5
5
  export const getMessagesTokenCount = async (messages: OpenAIChatMessage[]) =>
6
6
  encodeAsync(messages.map((m) => m.content).join(''));
7
7
 
8
- export const getMessageById = (messages: UIChatMessage[], id: string) =>
9
- messages.find((m) => m.id === id);
8
+ export const getMessageById = (
9
+ messages: UIChatMessage[],
10
+ id: string,
11
+ ): UIChatMessage | undefined => {
12
+ // First try to find in top-level messages
13
+ const directMatch = messages.find((m) => m.id === id);
14
+ if (directMatch) return directMatch;
15
+
16
+ // If not found, search in group message children (blocks)
17
+ for (const message of messages) {
18
+ if (message.role === 'group' && message.children) {
19
+ const blockMatch = message.children.find((block) => block.id === id);
20
+ if (blockMatch) {
21
+ // Return the block with parentId set to group message ID
22
+ return { ...blockMatch, parentId: message.id } as UIChatMessage;
23
+ }
24
+ }
25
+ }
26
+
27
+ return undefined;
28
+ };
10
29
 
11
30
  const getSlicedMessages = (
12
31
  messages: UIChatMessage[],
@@ -1,6 +1,6 @@
1
1
  export { aiChatSelectors } from './slices/aiChat/selectors';
2
2
  export { chatToolSelectors } from './slices/builtinTool/selectors';
3
- export { chatSelectors } from './slices/message/selectors';
3
+ export * from './slices/message/selectors';
4
4
  export * from './slices/portal/selectors';
5
5
  export { threadSelectors } from './slices/thread/selectors';
6
6
  export { topicSelectors } from './slices/topic/selectors';