@lobehub/chat 1.127.3 → 1.128.0

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 (38) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/locales/ar/editor.json +9 -1
  4. package/locales/bg-BG/editor.json +9 -1
  5. package/locales/de-DE/editor.json +9 -1
  6. package/locales/en-US/editor.json +9 -1
  7. package/locales/es-ES/editor.json +9 -1
  8. package/locales/fa-IR/editor.json +9 -1
  9. package/locales/fr-FR/editor.json +9 -1
  10. package/locales/it-IT/editor.json +9 -1
  11. package/locales/ja-JP/editor.json +9 -1
  12. package/locales/ko-KR/editor.json +9 -1
  13. package/locales/nl-NL/editor.json +9 -1
  14. package/locales/pl-PL/editor.json +9 -1
  15. package/locales/pt-BR/editor.json +9 -1
  16. package/locales/ru-RU/editor.json +9 -1
  17. package/locales/tr-TR/editor.json +9 -1
  18. package/locales/vi-VN/editor.json +9 -1
  19. package/locales/zh-CN/editor.json +9 -1
  20. package/locales/zh-TW/editor.json +9 -1
  21. package/package.json +3 -3
  22. package/packages/model-bank/src/aiModels/qwen.ts +46 -0
  23. package/packages/model-runtime/package.json +2 -1
  24. package/packages/model-runtime/src/const/modelProvider.ts +1 -0
  25. package/packages/model-runtime/src/core/RouterRuntime/createRuntime.ts +2 -0
  26. package/packages/model-runtime/src/core/RouterRuntime/index.ts +1 -0
  27. package/packages/model-runtime/src/core/streams/openai/openai.test.ts +36 -2
  28. package/packages/model-runtime/src/core/streams/openai/openai.ts +3 -2
  29. package/packages/model-runtime/src/index.ts +1 -0
  30. package/packages/types/src/user/settings/keyVaults.ts +1 -0
  31. package/src/features/ChatInput/Desktop/index.tsx +13 -5
  32. package/src/features/ChatInput/InputEditor/Placeholder.tsx +42 -0
  33. package/src/features/ChatInput/InputEditor/index.tsx +6 -41
  34. package/src/features/ChatInput/InputEditor/useSlashItems.tsx +92 -0
  35. package/src/features/ChatInput/TypoBar/index.tsx +1 -8
  36. package/src/locales/default/editor.ts +9 -1
  37. package/src/store/global/initialState.ts +2 -0
  38. package/src/store/global/selectors/systemStatus.ts +2 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 1.128.0](https://github.com/lobehub/lobe-chat/compare/v1.127.4...v1.128.0)
6
+
7
+ <sup>Released on **2025-09-12**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **misc**: ChatInput support resize.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's improved
19
+
20
+ - **misc**: ChatInput support resize, closes [#9215](https://github.com/lobehub/lobe-chat/issues/9215) ([5e814e0](https://github.com/lobehub/lobe-chat/commit/5e814e0))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
30
+ ### [Version 1.127.4](https://github.com/lobehub/lobe-chat/compare/v1.127.3...v1.127.4)
31
+
32
+ <sup>Released on **2025-09-11**</sup>
33
+
34
+ #### 🐛 Bug Fixes
35
+
36
+ - **misc**: Improve OpenAIStream processing to emit usage data for chunks lacking choices.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### What's fixed
44
+
45
+ - **misc**: Improve OpenAIStream processing to emit usage data for chunks lacking choices, closes [#9220](https://github.com/lobehub/lobe-chat/issues/9220) ([8ba662c](https://github.com/lobehub/lobe-chat/commit/8ba662c))
46
+
47
+ </details>
48
+
49
+ <div align="right">
50
+
51
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
52
+
53
+ </div>
54
+
5
55
  ### [Version 1.127.3](https://github.com/lobehub/lobe-chat/compare/v1.127.2...v1.127.3)
6
56
 
7
57
  <sup>Released on **2025-09-11**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,22 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "features": [
5
+ "ChatInput support resize."
6
+ ]
7
+ },
8
+ "date": "2025-09-12",
9
+ "version": "1.128.0"
10
+ },
11
+ {
12
+ "children": {
13
+ "fixes": [
14
+ "Improve OpenAIStream processing to emit usage data for chunks lacking choices."
15
+ ]
16
+ },
17
+ "date": "2025-09-11",
18
+ "version": "1.127.4"
19
+ },
2
20
  {
3
21
  "children": {
4
22
  "improvements": [
@@ -27,6 +27,14 @@
27
27
  "math": {
28
28
  "placeholder": "يرجى إدخال معادلة TeX"
29
29
  },
30
+ "slash": {
31
+ "h1": "عنوان رئيسي من المستوى الأول",
32
+ "h2": "عنوان فرعي من المستوى الثاني",
33
+ "h3": "عنوان فرعي من المستوى الثالث",
34
+ "hr": "خط فاصل",
35
+ "table": "جدول",
36
+ "tex": "معادلة TeX"
37
+ },
30
38
  "table": {
31
39
  "delete": "حذف الجدول",
32
40
  "deleteColumn": "حذف العمود",
@@ -46,7 +54,7 @@
46
54
  "link": "رابط",
47
55
  "numberList": "قائمة مرقمة",
48
56
  "strikethrough": "شطب",
49
- "table": "إدراج جدول",
57
+ "table": "جدول",
50
58
  "taskList": "قائمة المهام",
51
59
  "tex": "معادلة TeX",
52
60
  "underline": "تسطير"
@@ -27,6 +27,14 @@
27
27
  "math": {
28
28
  "placeholder": "Моля, въведете TeX формула"
29
29
  },
30
+ "slash": {
31
+ "h1": "Заглавие ниво 1",
32
+ "h2": "Заглавие ниво 2",
33
+ "h3": "Заглавие ниво 3",
34
+ "hr": "Разделителна линия",
35
+ "table": "Таблица",
36
+ "tex": "TeX формула"
37
+ },
30
38
  "table": {
31
39
  "delete": "Премахни таблицата",
32
40
  "deleteColumn": "Премахни колоната",
@@ -46,7 +54,7 @@
46
54
  "link": "Връзка",
47
55
  "numberList": "Номериран списък",
48
56
  "strikethrough": "Зачеркване",
49
- "table": "Вмъкване на таблица",
57
+ "table": "таблица",
50
58
  "taskList": "Списък със задачи",
51
59
  "tex": "TeX формула",
52
60
  "underline": "Подчертаване"
@@ -27,6 +27,14 @@
27
27
  "math": {
28
28
  "placeholder": "Bitte TeX-Formel eingeben"
29
29
  },
30
+ "slash": {
31
+ "h1": "Überschrift 1. Ordnung",
32
+ "h2": "Überschrift 2. Ordnung",
33
+ "h3": "Überschrift 3. Ordnung",
34
+ "hr": "Trennlinie",
35
+ "table": "Tabelle",
36
+ "tex": "TeX-Formel"
37
+ },
30
38
  "table": {
31
39
  "delete": "Tabelle löschen",
32
40
  "deleteColumn": "Spalte löschen",
@@ -46,7 +54,7 @@
46
54
  "link": "Link",
47
55
  "numberList": "Nummerierte Liste",
48
56
  "strikethrough": "Durchgestrichen",
49
- "table": "Tabelle einfügen",
57
+ "table": "Tabelle",
50
58
  "taskList": "Aufgabenliste",
51
59
  "tex": "TeX-Formel",
52
60
  "underline": "Unterstrichen"
@@ -27,6 +27,14 @@
27
27
  "math": {
28
28
  "placeholder": "Please enter a TeX formula"
29
29
  },
30
+ "slash": {
31
+ "h1": "Heading 1",
32
+ "h2": "Heading 2",
33
+ "h3": "Heading 3",
34
+ "hr": "Divider",
35
+ "table": "Table",
36
+ "tex": "TeX Formula"
37
+ },
30
38
  "table": {
31
39
  "delete": "Delete table",
32
40
  "deleteColumn": "Delete column",
@@ -46,7 +54,7 @@
46
54
  "link": "Link",
47
55
  "numberList": "Numbered list",
48
56
  "strikethrough": "Strikethrough",
49
- "table": "Insert table",
57
+ "table": "Table",
50
58
  "taskList": "Task List",
51
59
  "tex": "TeX Formula",
52
60
  "underline": "Underline"
@@ -27,6 +27,14 @@
27
27
  "math": {
28
28
  "placeholder": "Por favor, introduzca la fórmula TeX"
29
29
  },
30
+ "slash": {
31
+ "h1": "Título de nivel 1",
32
+ "h2": "Título de nivel 2",
33
+ "h3": "Título de nivel 3",
34
+ "hr": "Línea divisoria",
35
+ "table": "Tabla",
36
+ "tex": "Fórmula TeX"
37
+ },
30
38
  "table": {
31
39
  "delete": "Eliminar tabla",
32
40
  "deleteColumn": "Eliminar columna",
@@ -46,7 +54,7 @@
46
54
  "link": "Enlace",
47
55
  "numberList": "Lista ordenada",
48
56
  "strikethrough": "Tachado",
49
- "table": "Insertar tabla",
57
+ "table": "tabla",
50
58
  "taskList": "Lista de tareas",
51
59
  "tex": "Fórmula TeX",
52
60
  "underline": "Subrayado"
@@ -27,6 +27,14 @@
27
27
  "math": {
28
28
  "placeholder": "لطفاً فرمول TeX را وارد کنید"
29
29
  },
30
+ "slash": {
31
+ "h1": "عنوان سطح یک",
32
+ "h2": "عنوان سطح دو",
33
+ "h3": "عنوان سطح سه",
34
+ "hr": "خط جداکننده",
35
+ "table": "جدول",
36
+ "tex": "فرمول TeX"
37
+ },
30
38
  "table": {
31
39
  "delete": "حذف جدول",
32
40
  "deleteColumn": "حذف ستون",
@@ -46,7 +54,7 @@
46
54
  "link": "پیوند",
47
55
  "numberList": "فهرست شماره‌دار",
48
56
  "strikethrough": "خط خورده",
49
- "table": "درج جدول",
57
+ "table": "جدول",
50
58
  "taskList": "فهرست وظایف",
51
59
  "tex": "فرمول TeX",
52
60
  "underline": "زیرخط"
@@ -27,6 +27,14 @@
27
27
  "math": {
28
28
  "placeholder": "Veuillez saisir une formule TeX"
29
29
  },
30
+ "slash": {
31
+ "h1": "Titre de niveau 1",
32
+ "h2": "Titre de niveau 2",
33
+ "h3": "Titre de niveau 3",
34
+ "hr": "Ligne de séparation",
35
+ "table": "Tableau",
36
+ "tex": "Formule TeX"
37
+ },
30
38
  "table": {
31
39
  "delete": "Supprimer le tableau",
32
40
  "deleteColumn": "Supprimer la colonne",
@@ -46,7 +54,7 @@
46
54
  "link": "Lien",
47
55
  "numberList": "Liste numérotée",
48
56
  "strikethrough": "Barré",
49
- "table": "Insérer un tableau",
57
+ "table": "tableau",
50
58
  "taskList": "Liste des tâches",
51
59
  "tex": "Formule TeX",
52
60
  "underline": "Souligné"
@@ -27,6 +27,14 @@
27
27
  "math": {
28
28
  "placeholder": "Inserisci la formula TeX"
29
29
  },
30
+ "slash": {
31
+ "h1": "Titolo di primo livello",
32
+ "h2": "Titolo di secondo livello",
33
+ "h3": "Titolo di terzo livello",
34
+ "hr": "Linea di separazione",
35
+ "table": "Tabella",
36
+ "tex": "Formula TeX"
37
+ },
30
38
  "table": {
31
39
  "delete": "Elimina tabella",
32
40
  "deleteColumn": "Elimina colonna",
@@ -46,7 +54,7 @@
46
54
  "link": "Collegamento",
47
55
  "numberList": "Elenco numerato",
48
56
  "strikethrough": "Testo barrato",
49
- "table": "Inserisci tabella",
57
+ "table": "tabella",
50
58
  "taskList": "Elenco attività",
51
59
  "tex": "Formula TeX",
52
60
  "underline": "Sottolineato"
@@ -27,6 +27,14 @@
27
27
  "math": {
28
28
  "placeholder": "TeX 公式を入力してください"
29
29
  },
30
+ "slash": {
31
+ "h1": "見出し1",
32
+ "h2": "見出し2",
33
+ "h3": "見出し3",
34
+ "hr": "区切り線",
35
+ "table": "表",
36
+ "tex": "TeX 数式"
37
+ },
30
38
  "table": {
31
39
  "delete": "表を削除",
32
40
  "deleteColumn": "列を削除",
@@ -46,7 +54,7 @@
46
54
  "link": "リンク",
47
55
  "numberList": "番号付きリスト",
48
56
  "strikethrough": "取り消し線",
49
- "table": "表を挿入",
57
+ "table": "",
50
58
  "taskList": "タスクリスト",
51
59
  "tex": "TeX 公式",
52
60
  "underline": "下線"
@@ -27,6 +27,14 @@
27
27
  "math": {
28
28
  "placeholder": "TeX 수식을 입력하세요"
29
29
  },
30
+ "slash": {
31
+ "h1": "1단계 제목",
32
+ "h2": "2단계 제목",
33
+ "h3": "3단계 제목",
34
+ "hr": "구분선",
35
+ "table": "표",
36
+ "tex": "TeX 수식"
37
+ },
30
38
  "table": {
31
39
  "delete": "테이블 삭제",
32
40
  "deleteColumn": "열 삭제",
@@ -46,7 +54,7 @@
46
54
  "link": "링크",
47
55
  "numberList": "번호 매긴 목록",
48
56
  "strikethrough": "취소선",
49
- "table": "표 삽입",
57
+ "table": "표",
50
58
  "taskList": "작업 목록",
51
59
  "tex": "TeX 수식",
52
60
  "underline": "밑줄"
@@ -27,6 +27,14 @@
27
27
  "math": {
28
28
  "placeholder": "Voer TeX-formule in"
29
29
  },
30
+ "slash": {
31
+ "h1": "Kop 1",
32
+ "h2": "Kop 2",
33
+ "h3": "Kop 3",
34
+ "hr": "Scheidingslijn",
35
+ "table": "Tabel",
36
+ "tex": "TeX-formule"
37
+ },
30
38
  "table": {
31
39
  "delete": "Tabel verwijderen",
32
40
  "deleteColumn": "Kolom verwijderen",
@@ -46,7 +54,7 @@
46
54
  "link": "Link",
47
55
  "numberList": "Genummerde lijst",
48
56
  "strikethrough": "Doorhalen",
49
- "table": "Tabel invoegen",
57
+ "table": "tabel",
50
58
  "taskList": "Takenlijst",
51
59
  "tex": "TeX-formule",
52
60
  "underline": "Onderstrepen"
@@ -27,6 +27,14 @@
27
27
  "math": {
28
28
  "placeholder": "Wprowadź formułę TeX"
29
29
  },
30
+ "slash": {
31
+ "h1": "Nagłówek poziomu 1",
32
+ "h2": "Nagłówek poziomu 2",
33
+ "h3": "Nagłówek poziomu 3",
34
+ "hr": "Linia pozioma",
35
+ "table": "Tabela",
36
+ "tex": "Formuła TeX"
37
+ },
30
38
  "table": {
31
39
  "delete": "Usuń tabelę",
32
40
  "deleteColumn": "Usuń kolumnę",
@@ -46,7 +54,7 @@
46
54
  "link": "Link",
47
55
  "numberList": "Lista numerowana",
48
56
  "strikethrough": "Przekreślenie",
49
- "table": "Wstaw tabelę",
57
+ "table": "tabela",
50
58
  "taskList": "Lista zadań",
51
59
  "tex": "Formuła TeX",
52
60
  "underline": "Podkreślenie"
@@ -27,6 +27,14 @@
27
27
  "math": {
28
28
  "placeholder": "Por favor, insira a fórmula TeX"
29
29
  },
30
+ "slash": {
31
+ "h1": "Título de Nível 1",
32
+ "h2": "Título de Nível 2",
33
+ "h3": "Título de Nível 3",
34
+ "hr": "Linha Divisória",
35
+ "table": "Tabela",
36
+ "tex": "Fórmula TeX"
37
+ },
30
38
  "table": {
31
39
  "delete": "Excluir tabela",
32
40
  "deleteColumn": "Excluir coluna",
@@ -46,7 +54,7 @@
46
54
  "link": "Link",
47
55
  "numberList": "Lista numerada",
48
56
  "strikethrough": "Tachado",
49
- "table": "Inserir tabela",
57
+ "table": "tabela",
50
58
  "taskList": "Lista de Tarefas",
51
59
  "tex": "Fórmula TeX",
52
60
  "underline": "Sublinhado"
@@ -27,6 +27,14 @@
27
27
  "math": {
28
28
  "placeholder": "Введите формулу TeX"
29
29
  },
30
+ "slash": {
31
+ "h1": "Заголовок первого уровня",
32
+ "h2": "Заголовок второго уровня",
33
+ "h3": "Заголовок третьего уровня",
34
+ "hr": "Разделитель",
35
+ "table": "Таблица",
36
+ "tex": "Формула TeX"
37
+ },
30
38
  "table": {
31
39
  "delete": "Удалить таблицу",
32
40
  "deleteColumn": "Удалить столбец",
@@ -46,7 +54,7 @@
46
54
  "link": "Ссылка",
47
55
  "numberList": "Нумерованный список",
48
56
  "strikethrough": "Зачёркнутый",
49
- "table": "Вставить таблицу",
57
+ "table": "таблица",
50
58
  "taskList": "Список задач",
51
59
  "tex": "Формула TeX",
52
60
  "underline": "Подчёркнутый"
@@ -27,6 +27,14 @@
27
27
  "math": {
28
28
  "placeholder": "Lütfen TeX formülünü girin"
29
29
  },
30
+ "slash": {
31
+ "h1": "Birinci Seviye Başlık",
32
+ "h2": "İkinci Seviye Başlık",
33
+ "h3": "Üçüncü Seviye Başlık",
34
+ "hr": "Ayırıcı Çizgi",
35
+ "table": "Tablo",
36
+ "tex": "TeX Formülü"
37
+ },
30
38
  "table": {
31
39
  "delete": "Tabloyu sil",
32
40
  "deleteColumn": "Sütunu sil",
@@ -46,7 +54,7 @@
46
54
  "link": "Bağlantı",
47
55
  "numberList": "Numaralı liste",
48
56
  "strikethrough": "Üstü çizili",
49
- "table": "Tablo ekle",
57
+ "table": "tablo",
50
58
  "taskList": "Görev Listesi",
51
59
  "tex": "TeX Formülü",
52
60
  "underline": "Altı çizili"
@@ -27,6 +27,14 @@
27
27
  "math": {
28
28
  "placeholder": "Vui lòng nhập công thức TeX"
29
29
  },
30
+ "slash": {
31
+ "h1": "Tiêu đề cấp 1",
32
+ "h2": "Tiêu đề cấp 2",
33
+ "h3": "Tiêu đề cấp 3",
34
+ "hr": "Đường phân cách",
35
+ "table": "Bảng",
36
+ "tex": "Công thức TeX"
37
+ },
30
38
  "table": {
31
39
  "delete": "Xóa bảng",
32
40
  "deleteColumn": "Xóa cột",
@@ -46,7 +54,7 @@
46
54
  "link": "Liên kết",
47
55
  "numberList": "Danh sách có thứ tự",
48
56
  "strikethrough": "Gạch ngang",
49
- "table": "Chèn bảng",
57
+ "table": "bảng",
50
58
  "taskList": "Danh sách công việc",
51
59
  "tex": "Công thức TeX",
52
60
  "underline": "Gạch chân"
@@ -27,6 +27,14 @@
27
27
  "math": {
28
28
  "placeholder": "请输入 TeX 公式"
29
29
  },
30
+ "slash": {
31
+ "h1": "一级标题",
32
+ "h2": "二级标题",
33
+ "h3": "三级标题",
34
+ "hr": "分割线",
35
+ "table": "表格",
36
+ "tex": "TeX 公式"
37
+ },
30
38
  "table": {
31
39
  "delete": "删除表格",
32
40
  "deleteColumn": "删除列",
@@ -46,7 +54,7 @@
46
54
  "link": "链接",
47
55
  "numberList": "有序列表",
48
56
  "strikethrough": "删除线",
49
- "table": "插入表格",
57
+ "table": "table",
50
58
  "taskList": "任务列表",
51
59
  "tex": "TeX 公式",
52
60
  "underline": "下划线"
@@ -27,6 +27,14 @@
27
27
  "math": {
28
28
  "placeholder": "請輸入 TeX 公式"
29
29
  },
30
+ "slash": {
31
+ "h1": "一級標題",
32
+ "h2": "二級標題",
33
+ "h3": "三級標題",
34
+ "hr": "分隔線",
35
+ "table": "表格",
36
+ "tex": "TeX 公式"
37
+ },
30
38
  "table": {
31
39
  "delete": "刪除表格",
32
40
  "deleteColumn": "刪除列",
@@ -46,7 +54,7 @@
46
54
  "link": "連結",
47
55
  "numberList": "有序清單",
48
56
  "strikethrough": "刪除線",
49
- "table": "插入表格",
57
+ "table": "表格",
50
58
  "taskList": "任務列表",
51
59
  "tex": "TeX 公式",
52
60
  "underline": "底線"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.127.3",
3
+ "version": "1.128.0",
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",
@@ -155,7 +155,7 @@
155
155
  "@lobehub/charts": "^2.0.0",
156
156
  "@lobehub/chat-plugin-sdk": "^1.32.4",
157
157
  "@lobehub/chat-plugins-gateway": "^1.9.0",
158
- "@lobehub/editor": "^1.6.1",
158
+ "@lobehub/editor": "^1.8.0",
159
159
  "@lobehub/icons": "^2.31.0",
160
160
  "@lobehub/market-sdk": "^0.22.7",
161
161
  "@lobehub/tts": "^2.0.1",
@@ -363,7 +363,7 @@
363
363
  "typescript": "^5.9.2",
364
364
  "unified": "^11.0.5",
365
365
  "unist-util-visit": "^5.0.0",
366
- "vite": "^5.4.19",
366
+ "vite": "^7.1.5",
367
367
  "vitest": "^3.2.4"
368
368
  },
369
369
  "packageManager": "pnpm@10.15.1",
@@ -212,6 +212,52 @@ const qwenChatModels: AIChatModelCard[] = [
212
212
  releasedAt: '2025-07-29',
213
213
  type: 'chat',
214
214
  },
215
+ {
216
+ abilities: {
217
+ functionCall: true,
218
+ reasoning: true,
219
+ },
220
+ contextWindowTokens: 131_072,
221
+ description:
222
+ '基于 Qwen3 的新一代思考模式开源模型,相较上一版本(通义千问3-235B-A22B-Thinking-2507)指令遵循能力有提升、模型总结回复更加精简。',
223
+ displayName: 'Qwen3 Next 80B A3B Thinking',
224
+ id: 'qwen3-next-80b-a3b-thinking',
225
+ maxOutput: 32_768,
226
+ organization: 'Qwen',
227
+ pricing: {
228
+ currency: 'CNY',
229
+ units: [
230
+ { name: 'textInput', rate: 1, strategy: 'fixed', unit: 'millionTokens' },
231
+ { name: 'textOutput', rate: 4, strategy: 'fixed', unit: 'millionTokens' },
232
+ ],
233
+ },
234
+ releasedAt: '2025-09-12',
235
+ settings: {
236
+ extendParams: ['reasoningBudgetToken'],
237
+ },
238
+ type: 'chat',
239
+ },
240
+ {
241
+ abilities: {
242
+ functionCall: true,
243
+ },
244
+ contextWindowTokens: 131_072,
245
+ description:
246
+ '基于 Qwen3 的新一代非思考模式开源模型,相较上一版本(通义千问3-235B-A22B-Instruct-2507)中文文本理解能力更佳、逻辑推理能力有增强、文本生成类任务表现更好。',
247
+ displayName: 'Qwen3 Next 80B A3B Instruct',
248
+ id: 'qwen3-next-80b-a3b-instruct',
249
+ maxOutput: 32_768,
250
+ organization: 'Qwen',
251
+ pricing: {
252
+ currency: 'CNY',
253
+ units: [
254
+ { name: 'textInput', rate: 1, strategy: 'fixed', unit: 'millionTokens' },
255
+ { name: 'textOutput', rate: 10, strategy: 'fixed', unit: 'millionTokens' },
256
+ ],
257
+ },
258
+ releasedAt: '2025-09-12',
259
+ type: 'chat',
260
+ },
215
261
  {
216
262
  abilities: {
217
263
  functionCall: true,
@@ -4,7 +4,8 @@
4
4
  "private": true,
5
5
  "exports": {
6
6
  ".": "./src/index.ts",
7
- "./vertexai": "./src/providers/vertexai/index.ts"
7
+ "./vertexai": "./src/providers/vertexai/index.ts",
8
+ "./provider": "./src/const/modelProvider.ts"
8
9
  },
9
10
  "scripts": {
10
11
  "test": "vitest",
@@ -9,6 +9,7 @@ export enum ModelProvider {
9
9
  AzureAI = 'azureai',
10
10
  Baichuan = 'baichuan',
11
11
  Bedrock = 'bedrock',
12
+ Bfl = 'bfl',
12
13
  Cloudflare = 'cloudflare',
13
14
  Cohere = 'cohere',
14
15
  CometAPI = 'cometapi',
@@ -221,3 +221,5 @@ export const createRouterRuntime = ({
221
221
  }
222
222
  };
223
223
  };
224
+
225
+ export type UniformRuntime = InstanceType<ReturnType<typeof createRouterRuntime>>;
@@ -6,4 +6,5 @@ export interface RuntimeItem {
6
6
  runtime: LobeRuntimeAI;
7
7
  }
8
8
 
9
+ export type { UniformRuntime } from './createRuntime';
9
10
  export { createRouterRuntime } from './createRuntime';
@@ -295,6 +295,40 @@ describe('OpenAIStream', () => {
295
295
  ]);
296
296
  });
297
297
 
298
+ it('should emit usage when choices are missing but usage exists', async () => {
299
+ const mockOpenAIStream = new ReadableStream({
300
+ start(controller) {
301
+ controller.enqueue({
302
+ id: '99',
303
+ usage: {
304
+ prompt_tokens: 1,
305
+ completion_tokens: 2,
306
+ total_tokens: 3,
307
+ prompt_cache_miss_tokens: 1,
308
+ },
309
+ });
310
+
311
+ controller.close();
312
+ },
313
+ });
314
+
315
+ const protocolStream = OpenAIStream(mockOpenAIStream);
316
+
317
+ const decoder = new TextDecoder();
318
+ const chunks: string[] = [];
319
+
320
+ // @ts-ignore
321
+ for await (const chunk of protocolStream) {
322
+ chunks.push(decoder.decode(chunk, { stream: true }));
323
+ }
324
+
325
+ expect(chunks).toEqual([
326
+ 'id: 99\n',
327
+ 'event: usage\n',
328
+ `data: {"inputCacheMissTokens":1,"inputTextTokens":1,"outputTextTokens":2,"totalInputTokens":1,"totalOutputTokens":2,"totalTokens":3}\n\n`,
329
+ ]);
330
+ });
331
+
298
332
  it('should handle error when there is not correct error', async () => {
299
333
  const mockOpenAIStream = new ReadableStream({
300
334
  start(controller) {
@@ -331,8 +365,8 @@ describe('OpenAIStream', () => {
331
365
  'event: text',
332
366
  `data: "Hello"\n`,
333
367
  'id: 1',
334
- 'event: error',
335
- `data: {"body":{"message":"chat response streaming chunk parse error, please contact your API Provider to fix it.","context":{"error":{"message":"Cannot read properties of undefined (reading '0')","name":"TypeError"},"chunk":{"id":"1"}}},"type":"StreamChunkError"}\n`,
368
+ 'event: data',
369
+ `data: {"id":"1"}\n`,
336
370
  ].map((i) => `${i}\n`),
337
371
  );
338
372
  });
@@ -64,8 +64,7 @@ const transformOpenAIStream = (
64
64
 
65
65
  try {
66
66
  // maybe need another structure to add support for multiple choices
67
- const item = chunk.choices[0];
68
- if (!item) {
67
+ if (!Array.isArray(chunk.choices) || chunk.choices.length === 0) {
69
68
  if (chunk.usage) {
70
69
  const usage = chunk.usage;
71
70
  return { data: convertUsage(usage, provider), id: chunk.id, type: 'usage' };
@@ -74,6 +73,8 @@ const transformOpenAIStream = (
74
73
  return { data: chunk, id: chunk.id, type: 'data' };
75
74
  }
76
75
 
76
+ const item = chunk.choices[0];
77
+
77
78
  if (item && typeof item.delta?.tool_calls === 'object' && item.delta.tool_calls?.length > 0) {
78
79
  // tools calling
79
80
  const tool_calls = item.delta.tool_calls.filter(
@@ -1,6 +1,7 @@
1
1
  export * from './core/BaseAI';
2
2
  export { ModelRuntime } from './core/ModelRuntime';
3
3
  export { createOpenAICompatibleRuntime } from './core/openaiCompatibleFactory';
4
+ export * from './core/RouterRuntime';
4
5
  export * from './helpers';
5
6
  export { LobeAkashChatAI } from './providers/akashchat';
6
7
  export { LobeAnthropicAI } from './providers/anthropic';
@@ -47,6 +47,7 @@ export interface UserKeyVaults extends SearchEngineKeyVaults {
47
47
  azureai?: AzureOpenAIKeyVault;
48
48
  baichuan?: OpenAICompatibleKeyVault;
49
49
  bedrock?: AWSBedrockKeyVault;
50
+ bfl?: any;
50
51
  cloudflare?: CloudflareKeyVault;
51
52
  cohere?: OpenAICompatibleKeyVault;
52
53
  cometapi?: OpenAICompatibleKeyVault;
@@ -10,6 +10,8 @@ import { Center, Flexbox } from 'react-layout-kit';
10
10
  import { useChatInputStore } from '@/features/ChatInput/store';
11
11
  import { useChatStore } from '@/store/chat';
12
12
  import { chatSelectors } from '@/store/chat/selectors';
13
+ import { useGlobalStore } from '@/store/global';
14
+ import { systemStatusSelectors } from '@/store/global/selectors';
13
15
 
14
16
  import ActionBar from '../ActionBar';
15
17
  import InputEditor from '../InputEditor';
@@ -49,6 +51,10 @@ const useStyles = createStyles(({ css, token }) => ({
49
51
 
50
52
  const DesktopChatInput = memo<{ showFootnote?: boolean }>(({ showFootnote }) => {
51
53
  const { t } = useTranslation('chat');
54
+ const [chatInputHeight, updateSystemStatus] = useGlobalStore((s) => [
55
+ systemStatusSelectors.chatInputHeight(s),
56
+ s.updateSystemStatus,
57
+ ]);
52
58
  const [slashMenuRef, expand, showTypoBar, editor, leftActions] = useChatInputStore((s) => [
53
59
  s.slashMenuRef,
54
60
  s.expand,
@@ -77,6 +83,7 @@ const DesktopChatInput = memo<{ showFootnote?: boolean }>(({ showFootnote }) =>
77
83
  paddingInline={12}
78
84
  >
79
85
  <ChatInput
86
+ defaultHeight={chatInputHeight || 32}
80
87
  footer={
81
88
  <ChatInputActionBar
82
89
  left={<ActionBar />}
@@ -88,12 +95,13 @@ const DesktopChatInput = memo<{ showFootnote?: boolean }>(({ showFootnote }) =>
88
95
  }
89
96
  fullscreen={expand}
90
97
  header={showTypoBar && <TypoBar />}
91
- slashMenuRef={slashMenuRef}
92
- styles={{
93
- body: {
94
- minHeight: 64,
95
- },
98
+ maxHeight={320}
99
+ minHeight={36}
100
+ onSizeChange={(height) => {
101
+ updateSystemStatus({ chatInputHeight: height });
96
102
  }}
103
+ resize={true}
104
+ slashMenuRef={slashMenuRef}
97
105
  >
98
106
  {expand && fileNode}
99
107
  <InputEditor />
@@ -0,0 +1,42 @@
1
+ import { KeyEnum } from '@lobechat/types';
2
+ import { Hotkey, combineKeys } from '@lobehub/ui';
3
+ import { memo } from 'react';
4
+ import { Trans, useTranslation } from 'react-i18next';
5
+ import { Flexbox } from 'react-layout-kit';
6
+
7
+ import { useUserStore } from '@/store/user';
8
+ import { preferenceSelectors } from '@/store/user/selectors';
9
+
10
+ const Placeholder = memo(() => {
11
+ const { t } = useTranslation(['editor', 'chat']);
12
+
13
+ const useCmdEnterToSend = useUserStore(preferenceSelectors.useCmdEnterToSend);
14
+ const wrapperShortcut = useCmdEnterToSend
15
+ ? KeyEnum.Enter
16
+ : combineKeys([KeyEnum.Mod, KeyEnum.Enter]);
17
+
18
+ return (
19
+ <Flexbox align={'center'} as={'span'} gap={4} horizontal>
20
+ {t('sendPlaceholder', { ns: 'chat' }).replace('...', ', ')}
21
+ <Trans
22
+ as={'span'}
23
+ components={{
24
+ key: (
25
+ <Hotkey
26
+ as={'span'}
27
+ keys={wrapperShortcut}
28
+ style={{ color: 'inherit' }}
29
+ styles={{ kbdStyle: { color: 'inhert' } }}
30
+ variant={'borderless'}
31
+ />
32
+ ),
33
+ }}
34
+ i18nKey={'input.warpWithKey'}
35
+ ns={'chat'}
36
+ />
37
+ {'...'}
38
+ </Flexbox>
39
+ );
40
+ });
41
+
42
+ export default Placeholder;
@@ -2,7 +2,6 @@ import { isDesktop } from '@lobechat/const';
2
2
  import { HotkeyEnum, KeyEnum } from '@lobechat/types';
3
3
  import { isCommandPressed } from '@lobechat/utils';
4
4
  import {
5
- INSERT_TABLE_COMMAND,
6
5
  ReactCodePlugin,
7
6
  ReactCodeblockPlugin,
8
7
  ReactHRPlugin,
@@ -12,17 +11,16 @@ import {
12
11
  ReactTablePlugin,
13
12
  } from '@lobehub/editor';
14
13
  import { Editor, FloatMenu, SlashMenu, useEditorState } from '@lobehub/editor/react';
15
- import { Hotkey, combineKeys } from '@lobehub/ui';
16
- import { Table2Icon } from 'lucide-react';
14
+ import { combineKeys } from '@lobehub/ui';
17
15
  import { memo, useEffect, useRef } from 'react';
18
16
  import { useHotkeysContext } from 'react-hotkeys-hook';
19
- import { Trans, useTranslation } from 'react-i18next';
20
- import { Flexbox } from 'react-layout-kit';
21
17
 
22
18
  import { useUserStore } from '@/store/user';
23
19
  import { preferenceSelectors, settingsSelectors } from '@/store/user/selectors';
24
20
 
25
21
  import { useChatInputStore, useStoreApi } from '../store';
22
+ import Placeholder from './Placeholder';
23
+ import { useSlashItems } from './useSlashItems';
26
24
 
27
25
  const InputEditor = memo<{ defaultRows?: number }>(() => {
28
26
  const [editor, slashMenuRef, send, updateMarkdownContent, expand] = useChatInputStore((s) => [
@@ -37,14 +35,11 @@ const InputEditor = memo<{ defaultRows?: number }>(() => {
37
35
  const state = useEditorState(editor);
38
36
  const hotkey = useUserStore(settingsSelectors.getHotkeyById(HotkeyEnum.AddUserMessage));
39
37
  const { enableScope, disableScope } = useHotkeysContext();
40
- const { t } = useTranslation(['editor', 'chat']);
38
+ const slashItems = useSlashItems();
41
39
 
42
40
  const isChineseInput = useRef(false);
43
41
 
44
42
  const useCmdEnterToSend = useUserStore(preferenceSelectors.useCmdEnterToSend);
45
- const wrapperShortcut = useCmdEnterToSend
46
- ? KeyEnum.Enter
47
- : combineKeys([KeyEnum.Mod, KeyEnum.Enter]);
48
43
 
49
44
  useEffect(() => {
50
45
  const fn = (e: BeforeUnloadEvent) => {
@@ -113,28 +108,7 @@ const InputEditor = memo<{ defaultRows?: number }>(() => {
113
108
  }
114
109
  }
115
110
  }}
116
- placeholder={
117
- <Flexbox align={'center'} as={'span'} gap={4} horizontal>
118
- {t('sendPlaceholder', { ns: 'chat' }).replace('...', ', ')}
119
- <Trans
120
- as={'span'}
121
- components={{
122
- key: (
123
- <Hotkey
124
- as={'span'}
125
- keys={wrapperShortcut}
126
- style={{ color: 'inherit' }}
127
- styles={{ kbdStyle: { color: 'inhert' } }}
128
- variant={'borderless'}
129
- />
130
- ),
131
- }}
132
- i18nKey={'input.warpWithKey'}
133
- ns={'chat'}
134
- />
135
- {'...'}
136
- </Flexbox>
137
- }
111
+ placeholder={<Placeholder />}
138
112
  plugins={[
139
113
  ReactListPlugin,
140
114
  ReactLinkPlugin,
@@ -151,16 +125,7 @@ const InputEditor = memo<{ defaultRows?: number }>(() => {
151
125
  }),
152
126
  ]}
153
127
  slashOption={{
154
- items: [
155
- {
156
- icon: Table2Icon,
157
- key: 'table',
158
- label: t('typobar.table'),
159
- onSelect: (editor) => {
160
- editor.dispatchCommand(INSERT_TABLE_COMMAND, { columns: '3', rows: '3' });
161
- },
162
- },
163
- ],
128
+ items: slashItems,
164
129
  renderComp: expand
165
130
  ? undefined
166
131
  : (props) => {
@@ -0,0 +1,92 @@
1
+ import {
2
+ INSERT_HEADING_COMMAND,
3
+ INSERT_HORIZONTAL_RULE_COMMAND,
4
+ INSERT_MATH_COMMAND,
5
+ INSERT_TABLE_COMMAND,
6
+ SlashOptions,
7
+ } from '@lobehub/editor';
8
+ import { Text } from '@lobehub/ui';
9
+ import {
10
+ Heading1Icon,
11
+ Heading2Icon,
12
+ Heading3Icon,
13
+ MinusIcon,
14
+ SigmaIcon,
15
+ Table2Icon,
16
+ } from 'lucide-react';
17
+ import { useMemo } from 'react';
18
+ import { useTranslation } from 'react-i18next';
19
+
20
+ export const useSlashItems = (): SlashOptions['items'] => {
21
+ const { t } = useTranslation('editor');
22
+ return useMemo(() => {
23
+ const data: SlashOptions['items'] = [
24
+ {
25
+ icon: Heading1Icon,
26
+ key: 'h1',
27
+ label: t('slash.h1'),
28
+ onSelect: (editor) => {
29
+ editor.dispatchCommand(INSERT_HEADING_COMMAND, { tag: 'h1' });
30
+ },
31
+ },
32
+ {
33
+ icon: Heading2Icon,
34
+ key: 'h2',
35
+ label: t('slash.h2'),
36
+ onSelect: (editor) => {
37
+ editor.dispatchCommand(INSERT_HEADING_COMMAND, { tag: 'h2' });
38
+ },
39
+ },
40
+ {
41
+ icon: Heading3Icon,
42
+ key: 'h3',
43
+ label: t('slash.h3'),
44
+ onSelect: (editor) => {
45
+ editor.dispatchCommand(INSERT_HEADING_COMMAND, { tag: 'h3' });
46
+ },
47
+ },
48
+
49
+ {
50
+ type: 'divider',
51
+ },
52
+ {
53
+ icon: MinusIcon,
54
+ key: 'hr',
55
+ label: t('slash.hr'),
56
+ onSelect: (editor) => {
57
+ editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, {});
58
+ },
59
+ },
60
+ {
61
+ icon: Table2Icon,
62
+ key: 'table',
63
+ label: t('slash.table'),
64
+ onSelect: (editor) => {
65
+ editor.dispatchCommand(INSERT_TABLE_COMMAND, { columns: '3', rows: '3' });
66
+ },
67
+ },
68
+ {
69
+ icon: SigmaIcon,
70
+ key: 'tex',
71
+ label: t('slash.tex'),
72
+ onSelect: (editor) => {
73
+ editor.dispatchCommand(INSERT_MATH_COMMAND, { code: 'x^2 + y^2 = z^2' });
74
+ queueMicrotask(() => {
75
+ editor.focus();
76
+ });
77
+ },
78
+ },
79
+ ];
80
+ return data.map((item) => {
81
+ if (item.type === 'divider') return item;
82
+ return {
83
+ ...item,
84
+ extra: (
85
+ <Text code fontSize={12} type={'secondary'}>
86
+ {item.key}
87
+ </Text>
88
+ ),
89
+ };
90
+ });
91
+ }, [t]);
92
+ };
@@ -147,14 +147,7 @@ const TypoBar = memo(() => {
147
147
 
148
148
  return (
149
149
  <ChatInputActionBar
150
- left={
151
- <ChatInputActions
152
- items={items}
153
- onClick={() => {
154
- editor?.focus();
155
- }}
156
- />
157
- }
150
+ left={<ChatInputActions items={items} />}
158
151
  style={{
159
152
  background: theme.colorFillQuaternary,
160
153
  borderTopLeftRadius: 8,
@@ -27,6 +27,14 @@ export default {
27
27
  math: {
28
28
  placeholder: '请输入 TeX 公式',
29
29
  },
30
+ slash: {
31
+ h1: '一级标题',
32
+ h2: '二级标题',
33
+ h3: '三级标题',
34
+ hr: '分割线',
35
+ table: '表格',
36
+ tex: 'TeX 公式',
37
+ },
30
38
  table: {
31
39
  delete: '删除表格',
32
40
  deleteColumn: '删除列',
@@ -46,7 +54,7 @@ export default {
46
54
  link: '链接',
47
55
  numberList: '有序列表',
48
56
  strikethrough: '删除线',
49
- table: '插入表格',
57
+ table: 'table',
50
58
  taskList: '任务列表',
51
59
  tex: 'TeX 公式',
52
60
  underline: '下划线',
@@ -47,6 +47,7 @@ export enum ProfileTabs {
47
47
  }
48
48
 
49
49
  export interface SystemStatus {
50
+ chatInputHeight?: number;
50
51
  expandInputActionbar?: boolean;
51
52
  // which sessionGroup should expand
52
53
  expandSessionGroupKeys: string[];
@@ -115,6 +116,7 @@ export interface GlobalState {
115
116
  }
116
117
 
117
118
  export const INITIAL_STATUS = {
119
+ chatInputHeight: 64,
118
120
  expandInputActionbar: true,
119
121
  expandSessionGroupKeys: [SessionDefaultGroup.Pinned, SessionDefaultGroup.Default],
120
122
  filePanelWidth: 320,
@@ -29,6 +29,7 @@ const filePanelWidth = (s: GlobalState) => s.status.filePanelWidth;
29
29
  const imagePanelWidth = (s: GlobalState) => s.status.imagePanelWidth;
30
30
  const imageTopicPanelWidth = (s: GlobalState) => s.status.imageTopicPanelWidth;
31
31
  const wideScreen = (s: GlobalState) => !s.status.noWideScreen;
32
+ const chatInputHeight = (s: GlobalState) => s.status.chatInputHeight || 64;
32
33
  const expandInputActionbar = (s: GlobalState) => s.status.expandInputActionbar;
33
34
  const isStatusInit = (s: GlobalState) => !!s.isStatusInit;
34
35
  const isPgliteNotEnabled = (s: GlobalState) =>
@@ -63,6 +64,7 @@ const getAgentSystemRoleExpanded =
63
64
  };
64
65
 
65
66
  export const systemStatusSelectors = {
67
+ chatInputHeight,
66
68
  expandInputActionbar,
67
69
  filePanelWidth,
68
70
  getAgentSystemRoleExpanded,