@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.
Files changed (55) hide show
  1. package/.github/scripts/create-failure-issue.js +256 -0
  2. package/.github/workflows/auto-i18n.yml +359 -0
  3. package/CHANGELOG.md +25 -0
  4. package/changelog/v1.json +9 -0
  5. package/locales/ar/setting.json +13 -1
  6. package/locales/bg-BG/setting.json +13 -1
  7. package/locales/de-DE/setting.json +13 -1
  8. package/locales/en-US/setting.json +13 -1
  9. package/locales/es-ES/setting.json +13 -1
  10. package/locales/fa-IR/setting.json +13 -1
  11. package/locales/fr-FR/setting.json +13 -1
  12. package/locales/it-IT/setting.json +13 -1
  13. package/locales/ja-JP/setting.json +13 -1
  14. package/locales/ko-KR/setting.json +13 -1
  15. package/locales/nl-NL/setting.json +13 -1
  16. package/locales/pl-PL/setting.json +13 -1
  17. package/locales/pt-BR/setting.json +13 -1
  18. package/locales/ru-RU/setting.json +13 -1
  19. package/locales/tr-TR/setting.json +13 -1
  20. package/locales/vi-VN/setting.json +13 -1
  21. package/locales/zh-CN/setting.json +13 -1
  22. package/locales/zh-TW/setting.json +13 -1
  23. package/package.json +1 -1
  24. package/src/app/[variants]/(main)/settings/common/features/ChatAppearance/ChatTransitionPreview.tsx +111 -0
  25. package/src/app/[variants]/(main)/settings/common/features/ChatAppearance/index.tsx +50 -3
  26. package/src/components/Thinking/index.tsx +4 -2
  27. package/src/config/modelProviders/anthropic.ts +1 -6
  28. package/src/config/modelProviders/baichuan.ts +4 -8
  29. package/src/config/modelProviders/google.ts +4 -4
  30. package/src/config/modelProviders/lmstudio.ts +4 -4
  31. package/src/config/modelProviders/minimax.ts +3 -3
  32. package/src/config/modelProviders/moonshot.ts +4 -4
  33. package/src/config/modelProviders/openai.ts +1 -3
  34. package/src/config/modelProviders/perplexity.ts +3 -3
  35. package/src/config/modelProviders/qwen.ts +4 -4
  36. package/src/config/modelProviders/search1api.ts +4 -4
  37. package/src/config/modelProviders/spark.ts +4 -4
  38. package/src/config/modelProviders/stepfun.ts +4 -4
  39. package/src/config/modelProviders/vertexai.ts +1 -3
  40. package/src/config/modelProviders/volcengine.ts +4 -4
  41. package/src/config/modelProviders/wenxin.ts +3 -3
  42. package/src/const/settings/common.ts +1 -0
  43. package/src/features/Conversation/Messages/Assistant/Reasoning/index.tsx +11 -1
  44. package/src/features/Conversation/components/ChatItem/index.tsx +6 -2
  45. package/src/features/Conversation/components/MarkdownElements/LobeThinking/Render.tsx +4 -0
  46. package/src/features/Conversation/components/MarkdownElements/Thinking/Render.tsx +12 -1
  47. package/src/locales/default/setting.ts +12 -0
  48. package/src/services/chat.ts +15 -6
  49. package/src/store/user/slices/settings/selectors/general.test.ts +1 -0
  50. package/src/store/user/slices/settings/selectors/general.ts +2 -0
  51. package/src/types/aiProvider.ts +11 -11
  52. package/src/types/llm.ts +8 -10
  53. package/src/types/user/settings/general.ts +3 -0
  54. package/src/utils/fetch/__tests__/fetchSSE.test.ts +57 -12
  55. 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
- smoothing: false,
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
- smoothing: false,
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.skip('should handle text event with smoothing correctly', async () => {
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
- smoothing: true,
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
- smoothing: false,
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
- smoothing: true,
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
- smoothing: true,
353
+ responseAnimation: 'smooth',
321
354
  });
322
355
 
323
- const expectedMessages = [{ text: 'Hello World', type: 'text' }];
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, smoothing: false });
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, smoothing: false });
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, smoothing: false });
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, smoothing: false });
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 { SmoothingParams } from '@/types/llm';
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
- smoothing?: SmoothingParams | boolean;
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 { smoothing } = options;
317
-
318
- const textSmoothing = false;
319
- // const toolsCallingSmoothing = false;
320
- // TODO: 看下后面就是完全移除 smoothing 还是怎么说
321
- // const textSmoothing = typeof smoothing === 'boolean' ? smoothing : (smoothing?.text ?? true);
322
- const toolsCallingSmoothing =
323
- typeof smoothing === 'boolean' ? smoothing : (smoothing?.toolsCalling ?? false);
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 (textSmoothing) {
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();