@lobehub/chat 1.28.6 → 1.29.0

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 (87) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/locales/ar/chat.json +1 -0
  3. package/locales/ar/setting.json +7 -2
  4. package/locales/bg-BG/chat.json +1 -0
  5. package/locales/bg-BG/setting.json +7 -2
  6. package/locales/de-DE/chat.json +1 -0
  7. package/locales/de-DE/setting.json +7 -2
  8. package/locales/en-US/chat.json +1 -0
  9. package/locales/en-US/setting.json +7 -2
  10. package/locales/es-ES/chat.json +1 -0
  11. package/locales/es-ES/setting.json +7 -2
  12. package/locales/fa-IR/chat.json +1 -0
  13. package/locales/fa-IR/setting.json +7 -2
  14. package/locales/fr-FR/chat.json +1 -0
  15. package/locales/fr-FR/setting.json +7 -2
  16. package/locales/it-IT/chat.json +1 -0
  17. package/locales/it-IT/setting.json +7 -2
  18. package/locales/ja-JP/chat.json +1 -0
  19. package/locales/ja-JP/setting.json +7 -2
  20. package/locales/ko-KR/chat.json +1 -0
  21. package/locales/ko-KR/setting.json +7 -2
  22. package/locales/nl-NL/chat.json +1 -0
  23. package/locales/nl-NL/setting.json +7 -2
  24. package/locales/pl-PL/chat.json +1 -0
  25. package/locales/pl-PL/setting.json +7 -2
  26. package/locales/pt-BR/chat.json +1 -0
  27. package/locales/pt-BR/setting.json +7 -2
  28. package/locales/ru-RU/chat.json +1 -0
  29. package/locales/ru-RU/setting.json +7 -2
  30. package/locales/tr-TR/chat.json +1 -0
  31. package/locales/tr-TR/setting.json +7 -2
  32. package/locales/vi-VN/chat.json +1 -0
  33. package/locales/vi-VN/setting.json +7 -2
  34. package/locales/zh-CN/chat.json +1 -0
  35. package/locales/zh-CN/setting.json +7 -2
  36. package/locales/zh-TW/chat.json +1 -0
  37. package/locales/zh-TW/setting.json +7 -2
  38. package/package.json +1 -1
  39. package/src/app/(main)/settings/system-agent/index.tsx +1 -0
  40. package/src/chains/__tests__/__snapshots__/summaryHistory.test.ts.snap +21 -0
  41. package/src/chains/__tests__/summaryHistory.test.ts +24 -0
  42. package/src/chains/summaryHistory.ts +19 -0
  43. package/src/const/settings/agent.ts +3 -1
  44. package/src/const/settings/systemAgent.ts +1 -0
  45. package/src/database/client/models/__tests__/session.test.ts +0 -1
  46. package/src/database/server/migrations/0011_add_topic_history_summary.sql +2 -0
  47. package/src/database/server/migrations/meta/0011_snapshot.json +3196 -0
  48. package/src/database/server/migrations/meta/_journal.json +7 -0
  49. package/src/database/server/models/__tests__/topic.test.ts +4 -0
  50. package/src/database/server/models/topic.ts +16 -0
  51. package/src/database/server/schemas/lobechat/topic.ts +3 -2
  52. package/src/features/AgentSetting/AgentChat/index.tsx +4 -18
  53. package/src/features/ChatInput/ActionBar/History.tsx +24 -21
  54. package/src/features/ChatInput/ActionBar/Token/TokenTag.tsx +22 -7
  55. package/src/features/Conversation/Actions/index.ts +2 -2
  56. package/src/features/Conversation/components/ChatItem/index.tsx +6 -6
  57. package/src/features/Conversation/components/History/index.tsx +71 -0
  58. package/src/features/Conversation/types/index.tsx +1 -1
  59. package/src/locales/default/chat.ts +1 -0
  60. package/src/locales/default/setting.ts +7 -2
  61. package/src/prompts/chatMessages/index.test.ts +94 -0
  62. package/src/prompts/chatMessages/index.ts +11 -0
  63. package/src/prompts/systemRole/index.ts +22 -0
  64. package/src/server/routers/lambda/topic.ts +7 -0
  65. package/src/services/__tests__/chat.test.ts +13 -61
  66. package/src/services/chat.ts +45 -11
  67. package/src/store/agent/slices/chat/__snapshots__/selectors.test.ts.snap +3 -1
  68. package/src/store/chat/helpers.ts +6 -2
  69. package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +21 -8
  70. package/src/store/chat/slices/aiChat/actions/helpers.ts +9 -0
  71. package/src/store/chat/slices/aiChat/actions/index.ts +3 -1
  72. package/src/store/chat/slices/aiChat/actions/memory.ts +52 -0
  73. package/src/store/chat/slices/message/selectors.ts +1 -3
  74. package/src/store/chat/slices/topic/selectors.ts +24 -12
  75. package/src/store/chat/slices/{enchance → translate}/action.test.ts +0 -13
  76. package/src/store/chat/slices/{enchance → translate}/action.ts +5 -24
  77. package/src/store/chat/slices/tts/action.test.ts +63 -0
  78. package/src/store/chat/slices/tts/action.ts +35 -0
  79. package/src/store/chat/store.ts +6 -3
  80. package/src/store/file/reducers/uploadFileList.test.ts +197 -0
  81. package/src/store/user/slices/settings/selectors/__snapshots__/settings.test.ts.snap +3 -1
  82. package/src/store/user/slices/settings/selectors/systemAgent.ts +2 -0
  83. package/src/types/agent/index.ts +2 -4
  84. package/src/types/topic.ts +13 -0
  85. package/src/types/user/settings/systemAgent.ts +1 -0
  86. package/src/utils/tokenizer/client.ts +1 -1
  87. /package/src/features/Conversation/components/{ChatItem → History}/HistoryDivider.tsx +0 -0
@@ -28,6 +28,8 @@ import {
28
28
  import { AgentRuntime } from '@/libs/agent-runtime';
29
29
  import { useToolStore } from '@/store/tool';
30
30
  import { UserStore } from '@/store/user';
31
+ import { useUserStore } from '@/store/user';
32
+ import { modelConfigSelectors } from '@/store/user/selectors';
31
33
  import { UserSettingsState, initialSettingsState } from '@/store/user/slices/settings/initialState';
32
34
  import { DalleManifest } from '@/tools/dalle';
33
35
  import { ChatMessage } from '@/types/message';
@@ -35,8 +37,6 @@ import { ChatStreamPayload, type OpenAIChatMessage } from '@/types/openai/chat';
35
37
  import { LobeTool } from '@/types/tool';
36
38
 
37
39
  import { chatService, initializeWithClientStore } from '../chat';
38
- import { useUserStore } from '@/store/user';
39
- import {modelConfigSelectors} from "@/store/user/selectors";
40
40
 
41
41
  // Mocking external dependencies
42
42
  vi.mock('i18next', () => ({
@@ -150,8 +150,6 @@ describe('ChatService', () => {
150
150
  },
151
151
  ],
152
152
  }, // Message with files
153
- { content: 'Hi', role: 'tool', plugin: { identifier: 'plugin1', apiName: 'api1' } }, // Message with tool role
154
- { content: 'Hey', role: 'assistant' }, // Regular user message
155
153
  ] as ChatMessage[];
156
154
 
157
155
  const getChatCompletionSpy = vi.spyOn(chatService, 'getChatCompletion');
@@ -177,15 +175,6 @@ describe('ChatService', () => {
177
175
  ],
178
176
  role: 'user',
179
177
  },
180
- {
181
- content: 'Hi',
182
- name: 'plugin1____api1',
183
- role: 'tool',
184
- },
185
- {
186
- content: 'Hey',
187
- role: 'assistant',
188
- },
189
178
  ],
190
179
  model: 'gpt-4-vision-preview',
191
180
  },
@@ -196,7 +185,6 @@ describe('ChatService', () => {
196
185
  it('should not include image with vision models when can not find the image', async () => {
197
186
  const messages = [
198
187
  { content: 'Hello', role: 'user', files: ['file2'] }, // Message with files
199
- { content: 'Hi', role: 'tool', plugin: { identifier: 'plugin1', apiName: 'api1' } }, // Message with function role
200
188
  { content: 'Hey', role: 'assistant' }, // Regular user message
201
189
  ] as ChatMessage[];
202
190
 
@@ -207,7 +195,6 @@ describe('ChatService', () => {
207
195
  {
208
196
  messages: [
209
197
  { content: 'Hello', role: 'user' },
210
- { content: 'Hi', name: 'plugin1____api1', role: 'tool' },
211
198
  { content: 'Hey', role: 'assistant' },
212
199
  ],
213
200
  },
@@ -536,7 +523,9 @@ describe('ChatService', () => {
536
523
  };
537
524
 
538
525
  vi.spyOn(useUserStore, 'getState').mockImplementationOnce(() => mockUserStore as any);
539
- vi.spyOn(modelConfigSelectors, 'isProviderFetchOnClient').mockImplementationOnce(mockModelConfigSelectors.isProviderFetchOnClient);
526
+ vi.spyOn(modelConfigSelectors, 'isProviderFetchOnClient').mockImplementationOnce(
527
+ mockModelConfigSelectors.isProviderFetchOnClient,
528
+ );
540
529
 
541
530
  const params: Partial<ChatStreamPayload> = {
542
531
  model: 'test-model',
@@ -550,18 +539,15 @@ describe('ChatService', () => {
550
539
  ...params,
551
540
  };
552
541
 
553
- const result = await chatService.getChatCompletion(params,options);
542
+ const result = await chatService.getChatCompletion(params, options);
554
543
 
555
- expect(global.fetch).toHaveBeenCalledWith(
556
- expect.any(String),
557
- {
558
- body: JSON.stringify(expectedPayload),
559
- headers: expect.objectContaining({
560
- 'Content-Type': 'application/json',
561
- }),
562
- method: 'POST',
563
- },
564
- );
544
+ expect(global.fetch).toHaveBeenCalledWith(expect.any(String), {
545
+ body: JSON.stringify(expectedPayload),
546
+ headers: expect.objectContaining({
547
+ 'Content-Type': 'application/json',
548
+ }),
549
+ method: 'POST',
550
+ });
565
551
  expect(result.status).toBe(401);
566
552
  });
567
553
 
@@ -683,14 +669,6 @@ describe('ChatService', () => {
683
669
  id: 'tool_call_nXxXHW8Z',
684
670
  type: 'function',
685
671
  },
686
- {
687
- function: {
688
- arguments: '{"query":"LobeHub","searchEngines":["bilibili"]}',
689
- name: 'lobe-web-browsing____searchWithSearXNG____builtin',
690
- },
691
- id: 'tool_call_2f3CEKz9',
692
- type: 'function',
693
- },
694
672
  ],
695
673
  },
696
674
  {
@@ -749,14 +727,6 @@ describe('ChatService', () => {
749
727
  id: 'tool_call_nXxXHW8Z',
750
728
  type: 'function',
751
729
  },
752
- {
753
- function: {
754
- arguments: '{"query":"LobeHub","searchEngines":["bilibili"]}',
755
- name: 'lobe-web-browsing____searchWithSearXNG____builtin',
756
- },
757
- id: 'tool_call_2f3CEKz9',
758
- type: 'function',
759
- },
760
730
  ],
761
731
  },
762
732
  {
@@ -771,12 +741,6 @@ describe('ChatService', () => {
771
741
  role: 'tool',
772
742
  tool_call_id: 'tool_call_nXxXHW8Z',
773
743
  },
774
- {
775
- content: '[]',
776
- name: 'lobe-web-browsing____searchWithSearXNG____builtin',
777
- role: 'tool',
778
- tool_call_id: 'tool_call_2f3CEKz9',
779
- },
780
744
  {
781
745
  content: 'LobeHub 是一个专注于设计和开发现代人工智能生成内容(AIGC)工具和组件的团队。',
782
746
  role: 'assistant',
@@ -826,7 +790,6 @@ describe('ChatService', () => {
826
790
  },
827
791
  ],
828
792
  }, // Message with files
829
- { content: 'Hi', role: 'tool', plugin: { identifier: 'plugin1', apiName: 'api1' } }, // Message with tool role
830
793
  { content: 'Hey', role: 'assistant' }, // Regular user message
831
794
  ] as ChatMessage[];
832
795
 
@@ -861,11 +824,6 @@ describe('ChatService', () => {
861
824
  ],
862
825
  role: 'user',
863
826
  },
864
- {
865
- content: 'Hi',
866
- name: 'plugin1____api1',
867
- role: 'tool',
868
- },
869
827
  {
870
828
  content: 'Hey',
871
829
  role: 'assistant',
@@ -894,7 +852,6 @@ describe('ChatService', () => {
894
852
  },
895
853
  ],
896
854
  }, // Message with files
897
- { content: 'Hi', role: 'tool', plugin: { identifier: 'plugin1', apiName: 'api1' } }, // Message with tool role
898
855
  { content: 'Hey', role: 'assistant' }, // Regular user message
899
856
  ] as ChatMessage[];
900
857
 
@@ -929,11 +886,6 @@ describe('ChatService', () => {
929
886
  ],
930
887
  role: 'user',
931
888
  },
932
- {
933
- content: 'Hi',
934
- name: 'plugin1____api1',
935
- role: 'tool',
936
- },
937
889
  {
938
890
  content: 'Hey',
939
891
  role: 'assistant',
@@ -8,8 +8,14 @@ import { INBOX_SESSION_ID } from '@/const/session';
8
8
  import { DEFAULT_AGENT_CONFIG } from '@/const/settings';
9
9
  import { TracePayload, TraceTagMap } from '@/const/trace';
10
10
  import { isServerMode } from '@/const/version';
11
- import {AgentRuntime, AgentRuntimeError, ChatCompletionErrorPayload, ModelProvider} from '@/libs/agent-runtime';
11
+ import {
12
+ AgentRuntime,
13
+ AgentRuntimeError,
14
+ ChatCompletionErrorPayload,
15
+ ModelProvider,
16
+ } from '@/libs/agent-runtime';
12
17
  import { filesPrompts } from '@/prompts/files';
18
+ import { BuiltinSystemRolePrompts } from '@/prompts/systemRole';
13
19
  import { useSessionStore } from '@/store/session';
14
20
  import { sessionMetaSelectors } from '@/store/session/selectors';
15
21
  import { useToolStore } from '@/store/tool';
@@ -34,6 +40,7 @@ import { createHeaderWithAuth, getProviderAuthPayload } from './_auth';
34
40
  import { API_ENDPOINTS } from './_url';
35
41
 
36
42
  interface FetchOptions extends FetchSSEOptions {
43
+ historySummary?: string;
37
44
  isWelcomeQuestion?: boolean;
38
45
  signal?: AbortSignal | undefined;
39
46
  trace?: TracePayload;
@@ -60,6 +67,7 @@ interface FetchAITaskResultParams extends FetchSSEOptions {
60
67
 
61
68
  interface CreateAssistantMessageStream extends FetchSSEOptions {
62
69
  abortController?: AbortController;
70
+ historySummary?: string;
63
71
  isWelcomeQuestion?: boolean;
64
72
  params: GetChatCompletionPayload;
65
73
  trace?: TracePayload;
@@ -234,8 +242,10 @@ class ChatService {
234
242
  onFinish,
235
243
  trace,
236
244
  isWelcomeQuestion,
245
+ historySummary,
237
246
  }: CreateAssistantMessageStream) => {
238
247
  await this.createAssistantMessage(params, {
248
+ historySummary,
239
249
  isWelcomeQuestion,
240
250
  onAbort,
241
251
  onErrorHandle,
@@ -463,7 +473,7 @@ class ChatService {
463
473
  }
464
474
 
465
475
  default: {
466
- return { content: m.content, role: m.role };
476
+ return { content: m.content, role: m.role as any };
467
477
  }
468
478
  }
469
479
  });
@@ -483,9 +493,11 @@ class ChatService {
483
493
  const toolsSystemRoles =
484
494
  hasFC && toolSelectors.enabledSystemRoles(tools)(useToolStore.getState());
485
495
 
486
- const injectSystemRoles = [inboxGuideSystemRole, toolsSystemRoles]
487
- .filter(Boolean)
488
- .join('\n\n');
496
+ const injectSystemRoles = BuiltinSystemRolePrompts({
497
+ historySummary: options?.historySummary,
498
+ plugins: toolsSystemRoles as string,
499
+ welcome: inboxGuideSystemRole as string,
500
+ });
489
501
 
490
502
  if (!injectSystemRoles) return;
491
503
 
@@ -549,18 +561,40 @@ class ChatService {
549
561
  * see https://github.com/lobehub/lobe-chat/pull/3155
550
562
  */
551
563
  private reorderToolMessages = (messages: OpenAIChatMessage[]): OpenAIChatMessage[] => {
552
- const reorderedMessages: OpenAIChatMessage[] = [];
553
- const toolMessages: Record<string, OpenAIChatMessage> = {};
564
+ // 1. 先收集所有 assistant 消息中的有效 tool_call_id
565
+ const validToolCallIds = new Set<string>();
566
+ messages.forEach((message) => {
567
+ if (message.role === 'assistant' && message.tool_calls) {
568
+ message.tool_calls.forEach((toolCall) => {
569
+ validToolCallIds.add(toolCall.id);
570
+ });
571
+ }
572
+ });
554
573
 
555
- // 1. collect all tool messages
574
+ // 2. 收集所有有效的 tool 消息
575
+ const toolMessages: Record<string, OpenAIChatMessage> = {};
556
576
  messages.forEach((message) => {
557
- if (message.role === 'tool' && message.tool_call_id) {
577
+ if (
578
+ message.role === 'tool' &&
579
+ message.tool_call_id &&
580
+ validToolCallIds.has(message.tool_call_id)
581
+ ) {
558
582
  toolMessages[message.tool_call_id] = message;
559
583
  }
560
584
  });
561
585
 
562
- // 2. reorder messages
586
+ // 3. 重新排序消息
587
+ const reorderedMessages: OpenAIChatMessage[] = [];
563
588
  messages.forEach((message) => {
589
+ // 跳过无效的 tool 消息
590
+ if (
591
+ message.role === 'tool' &&
592
+ (!message.tool_call_id || !validToolCallIds.has(message.tool_call_id))
593
+ ) {
594
+ return;
595
+ }
596
+
597
+ // 检查是否已经添加过该 tool 消息
564
598
  const hasPushed = reorderedMessages.some(
565
599
  (m) => !!message.tool_call_id && m.tool_call_id === message.tool_call_id,
566
600
  );
@@ -569,12 +603,12 @@ class ChatService {
569
603
 
570
604
  reorderedMessages.push(message);
571
605
 
606
+ // 如果是 assistant 消息且有 tool_calls,添加对应的 tool 消息
572
607
  if (message.role === 'assistant' && message.tool_calls) {
573
608
  message.tool_calls.forEach((toolCall) => {
574
609
  const correspondingToolMessage = toolMessages[toolCall.id];
575
610
  if (correspondingToolMessage) {
576
611
  reorderedMessages.push(correspondingToolMessage);
577
- // 从 toolMessages 中删除已处理的消息,避免重复
578
612
  delete toolMessages[toolCall.id];
579
613
  }
580
614
  });
@@ -6,7 +6,9 @@ exports[`agentSelectors > defaultAgentConfig > should merge DEFAULT_AGENT_CONFIG
6
6
  "autoCreateTopicThreshold": 2,
7
7
  "displayMode": "chat",
8
8
  "enableAutoCreateTopic": true,
9
- "historyCount": 1,
9
+ "enableCompressHistory": true,
10
+ "enableHistoryCount": true,
11
+ "historyCount": 8,
10
12
  },
11
13
  "model": "gpt-3.5-turbo",
12
14
  "params": {
@@ -12,15 +12,19 @@ export const getMessageById = (messages: ChatMessage[], id: string) =>
12
12
  const getSlicedMessagesWithConfig = (
13
13
  messages: ChatMessage[],
14
14
  config: LobeAgentChatConfig,
15
+ includeNewUserMessage?: boolean,
15
16
  ): ChatMessage[] => {
16
17
  // if historyCount is not enabled or set to 0, return all messages
17
18
  if (!config.enableHistoryCount || !config.historyCount) return messages;
18
19
 
20
+ // if user send message, history will include this message so the total length should +1
21
+ const messagesCount = !!includeNewUserMessage ? config.historyCount + 1 : config.historyCount;
22
+
19
23
  // if historyCount is negative, return empty array
20
- if (config.historyCount <= 0) return [];
24
+ if (messagesCount <= 0) return [];
21
25
 
22
26
  // if historyCount is positive, return last N messages
23
- return messages.slice(-config.historyCount);
27
+ return messages.slice(-messagesCount);
24
28
  };
25
29
 
26
30
  export const chatHelpers = {
@@ -5,13 +5,13 @@ import { template } from 'lodash-es';
5
5
  import { StateCreator } from 'zustand/vanilla';
6
6
 
7
7
  import { LOADING_FLAT, MESSAGE_CANCEL_FLAT } from '@/const/message';
8
+ import { DEFAULT_AGENT_CHAT_CONFIG } from '@/const/settings';
8
9
  import { TraceEventType, TraceNameMap } from '@/const/trace';
9
10
  import { isServerMode } from '@/const/version';
10
11
  import { knowledgeBaseQAPrompts } from '@/prompts/knowledgeBaseQA';
11
12
  import { chatService } from '@/services/chat';
12
13
  import { messageService } from '@/services/message';
13
14
  import { useAgentStore } from '@/store/agent';
14
- import { agentSelectors } from '@/store/agent/selectors';
15
15
  import { chatHelpers } from '@/store/chat/helpers';
16
16
  import { ChatStore } from '@/store/chat/store';
17
17
  import { messageMapKey } from '@/store/chat/utils/messageMapKey';
@@ -21,6 +21,7 @@ import { MessageSemanticSearchChunk } from '@/types/rag';
21
21
  import { setNamespace } from '@/utils/storeDebug';
22
22
 
23
23
  import { chatSelectors, topicSelectors } from '../../../selectors';
24
+ import { getAgentChatConfig, getAgentConfig, getAgentKnowledge } from './helpers';
24
25
 
25
26
  const n = setNamespace('ai');
26
27
 
@@ -93,10 +94,6 @@ export interface AIGenerateAction {
93
94
  internal_toggleToolCallingStreaming: (id: string, streaming: boolean[] | undefined) => void;
94
95
  }
95
96
 
96
- const getAgentConfig = () => agentSelectors.currentAgentConfig(useAgentStore.getState());
97
- const getAgentChatConfig = () => agentSelectors.currentAgentChatConfig(useAgentStore.getState());
98
- const getAgentKnowledge = () => agentSelectors.currentEnabledKnowledge(useAgentStore.getState());
99
-
100
97
  export const generateAIChat: StateCreator<
101
98
  ChatStore,
102
99
  [['zustand/devtools', never]],
@@ -111,7 +108,7 @@ export const generateAIChat: StateCreator<
111
108
  // trace the delete and regenerate message
112
109
  get().internal_traceMessage(id, { eventType: TraceEventType.DeleteAndRegenerateMessage });
113
110
  },
114
- regenerateMessage: async (id: string) => {
111
+ regenerateMessage: async (id) => {
115
112
  const traceId = chatSelectors.getTraceIdByMessageId(id)(get());
116
113
  await get().internal_resendMessage(id, traceId);
117
114
 
@@ -265,7 +262,7 @@ export const generateAIChat: StateCreator<
265
262
  // create a new array to avoid the original messages array change
266
263
  const messages = [...originalMessages];
267
264
 
268
- const { model, provider } = getAgentConfig();
265
+ const { model, provider, chatConfig } = getAgentConfig();
269
266
 
270
267
  let fileChunks: MessageSemanticSearchChunk[] | undefined;
271
268
  let ragQueryId;
@@ -325,6 +322,20 @@ export const generateAIChat: StateCreator<
325
322
  await refreshMessages();
326
323
  await triggerToolCalls(assistantId);
327
324
  }
325
+
326
+ // 5. summary history if context messages is larger than historyCount
327
+ const historyCount =
328
+ chatConfig.historyCount || (DEFAULT_AGENT_CHAT_CONFIG.historyCount as number);
329
+
330
+ if (
331
+ chatConfig.enableHistoryCount &&
332
+ chatConfig.enableCompressHistory &&
333
+ originalMessages.length > historyCount
334
+ ) {
335
+ const historyMessages = originalMessages.slice(0, -historyCount);
336
+
337
+ await get().internal_summaryHistory(historyMessages);
338
+ }
328
339
  },
329
340
  internal_fetchAIChatMessage: async (messages, assistantId, params) => {
330
341
  const {
@@ -351,7 +362,7 @@ export const generateAIChat: StateCreator<
351
362
  // ================================== //
352
363
 
353
364
  // 1. slice messages with config
354
- let preprocessMsgs = chatHelpers.getSlicedMessagesWithConfig(messages, chatConfig);
365
+ let preprocessMsgs = chatHelpers.getSlicedMessagesWithConfig(messages, chatConfig, true);
355
366
 
356
367
  // 2. replace inputMessage template
357
368
  preprocessMsgs = !chatConfig.inputTemplate
@@ -394,6 +405,7 @@ export const generateAIChat: StateCreator<
394
405
  let msgTraceId: string | undefined;
395
406
  let output = '';
396
407
 
408
+ const historySummary = topicSelectors.currentActiveTopicSummary(get());
397
409
  await chatService.createAssistantMessageStream({
398
410
  abortController,
399
411
  params: {
@@ -403,6 +415,7 @@ export const generateAIChat: StateCreator<
403
415
  ...agentConfig.params,
404
416
  plugins: agentConfig.plugins,
405
417
  },
418
+ historySummary: historySummary?.content,
406
419
  trace: {
407
420
  traceId: params?.traceId,
408
421
  sessionId: get().activeId,
@@ -0,0 +1,9 @@
1
+ import { useAgentStore } from '@/store/agent';
2
+ import { agentSelectors } from '@/store/agent/selectors';
3
+
4
+ export const getAgentConfig = () => agentSelectors.currentAgentConfig(useAgentStore.getState());
5
+ export const getAgentChatConfig = () =>
6
+ agentSelectors.currentAgentChatConfig(useAgentStore.getState());
7
+
8
+ export const getAgentKnowledge = () =>
9
+ agentSelectors.currentEnabledKnowledge(useAgentStore.getState());
@@ -3,9 +3,10 @@ import { StateCreator } from 'zustand/vanilla';
3
3
  import { ChatStore } from '@/store/chat/store';
4
4
 
5
5
  import { AIGenerateAction, generateAIChat } from './generateAIChat';
6
+ import { ChatMemoryAction, chatMemory } from './memory';
6
7
  import { ChatRAGAction, chatRag } from './rag';
7
8
 
8
- export interface ChatAIChatAction extends ChatRAGAction, AIGenerateAction {
9
+ export interface ChatAIChatAction extends ChatRAGAction, ChatMemoryAction, AIGenerateAction {
9
10
  /**/
10
11
  }
11
12
 
@@ -17,4 +18,5 @@ export const chatAiChat: StateCreator<
17
18
  > = (...params) => ({
18
19
  ...chatRag(...params),
19
20
  ...generateAIChat(...params),
21
+ ...chatMemory(...params),
20
22
  });
@@ -0,0 +1,52 @@
1
+ import { StateCreator } from 'zustand/vanilla';
2
+
3
+ import { chainSummaryHistory } from '@/chains/summaryHistory';
4
+ import { chatService } from '@/services/chat';
5
+ import { topicService } from '@/services/topic';
6
+ import { ChatStore } from '@/store/chat';
7
+ import { useUserStore } from '@/store/user';
8
+ import { systemAgentSelectors } from '@/store/user/selectors';
9
+ import { ChatMessage } from '@/types/message';
10
+
11
+ import { getAgentConfig } from './helpers';
12
+
13
+ export interface ChatMemoryAction {
14
+ internal_summaryHistory: (messages: ChatMessage[]) => Promise<void>;
15
+ }
16
+
17
+ export const chatMemory: StateCreator<
18
+ ChatStore,
19
+ [['zustand/devtools', never]],
20
+ [],
21
+ ChatMemoryAction
22
+ > = (set, get) => ({
23
+ internal_summaryHistory: async (messages) => {
24
+ const topicId = get().activeTopicId;
25
+ if (messages.length <= 1 || !topicId) return;
26
+
27
+ const { model, provider } = getAgentConfig();
28
+
29
+ const historyCompressConfig = systemAgentSelectors.historyCompress(useUserStore.getState());
30
+
31
+ let historySummary = '';
32
+ await chatService.fetchPresetTaskResult({
33
+ onFinish: async (text) => {
34
+ historySummary = text;
35
+ },
36
+
37
+ params: {
38
+ ...chainSummaryHistory(messages),
39
+ model: historyCompressConfig.model,
40
+ provider: historyCompressConfig.provider,
41
+ stream: false,
42
+ },
43
+ });
44
+
45
+ await topicService.updateTopic(topicId, {
46
+ metadata: { model, provider },
47
+ summary: historySummary,
48
+ });
49
+ await get().refreshTopic();
50
+ await get().refreshMessages();
51
+ },
52
+ });
@@ -75,9 +75,7 @@ const showInboxWelcome = (s: ChatStoreState): boolean => {
75
75
  if (!isInbox) return false;
76
76
 
77
77
  const data = currentChats(s);
78
- const isBrandNewChat = data.length === 0;
79
-
80
- return isBrandNewChat;
78
+ return data.length === 0;
81
79
  };
82
80
 
83
81
  // Custom message for new assistant initialization
@@ -1,36 +1,47 @@
1
1
  import { t } from 'i18next';
2
2
 
3
- import { ChatTopic, GroupedTopic } from '@/types/topic';
3
+ import { ChatTopic, ChatTopicSummary, GroupedTopic } from '@/types/topic';
4
4
  import { groupTopicsByTime } from '@/utils/client/topic';
5
5
 
6
- import { ChatStore } from '../../store';
6
+ import { ChatStoreState } from '../../initialState';
7
7
 
8
- const currentTopics = (s: ChatStore): ChatTopic[] | undefined => s.topicMaps[s.activeId];
8
+ const currentTopics = (s: ChatStoreState): ChatTopic[] | undefined => s.topicMaps[s.activeId];
9
9
 
10
- const currentActiveTopic = (s: ChatStore): ChatTopic | undefined => {
10
+ const currentActiveTopic = (s: ChatStoreState): ChatTopic | undefined => {
11
11
  return currentTopics(s)?.find((topic) => topic.id === s.activeTopicId);
12
12
  };
13
- const searchTopics = (s: ChatStore): ChatTopic[] => s.searchTopics;
13
+ const searchTopics = (s: ChatStoreState): ChatTopic[] => s.searchTopics;
14
14
 
15
- const displayTopics = (s: ChatStore): ChatTopic[] | undefined =>
15
+ const displayTopics = (s: ChatStoreState): ChatTopic[] | undefined =>
16
16
  s.isSearchingTopic ? searchTopics(s) : currentTopics(s);
17
17
 
18
- const currentFavTopics = (s: ChatStore): ChatTopic[] =>
18
+ const currentFavTopics = (s: ChatStoreState): ChatTopic[] =>
19
19
  currentTopics(s)?.filter((s) => s.favorite) || [];
20
20
 
21
- const currentUnFavTopics = (s: ChatStore): ChatTopic[] =>
21
+ const currentUnFavTopics = (s: ChatStoreState): ChatTopic[] =>
22
22
  currentTopics(s)?.filter((s) => !s.favorite) || [];
23
23
 
24
- const currentTopicLength = (s: ChatStore): number => currentTopics(s)?.length || 0;
24
+ const currentTopicLength = (s: ChatStoreState): number => currentTopics(s)?.length || 0;
25
25
 
26
26
  const getTopicById =
27
27
  (id: string) =>
28
- (s: ChatStore): ChatTopic | undefined =>
28
+ (s: ChatStoreState): ChatTopic | undefined =>
29
29
  currentTopics(s)?.find((topic) => topic.id === id);
30
30
 
31
- const isCreatingTopic = (s: ChatStore) => s.creatingTopic;
31
+ const currentActiveTopicSummary = (s: ChatStoreState): ChatTopicSummary | undefined => {
32
+ const activeTopic = currentActiveTopic(s);
33
+ if (!activeTopic) return undefined;
32
34
 
33
- const groupedTopicsSelector = (s: ChatStore): GroupedTopic[] => {
35
+ return {
36
+ content: activeTopic.summary || '',
37
+ model: activeTopic.metadata?.model || '',
38
+ provider: activeTopic.metadata?.provider || '',
39
+ };
40
+ };
41
+
42
+ const isCreatingTopic = (s: ChatStoreState) => s.creatingTopic;
43
+
44
+ const groupedTopicsSelector = (s: ChatStoreState): GroupedTopic[] => {
34
45
  const topics = currentTopics(s);
35
46
 
36
47
  if (!topics) return [];
@@ -51,6 +62,7 @@ const groupedTopicsSelector = (s: ChatStore): GroupedTopic[] => {
51
62
 
52
63
  export const topicSelectors = {
53
64
  currentActiveTopic,
65
+ currentActiveTopicSummary,
54
66
  currentTopicLength,
55
67
  currentTopics,
56
68
  currentUnFavTopics,
@@ -52,19 +52,6 @@ afterEach(() => {
52
52
  });
53
53
 
54
54
  describe('ChatEnhanceAction', () => {
55
- describe('clearTTS', () => {
56
- it('should clear TTS for a message and refresh messages', async () => {
57
- const { result } = renderHook(() => useChatStore());
58
- const messageId = 'message-id';
59
-
60
- await act(async () => {
61
- await result.current.clearTTS(messageId);
62
- });
63
-
64
- expect(messageService.updateMessageTTS).toHaveBeenCalledWith(messageId, false);
65
- });
66
- });
67
-
68
55
  describe('translateMessage', () => {
69
56
  it('should translate a message to the target language and refresh messages', async () => {
70
57
  const { result } = renderHook(() => useChatStore());
@@ -11,38 +11,28 @@ import { chatSelectors } from '@/store/chat/selectors';
11
11
  import { ChatStore } from '@/store/chat/store';
12
12
  import { useUserStore } from '@/store/user';
13
13
  import { systemAgentSelectors } from '@/store/user/selectors';
14
- import { ChatTTS, ChatTranslate } from '@/types/message';
14
+ import { ChatTranslate } from '@/types/message';
15
15
  import { merge } from '@/utils/merge';
16
16
  import { setNamespace } from '@/utils/storeDebug';
17
17
 
18
18
  const n = setNamespace('enhance');
19
19
 
20
20
  /**
21
- * enhance chat action like translate,tts
21
+ * chat translate
22
22
  */
23
- export interface ChatEnhanceAction {
24
- clearTTS: (id: string) => Promise<void>;
23
+ export interface ChatTranslateAction {
25
24
  clearTranslate: (id: string) => Promise<void>;
26
25
  getCurrentTracePayload: (data: Partial<TracePayload>) => TracePayload;
27
26
  translateMessage: (id: string, targetLang: string) => Promise<void>;
28
- ttsMessage: (
29
- id: string,
30
- state?: { contentMd5?: string; file?: string; voice?: string },
31
- ) => Promise<void>;
32
- updateMessageTTS: (id: string, data: Partial<ChatTTS> | false) => Promise<void>;
33
27
  updateMessageTranslate: (id: string, data: Partial<ChatTranslate> | false) => Promise<void>;
34
28
  }
35
29
 
36
- export const chatEnhance: StateCreator<
30
+ export const chatTranslate: StateCreator<
37
31
  ChatStore,
38
32
  [['zustand/devtools', never]],
39
33
  [],
40
- ChatEnhanceAction
34
+ ChatTranslateAction
41
35
  > = (set, get) => ({
42
- clearTTS: async (id) => {
43
- await get().updateMessageTTS(id, false);
44
- },
45
-
46
36
  clearTranslate: async (id) => {
47
37
  await get().updateMessageTranslate(id, false);
48
38
  },
@@ -107,15 +97,6 @@ export const chatEnhance: StateCreator<
107
97
  });
108
98
  },
109
99
 
110
- ttsMessage: async (id, state = {}) => {
111
- await get().updateMessageTTS(id, state);
112
- },
113
-
114
- updateMessageTTS: async (id, data) => {
115
- await messageService.updateMessageTTS(id, data);
116
- await get().refreshMessages();
117
- },
118
-
119
100
  updateMessageTranslate: async (id, data) => {
120
101
  await messageService.updateMessageTranslate(id, data);
121
102