@lobehub/chat 1.94.3 → 1.94.4
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/.github/scripts/create-failure-issue.js +256 -0
- package/.github/workflows/auto-i18n.yml +359 -0
- package/CHANGELOG.md +25 -0
- package/changelog/v1.json +9 -0
- package/locales/ar/setting.json +13 -1
- package/locales/bg-BG/setting.json +13 -1
- package/locales/de-DE/setting.json +13 -1
- package/locales/en-US/setting.json +13 -1
- package/locales/es-ES/setting.json +13 -1
- package/locales/fa-IR/setting.json +13 -1
- package/locales/fr-FR/setting.json +13 -1
- package/locales/it-IT/setting.json +13 -1
- package/locales/ja-JP/setting.json +13 -1
- package/locales/ko-KR/setting.json +13 -1
- package/locales/nl-NL/setting.json +13 -1
- package/locales/pl-PL/setting.json +13 -1
- package/locales/pt-BR/setting.json +13 -1
- package/locales/ru-RU/setting.json +13 -1
- package/locales/tr-TR/setting.json +13 -1
- package/locales/vi-VN/setting.json +13 -1
- package/locales/zh-CN/setting.json +13 -1
- package/locales/zh-TW/setting.json +13 -1
- package/package.json +1 -1
- package/src/app/[variants]/(main)/settings/common/features/ChatAppearance/ChatTransitionPreview.tsx +111 -0
- package/src/app/[variants]/(main)/settings/common/features/ChatAppearance/index.tsx +50 -3
- package/src/components/Thinking/index.tsx +4 -2
- package/src/config/modelProviders/anthropic.ts +1 -6
- package/src/config/modelProviders/baichuan.ts +4 -8
- package/src/config/modelProviders/google.ts +4 -4
- package/src/config/modelProviders/lmstudio.ts +4 -4
- package/src/config/modelProviders/minimax.ts +3 -3
- package/src/config/modelProviders/moonshot.ts +4 -4
- package/src/config/modelProviders/openai.ts +1 -3
- package/src/config/modelProviders/perplexity.ts +3 -3
- package/src/config/modelProviders/qwen.ts +4 -4
- package/src/config/modelProviders/search1api.ts +4 -4
- package/src/config/modelProviders/spark.ts +4 -4
- package/src/config/modelProviders/stepfun.ts +4 -4
- package/src/config/modelProviders/vertexai.ts +1 -3
- package/src/config/modelProviders/volcengine.ts +4 -4
- package/src/config/modelProviders/wenxin.ts +3 -3
- package/src/const/settings/common.ts +1 -0
- package/src/features/Conversation/Messages/Assistant/Reasoning/index.tsx +11 -1
- package/src/features/Conversation/components/ChatItem/index.tsx +6 -2
- package/src/features/Conversation/components/MarkdownElements/LobeThinking/Render.tsx +4 -0
- package/src/features/Conversation/components/MarkdownElements/Thinking/Render.tsx +12 -1
- package/src/locales/default/setting.ts +12 -0
- package/src/services/chat.ts +15 -6
- package/src/store/user/slices/settings/selectors/general.test.ts +1 -0
- package/src/store/user/slices/settings/selectors/general.ts +2 -0
- package/src/types/aiProvider.ts +11 -11
- package/src/types/llm.ts +8 -10
- package/src/types/user/settings/general.ts +3 -0
- package/src/utils/fetch/__tests__/fetchSSE.test.ts +57 -12
- package/src/utils/fetch/fetchSSE.ts +22 -15
@@ -38,7 +38,7 @@ describe('fetchSSE', () => {
|
|
38
38
|
await fetchSSE('/', {
|
39
39
|
onMessageHandle: mockOnMessageHandle,
|
40
40
|
onFinish: mockOnFinish,
|
41
|
-
|
41
|
+
responseAnimation: 'fadeIn',
|
42
42
|
});
|
43
43
|
|
44
44
|
expect(mockOnMessageHandle).toHaveBeenNthCalledWith(1, { text: 'Hello World', type: 'text' });
|
@@ -75,7 +75,7 @@ describe('fetchSSE', () => {
|
|
75
75
|
await fetchSSE('/', {
|
76
76
|
onMessageHandle: mockOnMessageHandle,
|
77
77
|
onFinish: mockOnFinish,
|
78
|
-
|
78
|
+
responseAnimation: 'fadeIn',
|
79
79
|
});
|
80
80
|
|
81
81
|
expect(mockOnMessageHandle).toHaveBeenNthCalledWith(1, {
|
@@ -122,7 +122,7 @@ describe('fetchSSE', () => {
|
|
122
122
|
});
|
123
123
|
});
|
124
124
|
|
125
|
-
it
|
125
|
+
it('should handle text event with smoothing correctly', async () => {
|
126
126
|
const mockOnMessageHandle = vi.fn();
|
127
127
|
const mockOnFinish = vi.fn();
|
128
128
|
|
@@ -138,7 +138,7 @@ describe('fetchSSE', () => {
|
|
138
138
|
await fetchSSE('/', {
|
139
139
|
onMessageHandle: mockOnMessageHandle,
|
140
140
|
onFinish: mockOnFinish,
|
141
|
-
|
141
|
+
responseAnimation: 'smooth',
|
142
142
|
});
|
143
143
|
|
144
144
|
const expectedMessages = [
|
@@ -168,6 +168,39 @@ describe('fetchSSE', () => {
|
|
168
168
|
});
|
169
169
|
});
|
170
170
|
|
171
|
+
it('should not handle text events', async () => {
|
172
|
+
const mockOnMessageHandle = vi.fn();
|
173
|
+
const mockOnFinish = vi.fn();
|
174
|
+
|
175
|
+
(fetchEventSource as any).mockImplementationOnce(
|
176
|
+
async (url: string, options: FetchEventSourceInit) => {
|
177
|
+
options.onopen!({ clone: () => ({ ok: true, headers: new Headers() }) } as any);
|
178
|
+
options.onmessage!({ event: 'text', data: JSON.stringify('He') } as any);
|
179
|
+
await sleep(100);
|
180
|
+
options.onmessage!({ event: 'text', data: JSON.stringify('llo') } as any);
|
181
|
+
await sleep(60);
|
182
|
+
options.onmessage!({ event: 'text', data: JSON.stringify(' World') } as any);
|
183
|
+
},
|
184
|
+
);
|
185
|
+
|
186
|
+
await fetchSSE('/', {
|
187
|
+
onMessageHandle: mockOnMessageHandle,
|
188
|
+
onFinish: mockOnFinish,
|
189
|
+
responseAnimation: 'none',
|
190
|
+
});
|
191
|
+
|
192
|
+
expect(mockOnMessageHandle).toHaveBeenNthCalledWith(1, { text: 'He', type: 'text' });
|
193
|
+
expect(mockOnMessageHandle).toHaveBeenNthCalledWith(2, { text: 'llo', type: 'text' });
|
194
|
+
expect(mockOnMessageHandle).toHaveBeenNthCalledWith(3, { text: ' World', type: 'text' });
|
195
|
+
|
196
|
+
expect(mockOnFinish).toHaveBeenCalledWith('Hello World', {
|
197
|
+
observationId: null,
|
198
|
+
toolCalls: undefined,
|
199
|
+
traceId: null,
|
200
|
+
type: 'done',
|
201
|
+
});
|
202
|
+
});
|
203
|
+
|
171
204
|
describe('reasoning', () => {
|
172
205
|
it('should handle reasoning event without smoothing', async () => {
|
173
206
|
const mockOnMessageHandle = vi.fn();
|
@@ -187,7 +220,7 @@ describe('fetchSSE', () => {
|
|
187
220
|
await fetchSSE('/', {
|
188
221
|
onMessageHandle: mockOnMessageHandle,
|
189
222
|
onFinish: mockOnFinish,
|
190
|
-
|
223
|
+
responseAnimation: 'fadeIn',
|
191
224
|
});
|
192
225
|
|
193
226
|
expect(mockOnMessageHandle).toHaveBeenNthCalledWith(1, { text: 'Hello', type: 'reasoning' });
|
@@ -265,7 +298,7 @@ describe('fetchSSE', () => {
|
|
265
298
|
await fetchSSE('/', {
|
266
299
|
onMessageHandle: mockOnMessageHandle,
|
267
300
|
onFinish: mockOnFinish,
|
268
|
-
|
301
|
+
responseAnimation: 'smooth',
|
269
302
|
});
|
270
303
|
|
271
304
|
// TODO: need to check whether the `aarg1` is correct
|
@@ -317,10 +350,22 @@ describe('fetchSSE', () => {
|
|
317
350
|
onMessageHandle: mockOnMessageHandle,
|
318
351
|
onFinish: mockOnFinish,
|
319
352
|
signal: abortController.signal,
|
320
|
-
|
353
|
+
responseAnimation: 'smooth',
|
321
354
|
});
|
322
355
|
|
323
|
-
const expectedMessages = [
|
356
|
+
const expectedMessages = [
|
357
|
+
{ text: 'H', type: 'text' },
|
358
|
+
{ text: 'e', type: 'text' },
|
359
|
+
{ text: 'l', type: 'text' },
|
360
|
+
{ text: 'l', type: 'text' },
|
361
|
+
{ text: 'o', type: 'text' },
|
362
|
+
{ text: ' ', type: 'text' },
|
363
|
+
{ text: 'W', type: 'text' },
|
364
|
+
{ text: 'o', type: 'text' },
|
365
|
+
{ text: 'r', type: 'text' },
|
366
|
+
{ text: 'l', type: 'text' },
|
367
|
+
{ text: 'd', type: 'text' },
|
368
|
+
];
|
324
369
|
|
325
370
|
expectedMessages.forEach((message, index) => {
|
326
371
|
expect(mockOnMessageHandle).toHaveBeenNthCalledWith(index + 1, message);
|
@@ -344,7 +389,7 @@ describe('fetchSSE', () => {
|
|
344
389
|
},
|
345
390
|
);
|
346
391
|
|
347
|
-
await fetchSSE('/', { onFinish: mockOnFinish,
|
392
|
+
await fetchSSE('/', { onFinish: mockOnFinish, responseAnimation: 'fadeIn' });
|
348
393
|
|
349
394
|
expect(mockOnFinish).toHaveBeenCalledWith('Hello', {
|
350
395
|
observationId: null,
|
@@ -361,7 +406,7 @@ describe('fetchSSE', () => {
|
|
361
406
|
},
|
362
407
|
);
|
363
408
|
|
364
|
-
await fetchSSE('/', { onFinish: mockOnFinish,
|
409
|
+
await fetchSSE('/', { onFinish: mockOnFinish, responseAnimation: 'fadeIn' });
|
365
410
|
|
366
411
|
expect(mockOnFinish).toHaveBeenCalledWith('Hello', {
|
367
412
|
observationId: null,
|
@@ -382,7 +427,7 @@ describe('fetchSSE', () => {
|
|
382
427
|
},
|
383
428
|
);
|
384
429
|
|
385
|
-
await fetchSSE('/', { onAbort: mockOnAbort,
|
430
|
+
await fetchSSE('/', { onAbort: mockOnAbort, responseAnimation: 'fadeIn' });
|
386
431
|
|
387
432
|
expect(mockOnAbort).toHaveBeenCalledWith('Hello');
|
388
433
|
});
|
@@ -397,7 +442,7 @@ describe('fetchSSE', () => {
|
|
397
442
|
},
|
398
443
|
);
|
399
444
|
|
400
|
-
await fetchSSE('/', { onAbort: mockOnAbort,
|
445
|
+
await fetchSSE('/', { onAbort: mockOnAbort, responseAnimation: 'fadeIn' });
|
401
446
|
|
402
447
|
expect(mockOnAbort).toHaveBeenCalledWith('Hello');
|
403
448
|
});
|
@@ -1,10 +1,8 @@
|
|
1
|
-
import { isObject } from 'lodash-es';
|
2
|
-
|
3
1
|
import { MESSAGE_CANCEL_FLAT } from '@/const/message';
|
4
2
|
import { LOBE_CHAT_OBSERVATION_ID, LOBE_CHAT_TRACE_ID } from '@/const/trace';
|
5
3
|
import { parseToolCalls } from '@/libs/model-runtime';
|
6
4
|
import { ChatErrorType } from '@/types/fetch';
|
7
|
-
import {
|
5
|
+
import { ResponseAnimation, ResponseAnimationStyle } from '@/types/llm';
|
8
6
|
import {
|
9
7
|
ChatMessageError,
|
10
8
|
MessageToolCall,
|
@@ -92,7 +90,7 @@ export interface FetchSSEOptions {
|
|
92
90
|
| MessageBase64ImageChunk
|
93
91
|
| MessageSpeedChunk,
|
94
92
|
) => void;
|
95
|
-
|
93
|
+
responseAnimation?: ResponseAnimation;
|
96
94
|
}
|
97
95
|
|
98
96
|
const START_ANIMATION_SPEED = 10; // 默认起始速度
|
@@ -302,6 +300,14 @@ const createSmoothToolCalls = (params: {
|
|
302
300
|
};
|
303
301
|
};
|
304
302
|
|
303
|
+
export const standardizeAnimationStyle = (
|
304
|
+
animationStyle?: ResponseAnimation,
|
305
|
+
): Exclude<ResponseAnimation, ResponseAnimationStyle> => {
|
306
|
+
return typeof animationStyle === 'object'
|
307
|
+
? animationStyle
|
308
|
+
: { text: animationStyle, toolsCalling: animationStyle };
|
309
|
+
};
|
310
|
+
|
305
311
|
/**
|
306
312
|
* Fetch data using stream method
|
307
313
|
*/
|
@@ -313,16 +319,14 @@ export const fetchSSE = async (url: string, options: RequestInit & FetchSSEOptio
|
|
313
319
|
let finishedType: SSEFinishType = 'done';
|
314
320
|
let response!: Response;
|
315
321
|
|
316
|
-
const {
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
const
|
323
|
-
|
324
|
-
|
325
|
-
const smoothingSpeed = isObject(smoothing) ? smoothing.speed : undefined;
|
322
|
+
const {
|
323
|
+
text,
|
324
|
+
toolsCalling,
|
325
|
+
speed: smoothingSpeed,
|
326
|
+
} = standardizeAnimationStyle(options.responseAnimation ?? {});
|
327
|
+
const shouldSkipTextProcessing = text === 'none';
|
328
|
+
const textSmoothing = text === 'smooth';
|
329
|
+
const toolsCallingSmoothing = toolsCalling === 'smooth';
|
326
330
|
|
327
331
|
// 添加文本buffer和计时器相关变量
|
328
332
|
let textBuffer = '';
|
@@ -453,7 +457,10 @@ export const fetchSSE = async (url: string, options: RequestInit & FetchSSEOptio
|
|
453
457
|
// skip empty text
|
454
458
|
if (!data) break;
|
455
459
|
|
456
|
-
if (
|
460
|
+
if (shouldSkipTextProcessing) {
|
461
|
+
output += data;
|
462
|
+
options.onMessageHandle?.({ text: data, type: 'text' });
|
463
|
+
} else if (textSmoothing) {
|
457
464
|
textController.pushToQueue(data);
|
458
465
|
|
459
466
|
if (!textController.isAnimationActive) textController.startAnimation();
|