@lobehub/chat 1.94.3 → 1.94.5

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 (67) 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 +58 -0
  4. package/README.md +1 -1
  5. package/README.zh-CN.md +1 -1
  6. package/changelog/v1.json +18 -0
  7. package/locales/ar/setting.json +13 -1
  8. package/locales/bg-BG/setting.json +13 -1
  9. package/locales/de-DE/setting.json +13 -1
  10. package/locales/en-US/setting.json +13 -1
  11. package/locales/es-ES/setting.json +13 -1
  12. package/locales/fa-IR/setting.json +13 -1
  13. package/locales/fr-FR/setting.json +13 -1
  14. package/locales/it-IT/setting.json +13 -1
  15. package/locales/ja-JP/setting.json +13 -1
  16. package/locales/ko-KR/setting.json +13 -1
  17. package/locales/nl-NL/setting.json +13 -1
  18. package/locales/pl-PL/setting.json +13 -1
  19. package/locales/pt-BR/setting.json +13 -1
  20. package/locales/ru-RU/setting.json +13 -1
  21. package/locales/tr-TR/setting.json +13 -1
  22. package/locales/vi-VN/setting.json +13 -1
  23. package/locales/zh-CN/setting.json +13 -1
  24. package/locales/zh-TW/setting.json +13 -1
  25. package/package.json +3 -2
  26. package/scripts/i18nWorkflow/genDefaultLocale.ts +2 -2
  27. package/scripts/i18nWorkflow/genDiff.ts +8 -9
  28. package/scripts/i18nWorkflow/utils.ts +14 -1
  29. package/src/app/[variants]/(main)/settings/common/features/ChatAppearance/ChatTransitionPreview.tsx +111 -0
  30. package/src/app/[variants]/(main)/settings/common/features/ChatAppearance/index.tsx +50 -3
  31. package/src/components/Thinking/index.tsx +4 -2
  32. package/src/config/aiModels/openai.ts +120 -0
  33. package/src/config/modelProviders/anthropic.ts +1 -6
  34. package/src/config/modelProviders/baichuan.ts +4 -8
  35. package/src/config/modelProviders/google.ts +4 -4
  36. package/src/config/modelProviders/lmstudio.ts +4 -4
  37. package/src/config/modelProviders/minimax.ts +3 -3
  38. package/src/config/modelProviders/moonshot.ts +4 -4
  39. package/src/config/modelProviders/openai.ts +1 -3
  40. package/src/config/modelProviders/perplexity.ts +3 -3
  41. package/src/config/modelProviders/qwen.ts +4 -4
  42. package/src/config/modelProviders/search1api.ts +4 -4
  43. package/src/config/modelProviders/spark.ts +4 -4
  44. package/src/config/modelProviders/stepfun.ts +4 -4
  45. package/src/config/modelProviders/vertexai.ts +1 -3
  46. package/src/config/modelProviders/volcengine.ts +4 -4
  47. package/src/config/modelProviders/wenxin.ts +3 -3
  48. package/src/const/models.ts +26 -1
  49. package/src/const/settings/common.ts +1 -0
  50. package/src/features/Conversation/Messages/Assistant/Reasoning/index.tsx +11 -1
  51. package/src/features/Conversation/components/ChatItem/index.tsx +6 -2
  52. package/src/features/Conversation/components/MarkdownElements/LobeThinking/Render.tsx +4 -0
  53. package/src/features/Conversation/components/MarkdownElements/Thinking/Render.tsx +12 -1
  54. package/src/libs/model-runtime/openai/index.ts +33 -11
  55. package/src/libs/model-runtime/types/chat.ts +2 -0
  56. package/src/libs/model-runtime/utils/openaiCompatibleFactory/index.ts +10 -8
  57. package/src/libs/model-runtime/utils/streams/openai/__snapshots__/responsesStream.test.ts.snap +6 -6
  58. package/src/libs/model-runtime/utils/streams/openai/responsesStream.ts +38 -2
  59. package/src/locales/default/setting.ts +12 -0
  60. package/src/services/chat.ts +19 -6
  61. package/src/store/user/slices/settings/selectors/general.test.ts +1 -0
  62. package/src/store/user/slices/settings/selectors/general.ts +2 -0
  63. package/src/types/aiProvider.ts +11 -11
  64. package/src/types/llm.ts +8 -10
  65. package/src/types/user/settings/general.ts +3 -0
  66. package/src/utils/fetch/__tests__/fetchSSE.test.ts +57 -12
  67. package/src/utils/fetch/fetchSSE.ts +22 -15
package/README.zh-CN.md CHANGED
@@ -328,7 +328,7 @@ LobeChat 的插件生态系统是其核心功能的重要扩展,它极大地
328
328
  | [必应网页搜索](https://lobechat.com/discover/plugin/Bingsearch-identifier)<br/><sup>By **FineHow** on **2024-12-22**</sup> | 通过 BingApi 搜索互联网上的信息<br/>`bingsearch` |
329
329
  | [谷歌自定义搜索引擎](https://lobechat.com/discover/plugin/google-cse)<br/><sup>By **vsnthdev** on **2024-12-02**</sup> | 通过他们的官方自定义搜索引擎 API 搜索谷歌。<br/>`网络` `搜索` |
330
330
 
331
- > 📊 Total plugins: [<kbd>**43**</kbd>](https://lobechat.com/discover/plugins)
331
+ > 📊 Total plugins: [<kbd>**42**</kbd>](https://lobechat.com/discover/plugins)
332
332
 
333
333
  <!-- PLUGIN LIST -->
334
334
 
package/changelog/v1.json CHANGED
@@ -1,4 +1,22 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "improvements": [
5
+ "Support web_search_preview & fix some bug form OpenAI Response API."
6
+ ]
7
+ },
8
+ "date": "2025-06-12",
9
+ "version": "1.94.5"
10
+ },
11
+ {
12
+ "children": {
13
+ "improvements": [
14
+ "Transition animation switch."
15
+ ]
16
+ },
17
+ "date": "2025-06-11",
18
+ "version": "1.94.4"
19
+ },
2
20
  {
3
21
  "children": {},
4
22
  "date": "2025-06-11",
@@ -240,7 +240,19 @@
240
240
  "mermaidTheme": {
241
241
  "title": "موضوع حورية البحر"
242
242
  },
243
- "title": "مظهر الدردشة"
243
+ "title": "مظهر الدردشة",
244
+ "transitionMode": {
245
+ "desc": "رسوم انتقال رسائل الدردشة",
246
+ "options": {
247
+ "fadeIn": "تلاشي",
248
+ "none": {
249
+ "desc": "يعتمد هذا على طريقة إخراج استجابة النموذج، يرجى الاختبار بنفسك.",
250
+ "value": "بدون"
251
+ },
252
+ "smooth": "سلس"
253
+ },
254
+ "title": "رسوم الانتقال"
255
+ }
244
256
  },
245
257
  "settingCommon": {
246
258
  "lang": {
@@ -240,7 +240,19 @@
240
240
  "mermaidTheme": {
241
241
  "title": "Тема русалка"
242
242
  },
243
- "title": "Външен вид на чата"
243
+ "title": "Външен вид на чата",
244
+ "transitionMode": {
245
+ "desc": "Анимация на прехода на съобщенията в чата",
246
+ "options": {
247
+ "fadeIn": "Постепенно появяване",
248
+ "none": {
249
+ "desc": "Зависи от начина на отговор на модела, моля, тествайте сами.",
250
+ "value": "Без"
251
+ },
252
+ "smooth": "Плавно"
253
+ },
254
+ "title": "Анимация на прехода"
255
+ }
244
256
  },
245
257
  "settingCommon": {
246
258
  "lang": {
@@ -240,7 +240,19 @@
240
240
  "mermaidTheme": {
241
241
  "title": "Mermaid-Thema"
242
242
  },
243
- "title": "Chatdesign"
243
+ "title": "Chatdesign",
244
+ "transitionMode": {
245
+ "desc": "Übergangsanimation der Chatnachrichten",
246
+ "options": {
247
+ "fadeIn": "Einblenden",
248
+ "none": {
249
+ "desc": "Dies hängt von der Art der Antwort des Modells ab, bitte testen Sie es selbst.",
250
+ "value": "Keine"
251
+ },
252
+ "smooth": "Sanft"
253
+ },
254
+ "title": "Übergangsanimation"
255
+ }
244
256
  },
245
257
  "settingCommon": {
246
258
  "lang": {
@@ -240,7 +240,19 @@
240
240
  "mermaidTheme": {
241
241
  "title": "Mermaid Theme"
242
242
  },
243
- "title": "Chat Appearance"
243
+ "title": "Chat Appearance",
244
+ "transitionMode": {
245
+ "desc": "Transition animation for chat messages",
246
+ "options": {
247
+ "fadeIn": "Fade In",
248
+ "none": {
249
+ "desc": "This depends on the model's response output method; please test it yourself.",
250
+ "value": "None"
251
+ },
252
+ "smooth": "Smooth"
253
+ },
254
+ "title": "Transition Animation"
255
+ }
244
256
  },
245
257
  "settingCommon": {
246
258
  "lang": {
@@ -240,7 +240,19 @@
240
240
  "mermaidTheme": {
241
241
  "title": "Tema Sirena"
242
242
  },
243
- "title": "Apariencia del Chat"
243
+ "title": "Apariencia del Chat",
244
+ "transitionMode": {
245
+ "desc": "Animación de transición de los mensajes de chat",
246
+ "options": {
247
+ "fadeIn": "Aparecer gradualmente",
248
+ "none": {
249
+ "desc": "Depende de la forma en que el modelo genera la respuesta, por favor pruébelo usted mismo.",
250
+ "value": "Ninguna"
251
+ },
252
+ "smooth": "Suave"
253
+ },
254
+ "title": "Animación de transición"
255
+ }
244
256
  },
245
257
  "settingCommon": {
246
258
  "lang": {
@@ -240,7 +240,19 @@
240
240
  "mermaidTheme": {
241
241
  "title": "تم مرمید"
242
242
  },
243
- "title": "ظاهر چت"
243
+ "title": "ظاهر چت",
244
+ "transitionMode": {
245
+ "desc": "انیمیشن انتقال پیام‌های چت",
246
+ "options": {
247
+ "fadeIn": "نمایش تدریجی",
248
+ "none": {
249
+ "desc": "این بستگی به نحوه خروجی مدل دارد، لطفاً خودتان تست کنید.",
250
+ "value": "بدون انیمیشن"
251
+ },
252
+ "smooth": "نرم"
253
+ },
254
+ "title": "انیمیشن انتقال"
255
+ }
244
256
  },
245
257
  "settingCommon": {
246
258
  "lang": {
@@ -240,7 +240,19 @@
240
240
  "mermaidTheme": {
241
241
  "title": "Thème Sirène"
242
242
  },
243
- "title": "Apparence du chat"
243
+ "title": "Apparence du chat",
244
+ "transitionMode": {
245
+ "desc": "Animation de transition des messages de chat",
246
+ "options": {
247
+ "fadeIn": "Fondu en entrée",
248
+ "none": {
249
+ "desc": "Cela dépend de la manière dont le modèle génère la réponse, veuillez tester par vous-même.",
250
+ "value": "Aucun"
251
+ },
252
+ "smooth": "Fluide"
253
+ },
254
+ "title": "Animation de transition"
255
+ }
244
256
  },
245
257
  "settingCommon": {
246
258
  "lang": {
@@ -240,7 +240,19 @@
240
240
  "mermaidTheme": {
241
241
  "title": "Tema Sirena"
242
242
  },
243
- "title": "Aspetto della Chat"
243
+ "title": "Aspetto della Chat",
244
+ "transitionMode": {
245
+ "desc": "Animazione di transizione dei messaggi di chat",
246
+ "options": {
247
+ "fadeIn": "Dissolvenza in entrata",
248
+ "none": {
249
+ "desc": "Dipende dal modo in cui il modello restituisce la risposta, si prega di testare autonomamente.",
250
+ "value": "Nessuna"
251
+ },
252
+ "smooth": "Fluido"
253
+ },
254
+ "title": "Animazione di transizione"
255
+ }
244
256
  },
245
257
  "settingCommon": {
246
258
  "lang": {
@@ -240,7 +240,19 @@
240
240
  "mermaidTheme": {
241
241
  "title": "マーメイドテーマ"
242
242
  },
243
- "title": "チャットの外観"
243
+ "title": "チャットの外観",
244
+ "transitionMode": {
245
+ "desc": "チャットメッセージの遷移アニメーション",
246
+ "options": {
247
+ "fadeIn": "フェードイン",
248
+ "none": {
249
+ "desc": "これはモデルの応答出力方法によりますので、ご自身でテストしてください。",
250
+ "value": "なし"
251
+ },
252
+ "smooth": "スムーズ"
253
+ },
254
+ "title": "遷移アニメーション"
255
+ }
244
256
  },
245
257
  "settingCommon": {
246
258
  "lang": {
@@ -240,7 +240,19 @@
240
240
  "mermaidTheme": {
241
241
  "title": "인어 테마"
242
242
  },
243
- "title": "채팅 외관"
243
+ "title": "채팅 외관",
244
+ "transitionMode": {
245
+ "desc": "채팅 메시지의 전환 애니메이션",
246
+ "options": {
247
+ "fadeIn": "서서히 나타남",
248
+ "none": {
249
+ "desc": "이는 모델의 응답 출력 방식에 따라 다르므로 직접 테스트해 보세요.",
250
+ "value": "없음"
251
+ },
252
+ "smooth": "부드럽게"
253
+ },
254
+ "title": "전환 애니메이션"
255
+ }
244
256
  },
245
257
  "settingCommon": {
246
258
  "lang": {
@@ -240,7 +240,19 @@
240
240
  "mermaidTheme": {
241
241
  "title": "Mermaid-thema"
242
242
  },
243
- "title": "Chat uiterlijk"
243
+ "title": "Chat uiterlijk",
244
+ "transitionMode": {
245
+ "desc": "Overgangsanimatie van chatberichten",
246
+ "options": {
247
+ "fadeIn": "Vervagen",
248
+ "none": {
249
+ "desc": "Dit hangt af van de manier waarop het model reageert; test dit zelf.",
250
+ "value": "Geen"
251
+ },
252
+ "smooth": "Vloeiend"
253
+ },
254
+ "title": "Overgangsanimatie"
255
+ }
244
256
  },
245
257
  "settingCommon": {
246
258
  "lang": {
@@ -240,7 +240,19 @@
240
240
  "mermaidTheme": {
241
241
  "title": "Motyw Syreny"
242
242
  },
243
- "title": "Wygląd czatu"
243
+ "title": "Wygląd czatu",
244
+ "transitionMode": {
245
+ "desc": "Animacja przejścia wiadomości czatu",
246
+ "options": {
247
+ "fadeIn": "Pojawianie się",
248
+ "none": {
249
+ "desc": "To zależy od sposobu generowania odpowiedzi przez model, proszę przetestować samodzielnie.",
250
+ "value": "Brak"
251
+ },
252
+ "smooth": "Płynne"
253
+ },
254
+ "title": "Animacja przejścia"
255
+ }
244
256
  },
245
257
  "settingCommon": {
246
258
  "lang": {
@@ -240,7 +240,19 @@
240
240
  "mermaidTheme": {
241
241
  "title": "Tema Sereia"
242
242
  },
243
- "title": "Aparência do Chat"
243
+ "title": "Aparência do Chat",
244
+ "transitionMode": {
245
+ "desc": "Animação de transição das mensagens do chat",
246
+ "options": {
247
+ "fadeIn": "Desvanecer",
248
+ "none": {
249
+ "desc": "Depende da forma como o modelo responde, por favor teste por conta própria.",
250
+ "value": "Nenhuma"
251
+ },
252
+ "smooth": "Suave"
253
+ },
254
+ "title": "Animação de transição"
255
+ }
244
256
  },
245
257
  "settingCommon": {
246
258
  "lang": {
@@ -240,7 +240,19 @@
240
240
  "mermaidTheme": {
241
241
  "title": "Тема Русалки"
242
242
  },
243
- "title": "Внешний вид чата"
243
+ "title": "Внешний вид чата",
244
+ "transitionMode": {
245
+ "desc": "Анимация перехода сообщений чата",
246
+ "options": {
247
+ "fadeIn": "Появление",
248
+ "none": {
249
+ "desc": "Зависит от способа вывода ответа модели, рекомендуется протестировать самостоятельно.",
250
+ "value": "Нет"
251
+ },
252
+ "smooth": "Плавно"
253
+ },
254
+ "title": "Анимация перехода"
255
+ }
244
256
  },
245
257
  "settingCommon": {
246
258
  "lang": {
@@ -240,7 +240,19 @@
240
240
  "mermaidTheme": {
241
241
  "title": "Deniz Kızı Teması"
242
242
  },
243
- "title": "Sohbet Görünümü"
243
+ "title": "Sohbet Görünümü",
244
+ "transitionMode": {
245
+ "desc": "Sohbet mesajlarının geçiş animasyonu",
246
+ "options": {
247
+ "fadeIn": "Solma",
248
+ "none": {
249
+ "desc": "Bu, modelin yanıt çıktısına bağlıdır, lütfen kendiniz test edin.",
250
+ "value": "Yok"
251
+ },
252
+ "smooth": "Yumuşak"
253
+ },
254
+ "title": "Geçiş Animasyonu"
255
+ }
244
256
  },
245
257
  "settingCommon": {
246
258
  "lang": {
@@ -240,7 +240,19 @@
240
240
  "mermaidTheme": {
241
241
  "title": "Chủ đề Nàng Tiên Cá"
242
242
  },
243
- "title": "Giao diện trò chuyện"
243
+ "title": "Giao diện trò chuyện",
244
+ "transitionMode": {
245
+ "desc": "Hiệu ứng chuyển tiếp của tin nhắn trò chuyện",
246
+ "options": {
247
+ "fadeIn": "Mờ dần",
248
+ "none": {
249
+ "desc": "Điều này phụ thuộc vào cách mô hình phản hồi, vui lòng tự kiểm tra.",
250
+ "value": "Không"
251
+ },
252
+ "smooth": "Mượt mà"
253
+ },
254
+ "title": "Hiệu ứng chuyển tiếp"
255
+ }
244
256
  },
245
257
  "settingCommon": {
246
258
  "lang": {
@@ -240,7 +240,19 @@
240
240
  "mermaidTheme": {
241
241
  "title": "Mermaid 主题"
242
242
  },
243
- "title": "聊天外观"
243
+ "title": "聊天外观",
244
+ "transitionMode": {
245
+ "desc": "聊天消息的过渡动画",
246
+ "options": {
247
+ "fadeIn": "淡入",
248
+ "none": {
249
+ "desc": "这取决于模型的响应输出方式,请自行测试。",
250
+ "value": "无"
251
+ },
252
+ "smooth": "平滑"
253
+ },
254
+ "title": "过渡动画"
255
+ }
244
256
  },
245
257
  "settingCommon": {
246
258
  "lang": {
@@ -240,7 +240,19 @@
240
240
  "mermaidTheme": {
241
241
  "title": "美人魚主題"
242
242
  },
243
- "title": "聊天外觀"
243
+ "title": "聊天外觀",
244
+ "transitionMode": {
245
+ "desc": "聊天訊息的過渡動畫",
246
+ "options": {
247
+ "fadeIn": "淡入",
248
+ "none": {
249
+ "desc": "這取決於模型的回應輸出方式,請自行測試。",
250
+ "value": "無"
251
+ },
252
+ "smooth": "平滑"
253
+ },
254
+ "title": "過渡動畫"
255
+ }
244
256
  },
245
257
  "settingCommon": {
246
258
  "lang": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.94.3",
3
+ "version": "1.94.5",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -54,7 +54,7 @@
54
54
  "dev:desktop": "next dev --turbopack -p 3015",
55
55
  "docs:i18n": "lobe-i18n md && npm run lint:md && npm run lint:mdx && prettier -c --write locales/**/*",
56
56
  "docs:seo": "lobe-seo && npm run lint:mdx",
57
- "i18n": "npm run workflow:i18n && lobe-i18n",
57
+ "i18n": "npm run workflow:i18n && lobe-i18n && prettier -c --write \"locales/**\"",
58
58
  "lint": "npm run lint:ts && npm run lint:style && npm run type-check && npm run lint:circular",
59
59
  "lint:circular": "dpdm src/**/*.ts --no-warning --no-tree --exit-code circular:1 --no-progress -T true --skip-dynamic-imports circular",
60
60
  "lint:md": "remark . --silent --output",
@@ -282,6 +282,7 @@
282
282
  "@next/bundle-analyzer": "^15.3.3",
283
283
  "@next/eslint-plugin-next": "^15.3.3",
284
284
  "@peculiar/webcrypto": "^1.5.0",
285
+ "@prettier/sync": "^0.6.1",
285
286
  "@semantic-release/exec": "^6.0.3",
286
287
  "@testing-library/jest-dom": "^6.6.3",
287
288
  "@testing-library/react": "^16.3.0",
@@ -2,7 +2,7 @@ import { consola } from 'consola';
2
2
  import { colors } from 'consola/utils';
3
3
 
4
4
  import { entryLocaleJsonFilepath, i18nConfig, srcDefaultLocales } from './const';
5
- import { tagWhite, writeJSON } from './utils';
5
+ import { tagWhite, writeJSONWithPrettier } from './utils';
6
6
 
7
7
  export const genDefaultLocale = () => {
8
8
  consola.info(`Default locale is ${i18nConfig.entryLocale}...`);
@@ -13,7 +13,7 @@ export const genDefaultLocale = () => {
13
13
 
14
14
  for (const [ns, value] of data) {
15
15
  const filepath = entryLocaleJsonFilepath(`${ns}.json`);
16
- writeJSON(filepath, value);
16
+ writeJSONWithPrettier(filepath, value);
17
17
  consola.success(tagWhite(ns), colors.gray(filepath));
18
18
  }
19
19
  };
@@ -10,10 +10,10 @@ import {
10
10
  outputLocaleJsonFilepath,
11
11
  srcDefaultLocales,
12
12
  } from './const';
13
- import { readJSON, tagWhite, writeJSON } from './utils';
13
+ import { readJSON, tagWhite, writeJSONWithPrettier } from './utils';
14
14
 
15
15
  export const genDiff = () => {
16
- consola.start(`Diff between Dev/Prod local...`);
16
+ consola.start(`Remove diff analysis...`);
17
17
 
18
18
  const resources = require(srcDefaultLocales);
19
19
  const data = Object.entries(resources.default);
@@ -21,27 +21,26 @@ export const genDiff = () => {
21
21
  for (const [ns, devJSON] of data) {
22
22
  const filepath = entryLocaleJsonFilepath(`${ns}.json`);
23
23
  if (!existsSync(filepath)) continue;
24
- const prodJSON = readJSON(filepath);
24
+ const previousProdJSON = readJSON(filepath);
25
25
 
26
- const diffResult = diff(prodJSON, devJSON as any);
27
- const remove = diffResult.filter((item) => item.op === 'remove');
28
- if (remove.length === 0) {
26
+ const diffResult = diff(previousProdJSON, devJSON as any);
27
+ if (diffResult.length === 0) {
29
28
  consola.success(tagWhite(ns), colors.gray(filepath));
30
29
  continue;
31
30
  }
32
31
 
33
32
  const clearLocals = [];
34
33
 
35
- for (const locale of [i18nConfig.entryLocale, ...i18nConfig.outputLocales]) {
34
+ for (const locale of i18nConfig.outputLocales) {
36
35
  const localeFilepath = outputLocaleJsonFilepath(locale, `${ns}.json`);
37
36
  if (!existsSync(localeFilepath)) continue;
38
37
  const localeJSON = readJSON(localeFilepath);
39
38
 
40
- for (const item of remove) {
39
+ for (const item of diffResult) {
41
40
  unset(localeJSON, item.path);
42
41
  }
43
42
 
44
- writeJSON(localeFilepath, localeJSON);
43
+ writeJSONWithPrettier(localeFilepath, localeJSON);
45
44
  clearLocals.push(locale);
46
45
  }
47
46
  consola.info('clear', clearLocals);
@@ -2,9 +2,13 @@ import { consola } from 'consola';
2
2
  import { colors } from 'consola/utils';
3
3
  import { readFileSync, writeFileSync } from 'node:fs';
4
4
  import { resolve } from 'node:path';
5
-
5
+ import prettier from "@prettier/sync";
6
6
  import i18nConfig from '../../.i18nrc';
7
7
 
8
+ let prettierOptions = prettier.resolveConfig(
9
+ resolve(__dirname, '../../.prettierrc.js')
10
+ );
11
+
8
12
  export const readJSON = (filePath: string) => {
9
13
  const data = readFileSync(filePath, 'utf8');
10
14
  return JSON.parse(data);
@@ -15,6 +19,15 @@ export const writeJSON = (filePath: string, data: any) => {
15
19
  writeFileSync(filePath, jsonStr, 'utf8');
16
20
  };
17
21
 
22
+ export const writeJSONWithPrettier = (filePath: string, data: any) => {
23
+ const jsonStr = JSON.stringify(data, null, 2);
24
+ const formatted = prettier.format(jsonStr, {
25
+ ...prettierOptions,
26
+ parser: 'json',
27
+ });
28
+ writeFileSync(filePath, formatted, 'utf8');
29
+ };
30
+
18
31
  export const genResourcesContent = (locales: string[]) => {
19
32
  let index = '';
20
33
  let indexObj = '';
@@ -0,0 +1,111 @@
1
+ import { ActionIconGroup, Block } from '@lobehub/ui';
2
+ import { ChatItem } from '@lobehub/ui/chat';
3
+ import { useTheme } from 'antd-style';
4
+ import { RotateCwIcon } from 'lucide-react';
5
+ import { memo, useEffect, useMemo, useState } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+
8
+ import { DEFAULT_INBOX_AVATAR } from '@/const/meta';
9
+ import { UserGeneralConfig } from '@/types/user/settings';
10
+
11
+ const data = `
12
+ ### Features
13
+
14
+ **Key Highlights**
15
+ - 🌐 Multi-model: GPT-4/Gemini/Ollama
16
+ - 🖼️ Vision: \`gpt-4-vision\` integration
17
+ - 🛠️ Plugins: Function Calling & real-time data
18
+ `;
19
+
20
+ const streamingSpeed = 25; // ms per character
21
+
22
+ interface ChatTransitionPreviewProps {
23
+ mode: UserGeneralConfig['transitionMode'];
24
+ }
25
+
26
+ const randomInlRange = (min = 0, max = min + 10) => {
27
+ return Math.floor(Math.random() * (max - min + 1)) + min;
28
+ };
29
+
30
+ const ChatTransitionPreview = memo<ChatTransitionPreviewProps>(({ mode }) => {
31
+ const [streamedContent, setStreamedContent] = useState(() => {
32
+ if (mode === 'none') {
33
+ return data.slice(0, Math.max(0, randomInlRange(10, 100)));
34
+ }
35
+ return '';
36
+ });
37
+
38
+ const chunkStep = useMemo(() => {
39
+ if (mode === 'none') {
40
+ return Math.ceil(data.length / randomInlRange(3, 5));
41
+ }
42
+ return 3;
43
+ }, [mode]);
44
+
45
+ const [isStreaming, setIsStreaming] = useState(true);
46
+ const { t } = useTranslation('common');
47
+ const token = useTheme();
48
+
49
+ useEffect(() => {
50
+ if (!isStreaming) return;
51
+
52
+ let currentPosition = 0;
53
+ if (streamedContent.length > 0) {
54
+ currentPosition = streamedContent.length;
55
+ }
56
+
57
+ const intervalId = setInterval(() => {
58
+ if (currentPosition < data.length) {
59
+ // Stream character by character
60
+ const nextChunkSize = Math.min(chunkStep, data.length - currentPosition);
61
+ const nextContent = data.slice(0, Math.max(0, currentPosition + nextChunkSize));
62
+ setStreamedContent(nextContent);
63
+ currentPosition += nextChunkSize;
64
+ } else {
65
+ clearInterval(intervalId);
66
+ setIsStreaming(false);
67
+ }
68
+ }, streamingSpeed);
69
+
70
+ return () => clearInterval(intervalId);
71
+ }, [isStreaming, streamedContent.length, chunkStep]);
72
+
73
+ const handleReset = () => {
74
+ setStreamedContent('');
75
+ setIsStreaming(true);
76
+ };
77
+
78
+ return (
79
+ <Block
80
+ style={{
81
+ background: token.colorBgContainerSecondary,
82
+ marginBlock: 16,
83
+ minHeight: 280,
84
+ paddingBottom: 16,
85
+ }}
86
+ >
87
+ <ChatItem
88
+ actions={
89
+ <ActionIconGroup
90
+ items={[
91
+ {
92
+ icon: RotateCwIcon,
93
+ key: 'reset',
94
+ onClick: handleReset,
95
+ title: t('retry'),
96
+ },
97
+ ]}
98
+ size="small"
99
+ />
100
+ }
101
+ avatar={{ avatar: DEFAULT_INBOX_AVATAR }}
102
+ markdownProps={{ animated: mode === 'fadeIn' }}
103
+ message={streamedContent}
104
+ variant="bubble"
105
+ width={'100%'}
106
+ />
107
+ </Block>
108
+ );
109
+ });
110
+
111
+ export default ChatTransitionPreview;