@lobehub/chat 1.64.3 → 1.65.1
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.
- package/CHANGELOG.md +58 -0
- package/README.md +1 -1
- package/changelog/v1.json +21 -0
- package/locales/ar/chat.json +7 -1
- package/locales/ar/models.json +6 -9
- package/locales/bg-BG/chat.json +7 -1
- package/locales/bg-BG/models.json +6 -9
- package/locales/de-DE/chat.json +7 -1
- package/locales/de-DE/models.json +6 -9
- package/locales/en-US/chat.json +7 -1
- package/locales/en-US/models.json +6 -9
- package/locales/es-ES/chat.json +8 -2
- package/locales/es-ES/models.json +6 -9
- package/locales/fa-IR/chat.json +7 -1
- package/locales/fa-IR/models.json +6 -3
- package/locales/fr-FR/chat.json +7 -1
- package/locales/fr-FR/models.json +6 -9
- package/locales/it-IT/chat.json +7 -1
- package/locales/it-IT/models.json +6 -9
- package/locales/ja-JP/chat.json +7 -1
- package/locales/ja-JP/models.json +6 -9
- package/locales/ko-KR/chat.json +7 -1
- package/locales/ko-KR/models.json +6 -9
- package/locales/nl-NL/chat.json +8 -2
- package/locales/nl-NL/models.json +6 -9
- package/locales/pl-PL/chat.json +7 -1
- package/locales/pl-PL/models.json +6 -9
- package/locales/pt-BR/chat.json +7 -1
- package/locales/pt-BR/models.json +6 -9
- package/locales/ru-RU/chat.json +8 -2
- package/locales/ru-RU/models.json +6 -9
- package/locales/tr-TR/chat.json +7 -1
- package/locales/tr-TR/models.json +6 -9
- package/locales/vi-VN/chat.json +7 -1
- package/locales/vi-VN/models.json +6 -9
- package/locales/zh-CN/chat.json +7 -1
- package/locales/zh-CN/models.json +6 -9
- package/locales/zh-TW/chat.json +7 -1
- package/locales/zh-TW/models.json +6 -9
- package/package.json +2 -2
- package/src/app/(backend)/middleware/auth/index.ts +6 -0
- package/src/config/aiModels/anthropic.ts +5 -2
- package/src/config/aiModels/google.ts +7 -0
- package/src/const/message.ts +3 -0
- package/src/const/settings/agent.ts +2 -0
- package/src/features/ChatInput/ActionBar/Model/ControlsForm.tsx +38 -13
- package/src/features/ChatInput/ActionBar/Model/ReasoningTokenSlider.tsx +92 -0
- package/src/features/ChatInput/ActionBar/Model/index.tsx +13 -18
- package/src/libs/agent-runtime/anthropic/index.ts +32 -14
- package/src/libs/agent-runtime/google/index.test.ts +8 -0
- package/src/libs/agent-runtime/google/index.ts +18 -5
- package/src/libs/agent-runtime/types/chat.ts +16 -2
- package/src/libs/agent-runtime/utils/anthropicHelpers.test.ts +113 -0
- package/src/libs/agent-runtime/utils/anthropicHelpers.ts +7 -4
- package/src/libs/agent-runtime/utils/streams/anthropic.test.ts +371 -0
- package/src/libs/agent-runtime/utils/streams/anthropic.ts +80 -30
- package/src/libs/agent-runtime/utils/streams/openai.test.ts +181 -0
- package/src/libs/agent-runtime/utils/streams/openai.ts +40 -30
- package/src/libs/agent-runtime/utils/streams/protocol.ts +8 -0
- package/src/locales/default/chat.ts +7 -1
- package/src/services/__tests__/chat.test.ts +89 -50
- package/src/services/chat.ts +39 -1
- package/src/store/agent/slices/chat/__snapshots__/selectors.test.ts.snap +2 -0
- package/src/store/aiInfra/slices/aiModel/selectors.ts +6 -6
- package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +1 -1
- package/src/store/user/slices/settings/selectors/__snapshots__/settings.test.ts.snap +2 -0
- package/src/types/agent/index.ts +23 -9
- package/src/types/aiModel.ts +3 -8
- package/src/types/message/base.ts +1 -0
- package/src/utils/fetch/__tests__/fetchSSE.test.ts +113 -10
- package/src/utils/fetch/fetchSSE.ts +12 -3
- package/src/features/ChatInput/ActionBar/Model/ExtendControls.tsx +0 -40
package/src/types/aiModel.ts
CHANGED
@@ -138,17 +138,12 @@ export interface AiModelConfig {
|
|
138
138
|
enabledSearch?: boolean;
|
139
139
|
}
|
140
140
|
|
141
|
-
export interface ExtendedControl {
|
142
|
-
key: string;
|
143
|
-
requestParams: string | string[];
|
144
|
-
type: 'params' | 'tool';
|
145
|
-
valueType: 'boolean';
|
146
|
-
}
|
147
|
-
|
148
141
|
export type ModelSearchImplementType = 'tool' | 'params' | 'internal';
|
149
142
|
|
143
|
+
export type ExtendParamsType = 'reasoningBudgetToken' | 'enableReasoning';
|
144
|
+
|
150
145
|
export interface AiModelSettings {
|
151
|
-
|
146
|
+
extendParams?: ExtendParamsType[];
|
152
147
|
/**
|
153
148
|
* 模型层实现搜索的方式
|
154
149
|
*/
|
@@ -154,16 +154,119 @@ describe('fetchSSE', () => {
|
|
154
154
|
});
|
155
155
|
});
|
156
156
|
|
157
|
-
|
157
|
+
describe('reasoning', () => {
|
158
|
+
it('should handle reasoning event without smoothing', async () => {
|
159
|
+
const mockOnMessageHandle = vi.fn();
|
160
|
+
const mockOnFinish = vi.fn();
|
161
|
+
|
162
|
+
(fetchEventSource as any).mockImplementationOnce(
|
163
|
+
async (url: string, options: FetchEventSourceInit) => {
|
164
|
+
options.onopen!({ clone: () => ({ ok: true, headers: new Headers() }) } as any);
|
165
|
+
options.onmessage!({ event: 'reasoning', data: JSON.stringify('Hello') } as any);
|
166
|
+
await sleep(100);
|
167
|
+
options.onmessage!({ event: 'reasoning', data: JSON.stringify(' World') } as any);
|
168
|
+
await sleep(100);
|
169
|
+
options.onmessage!({ event: 'text', data: JSON.stringify('hi') } as any);
|
170
|
+
},
|
171
|
+
);
|
172
|
+
|
173
|
+
await fetchSSE('/', {
|
174
|
+
onMessageHandle: mockOnMessageHandle,
|
175
|
+
onFinish: mockOnFinish,
|
176
|
+
});
|
177
|
+
|
178
|
+
expect(mockOnMessageHandle).toHaveBeenNthCalledWith(1, { text: 'Hello', type: 'reasoning' });
|
179
|
+
expect(mockOnMessageHandle).toHaveBeenNthCalledWith(2, { text: ' World', type: 'reasoning' });
|
180
|
+
|
181
|
+
expect(mockOnFinish).toHaveBeenCalledWith('hi', {
|
182
|
+
observationId: null,
|
183
|
+
toolCalls: undefined,
|
184
|
+
reasoning: { content: 'Hello World' },
|
185
|
+
traceId: null,
|
186
|
+
type: 'done',
|
187
|
+
});
|
188
|
+
});
|
189
|
+
|
190
|
+
it('should handle reasoning event with smoothing correctly', async () => {
|
191
|
+
const mockOnMessageHandle = vi.fn();
|
192
|
+
const mockOnFinish = vi.fn();
|
193
|
+
|
194
|
+
(fetchEventSource as any).mockImplementationOnce(
|
195
|
+
async (url: string, options: FetchEventSourceInit) => {
|
196
|
+
options.onopen!({ clone: () => ({ ok: true, headers: new Headers() }) } as any);
|
197
|
+
options.onmessage!({ event: 'reasoning', data: JSON.stringify('Hello') } as any);
|
198
|
+
await sleep(100);
|
199
|
+
options.onmessage!({ event: 'reasoning', data: JSON.stringify(' World') } as any);
|
200
|
+
await sleep(100);
|
201
|
+
options.onmessage!({ event: 'text', data: JSON.stringify('hi') } as any);
|
202
|
+
},
|
203
|
+
);
|
204
|
+
|
205
|
+
await fetchSSE('/', {
|
206
|
+
onMessageHandle: mockOnMessageHandle,
|
207
|
+
onFinish: mockOnFinish,
|
208
|
+
smoothing: true,
|
209
|
+
});
|
210
|
+
|
211
|
+
expect(mockOnMessageHandle).toHaveBeenNthCalledWith(1, { text: 'Hell', type: 'reasoning' });
|
212
|
+
expect(mockOnMessageHandle).toHaveBeenNthCalledWith(2, { text: 'o', type: 'reasoning' });
|
213
|
+
expect(mockOnMessageHandle).toHaveBeenNthCalledWith(3, { text: ' Wor', type: 'reasoning' });
|
214
|
+
// more assertions for each character...
|
215
|
+
expect(mockOnFinish).toHaveBeenCalledWith('hi', {
|
216
|
+
observationId: null,
|
217
|
+
toolCalls: undefined,
|
218
|
+
reasoning: { content: 'Hello World' },
|
219
|
+
traceId: null,
|
220
|
+
type: 'done',
|
221
|
+
});
|
222
|
+
});
|
223
|
+
it('should handle reasoning with signature', async () => {
|
224
|
+
const mockOnMessageHandle = vi.fn();
|
225
|
+
const mockOnFinish = vi.fn();
|
226
|
+
|
227
|
+
(fetchEventSource as any).mockImplementationOnce(
|
228
|
+
async (url: string, options: FetchEventSourceInit) => {
|
229
|
+
options.onopen!({ clone: () => ({ ok: true, headers: new Headers() }) } as any);
|
230
|
+
options.onmessage!({ event: 'reasoning', data: JSON.stringify('Hello') } as any);
|
231
|
+
await sleep(100);
|
232
|
+
options.onmessage!({ event: 'reasoning', data: JSON.stringify(' World') } as any);
|
233
|
+
options.onmessage!({
|
234
|
+
event: 'reasoning_signature',
|
235
|
+
data: JSON.stringify('abcbcd'),
|
236
|
+
} as any);
|
237
|
+
await sleep(100);
|
238
|
+
options.onmessage!({ event: 'text', data: JSON.stringify('hi') } as any);
|
239
|
+
},
|
240
|
+
);
|
241
|
+
|
242
|
+
await fetchSSE('/', {
|
243
|
+
onMessageHandle: mockOnMessageHandle,
|
244
|
+
onFinish: mockOnFinish,
|
245
|
+
smoothing: true,
|
246
|
+
});
|
247
|
+
|
248
|
+
expect(mockOnMessageHandle).toHaveBeenNthCalledWith(1, { text: 'Hell', type: 'reasoning' });
|
249
|
+
expect(mockOnMessageHandle).toHaveBeenNthCalledWith(2, { text: 'o', type: 'reasoning' });
|
250
|
+
expect(mockOnMessageHandle).toHaveBeenNthCalledWith(3, { text: ' Wor', type: 'reasoning' });
|
251
|
+
// more assertions for each character...
|
252
|
+
expect(mockOnFinish).toHaveBeenCalledWith('hi', {
|
253
|
+
observationId: null,
|
254
|
+
toolCalls: undefined,
|
255
|
+
reasoning: { content: 'Hello World', signature: 'abcbcd' },
|
256
|
+
traceId: null,
|
257
|
+
type: 'done',
|
258
|
+
});
|
259
|
+
});
|
260
|
+
});
|
261
|
+
|
262
|
+
it('should handle grounding event', async () => {
|
158
263
|
const mockOnMessageHandle = vi.fn();
|
159
264
|
const mockOnFinish = vi.fn();
|
160
265
|
|
161
266
|
(fetchEventSource as any).mockImplementationOnce(
|
162
267
|
async (url: string, options: FetchEventSourceInit) => {
|
163
268
|
options.onopen!({ clone: () => ({ ok: true, headers: new Headers() }) } as any);
|
164
|
-
options.onmessage!({ event: '
|
165
|
-
await sleep(100);
|
166
|
-
options.onmessage!({ event: 'reasoning', data: JSON.stringify(' World') } as any);
|
269
|
+
options.onmessage!({ event: 'grounding', data: JSON.stringify('Hello') } as any);
|
167
270
|
await sleep(100);
|
168
271
|
options.onmessage!({ event: 'text', data: JSON.stringify('hi') } as any);
|
169
272
|
},
|
@@ -172,17 +275,17 @@ describe('fetchSSE', () => {
|
|
172
275
|
await fetchSSE('/', {
|
173
276
|
onMessageHandle: mockOnMessageHandle,
|
174
277
|
onFinish: mockOnFinish,
|
175
|
-
smoothing: true,
|
176
278
|
});
|
177
279
|
|
178
|
-
expect(mockOnMessageHandle).toHaveBeenNthCalledWith(1, {
|
179
|
-
|
180
|
-
|
181
|
-
|
280
|
+
expect(mockOnMessageHandle).toHaveBeenNthCalledWith(1, {
|
281
|
+
grounding: 'Hello',
|
282
|
+
type: 'grounding',
|
283
|
+
});
|
284
|
+
|
182
285
|
expect(mockOnFinish).toHaveBeenCalledWith('hi', {
|
183
286
|
observationId: null,
|
184
287
|
toolCalls: undefined,
|
185
|
-
|
288
|
+
grounding: 'Hello',
|
186
289
|
traceId: null,
|
187
290
|
type: 'done',
|
188
291
|
});
|
@@ -9,6 +9,7 @@ import {
|
|
9
9
|
MessageToolCall,
|
10
10
|
MessageToolCallChunk,
|
11
11
|
MessageToolCallSchema,
|
12
|
+
ModelReasoning,
|
12
13
|
} from '@/types/message';
|
13
14
|
import { GroundingSearch } from '@/types/search';
|
14
15
|
|
@@ -23,7 +24,7 @@ export type OnFinishHandler = (
|
|
23
24
|
context: {
|
24
25
|
grounding?: GroundingSearch;
|
25
26
|
observationId?: string | null;
|
26
|
-
reasoning?:
|
27
|
+
reasoning?: ModelReasoning;
|
27
28
|
toolCalls?: MessageToolCall[];
|
28
29
|
traceId?: string | null;
|
29
30
|
type?: SSEFinishType;
|
@@ -36,7 +37,8 @@ export interface MessageTextChunk {
|
|
36
37
|
}
|
37
38
|
|
38
39
|
export interface MessageReasoningChunk {
|
39
|
-
|
40
|
+
signature?: string;
|
41
|
+
text?: string;
|
40
42
|
type: 'reasoning';
|
41
43
|
}
|
42
44
|
|
@@ -271,6 +273,8 @@ export const fetchSSE = async (url: string, options: RequestInit & FetchSSEOptio
|
|
271
273
|
});
|
272
274
|
|
273
275
|
let thinking = '';
|
276
|
+
let thinkingSignature: string | undefined;
|
277
|
+
|
274
278
|
const thinkingController = createSmoothMessage({
|
275
279
|
onTextUpdate: (delta, text) => {
|
276
280
|
thinking = text;
|
@@ -365,6 +369,11 @@ export const fetchSSE = async (url: string, options: RequestInit & FetchSSEOptio
|
|
365
369
|
break;
|
366
370
|
}
|
367
371
|
|
372
|
+
case 'reasoning_signature': {
|
373
|
+
thinkingSignature = data;
|
374
|
+
break;
|
375
|
+
}
|
376
|
+
|
368
377
|
case 'reasoning': {
|
369
378
|
if (textSmoothing) {
|
370
379
|
thinkingController.pushToQueue(data);
|
@@ -436,7 +445,7 @@ export const fetchSSE = async (url: string, options: RequestInit & FetchSSEOptio
|
|
436
445
|
await options?.onFinish?.(output, {
|
437
446
|
grounding,
|
438
447
|
observationId,
|
439
|
-
reasoning: !!thinking ? thinking : undefined,
|
448
|
+
reasoning: !!thinking ? { content: thinking, signature: thinkingSignature } : undefined,
|
440
449
|
toolCalls,
|
441
450
|
traceId,
|
442
451
|
type: finishedType,
|
@@ -1,40 +0,0 @@
|
|
1
|
-
import { ActionIcon } from '@lobehub/ui';
|
2
|
-
import { Popover } from 'antd';
|
3
|
-
import { Settings2Icon } from 'lucide-react';
|
4
|
-
import { memo } from 'react';
|
5
|
-
import { useTranslation } from 'react-i18next';
|
6
|
-
import { Flexbox } from 'react-layout-kit';
|
7
|
-
|
8
|
-
import { useIsMobile } from '@/hooks/useIsMobile';
|
9
|
-
|
10
|
-
import ControlsForm from './ControlsForm';
|
11
|
-
|
12
|
-
const ExtendControls = memo(() => {
|
13
|
-
const { t } = useTranslation('chat');
|
14
|
-
|
15
|
-
const isMobile = useIsMobile();
|
16
|
-
return (
|
17
|
-
<Flexbox style={{ marginInlineStart: -4 }}>
|
18
|
-
<Popover
|
19
|
-
arrow={false}
|
20
|
-
content={<ControlsForm />}
|
21
|
-
open
|
22
|
-
styles={{
|
23
|
-
body: {
|
24
|
-
minWidth: isMobile ? undefined : 250,
|
25
|
-
width: isMobile ? '100vw' : undefined,
|
26
|
-
},
|
27
|
-
}}
|
28
|
-
>
|
29
|
-
<ActionIcon
|
30
|
-
icon={Settings2Icon}
|
31
|
-
placement={'bottom'}
|
32
|
-
style={{ borderRadius: 20 }}
|
33
|
-
title={t('extendControls.title')}
|
34
|
-
/>
|
35
|
-
</Popover>
|
36
|
-
</Flexbox>
|
37
|
-
);
|
38
|
-
});
|
39
|
-
|
40
|
-
export default ExtendControls;
|