@lobehub/chat 1.0.12 → 1.0.14

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 (123) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +16 -14
  3. package/README.zh-CN.md +16 -14
  4. package/docs/self-hosting/advanced/feature-flags.zh-CN.mdx +2 -1
  5. package/docs/self-hosting/advanced/model-list.zh-CN.mdx +1 -0
  6. package/docs/self-hosting/advanced/server-database.mdx +4 -3
  7. package/docs/self-hosting/advanced/server-database.zh-CN.mdx +1 -0
  8. package/docs/self-hosting/advanced/settings-url-share.mdx +5 -4
  9. package/docs/self-hosting/advanced/settings-url-share.zh-CN.mdx +5 -4
  10. package/docs/self-hosting/advanced/sso-providers/zitadel.mdx +3 -3
  11. package/docs/self-hosting/advanced/sso-providers/zitadel.zh-CN.mdx +2 -1
  12. package/docs/self-hosting/advanced/upstream-sync.zh-CN.mdx +10 -10
  13. package/docs/self-hosting/examples/ollama.mdx +23 -23
  14. package/docs/self-hosting/examples/ollama.zh-CN.mdx +23 -24
  15. package/docs/self-hosting/platform/docker-compose.zh-CN.mdx +2 -3
  16. package/docs/self-hosting/platform/vercel.mdx +2 -2
  17. package/docs/self-hosting/platform/vercel.zh-CN.mdx +2 -1
  18. package/docs/self-hosting/platform/zeabur.mdx +6 -7
  19. package/docs/self-hosting/platform/zeabur.zh-CN.mdx +6 -7
  20. package/docs/usage/agents/prompt.mdx +1 -1
  21. package/docs/usage/agents/prompt.zh-CN.mdx +1 -1
  22. package/docs/usage/features/auth.mdx +13 -8
  23. package/docs/usage/features/auth.zh-CN.mdx +5 -3
  24. package/docs/usage/features/database.mdx +20 -14
  25. package/docs/usage/features/database.zh-CN.mdx +1 -1
  26. package/docs/usage/features/local-llm.mdx +5 -0
  27. package/docs/usage/features/local-llm.zh-CN.mdx +5 -0
  28. package/docs/usage/features/multi-ai-providers.mdx +5 -0
  29. package/docs/usage/features/multi-ai-providers.zh-CN.mdx +5 -0
  30. package/docs/usage/features/plugin-system.zh-CN.mdx +2 -1
  31. package/docs/usage/features/pwa.mdx +3 -2
  32. package/docs/usage/features/pwa.zh-CN.mdx +3 -2
  33. package/docs/usage/providers/01ai.mdx +86 -0
  34. package/docs/usage/providers/01ai.zh-CN.mdx +85 -0
  35. package/docs/usage/providers/anthropic.mdx +79 -0
  36. package/docs/usage/providers/anthropic.zh-CN.mdx +74 -0
  37. package/docs/usage/providers/azure.mdx +89 -0
  38. package/docs/usage/providers/azure.zh-CN.mdx +82 -0
  39. package/docs/usage/providers/bedrock.mdx +140 -0
  40. package/docs/usage/providers/bedrock.zh-CN.mdx +135 -0
  41. package/docs/usage/providers/deepseek.mdx +91 -0
  42. package/docs/usage/providers/deepseek.zh-CN.mdx +86 -0
  43. package/docs/usage/providers/gemini.mdx +83 -0
  44. package/docs/usage/providers/gemini.zh-CN.mdx +80 -0
  45. package/docs/usage/providers/groq.mdx +1 -3
  46. package/docs/usage/providers/groq.zh-CN.mdx +1 -1
  47. package/docs/usage/providers/minimax.mdx +89 -0
  48. package/docs/usage/providers/minimax.zh-CN.mdx +85 -0
  49. package/docs/usage/providers/mistral.mdx +71 -0
  50. package/docs/usage/providers/mistral.zh-CN.mdx +66 -0
  51. package/docs/usage/providers/moonshot.mdx +70 -0
  52. package/docs/usage/providers/moonshot.zh-CN.mdx +66 -0
  53. package/docs/usage/providers/ollama/gemma.mdx +1 -1
  54. package/docs/usage/providers/ollama/gemma.zh-CN.mdx +1 -1
  55. package/docs/usage/providers/ollama/qwen.mdx +1 -1
  56. package/docs/usage/providers/ollama/qwen.zh-CN.mdx +1 -1
  57. package/docs/usage/providers/ollama.mdx +1 -1
  58. package/docs/usage/providers/ollama.zh-CN.mdx +1 -1
  59. package/docs/usage/providers/openai.mdx +95 -0
  60. package/docs/usage/providers/openai.zh-CN.mdx +87 -0
  61. package/docs/usage/providers/openrouter.mdx +111 -0
  62. package/docs/usage/providers/openrouter.zh-CN.mdx +109 -0
  63. package/docs/usage/providers/perplexity.mdx +64 -0
  64. package/docs/usage/providers/perplexity.zh-CN.mdx +61 -0
  65. package/docs/usage/providers/qwen.mdx +93 -0
  66. package/docs/usage/providers/qwen.zh-CN.mdx +86 -0
  67. package/docs/usage/providers/stepfun.mdx +69 -0
  68. package/docs/usage/providers/stepfun.zh-CN.mdx +64 -0
  69. package/docs/usage/providers/togetherai.mdx +74 -0
  70. package/docs/usage/providers/togetherai.zh-CN.mdx +71 -0
  71. package/docs/usage/providers/zhipu.mdx +69 -0
  72. package/docs/usage/providers/zhipu.zh-CN.mdx +64 -0
  73. package/docs/usage/providers.mdx +36 -0
  74. package/docs/usage/providers.zh-CN.mdx +34 -0
  75. package/docs/usage/start.mdx +2 -0
  76. package/docs/usage/start.zh-CN.mdx +2 -0
  77. package/locales/ar/setting.json +1 -0
  78. package/locales/bg-BG/setting.json +1 -0
  79. package/locales/de-DE/setting.json +1 -0
  80. package/locales/en-US/setting.json +1 -0
  81. package/locales/es-ES/setting.json +1 -0
  82. package/locales/fr-FR/setting.json +1 -0
  83. package/locales/it-IT/setting.json +1 -0
  84. package/locales/ja-JP/setting.json +1 -0
  85. package/locales/ko-KR/setting.json +1 -0
  86. package/locales/nl-NL/setting.json +1 -0
  87. package/locales/pl-PL/setting.json +1 -0
  88. package/locales/pt-BR/setting.json +1 -0
  89. package/locales/ru-RU/setting.json +1 -0
  90. package/locales/tr-TR/setting.json +1 -0
  91. package/locales/vi-VN/setting.json +1 -0
  92. package/locales/zh-CN/setting.json +1 -0
  93. package/locales/zh-TW/setting.json +1 -0
  94. package/package.json +9 -7
  95. package/src/app/(main)/settings/llm/ProviderList/providers.tsx +33 -4
  96. package/src/app/(main)/settings/llm/components/ProviderConfig/index.tsx +41 -11
  97. package/src/app/(main)/settings/llm/index.tsx +1 -2
  98. package/src/features/ChatInput/useSend.ts +2 -1
  99. package/src/features/Conversation/Error/ErrorJsonViewer.tsx +1 -1
  100. package/src/features/Conversation/Messages/Assistant/ToolCalls/index.tsx +4 -3
  101. package/src/features/Conversation/Messages/Tool/index.tsx +3 -5
  102. package/src/features/Conversation/Messages/components/Arguments.tsx +22 -0
  103. package/src/features/Conversation/Messages/hooks/useYamlArguments.ts +14 -0
  104. package/src/libs/agent-runtime/anthropic/index.test.ts +1 -31
  105. package/src/libs/agent-runtime/anthropic/index.ts +9 -63
  106. package/src/libs/agent-runtime/openai/index.test.ts +7 -7
  107. package/src/libs/agent-runtime/openai/index.ts +0 -6
  108. package/src/libs/agent-runtime/utils/anthropicHelpers.ts +2 -2
  109. package/src/libs/agent-runtime/utils/streams/anthropic.test.ts +102 -28
  110. package/src/libs/agent-runtime/utils/streams/anthropic.ts +29 -12
  111. package/src/libs/agent-runtime/utils/streams/protocol.ts +6 -1
  112. package/src/locales/default/setting.ts +1 -0
  113. package/src/services/message/server.ts +4 -0
  114. package/src/services/message/type.ts +1 -1
  115. package/src/store/chat/slices/message/action.ts +48 -48
  116. package/src/store/chat/slices/message/initialState.ts +2 -0
  117. package/src/store/chat/slices/message/selectors.ts +3 -0
  118. package/src/store/chat/slices/plugin/action.test.ts +20 -20
  119. package/src/store/chat/slices/plugin/action.ts +133 -116
  120. package/src/store/chat/utils/index.ts +19 -0
  121. package/src/store/tool/slices/builtin/action.test.ts +8 -11
  122. package/src/store/tool/slices/builtin/action.ts +9 -9
  123. package/src/types/message/index.ts +1 -1
@@ -49,7 +49,7 @@ describe('LobeOpenAI', () => {
49
49
  });
50
50
 
51
51
  describe('Error', () => {
52
- it('should return OpenAIBizError with an openai error response when OpenAI.APIError is thrown', async () => {
52
+ it('should return ProviderBizError with an openai error response when OpenAI.APIError is thrown', async () => {
53
53
  // Arrange
54
54
  const apiError = new OpenAI.APIError(
55
55
  400,
@@ -79,7 +79,7 @@ describe('LobeOpenAI', () => {
79
79
  error: { message: 'Bad Request' },
80
80
  status: 400,
81
81
  },
82
- errorType: 'OpenAIBizError',
82
+ errorType: 'ProviderBizError',
83
83
  provider: 'openai',
84
84
  });
85
85
  }
@@ -89,11 +89,11 @@ describe('LobeOpenAI', () => {
89
89
  try {
90
90
  new LobeOpenAI({});
91
91
  } catch (e) {
92
- expect(e).toEqual({ errorType: 'NoOpenAIAPIKey' });
92
+ expect(e).toEqual({ errorType: 'InvalidProviderAPIKey' });
93
93
  }
94
94
  });
95
95
 
96
- it('should return OpenAIBizError with the cause when OpenAI.APIError is thrown with cause', async () => {
96
+ it('should return ProviderBizError with the cause when OpenAI.APIError is thrown with cause', async () => {
97
97
  // Arrange
98
98
  const errorInfo = {
99
99
  stack: 'abc',
@@ -119,13 +119,13 @@ describe('LobeOpenAI', () => {
119
119
  cause: { message: 'api is undefined' },
120
120
  stack: 'abc',
121
121
  },
122
- errorType: 'OpenAIBizError',
122
+ errorType: 'ProviderBizError',
123
123
  provider: 'openai',
124
124
  });
125
125
  }
126
126
  });
127
127
 
128
- it('should return OpenAIBizError with an cause response with desensitize Url', async () => {
128
+ it('should return ProviderBizError with an cause response with desensitize Url', async () => {
129
129
  // Arrange
130
130
  const errorInfo = {
131
131
  stack: 'abc',
@@ -155,7 +155,7 @@ describe('LobeOpenAI', () => {
155
155
  cause: { message: 'api is undefined' },
156
156
  stack: 'abc',
157
157
  },
158
- errorType: 'OpenAIBizError',
158
+ errorType: 'ProviderBizError',
159
159
  provider: 'openai',
160
160
  });
161
161
  }
@@ -1,4 +1,3 @@
1
- import { AgentRuntimeErrorType } from '../error';
2
1
  import { ModelProvider } from '../types';
3
2
  import { LobeOpenAICompatibleFactory } from '../utils/openaiCompatibleFactory';
4
3
 
@@ -7,10 +6,5 @@ export const LobeOpenAI = LobeOpenAICompatibleFactory({
7
6
  debug: {
8
7
  chatCompletion: () => process.env.DEBUG_OPENAI_CHAT_COMPLETION === '1',
9
8
  },
10
- errorType: {
11
- bizError: AgentRuntimeErrorType.OpenAIBizError,
12
- invalidAPIKey: AgentRuntimeErrorType.NoOpenAIAPIKey,
13
- },
14
-
15
9
  provider: ModelProvider.OpenAI,
16
10
  });
@@ -112,9 +112,9 @@ export const buildAnthropicMessages = (
112
112
 
113
113
  export const buildAnthropicTools = (tools?: OpenAI.ChatCompletionTool[]) =>
114
114
  tools?.map(
115
- (tool): Anthropic.Beta.Tools.Tool => ({
115
+ (tool): Anthropic.Tool => ({
116
116
  description: tool.function.description,
117
- input_schema: tool.function.parameters as Anthropic.Beta.Tools.Tool.InputSchema,
117
+ input_schema: tool.function.parameters as Anthropic.Tool.InputSchema,
118
118
  name: tool.function.name,
119
119
  }),
120
120
  );
@@ -99,27 +99,67 @@ describe('AnthropicStream', () => {
99
99
  });
100
100
 
101
101
  it('should handle tool use event and ReadableStream input', async () => {
102
- const toolUseEvent = {
103
- type: 'content_block_delta',
104
- delta: {
105
- type: 'tool_use',
106
- tool_use: {
107
- id: 'tool_use_1',
108
- name: 'example_tool',
109
- input: { arg1: 'value1' },
102
+ const streams = [
103
+ {
104
+ type: 'message_start',
105
+ message: {
106
+ id: 'msg_017aTuY86wNxth5TE544yqJq',
107
+ type: 'message',
108
+ role: 'assistant',
109
+ model: 'claude-3-sonnet-20240229',
110
+ content: [],
111
+ stop_reason: null,
112
+ stop_sequence: null,
113
+ usage: { input_tokens: 457, output_tokens: 1 },
110
114
  },
111
115
  },
112
- };
116
+ { type: 'content_block_start', index: 0, content_block: { type: 'text', text: '' } },
117
+ { type: 'content_block_delta', index: 0, delta: { type: 'text_delta', text: '好' } },
118
+ { type: 'content_block_delta', index: 0, delta: { type: 'text_delta', text: '的:' } },
119
+
120
+ { type: 'content_block_stop', index: 0 },
121
+ {
122
+ type: 'content_block_start',
123
+ index: 1,
124
+ content_block: {
125
+ type: 'tool_use',
126
+ id: 'toolu_01WdYWxYFQ8iu5iZq1Dy9Saf',
127
+ name: 'realtime-weather____fetchCurrentWeather',
128
+ input: {},
129
+ },
130
+ },
131
+ {
132
+ type: 'content_block_delta',
133
+ index: 1,
134
+ delta: { type: 'input_json_delta', partial_json: '' },
135
+ },
136
+ {
137
+ type: 'content_block_delta',
138
+ index: 1,
139
+ delta: { type: 'input_json_delta', partial_json: '{"city": "' },
140
+ },
141
+ {
142
+ type: 'content_block_delta',
143
+ index: 1,
144
+ delta: { type: 'input_json_delta', partial_json: '杭' },
145
+ },
146
+ {
147
+ type: 'content_block_delta',
148
+ index: 1,
149
+ delta: { type: 'input_json_delta', partial_json: '州"}' },
150
+ },
151
+ { type: 'content_block_stop', index: 1 },
152
+ {
153
+ type: 'message_delta',
154
+ delta: { stop_reason: 'tool_use', stop_sequence: null },
155
+ usage: { output_tokens: 83 },
156
+ },
157
+ ];
113
158
 
114
159
  const mockReadableStream = new ReadableStream({
115
160
  start(controller) {
116
- controller.enqueue({
117
- type: 'message_start',
118
- message: { id: 'message_1', metadata: {} },
119
- });
120
- controller.enqueue(toolUseEvent);
121
- controller.enqueue({
122
- type: 'message_stop',
161
+ streams.forEach((chunk) => {
162
+ controller.enqueue(chunk);
123
163
  });
124
164
  controller.close();
125
165
  },
@@ -139,19 +179,53 @@ describe('AnthropicStream', () => {
139
179
  chunks.push(decoder.decode(chunk, { stream: true }));
140
180
  }
141
181
 
142
- expect(chunks).toEqual([
143
- 'id: message_1\n',
144
- 'event: data\n',
145
- `data: {"id":"message_1","metadata":{}}\n\n`,
146
- 'id: message_1\n',
147
- 'event: tool_calls\n',
148
- `data: [{"function":{"arguments":"{\\"arg1\\":\\"value1\\"}","name":"example_tool"},"id":"tool_use_1","index":0,"type":"function"}]\n\n`,
149
- 'id: message_1\n',
150
- 'event: stop\n',
151
- `data: "message_stop"\n\n`,
152
- ]);
182
+ expect(chunks).toEqual(
183
+ [
184
+ 'id: msg_017aTuY86wNxth5TE544yqJq',
185
+ 'event: data',
186
+ 'data: {"id":"msg_017aTuY86wNxth5TE544yqJq","type":"message","role":"assistant","model":"claude-3-sonnet-20240229","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":457,"output_tokens":1}}\n',
187
+ 'id: msg_017aTuY86wNxth5TE544yqJq',
188
+ 'event: data',
189
+ 'data: ""\n',
190
+ 'id: msg_017aTuY86wNxth5TE544yqJq',
191
+ 'event: text',
192
+ 'data: "好"\n',
193
+ 'id: msg_017aTuY86wNxth5TE544yqJq',
194
+ 'event: text',
195
+ 'data: "的:"\n',
196
+ 'id: msg_017aTuY86wNxth5TE544yqJq',
197
+ 'event: data',
198
+ 'data: {"type":"content_block_stop","index":0}\n',
199
+ // Tool calls
200
+ 'id: msg_017aTuY86wNxth5TE544yqJq',
201
+ 'event: tool_calls',
202
+ `data: [{"function":{"arguments":"","name":"realtime-weather____fetchCurrentWeather"},"id":"toolu_01WdYWxYFQ8iu5iZq1Dy9Saf","index":0,"type":"function"}]\n`,
203
+ 'id: msg_017aTuY86wNxth5TE544yqJq',
204
+ 'event: tool_calls',
205
+ `data: [{"function":{"arguments":""},"index":0,"type":"function"}]\n`,
206
+ 'id: msg_017aTuY86wNxth5TE544yqJq',
207
+ 'event: tool_calls',
208
+ `data: [{"function":{"arguments":"{\\"city\\": \\""},"index":0,"type":"function"}]\n`,
209
+ 'id: msg_017aTuY86wNxth5TE544yqJq',
210
+ 'event: tool_calls',
211
+
212
+ `data: [{"function":{"arguments":"杭"},"index":0,"type":"function"}]\n`,
213
+ 'id: msg_017aTuY86wNxth5TE544yqJq',
214
+ 'event: tool_calls',
215
+
216
+ `data: [{"function":{"arguments":"州\\"}"},"index":0,"type":"function"}]\n`,
217
+
218
+ 'id: msg_017aTuY86wNxth5TE544yqJq',
219
+ 'event: data',
220
+ 'data: {"type":"content_block_stop","index":1}\n',
221
+
222
+ 'id: msg_017aTuY86wNxth5TE544yqJq',
223
+ 'event: stop',
224
+ 'data: "tool_use"\n',
225
+ ].map((item) => `${item}\n`),
226
+ );
153
227
 
154
- expect(onToolCallMock).toHaveBeenCalledTimes(1);
228
+ expect(onToolCallMock).toHaveBeenCalledTimes(5);
155
229
  });
156
230
 
157
231
  it('should handle ReadableStream input', async () => {
@@ -22,27 +22,39 @@ export const transformAnthropicStream = (
22
22
  stack.id = chunk.message.id;
23
23
  return { data: chunk.message, id: chunk.message.id, type: 'data' };
24
24
  }
25
+ case 'content_block_start': {
26
+ if (chunk.content_block.type === 'tool_use') {
27
+ const toolChunk = chunk.content_block;
25
28
 
26
- // case 'content_block_start': {
27
- // return { data: chunk.content_block.text, id: stack.id, type: 'data' };
28
- // }
29
+ const toolCall: StreamToolCallChunkData = {
30
+ function: {
31
+ arguments: '',
32
+ name: toolChunk.name,
33
+ },
34
+ id: toolChunk.id,
35
+ index: 0,
36
+ type: 'function',
37
+ };
38
+
39
+ stack.tool = { id: toolChunk.id, index: 0, name: toolChunk.name };
40
+
41
+ return { data: [toolCall], id: stack.id, type: 'tool_calls' };
42
+ }
43
+
44
+ return { data: chunk.content_block.text, id: stack.id, type: 'data' };
45
+ }
29
46
 
30
47
  case 'content_block_delta': {
31
- switch (chunk.delta.type as string) {
32
- default:
48
+ switch (chunk.delta.type) {
33
49
  case 'text_delta': {
34
50
  return { data: chunk.delta.text, id: stack.id, type: 'text' };
35
51
  }
36
52
 
37
- // TODO: due to anthropic currently don't support streaming tool calling
38
- // we need to add this new `tool_use` type to support streaming
39
- // and maybe we need to update it when the feature is available
40
- case 'tool_use': {
41
- const delta = (chunk.delta as any).tool_use as Anthropic.Beta.Tools.ToolUseBlock;
53
+ case 'input_json_delta': {
54
+ const delta = chunk.delta.partial_json;
42
55
 
43
56
  const toolCall: StreamToolCallChunkData = {
44
- function: { arguments: JSON.stringify(delta.input), name: delta.name },
45
- id: delta.id,
57
+ function: { arguments: delta },
46
58
  index: 0,
47
59
  type: 'function',
48
60
  };
@@ -53,7 +65,12 @@ export const transformAnthropicStream = (
53
65
  type: 'tool_calls',
54
66
  } as StreamProtocolToolCallChunk;
55
67
  }
68
+
69
+ default: {
70
+ break;
71
+ }
56
72
  }
73
+ return { data: chunk, id: stack.id, type: 'data' };
57
74
  }
58
75
 
59
76
  case 'message_delta': {
@@ -2,6 +2,11 @@ import { ChatStreamCallbacks } from '@/libs/agent-runtime';
2
2
 
3
3
  export interface StreamStack {
4
4
  id: string;
5
+ tool?: {
6
+ id: string;
7
+ index: number;
8
+ name: string;
9
+ };
5
10
  }
6
11
 
7
12
  export interface StreamProtocolChunk {
@@ -15,7 +20,7 @@ export interface StreamToolCallChunkData {
15
20
  arguments?: string;
16
21
  name?: string | null;
17
22
  };
18
- id: string;
23
+ id?: string;
19
24
  index: number;
20
25
  type: 'function' | string;
21
26
  }
@@ -105,6 +105,7 @@ export default {
105
105
  latestTime: '上次更新时间:{{time}}',
106
106
  noLatestTime: '暂未获取列表',
107
107
  },
108
+ helpDoc: '配置教程',
108
109
  modelList: {
109
110
  desc: '选择在会话中展示的模型,选择的模型会在模型列表中展示',
110
111
  placeholder: '请从列表中选择模型',
@@ -44,6 +44,10 @@ export class ServerService implements IMessageService {
44
44
  return lambdaClient.message.update.mutate({ id, value: { error } });
45
45
  }
46
46
 
47
+ async updateMessagePluginError(id: string, error: ChatMessageError): Promise<any> {
48
+ return lambdaClient.message.update.mutate({ id, value: { pluginError: error } });
49
+ }
50
+
47
51
  updateMessage(id: string, message: Partial<ChatMessage>): Promise<any> {
48
52
  return lambdaClient.message.update.mutate({ id, value: message });
49
53
  }
@@ -17,7 +17,7 @@ export interface CreateMessageParams
17
17
  traceId?: string;
18
18
  topicId?: string;
19
19
  content: string;
20
- error?: ChatMessageError;
20
+ error?: ChatMessageError | null;
21
21
  role: MessageRoleType;
22
22
  }
23
23
 
@@ -19,12 +19,14 @@ import { agentSelectors } from '@/store/agent/selectors';
19
19
  import { chatHelpers } from '@/store/chat/helpers';
20
20
  import { messageMapKey } from '@/store/chat/slices/message/utils';
21
21
  import { ChatStore } from '@/store/chat/store';
22
- import { ChatMessage, MessageToolCall } from '@/types/message';
22
+ import { ChatMessage, ChatMessageError, MessageToolCall } from '@/types/message';
23
23
  import { TraceEventPayloads } from '@/types/trace';
24
24
  import { setNamespace } from '@/utils/storeDebug';
25
25
  import { nanoid } from '@/utils/uuid';
26
26
 
27
+ import type { ChatStoreState } from '../../initialState';
27
28
  import { chatSelectors, topicSelectors } from '../../selectors';
29
+ import { preventLeavingFn, toggleBooleanList } from '../../utils';
28
30
  import { MessageDispatch, messagesReducer } from './reducer';
29
31
 
30
32
  const n = setNamespace('m');
@@ -84,6 +86,12 @@ export interface ChatMessageAction {
84
86
  id?: string,
85
87
  action?: string,
86
88
  ) => AbortController | undefined;
89
+ internal_toggleLoadingArrays: (
90
+ key: keyof ChatStoreState,
91
+ loading: boolean,
92
+ id?: string,
93
+ action?: string,
94
+ ) => AbortController | undefined;
87
95
  internal_toggleToolCallingStreaming: (id: string, streaming: boolean[] | undefined) => void;
88
96
  internal_toggleMessageLoading: (loading: boolean, id: string) => void;
89
97
  /**
@@ -123,6 +131,7 @@ export interface ChatMessageAction {
123
131
  content: string,
124
132
  toolCalls?: MessageToolCall[],
125
133
  ) => Promise<void>;
134
+ internal_updateMessageError: (id: string, error: ChatMessageError | null) => Promise<void>;
126
135
  internal_createMessage: (
127
136
  params: CreateMessageParams,
128
137
  context?: { tempMessageId?: string; skipRefresh?: boolean },
@@ -136,24 +145,6 @@ export interface ChatMessageAction {
136
145
  const getAgentConfig = () => agentSelectors.currentAgentConfig(useAgentStore.getState());
137
146
  const getAgentChatConfig = () => agentSelectors.currentAgentChatConfig(useAgentStore.getState());
138
147
 
139
- const preventLeavingFn = (e: BeforeUnloadEvent) => {
140
- // set returnValue to trigger alert modal
141
- // Note: No matter what value is set, the browser will display the standard text
142
- e.returnValue = '你有正在生成中的请求,确定要离开吗?';
143
- };
144
-
145
- const toggleBooleanList = (ids: string[], id: string, loading: boolean) => {
146
- return produce(ids, (draft) => {
147
- if (loading) {
148
- draft.push(id);
149
- } else {
150
- const index = draft.indexOf(id);
151
-
152
- if (index >= 0) draft.splice(index, 1);
153
- }
154
- });
155
- };
156
-
157
148
  export const chatMessage: StateCreator<
158
149
  ChatStore,
159
150
  [['zustand/devtools', never]],
@@ -591,35 +582,7 @@ export const chatMessage: StateCreator<
591
582
  };
592
583
  },
593
584
  internal_toggleChatLoading: (loading, id, action) => {
594
- if (loading) {
595
- window.addEventListener('beforeunload', preventLeavingFn);
596
-
597
- const abortController = new AbortController();
598
- set(
599
- {
600
- abortController,
601
- chatLoadingIds: toggleBooleanList(get().messageLoadingIds, id!, loading),
602
- },
603
- false,
604
- action,
605
- );
606
-
607
- return abortController;
608
- } else {
609
- if (!id) {
610
- set({ abortController: undefined, chatLoadingIds: [] }, false, action);
611
- } else
612
- set(
613
- {
614
- abortController: undefined,
615
- chatLoadingIds: toggleBooleanList(get().messageLoadingIds, id, loading),
616
- },
617
- false,
618
- action,
619
- );
620
-
621
- window.removeEventListener('beforeunload', preventLeavingFn);
622
- }
585
+ return get().internal_toggleLoadingArrays('chatLoadingIds', loading, id, action);
623
586
  },
624
587
  internal_toggleMessageLoading: (loading, id) => {
625
588
  set(
@@ -684,6 +647,11 @@ export const chatMessage: StateCreator<
684
647
  await internal_coreProcessMessage(contextMessages, latestMsg.id, { traceId });
685
648
  },
686
649
 
650
+ internal_updateMessageError: async (id, error) => {
651
+ get().internal_dispatchMessage({ id, type: 'updateMessages', value: { error } });
652
+ await messageService.updateMessage(id, { error });
653
+ await get().refreshMessages();
654
+ },
687
655
  internal_updateMessageContent: async (id, content, toolCalls) => {
688
656
  const { internal_dispatchMessage, refreshMessages, internal_transformToolCalls } = get();
689
657
 
@@ -762,4 +730,36 @@ export const chatMessage: StateCreator<
762
730
  .catch();
763
731
  }
764
732
  },
733
+
734
+ internal_toggleLoadingArrays: (key, loading, id, action) => {
735
+ if (loading) {
736
+ window.addEventListener('beforeunload', preventLeavingFn);
737
+
738
+ const abortController = new AbortController();
739
+ set(
740
+ {
741
+ abortController,
742
+ [key]: toggleBooleanList(get()[key] as string[], id!, loading),
743
+ },
744
+ false,
745
+ action,
746
+ );
747
+
748
+ return abortController;
749
+ } else {
750
+ if (!id) {
751
+ set({ abortController: undefined, [key]: [] }, false, action);
752
+ } else
753
+ set(
754
+ {
755
+ abortController: undefined,
756
+ [key]: toggleBooleanList(get()[key] as string[], id, loading),
757
+ },
758
+ false,
759
+ action,
760
+ );
761
+
762
+ window.removeEventListener('beforeunload', preventLeavingFn);
763
+ }
764
+ },
765
765
  });
@@ -26,6 +26,7 @@ export interface ChatMessageState {
26
26
  */
27
27
  messagesInit: boolean;
28
28
  messagesMap: Record<string, ChatMessage[]>;
29
+ pluginApiLoadingIds: string[];
29
30
  /**
30
31
  * the tool calling stream ids
31
32
  */
@@ -41,5 +42,6 @@ export const initialMessageState: ChatMessageState = {
41
42
  messageLoadingIds: [],
42
43
  messagesInit: false,
43
44
  messagesMap: {},
45
+ pluginApiLoadingIds: [],
44
46
  toolCallingStreamIds: {},
45
47
  };
@@ -129,6 +129,8 @@ const isHasMessageLoading = (s: ChatStore) => s.messageLoadingIds.length > 0;
129
129
  const isCreatingMessage = (s: ChatStore) => s.isCreatingMessage;
130
130
 
131
131
  const isMessageGenerating = (id: string) => (s: ChatStore) => s.chatLoadingIds.includes(id);
132
+ const isPluginApiInvoking = (id: string) => (s: ChatStore) => s.pluginApiLoadingIds.includes(id);
133
+
132
134
  const isToolCallStreaming = (id: string, index: number) => (s: ChatStore) => {
133
135
  const isLoading = s.toolCallingStreamIds[id];
134
136
 
@@ -155,6 +157,7 @@ export const chatSelectors = {
155
157
  isMessageEditing,
156
158
  isMessageGenerating,
157
159
  isMessageLoading,
160
+ isPluginApiInvoking,
158
161
  isToolCallStreaming,
159
162
  latestMessage,
160
163
  showInboxWelcome,