Compare commits

...

17 Commits

Author SHA1 Message Date
semantic-release-bot 5d8648c7d6 🔖 chore(release): v2.0.0-next.92 [skip ci]
## [Version 2.0.0-next.92](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.91...v2.0.0-next.92)
<sup>Released on **2025-11-19**</sup>

#### 💄 Styles

- **misc**: Remove debug console logs and add loading state.

<br/>

<details>
<summary><kbd>Improvements and Fixes</kbd></summary>

#### Styles

* **misc**: Remove debug console logs and add loading state, closes [#10314](https://github.com/lobehub/lobe-chat/issues/10314) ([094cdff](https://github.com/lobehub/lobe-chat/commit/094cdff))

</details>

<div align="right">

[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)

</div>
2025-11-19 16:49:55 +00:00
Arvin Xu 094cdff097 💄 style: remove debug console logs and add loading state (#10314)
perf
2025-11-20 00:32:33 +08:00
lobehubbot 83e0cea322 📝 docs(bot): Auto sync agents & plugin to readme 2025-11-19 16:17:38 +00:00
semantic-release-bot 21c67d6700 🔖 chore(release): v2.0.0-next.91 [skip ci]
## [Version&nbsp;2.0.0-next.91](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.90...v2.0.0-next.91)
<sup>Released on **2025-11-19**</sup>

#### 🐛 Bug Fixes

- **misc**: Fixed the hydrated false problem.

<br/>

<details>
<summary><kbd>Improvements and Fixes</kbd></summary>

#### What's fixed

* **misc**: Fixed the hydrated false problem, closes [#10308](https://github.com/lobehub/lobe-chat/issues/10308) ([340aa2a](https://github.com/lobehub/lobe-chat/commit/340aa2a))

</details>

<div align="right">

[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)

</div>
2025-11-19 16:16:27 +00:00
Shinji-Li 340aa2a9e9 🐛 fix: fixed the hydrated false problem (#10308)
* fix: fixed the hydrated error problem

* fix: use next/dynamic to replace react-router-dom lazy import

* fix: add registor NavigatorRegistrar back

* fix: add dynamic loading components

* fix: change the dynamic config

* fix: add losting loading layout

* fix: delete useless memo

* fix: add  ErrorBoundary in some layout
2025-11-20 00:04:04 +08:00
lobehubbot a7d1878630 📝 docs(bot): Auto sync agents & plugin to readme 2025-11-19 14:38:29 +00:00
semantic-release-bot 6a2d439f5c 🔖 chore(release): v2.0.0-next.90 [skip ci]
## [Version&nbsp;2.0.0-next.90](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.89...v2.0.0-next.90)
<sup>Released on **2025-11-19**</sup>

#### 💄 Styles

- **misc**: Extract StatusIndicator component and improve tools display.

<br/>

<details>
<summary><kbd>Improvements and Fixes</kbd></summary>

#### Styles

* **misc**: Extract StatusIndicator component and improve tools display, closes [#10311](https://github.com/lobehub/lobe-chat/issues/10311) ([b5ae53a](https://github.com/lobehub/lobe-chat/commit/b5ae53a))

</details>

<div align="right">

[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)

</div>
2025-11-19 14:37:18 +00:00
Arvin Xu b5ae53ab30 💄 style: extract StatusIndicator component and improve tools display (#10311)
improve
2025-11-19 22:24:01 +08:00
YuTengjing 474af231b5 🔧 chore: sync cloud changes (#10307) 2025-11-19 19:05:38 +08:00
lobehubbot 7ec5594e1c 📝 docs(bot): Auto sync agents & plugin to readme 2025-11-19 09:38:11 +00:00
semantic-release-bot ffff700c6c 🔖 chore(release): v2.0.0-next.89 [skip ci]
## [Version&nbsp;2.0.0-next.89](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.88...v2.0.0-next.89)
<sup>Released on **2025-11-19**</sup>

####  Features

- **misc**: Support gemini 3.0 tools calling.

<br/>

<details>
<summary><kbd>Improvements and Fixes</kbd></summary>

#### What's improved

* **misc**: Support gemini 3.0 tools calling, closes [#10301](https://github.com/lobehub/lobe-chat/issues/10301) ([7114fc1](https://github.com/lobehub/lobe-chat/commit/7114fc1))

</details>

<div align="right">

[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)

</div>
2025-11-19 09:37:02 +00:00
Arvin Xu 7114fc10c4 feat: support gemini 3.0 tools calling (#10301)
* fix error display

* 完整支持 gemini 的 Function calling 机制

* add fetchsse

* fix continue mode

* improve

* refactor

* fix
2025-11-19 17:24:46 +08:00
lobehubbot 973367c7ac 📝 docs(bot): Auto sync agents & plugin to readme 2025-11-19 06:11:56 +00:00
semantic-release-bot d1c57a1f97 🔖 chore(release): v2.0.0-next.88 [skip ci]
## [Version&nbsp;2.0.0-next.88](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.87...v2.0.0-next.88)
<sup>Released on **2025-11-19**</sup>

#### 💄 Styles

- **misc**: Fully support Gemini 3.0 model.

<br/>

<details>
<summary><kbd>Improvements and Fixes</kbd></summary>

#### Styles

* **misc**: Fully support Gemini 3.0 model, closes [#10292](https://github.com/lobehub/lobe-chat/issues/10292) ([6545ef8](https://github.com/lobehub/lobe-chat/commit/6545ef8))

</details>

<div align="right">

[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)

</div>
2025-11-19 06:10:46 +00:00
sxjeru 6545ef863c 💄 style: Fully support Gemini 3.0 model (#10292)
* feat: 添加 Gemini 3 Pro 模型并移除 Gemini 2.0 Flash 预览模型

* feat: 添加思考水平功能,更新相关模型和配置

* feat: 添加 Gemini 3 Pro 模型并移除旧版 Gemini 2.5 Flash 和 Flash-Lite 模型

* feat: 添加 Gemini 3 Pro 预览模型及其相关配置

* fix: 调整 ThinkingLevelSlider 组件的最小宽度为 130

* fix: 修正对 3.0 模型的思考级别判断条件
2025-11-19 13:57:52 +08:00
lobehubbot de60a6732e 📝 docs(bot): Auto sync agents & plugin to readme 2025-11-19 05:14:48 +00:00
semantic-release-bot d178d4f931 🔖 chore(release): v2.0.0-next.87 [skip ci]
## [Version&nbsp;2.0.0-next.87](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.86...v2.0.0-next.87)
<sup>Released on **2025-11-19**</sup>

#### ♻ Code Refactoring

- **misc**: Refactor chat selectors.

<br/>

<details>
<summary><kbd>Improvements and Fixes</kbd></summary>

#### Code refactoring

* **misc**: Refactor chat selectors, closes [#10274](https://github.com/lobehub/lobe-chat/issues/10274) ([0a056f3](https://github.com/lobehub/lobe-chat/commit/0a056f3))

</details>

<div align="right">

[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)

</div>
2025-11-19 05:13:36 +00:00
101 changed files with 3289 additions and 1680 deletions
+1 -1
View File
@@ -103,8 +103,8 @@ vertex-ai-key.json
.local/
.claude/
.mcp.json
CLAUDE.local.md
.agent/
# MCP tools
.serena/**
+150
View File
@@ -2,6 +2,156 @@
# Changelog
## [Version 2.0.0-next.92](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.91...v2.0.0-next.92)
<sup>Released on **2025-11-19**</sup>
#### 💄 Styles
- **misc**: Remove debug console logs and add loading state.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### Styles
- **misc**: Remove debug console logs and add loading state, closes [#10314](https://github.com/lobehub/lobe-chat/issues/10314) ([094cdff](https://github.com/lobehub/lobe-chat/commit/094cdff))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.91](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.90...v2.0.0-next.91)
<sup>Released on **2025-11-19**</sup>
#### 🐛 Bug Fixes
- **misc**: Fixed the hydrated false problem.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### What's fixed
- **misc**: Fixed the hydrated false problem, closes [#10308](https://github.com/lobehub/lobe-chat/issues/10308) ([340aa2a](https://github.com/lobehub/lobe-chat/commit/340aa2a))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.90](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.89...v2.0.0-next.90)
<sup>Released on **2025-11-19**</sup>
#### 💄 Styles
- **misc**: Extract StatusIndicator component and improve tools display.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### Styles
- **misc**: Extract StatusIndicator component and improve tools display, closes [#10311](https://github.com/lobehub/lobe-chat/issues/10311) ([b5ae53a](https://github.com/lobehub/lobe-chat/commit/b5ae53a))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.89](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.88...v2.0.0-next.89)
<sup>Released on **2025-11-19**</sup>
#### ✨ Features
- **misc**: Support gemini 3.0 tools calling.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### What's improved
- **misc**: Support gemini 3.0 tools calling, closes [#10301](https://github.com/lobehub/lobe-chat/issues/10301) ([7114fc1](https://github.com/lobehub/lobe-chat/commit/7114fc1))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.88](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.87...v2.0.0-next.88)
<sup>Released on **2025-11-19**</sup>
#### 💄 Styles
- **misc**: Fully support Gemini 3.0 model.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### Styles
- **misc**: Fully support Gemini 3.0 model, closes [#10292](https://github.com/lobehub/lobe-chat/issues/10292) ([6545ef8](https://github.com/lobehub/lobe-chat/commit/6545ef8))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.87](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.86...v2.0.0-next.87)
<sup>Released on **2025-11-19**</sup>
#### ♻ Code Refactoring
- **misc**: Refactor chat selectors.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### Code refactoring
- **misc**: Refactor chat selectors, closes [#10274](https://github.com/lobehub/lobe-chat/issues/10274) ([0a056f3](https://github.com/lobehub/lobe-chat/commit/0a056f3))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.86](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.85...v2.0.0-next.86)
<sup>Released on **2025-11-19**</sup>
+35
View File
@@ -1,4 +1,39 @@
[
{
"children": {
"fixes": ["Fixed the hydrated false problem."]
},
"date": "2025-11-19",
"version": "2.0.0-next.91"
},
{
"children": {
"improvements": ["Extract StatusIndicator component and improve tools display."]
},
"date": "2025-11-19",
"version": "2.0.0-next.90"
},
{
"children": {
"features": ["Support gemini 3.0 tools calling."]
},
"date": "2025-11-19",
"version": "2.0.0-next.89"
},
{
"children": {
"improvements": ["Fully support Gemini 3.0 model."]
},
"date": "2025-11-19",
"version": "2.0.0-next.88"
},
{
"children": {
"improvements": ["Refactor chat selectors."]
},
"date": "2025-11-19",
"version": "2.0.0-next.87"
},
{
"children": {
"features": ["Support user abort in the agent runtime."]
+4
View File
@@ -65,6 +65,9 @@
"thinking": {
"title": "مفتاح التفكير العميق"
},
"thinkingLevel": {
"title": "مستوى التفكير"
},
"title": "وظائف توسيع النموذج",
"urlContext": {
"desc": "عند التفعيل، سيتم تحليل روابط الويب تلقائيًا للحصول على محتوى السياق الفعلي للصفحة",
@@ -396,6 +399,7 @@
"rejectReasonPlaceholder": "إدخال سبب الرفض سيساعد الوكيل على الفهم وتحسين الإجراءات المستقبلية",
"rejectTitle": "رفض استدعاء الأداة هذه المرة",
"rejectedWithReason": "تم رفض استدعاء الأداة هذه المرة بشكل يدوي: {{reason}}",
"toolAbort": "تم إلغاء استدعاء الأداة من قبل المستخدم",
"toolRejected": "تم رفض استدعاء الأداة هذه المرة بشكل يدوي"
}
},
+3 -6
View File
@@ -1481,9 +1481,6 @@
"gemini-2.0-flash-lite-001": {
"description": "نموذج جمنّي 2.0 فلاش هو نسخة معدلة، تم تحسينها لتحقيق الكفاءة من حيث التكلفة والحد من التأخير."
},
"gemini-2.0-flash-preview-image-generation": {
"description": "نموذج معاينة Gemini 2.0 Flash، يدعم توليد الصور"
},
"gemini-2.5-flash": {
"description": "Gemini 2.5 Flash هو نموذج Google الأكثر فعالية من حيث التكلفة، ويوفر وظائف شاملة."
},
@@ -1511,9 +1508,6 @@
"gemini-2.5-flash-preview-04-17": {
"description": "معاينة فلاش جمنّي 2.5 هي النموذج الأكثر كفاءة من جوجل، حيث تقدم مجموعة شاملة من الميزات."
},
"gemini-2.5-flash-preview-05-20": {
"description": "Gemini 2.5 Flash Preview هو نموذج Google الأكثر فعالية من حيث التكلفة، يقدم وظائف شاملة."
},
"gemini-2.5-flash-preview-09-2025": {
"description": "إصدار معاينة (25 سبتمبر 2025) من Gemini 2.5 Flash"
},
@@ -1529,6 +1523,9 @@
"gemini-2.5-pro-preview-06-05": {
"description": "جيميني 2.5 برو بريڤيو هو أحدث نموذج تفكيري من جوجل، قادر على استنتاج حلول للمشكلات المعقدة في مجالات البرمجة، الرياضيات، والعلوم والتكنولوجيا والهندسة والرياضيات (STEM)، بالإضافة إلى تحليل مجموعات بيانات كبيرة، قواعد بيانات البرمجة، والوثائق باستخدام سياق طويل."
},
"gemini-3-pro-preview": {
"description": "Gemini 3 Pro هو النموذج الأذكى من Google، يتميز بأحدث تقنيات الاستدلال والفهم متعدد الوسائط، بالإضافة إلى قدرات قوية في التمثيل الذكي وترميز السياق."
},
"gemini-flash-latest": {
"description": "أحدث إصدار من Gemini Flash"
},
+4
View File
@@ -65,6 +65,9 @@
"thinking": {
"title": "Превключвател за дълбоко мислене"
},
"thinkingLevel": {
"title": "Ниво на мислене"
},
"title": "Разширени функции на модела",
"urlContext": {
"desc": "Когато е включено, автоматично ще се анализират уеб връзки, за да се получи реалното съдържание на уеб страницата",
@@ -396,6 +399,7 @@
"rejectReasonPlaceholder": "Въведете причина за отхвърляне, за да помогнете на агента да разбере и подобри бъдещите действия",
"rejectTitle": "Отхвърляне на това извикване на инструмент",
"rejectedWithReason": "Това извикване на инструмент беше умишлено отхвърлено: {{reason}}",
"toolAbort": "Този инструмент беше отменен от потребителя",
"toolRejected": "Това извикване на инструмент беше умишлено отхвърлено"
}
},
+3 -6
View File
@@ -1481,9 +1481,6 @@
"gemini-2.0-flash-lite-001": {
"description": "Gemini 2.0 Flash е вариант на модела, оптимизиран за икономичност и ниска латентност."
},
"gemini-2.0-flash-preview-image-generation": {
"description": "Gemini 2.0 Flash предварителен модел, поддържащ генериране на изображения"
},
"gemini-2.5-flash": {
"description": "Gemini 2.5 Flash е най-ефективният модел на Google, предлагащ пълна функционалност."
},
@@ -1511,9 +1508,6 @@
"gemini-2.5-flash-preview-04-17": {
"description": "Gemini 2.5 Flash Preview е моделът с най-добро съотношение цена-качество на Google, предлагащ пълна функционалност."
},
"gemini-2.5-flash-preview-05-20": {
"description": "Gemini 2.5 Flash Preview е най-ефективният модел на Google, предлагащ пълна функционалност."
},
"gemini-2.5-flash-preview-09-2025": {
"description": "Прегледна версия (25 септември 2025 г.) на Gemini 2.5 Flash"
},
@@ -1529,6 +1523,9 @@
"gemini-2.5-pro-preview-06-05": {
"description": "Gemini 2.5 Pro Preview е най-напредналият мисловен модел на Google, способен да разсъждава върху сложни проблеми в областта на кодирането, математиката и STEM, както и да анализира големи набори от данни, кодови бази и документи с дълъг контекст."
},
"gemini-3-pro-preview": {
"description": "Gemini 3 Pro е най-интелигентният модел на Google, с най-съвременно извеждане на заключения и мултимодално разбиране, както и с мощни възможности за агентно поведение и кодиране на контекста."
},
"gemini-flash-latest": {
"description": "Последно издание на Gemini Flash"
},
+4
View File
@@ -65,6 +65,9 @@
"thinking": {
"title": "Tiefdenk-Schalter"
},
"thinkingLevel": {
"title": "Denkebene"
},
"title": "Modell Erweiterungsfunktionen",
"urlContext": {
"desc": "Wenn aktiviert, werden Webseiten-Links automatisch analysiert, um den tatsächlichen Webseiteninhalt zu erfassen",
@@ -396,6 +399,7 @@
"rejectReasonPlaceholder": "Die Angabe eines Ablehnungsgrundes hilft dem Agenten, zukünftige Aktionen zu verbessern",
"rejectTitle": "Tool-Ausführung ablehnen",
"rejectedWithReason": "Die Tool-Ausführung wurde abgelehnt: {{reason}}",
"toolAbort": "Dieser Werkzeugaufruf wurde vom Benutzer abgebrochen",
"toolRejected": "Die Tool-Ausführung wurde abgelehnt"
}
},
+3 -6
View File
@@ -1481,9 +1481,6 @@
"gemini-2.0-flash-lite-001": {
"description": "Gemini 2.0 Flash ist eine Modellvariante, die auf Kosteneffizienz und niedrige Latenz optimiert ist."
},
"gemini-2.0-flash-preview-image-generation": {
"description": "Gemini 2.0 Flash Vorschau-Modell, unterstützt die Bildgenerierung"
},
"gemini-2.5-flash": {
"description": "Gemini 2.5 Flash ist Googles kosteneffizientestes Modell und bietet umfassende Funktionen."
},
@@ -1511,9 +1508,6 @@
"gemini-2.5-flash-preview-04-17": {
"description": "Gemini 2.5 Flash Preview ist das kosteneffizienteste Modell von Google und bietet umfassende Funktionen."
},
"gemini-2.5-flash-preview-05-20": {
"description": "Gemini 2.5 Flash Preview ist Googles kosteneffizientestes Modell mit umfassenden Funktionen."
},
"gemini-2.5-flash-preview-09-2025": {
"description": "Vorschauversion (25. September 2025) von Gemini 2.5 Flash"
},
@@ -1529,6 +1523,9 @@
"gemini-2.5-pro-preview-06-05": {
"description": "Gemini 2.5 Pro Preview ist Googles fortschrittlichstes Denkmodell, das komplexe Probleme in den Bereichen Code, Mathematik und MINT-Fächer lösen kann und große Datensätze, Codebasen und Dokumente mit langem Kontext analysiert."
},
"gemini-3-pro-preview": {
"description": "Gemini 3 Pro ist das intelligenteste Modell von Google mit modernster Schlussfolgerungsfähigkeit, multimodaler Verarbeitung sowie leistungsstarken Agenten- und Kontextkodierungsfunktionen."
},
"gemini-flash-latest": {
"description": "Neueste Version von Gemini Flash"
},
+4
View File
@@ -65,6 +65,9 @@
"thinking": {
"title": "Deep Thinking Switch"
},
"thinkingLevel": {
"title": "Level of Thinking"
},
"title": "Model Extension Features",
"urlContext": {
"desc": "When enabled, web links will be automatically parsed to retrieve the actual webpage context content",
@@ -396,6 +399,7 @@
"rejectReasonPlaceholder": "Providing a reason will help the Agent understand and improve future actions",
"rejectTitle": "Reject This Tool Invocation",
"rejectedWithReason": "This tool invocation was actively rejected: {{reason}}",
"toolAbort": "This tool invocation was canceled by the user",
"toolRejected": "This tool invocation was actively rejected"
}
},
+1
View File
@@ -283,6 +283,7 @@
"business": "Business Cooperation",
"support": "Email Support"
},
"new": "NEW",
"oauth": "SSO Login",
"officialSite": "Official Website",
"ok": "OK",
+3 -6
View File
@@ -1481,9 +1481,6 @@
"gemini-2.0-flash-lite-001": {
"description": "Gemini 2.0 Flash is a variant of the model optimized for cost-effectiveness and low latency."
},
"gemini-2.0-flash-preview-image-generation": {
"description": "Gemini 2.0 Flash preview model, supports image generation"
},
"gemini-2.5-flash": {
"description": "Gemini 2.5 Flash is Google's most cost-effective model, offering comprehensive capabilities."
},
@@ -1511,9 +1508,6 @@
"gemini-2.5-flash-preview-04-17": {
"description": "Gemini 2.5 Flash Preview is Google's most cost-effective model, offering a comprehensive set of features."
},
"gemini-2.5-flash-preview-05-20": {
"description": "Gemini 2.5 Flash Preview is Google's most cost-effective model, offering comprehensive capabilities."
},
"gemini-2.5-flash-preview-09-2025": {
"description": "Preview release (September 25th, 2025) of Gemini 2.5 Flash"
},
@@ -1529,6 +1523,9 @@
"gemini-2.5-pro-preview-06-05": {
"description": "Gemini 2.5 Pro Preview is Google's most advanced cognitive model, capable of reasoning through complex problems in code, mathematics, and STEM fields, as well as analyzing large datasets, codebases, and documents using long-context understanding."
},
"gemini-3-pro-preview": {
"description": "Gemini 3 Pro is Google's most advanced model, featuring state-of-the-art reasoning, multimodal understanding, and powerful agent capabilities with contextual awareness."
},
"gemini-flash-latest": {
"description": "Latest release of Gemini Flash"
},
+4
View File
@@ -65,6 +65,9 @@
"thinking": {
"title": "Interruptor de pensamiento profundo"
},
"thinkingLevel": {
"title": "Nivel de pensamiento"
},
"title": "Funcionalidad de extensión del modelo",
"urlContext": {
"desc": "Al activarlo, se analizarán automáticamente los enlaces web para obtener el contenido contextual real de la página",
@@ -396,6 +399,7 @@
"rejectReasonPlaceholder": "Ingresar una razón ayudará al agente a comprender y mejorar futuras acciones",
"rejectTitle": "Rechazar esta ejecución de herramienta",
"rejectedWithReason": "Esta ejecución de herramienta fue rechazada: {{reason}}",
"toolAbort": "La llamada a la herramienta fue cancelada por el usuario",
"toolRejected": "Esta ejecución de herramienta fue rechazada"
}
},
+3 -6
View File
@@ -1481,9 +1481,6 @@
"gemini-2.0-flash-lite-001": {
"description": "Variante del modelo Gemini 2.0 Flash, optimizada para objetivos como la rentabilidad y la baja latencia."
},
"gemini-2.0-flash-preview-image-generation": {
"description": "Modelo de vista previa Gemini 2.0 Flash, que admite la generación de imágenes"
},
"gemini-2.5-flash": {
"description": "Gemini 2.5 Flash es el modelo de mejor relación calidad-precio de Google, que ofrece funcionalidades completas."
},
@@ -1511,9 +1508,6 @@
"gemini-2.5-flash-preview-04-17": {
"description": "Gemini 2.5 Flash Preview es el modelo más rentable de Google, que ofrece una funcionalidad completa."
},
"gemini-2.5-flash-preview-05-20": {
"description": "Gemini 2.5 Flash Preview es el modelo de mejor relación calidad-precio de Google, que ofrece funcionalidades completas."
},
"gemini-2.5-flash-preview-09-2025": {
"description": "Versión preliminar (25 de septiembre de 2025) de Gemini 2.5 Flash"
},
@@ -1529,6 +1523,9 @@
"gemini-2.5-pro-preview-06-05": {
"description": "Gemini 2.5 Pro Preview es el modelo de pensamiento más avanzado de Google, capaz de razonar sobre problemas complejos en código, matemáticas y áreas STEM, así como analizar grandes conjuntos de datos, bases de código y documentos utilizando contextos extensos."
},
"gemini-3-pro-preview": {
"description": "Gemini 3 Pro es el modelo más inteligente de Google, con razonamiento de última generación, comprensión multimodal y potentes capacidades de agente y codificación contextual."
},
"gemini-flash-latest": {
"description": "Última versión de Gemini Flash"
},
+4
View File
@@ -65,6 +65,9 @@
"thinking": {
"title": "کلید تفکر عمیق"
},
"thinkingLevel": {
"title": "سطح تفکر"
},
"title": "ویژگی‌های گسترش مدل",
"urlContext": {
"desc": "با فعال‌سازی، لینک‌های وب به‌طور خودکار تجزیه می‌شوند تا محتوای واقعی زمینه وب‌سایت به‌دست آید",
@@ -396,6 +399,7 @@
"rejectReasonPlaceholder": "وارد کردن دلیل رد به Agent کمک می‌کند تا اقدامات بعدی را بهینه کند",
"rejectTitle": "رد این فراخوانی ابزار",
"rejectedWithReason": "این فراخوانی ابزار با دلیل زیر رد شد:{{reason}}",
"toolAbort": "این فراخوانی ابزار توسط کاربر لغو شد",
"toolRejected": "این فراخوانی ابزار به‌صورت دستی رد شد"
}
},
+3 -6
View File
@@ -1481,9 +1481,6 @@
"gemini-2.0-flash-lite-001": {
"description": "مدل متغیر Gemini 2.0 Flash برای بهینه‌سازی هزینه و تأخیر کم طراحی شده است."
},
"gemini-2.0-flash-preview-image-generation": {
"description": "مدل پیش‌نمایش Gemini 2.0 Flash، از تولید تصویر پشتیبانی می‌کند"
},
"gemini-2.5-flash": {
"description": "Gemini 2.5 Flash مدل با بهترین نسبت قیمت به کارایی گوگل است که امکانات جامع را ارائه می‌دهد."
},
@@ -1511,9 +1508,6 @@
"gemini-2.5-flash-preview-04-17": {
"description": "پیش‌نمایش فلش Gemini 2.5 مدل با بهترین قیمت و کیفیت گوگل است که امکانات جامع و کاملی را ارائه می‌دهد."
},
"gemini-2.5-flash-preview-05-20": {
"description": "Gemini 2.5 Flash Preview مقرون‌به‌صرفه‌ترین مدل گوگل است که امکانات جامع ارائه می‌دهد."
},
"gemini-2.5-flash-preview-09-2025": {
"description": "نسخه پیش‌نمایش (25 سپتامبر 2025) از Gemini 2.5 Flash"
},
@@ -1529,6 +1523,9 @@
"gemini-2.5-pro-preview-06-05": {
"description": "Gemini 2.5 Pro Preview پیشرفته‌ترین مدل تفکر گوگل است که قادر به استدلال درباره مسائل پیچیده در حوزه کد، ریاضیات و STEM است و می‌تواند با استفاده از زمینه طولانی، داده‌های بزرگ، مخازن کد و مستندات را تحلیل کند."
},
"gemini-3-pro-preview": {
"description": "Gemini 3 Pro هوشمندترین مدل Google است که دارای استدلال پیشرفته (SOTA)، درک چندوجهی، و همچنین قابلیت‌های قدرتمند نمایندگی و رمزگذاری زمینه‌ای می‌باشد."
},
"gemini-flash-latest": {
"description": "جدیدترین نسخه Gemini Flash"
},
+4
View File
@@ -65,6 +65,9 @@
"thinking": {
"title": "Interrupteur de réflexion approfondie"
},
"thinkingLevel": {
"title": "Niveau de réflexion"
},
"title": "Fonctionnalités d'extension du modèle",
"urlContext": {
"desc": "Une fois activé, il analysera automatiquement les liens web pour obtenir le contenu contextuel réel de la page web",
@@ -396,6 +399,7 @@
"rejectReasonPlaceholder": "Saisir une raison de rejet aidera l'agent à comprendre et à améliorer ses actions futures",
"rejectTitle": "Rejeter cet appel d'outil",
"rejectedWithReason": "Cet appel d'outil a été rejeté volontairement : {{reason}}",
"toolAbort": "L'appel de l'outil a été annulé par l'utilisateur",
"toolRejected": "Cet appel d'outil a été rejeté volontairement"
}
},
+3 -6
View File
@@ -1481,9 +1481,6 @@
"gemini-2.0-flash-lite-001": {
"description": "Une variante du modèle Gemini 2.0 Flash, optimisée pour des objectifs tels que le rapport coût-efficacité et la faible latence."
},
"gemini-2.0-flash-preview-image-generation": {
"description": "Modèle de prévisualisation Gemini 2.0 Flash, prenant en charge la génération d'images"
},
"gemini-2.5-flash": {
"description": "Gemini 2.5 Flash est le modèle le plus rentable de Google, offrant des fonctionnalités complètes."
},
@@ -1511,9 +1508,6 @@
"gemini-2.5-flash-preview-04-17": {
"description": "Gemini 2.5 Flash Preview est le modèle le plus rentable de Google, offrant des fonctionnalités complètes."
},
"gemini-2.5-flash-preview-05-20": {
"description": "Gemini 2.5 Flash Preview est le modèle le plus rentable de Google, offrant des fonctionnalités complètes."
},
"gemini-2.5-flash-preview-09-2025": {
"description": "Version de prévisualisation (25 septembre 2025) de Gemini 2.5 Flash"
},
@@ -1529,6 +1523,9 @@
"gemini-2.5-pro-preview-06-05": {
"description": "Gemini 2.5 Pro Preview est le modèle de pensée le plus avancé de Google, capable de raisonner sur des problèmes complexes en code, mathématiques et domaines STEM, ainsi que d'analyser de grands ensembles de données, bibliothèques de code et documents avec un contexte étendu."
},
"gemini-3-pro-preview": {
"description": "Gemini 3 Pro est le modèle le plus intelligent de Google, doté dun raisonnement de pointe, dune compréhension multimodale, ainsi que de puissantes capacités dagent et de codage dambiance."
},
"gemini-flash-latest": {
"description": "Dernière version de Gemini Flash"
},
+4
View File
@@ -65,6 +65,9 @@
"thinking": {
"title": "Interruttore di pensiero profondo"
},
"thinkingLevel": {
"title": "Livello di pensiero"
},
"title": "Funzionalità di estensione del modello",
"urlContext": {
"desc": "Se abilitato, analizzerà automaticamente i link delle pagine web per ottenere il contenuto contestuale reale della pagina",
@@ -396,6 +399,7 @@
"rejectReasonPlaceholder": "Inserire il motivo del rifiuto aiuterà l'agente a comprendere e ottimizzare le azioni future",
"rejectTitle": "Rifiuta questa esecuzione dello strumento",
"rejectedWithReason": "Questa esecuzione dello strumento è stata rifiutata: {{reason}}",
"toolAbort": "L'utilizzo dello strumento è stato annullato dall'utente",
"toolRejected": "Questa esecuzione dello strumento è stata rifiutata"
}
},
+3 -6
View File
@@ -1481,9 +1481,6 @@
"gemini-2.0-flash-lite-001": {
"description": "Gemini 2.0 Flash è una variante del modello Flash, ottimizzata per obiettivi come il rapporto costo-efficacia e la bassa latenza."
},
"gemini-2.0-flash-preview-image-generation": {
"description": "Gemini 2.0 Flash modello di anteprima, supporta la generazione di immagini"
},
"gemini-2.5-flash": {
"description": "Gemini 2.5 Flash è il modello Google con il miglior rapporto qualità-prezzo, offrendo funzionalità complete."
},
@@ -1511,9 +1508,6 @@
"gemini-2.5-flash-preview-04-17": {
"description": "Gemini 2.5 Flash Preview è il modello più conveniente di Google, che offre funzionalità complete."
},
"gemini-2.5-flash-preview-05-20": {
"description": "Gemini 2.5 Flash Preview è il modello Google con il miglior rapporto qualità-prezzo, che offre funzionalità complete."
},
"gemini-2.5-flash-preview-09-2025": {
"description": "Versione anteprima (25 settembre 2025) di Gemini 2.5 Flash"
},
@@ -1529,6 +1523,9 @@
"gemini-2.5-pro-preview-06-05": {
"description": "Gemini 2.5 Pro Preview è il modello di pensiero più avanzato di Google, capace di ragionare su problemi complessi in codice, matematica e ambito STEM, oltre a utilizzare contesti estesi per analizzare grandi dataset, librerie di codice e documenti."
},
"gemini-3-pro-preview": {
"description": "Gemini 3 Pro è il modello più intelligente di Google, con capacità all'avanguardia di ragionamento e comprensione multimodale, oltre a potenti funzionalità di agente e codifica del contesto."
},
"gemini-flash-latest": {
"description": "Ultima versione di Gemini Flash"
},
+4
View File
@@ -65,6 +65,9 @@
"thinking": {
"title": "深い思考のスイッチ"
},
"thinkingLevel": {
"title": "思考レベル"
},
"title": "モデル拡張機能",
"urlContext": {
"desc": "有効にすると、実際のウェブページのコンテキスト内容を取得するためにウェブリンクを自動的に解析します",
@@ -396,6 +399,7 @@
"rejectReasonPlaceholder": "拒否理由を入力すると、エージェントが理解し今後の行動を最適化するのに役立ちます",
"rejectTitle": "今回のツール呼び出しを拒否",
"rejectedWithReason": "今回のツール呼び出しは次の理由で拒否されました:{{reason}}",
"toolAbort": "このツールの呼び出しはユーザーによってキャンセルされました",
"toolRejected": "今回のツール呼び出しは拒否されました"
}
},
+3 -6
View File
@@ -1481,9 +1481,6 @@
"gemini-2.0-flash-lite-001": {
"description": "Gemini 2.0 Flashモデルのバリアントで、コスト効率と低遅延などの目標に最適化されています。"
},
"gemini-2.0-flash-preview-image-generation": {
"description": "Gemini 2.0 Flash プレビュー モデル、画像生成をサポート"
},
"gemini-2.5-flash": {
"description": "Gemini 2.5 FlashはGoogleのコストパフォーマンスに優れたモデルで、包括的な機能を提供します。"
},
@@ -1511,9 +1508,6 @@
"gemini-2.5-flash-preview-04-17": {
"description": "Gemini 2.5 Flash Previewは、Googleのコストパフォーマンスに優れたモデルで、包括的な機能を提供します。"
},
"gemini-2.5-flash-preview-05-20": {
"description": "Gemini 2.5 Flash PreviewはGoogleのコストパフォーマンスに優れたモデルで、包括的な機能を提供します。"
},
"gemini-2.5-flash-preview-09-2025": {
"description": "Gemini 2.5 Flash のプレビューリリース(2025年9月25日)"
},
@@ -1529,6 +1523,9 @@
"gemini-2.5-pro-preview-06-05": {
"description": "Gemini 2.5 Pro Preview は Google の最先端思考モデルで、コード、数学、STEM 分野の複雑な問題を推論し、長いコンテキストを用いて大規模なデータセット、コードベース、ドキュメントを分析できます。"
},
"gemini-3-pro-preview": {
"description": "Gemini 3 Pro は、Google で最も高度なモデルであり、最先端の推論能力とマルチモーダルな理解力、さらに強力なエージェント機能とコンテキスト認識機能を備えています。"
},
"gemini-flash-latest": {
"description": "Gemini Flash の最新リリース"
},
+4
View File
@@ -65,6 +65,9 @@
"thinking": {
"title": "심층 사고 스위치"
},
"thinkingLevel": {
"title": "사고 수준"
},
"title": "모델 확장 기능",
"urlContext": {
"desc": "활성화하면 실제 웹페이지 컨텍스트 내용을 얻기 위해 웹 링크를 자동으로 분석합니다",
@@ -396,6 +399,7 @@
"rejectReasonPlaceholder": "거부 사유를 입력하면 에이전트가 이해하고 이후 행동을 최적화하는 데 도움이 됩니다",
"rejectTitle": "이번 도구 호출 거부",
"rejectedWithReason": "이번 도구 호출이 다음 사유로 거부됨: {{reason}}",
"toolAbort": "이번 도구 호출이 사용자에 의해 취소되었습니다",
"toolRejected": "이번 도구 호출이 거부되었습니다"
}
},
+3 -6
View File
@@ -1481,9 +1481,6 @@
"gemini-2.0-flash-lite-001": {
"description": "Gemini 2.0 플래시 모델 변형으로, 비용 효율성과 낮은 지연 시간 등의 목표를 위해 최적화되었습니다."
},
"gemini-2.0-flash-preview-image-generation": {
"description": "Gemini 2.0 Flash 미리보기 모델로, 이미지 생성을 지원합니다."
},
"gemini-2.5-flash": {
"description": "Gemini 2.5 Flash는 구글에서 가장 가성비가 뛰어난 모델로, 포괄적인 기능을 제공합니다."
},
@@ -1511,9 +1508,6 @@
"gemini-2.5-flash-preview-04-17": {
"description": "Gemini 2.5 Flash Preview는 Google의 가장 가성비 높은 모델로, 포괄적인 기능을 제공합니다."
},
"gemini-2.5-flash-preview-05-20": {
"description": "Gemini 2.5 Flash Preview는 Google의 최고의 가성비 모델로, 포괄적인 기능을 제공합니다."
},
"gemini-2.5-flash-preview-09-2025": {
"description": "Gemini 2.5 Flash의 미리보기 버전 (2025년 9월 25일)"
},
@@ -1529,6 +1523,9 @@
"gemini-2.5-pro-preview-06-05": {
"description": "Gemini 2.5 Pro Preview는 구글의 최첨단 사고 모델로, 코드, 수학 및 STEM 분야의 복잡한 문제를 추론할 수 있으며, 긴 문맥을 활용해 대규모 데이터셋, 코드베이스 및 문서를 분석합니다."
},
"gemini-3-pro-preview": {
"description": "Gemini 3 Pro는 Google의 가장 지능적인 모델로, 최첨단 추론 및 멀티모달 이해 능력과 강력한 에이전트 기능 및 분위기 인코딩 기능을 갖추고 있습니다."
},
"gemini-flash-latest": {
"description": "Gemini Flash 최신 버전"
},
+4
View File
@@ -65,6 +65,9 @@
"thinking": {
"title": "Diepdenkschakelaar"
},
"thinkingLevel": {
"title": "Denkniveau"
},
"title": "Modeluitbreidingsfunctie",
"urlContext": {
"desc": "Na inschakeling worden webkoppelingen automatisch geanalyseerd om de daadwerkelijke inhoud van de webpagina te verkrijgen",
@@ -396,6 +399,7 @@
"rejectReasonPlaceholder": "Het opgeven van een reden helpt de agent om toekomstige acties te verbeteren",
"rejectTitle": "Huidige tooloproep weigeren",
"rejectedWithReason": "Deze tooloproep is geweigerd met reden: {{reason}}",
"toolAbort": "Deze hulpmiddeloproep is door de gebruiker geannuleerd",
"toolRejected": "Deze tooloproep is geweigerd"
}
},
+3 -6
View File
@@ -1481,9 +1481,6 @@
"gemini-2.0-flash-lite-001": {
"description": "Gemini 2.0 Flash is een modelvariant die is geoptimaliseerd voor kosteneffectiviteit en lage latentie."
},
"gemini-2.0-flash-preview-image-generation": {
"description": "Gemini 2.0 Flash previewmodel, ondersteunt beeldgeneratie"
},
"gemini-2.5-flash": {
"description": "Gemini 2.5 Flash is het meest kosteneffectieve model van Google en biedt uitgebreide functionaliteiten."
},
@@ -1511,9 +1508,6 @@
"gemini-2.5-flash-preview-04-17": {
"description": "Gemini 2.5 Flash Preview is het meest kosteneffectieve model van Google, dat uitgebreide functionaliteit biedt."
},
"gemini-2.5-flash-preview-05-20": {
"description": "Gemini 2.5 Flash Preview is het meest kosteneffectieve model van Google en biedt uitgebreide functionaliteiten."
},
"gemini-2.5-flash-preview-09-2025": {
"description": "Preview release (25 september 2025) van Gemini 2.5 Flash"
},
@@ -1529,6 +1523,9 @@
"gemini-2.5-pro-preview-06-05": {
"description": "Gemini 2.5 Pro Preview is Google's meest geavanceerde denkwijze-model, in staat om complexe problemen op het gebied van code, wiskunde en STEM te redeneren, en grote datasets, codebases en documenten te analyseren met lange context."
},
"gemini-3-pro-preview": {
"description": "Gemini 3 Pro is het meest geavanceerde model van Google, met state-of-the-art redeneringsvermogen en multimodale interpretatie, evenals krachtige agent- en contextcodering."
},
"gemini-flash-latest": {
"description": "Laatste release van Gemini Flash"
},
+4
View File
@@ -65,6 +65,9 @@
"thinking": {
"title": "Przełącznik głębokiego myślenia"
},
"thinkingLevel": {
"title": "Poziom myślenia"
},
"title": "Funkcje rozszerzenia modelu",
"urlContext": {
"desc": "Po włączeniu automatycznie analizuje linki do stron internetowych, aby uzyskać rzeczywistą zawartość kontekstu strony",
@@ -396,6 +399,7 @@
"rejectReasonPlaceholder": "Wprowadzenie powodu odrzucenia pomoże Agentowi zrozumieć i zoptymalizować przyszłe działania",
"rejectTitle": "Odrzuć to wywołanie narzędzia",
"rejectedWithReason": "To wywołanie narzędzia zostało odrzucone: {{reason}}",
"toolAbort": "To wywołanie narzędzia zostało anulowane przez użytkownika",
"toolRejected": "To wywołanie narzędzia zostało odrzucone"
}
},
+3 -6
View File
@@ -1481,9 +1481,6 @@
"gemini-2.0-flash-lite-001": {
"description": "Gemini 2.0 Flash to wariant modelu, zoptymalizowany pod kątem efektywności kosztowej i niskiego opóźnienia."
},
"gemini-2.0-flash-preview-image-generation": {
"description": "Model Gemini 2.0 Flash do generowania obrazów, wspierający generację obrazów"
},
"gemini-2.5-flash": {
"description": "Gemini 2.5 Flash to najbardziej opłacalny model Google, oferujący wszechstronne funkcje."
},
@@ -1511,9 +1508,6 @@
"gemini-2.5-flash-preview-04-17": {
"description": "Gemini 2.5 Flash Preview to najbardziej opłacalny model Google, oferujący wszechstronne funkcje."
},
"gemini-2.5-flash-preview-05-20": {
"description": "Gemini 2.5 Flash Preview to najbardziej opłacalny model Google, oferujący wszechstronne funkcje."
},
"gemini-2.5-flash-preview-09-2025": {
"description": "Wersja podglądowa (25 września 2025) Gemini 2.5 Flash"
},
@@ -1529,6 +1523,9 @@
"gemini-2.5-pro-preview-06-05": {
"description": "Gemini 2.5 Pro Preview to najnowocześniejszy model myślowy Google, zdolny do rozumowania nad złożonymi problemami w dziedzinach kodowania, matematyki i STEM oraz do analizy dużych zbiorów danych, repozytoriów kodu i dokumentów z wykorzystaniem długich kontekstów."
},
"gemini-3-pro-preview": {
"description": "Gemini 3 Pro to najinteligentniejszy model Google, oferujący najnowocześniejsze wnioskowanie i rozumienie multimodalne, a także zaawansowane funkcje agenta i kodowania kontekstu."
},
"gemini-flash-latest": {
"description": "Najnowsze wydanie Gemini Flash"
},
+4
View File
@@ -65,6 +65,9 @@
"thinking": {
"title": "Interruptor de Pensamento Profundo"
},
"thinkingLevel": {
"title": "Nível de raciocínio"
},
"title": "Funcionalidade de Extensão do Modelo",
"urlContext": {
"desc": "Ao ativar, os links da web serão automaticamente analisados para obter o conteúdo real do contexto da página",
@@ -396,6 +399,7 @@
"rejectReasonPlaceholder": "Inserir o motivo da rejeição ajudará o Agente a entender e melhorar ações futuras",
"rejectTitle": "Rejeitar esta chamada de ferramenta",
"rejectedWithReason": "Esta chamada de ferramenta foi rejeitada: {{reason}}",
"toolAbort": "Esta chamada de ferramenta foi cancelada pelo usuário",
"toolRejected": "Esta chamada de ferramenta foi rejeitada"
}
},
+3 -6
View File
@@ -1481,9 +1481,6 @@
"gemini-2.0-flash-lite-001": {
"description": "Variante do modelo Gemini 2.0 Flash, otimizada para custo-benefício e baixa latência."
},
"gemini-2.0-flash-preview-image-generation": {
"description": "Modelo de pré-visualização Gemini 2.0 Flash, suporta geração de imagens"
},
"gemini-2.5-flash": {
"description": "Gemini 2.5 Flash é o modelo com melhor custo-benefício do Google, oferecendo funcionalidades abrangentes."
},
@@ -1511,9 +1508,6 @@
"gemini-2.5-flash-preview-04-17": {
"description": "O Gemini 2.5 Flash Preview é o modelo mais acessível do Google, oferecendo uma gama completa de funcionalidades."
},
"gemini-2.5-flash-preview-05-20": {
"description": "Gemini 2.5 Flash Preview é o modelo com melhor custo-benefício do Google, oferecendo funcionalidades abrangentes."
},
"gemini-2.5-flash-preview-09-2025": {
"description": "Versão prévia (25 de setembro de 2025) do Gemini 2.5 Flash"
},
@@ -1529,6 +1523,9 @@
"gemini-2.5-pro-preview-06-05": {
"description": "Gemini 2.5 Pro Preview é o modelo de pensamento mais avançado do Google, capaz de raciocinar sobre problemas complexos em código, matemática e áreas STEM, além de analisar grandes conjuntos de dados, bibliotecas de código e documentos usando contexto extenso."
},
"gemini-3-pro-preview": {
"description": "O Gemini 3 Pro é o modelo mais inteligente do Google, com raciocínio de última geração, compreensão multimodal e poderosos recursos de agente e codificação de contexto."
},
"gemini-flash-latest": {
"description": "Última versão do Gemini Flash"
},
+4
View File
@@ -65,6 +65,9 @@
"thinking": {
"title": "Переключатель глубокого мышления"
},
"thinkingLevel": {
"title": "Уровень мышления"
},
"title": "Расширенные функции модели",
"urlContext": {
"desc": "При включении автоматически будет анализироваться ссылка на веб-страницу для получения фактического содержимого контекста страницы",
@@ -396,6 +399,7 @@
"rejectReasonPlaceholder": "Указание причины отклонения поможет агенту лучше понять и улучшить последующие действия",
"rejectTitle": "Отклонить вызов инструмента",
"rejectedWithReason": "Вызов инструмента был отклонён по следующей причине: {{reason}}",
"toolAbort": "Вызов инструмента был отменён пользователем",
"toolRejected": "Вызов инструмента был отклонён"
}
},
+3 -6
View File
@@ -1481,9 +1481,6 @@
"gemini-2.0-flash-lite-001": {
"description": "Модельный вариант Gemini 2.0 Flash, оптимизированный для достижения таких целей, как экономическая эффективность и низкая задержка."
},
"gemini-2.0-flash-preview-image-generation": {
"description": "Модель предварительного просмотра Gemini 2.0 Flash, поддерживающая генерацию изображений"
},
"gemini-2.5-flash": {
"description": "Gemini 2.5 Flash — самая экономичная модель Google, предоставляющая полный набор функций."
},
@@ -1511,9 +1508,6 @@
"gemini-2.5-flash-preview-04-17": {
"description": "Gemini 2.5 Flash Preview — это наиболее выгодная модель от Google, предлагающая широкий спектр возможностей."
},
"gemini-2.5-flash-preview-05-20": {
"description": "Gemini 2.5 Flash Preview — самая экономичная модель Google с полным набором функций."
},
"gemini-2.5-flash-preview-09-2025": {
"description": "Предварительный релиз Gemini 2.5 Flash (25 сентября 2025 года)"
},
@@ -1529,6 +1523,9 @@
"gemini-2.5-pro-preview-06-05": {
"description": "Gemini 2.5 Pro Preview — передовая модель мышления от Google, способная рассуждать над сложными задачами в области кода, математики и STEM, а также анализировать большие наборы данных, кодовые базы и документы с использованием длинного контекста."
},
"gemini-3-pro-preview": {
"description": "Gemini 3 Pro — самая интеллектуальная модель от Google с передовыми возможностями рассуждения, мультимодального понимания, а также мощными функциями агента и кодирования контекста."
},
"gemini-flash-latest": {
"description": "Последний релиз Gemini Flash"
},
+4
View File
@@ -65,6 +65,9 @@
"thinking": {
"title": "Derin Düşünme Anahtarı"
},
"thinkingLevel": {
"title": "Düşünme Seviyesi"
},
"title": "Model Genişletme Özellikleri",
"urlContext": {
"desc": "Etkinleştirildiğinde, gerçek web sayfası bağlam içeriğini almak için web bağlantıları otomatik olarak çözümlenir",
@@ -396,6 +399,7 @@
"rejectReasonPlaceholder": "Reddetme nedenini girmek, Agent'ın anlamasına ve sonraki eylemleri iyileştirmesine yardımcı olur",
"rejectTitle": "Bu Araç Çağrısını Reddet",
"rejectedWithReason": "Bu araç çağrısı şu nedenle reddedildi: {{reason}}",
"toolAbort": "Bu araç çağrısı kullanıcı tarafından iptal edildi",
"toolRejected": "Bu araç çağrısı kullanıcı tarafından reddedildi"
}
},
+3 -6
View File
@@ -1481,9 +1481,6 @@
"gemini-2.0-flash-lite-001": {
"description": "Gemini 2.0 Flash model varyantı, maliyet etkinliği ve düşük gecikme gibi hedefler için optimize edilmiştir."
},
"gemini-2.0-flash-preview-image-generation": {
"description": "Gemini 2.0 Flash önizleme modeli, görüntü üretimini destekler."
},
"gemini-2.5-flash": {
"description": "Gemini 2.5 Flash, Google'ın en yüksek maliyet-performans modelidir ve kapsamlı özellikler sunar."
},
@@ -1511,9 +1508,6 @@
"gemini-2.5-flash-preview-04-17": {
"description": "Gemini 2.5 Flash Önizleme, Google'ın en iyi fiyat-performans oranına sahip modelidir ve kapsamlı özellikler sunar."
},
"gemini-2.5-flash-preview-05-20": {
"description": "Gemini 2.5 Flash Önizleme, Google'ın en yüksek maliyet-performans modelidir ve kapsamlı özellikler sunar."
},
"gemini-2.5-flash-preview-09-2025": {
"description": "Gemini 2.5 Flash'ın önizleme sürümü (25 Eylül 2025)"
},
@@ -1529,6 +1523,9 @@
"gemini-2.5-pro-preview-06-05": {
"description": "Gemini 2.5 Pro Önizlemesi, Google'ın en gelişmiş düşünce modelidir; kodlama, matematik ve STEM alanlarındaki karmaşık problemleri çözebilir ve uzun bağlam kullanarak büyük veri setleri, kod kütüphaneleri ve belgeleri analiz edebilir."
},
"gemini-3-pro-preview": {
"description": "Gemini 3 Pro, Google'ın en akıllı modeli olup, en son teknoloji çıkarım ve çok modlu anlama yeteneklerinin yanı sıra güçlü aracı ve bağlam kodlama özelliklerine sahiptir."
},
"gemini-flash-latest": {
"description": "Gemini Flash'ın en son sürümü"
},
+4
View File
@@ -65,6 +65,9 @@
"thinking": {
"title": "Công tắc suy nghĩ sâu"
},
"thinkingLevel": {
"title": "Mức độ tư duy"
},
"title": "Chức năng mở rộng mô hình",
"urlContext": {
"desc": "Bật tính năng này sẽ tự động phân tích liên kết trang web để lấy nội dung ngữ cảnh thực tế của trang web",
@@ -396,6 +399,7 @@
"rejectReasonPlaceholder": "Nhập lý do từ chối sẽ giúp Agent hiểu và cải thiện hành động sau này",
"rejectTitle": "Từ chối lần gọi công cụ này",
"rejectedWithReason": "Lần gọi công cụ này đã bị từ chối: {{reason}}",
"toolAbort": "Lần gọi công cụ này đã bị người dùng hủy",
"toolRejected": "Lần gọi công cụ này đã bị từ chối"
}
},
+3 -6
View File
@@ -1481,9 +1481,6 @@
"gemini-2.0-flash-lite-001": {
"description": "Biến thể mô hình Gemini 2.0 Flash được tối ưu hóa cho hiệu quả chi phí và độ trễ thấp."
},
"gemini-2.0-flash-preview-image-generation": {
"description": "Mô hình xem trước Gemini 2.0 Flash, hỗ trợ tạo hình ảnh"
},
"gemini-2.5-flash": {
"description": "Gemini 2.5 Flash là mô hình có hiệu suất chi phí tốt nhất của Google, cung cấp đầy đủ các chức năng."
},
@@ -1511,9 +1508,6 @@
"gemini-2.5-flash-preview-04-17": {
"description": "Gemini 2.5 Flash Preview là mô hình có giá trị tốt nhất của Google, cung cấp đầy đủ các tính năng."
},
"gemini-2.5-flash-preview-05-20": {
"description": "Gemini 2.5 Flash Preview là mô hình có hiệu suất chi phí tốt nhất của Google, cung cấp các tính năng toàn diện."
},
"gemini-2.5-flash-preview-09-2025": {
"description": "Phiên bản xem trước (25 tháng 9 năm 2025) của Gemini 2.5 Flash"
},
@@ -1529,6 +1523,9 @@
"gemini-2.5-pro-preview-06-05": {
"description": "Gemini 2.5 Pro Preview là mô hình tư duy tiên tiến nhất của Google, có khả năng suy luận các vấn đề phức tạp trong lĩnh vực mã nguồn, toán học và STEM, cũng như phân tích dữ liệu lớn, kho mã và tài liệu với ngữ cảnh dài."
},
"gemini-3-pro-preview": {
"description": "Gemini 3 Pro là mô hình thông minh nhất của Google, với khả năng suy luận tiên tiến hàng đầu và hiểu đa phương thức, cùng với các tính năng đại lý mạnh mẽ và mã hóa ngữ cảnh vượt trội."
},
"gemini-flash-latest": {
"description": "Phiên bản mới nhất của Gemini Flash"
},
+4
View File
@@ -65,6 +65,9 @@
"thinking": {
"title": "深度思考开关"
},
"thinkingLevel": {
"title": "思考水平"
},
"title": "模型扩展功能",
"urlContext": {
"desc": "开启后将自动解析网页链接,以获取实际网页上下文内容",
@@ -396,6 +399,7 @@
"rejectReasonPlaceholder": "输入拒绝原因将帮助 Agent 理解并优化后续行动",
"rejectTitle": "拒绝本次工具调用",
"rejectedWithReason": "本次工具调用被主动拒绝:{{reason}}",
"toolAbort": "本次工具调用被用户取消",
"toolRejected": "本次工具调用被主动拒绝"
}
},
+1
View File
@@ -283,6 +283,7 @@
"business": "商务合作",
"support": "邮件支持"
},
"new": "新",
"oauth": "SSO 登录",
"officialSite": "官方网站",
"ok": "确定",
+3 -6
View File
@@ -1481,9 +1481,6 @@
"gemini-2.0-flash-lite-001": {
"description": "Gemini 2.0 Flash 模型变体,针对成本效益和低延迟等目标进行了优化。"
},
"gemini-2.0-flash-preview-image-generation": {
"description": "Gemini 2.0 Flash 预览模型,支持图像生成"
},
"gemini-2.5-flash": {
"description": "Gemini 2.5 Flash 是 Google 性价比最高的模型,提供全面的功能。"
},
@@ -1511,9 +1508,6 @@
"gemini-2.5-flash-preview-04-17": {
"description": "Gemini 2.5 Flash Preview 是 Google 性价比最高的模型,提供全面的功能。"
},
"gemini-2.5-flash-preview-05-20": {
"description": "Gemini 2.5 Flash Preview 是 Google 性价比最高的模型,提供全面的功能。"
},
"gemini-2.5-flash-preview-09-2025": {
"description": "Preview release (Septempber 25th, 2025) of Gemini 2.5 Flash"
},
@@ -1529,6 +1523,9 @@
"gemini-2.5-pro-preview-06-05": {
"description": "Gemini 2.5 Pro Preview 是 Google 最先进的思维模型,能够对代码、数学和STEM领域的复杂问题进行推理,以及使用长上下文分析大型数据集、代码库和文档。"
},
"gemini-3-pro-preview": {
"description": "Gemini 3 Pro 是 Google 最智能的模型,具有 SOTA 推理和多模式理解,以及强大的代理和氛围编码功能。"
},
"gemini-flash-latest": {
"description": "Latest release of Gemini Flash"
},
+4
View File
@@ -65,6 +65,9 @@
"thinking": {
"title": "深度思考開關"
},
"thinkingLevel": {
"title": "思考層級"
},
"title": "模型擴展功能",
"urlContext": {
"desc": "開啟後將自動解析網頁連結,以取得實際網頁上下文內容",
@@ -396,6 +399,7 @@
"rejectReasonPlaceholder": "輸入拒絕原因將幫助 Agent 理解並優化後續行動",
"rejectTitle": "拒絕本次工具調用",
"rejectedWithReason": "本次工具調用被主動拒絕:{{reason}}",
"toolAbort": "本次工具呼叫已被使用者取消",
"toolRejected": "本次工具調用被主動拒絕"
}
},
+3 -6
View File
@@ -1481,9 +1481,6 @@
"gemini-2.0-flash-lite-001": {
"description": "Gemini 2.0 Flash 模型變體,針對成本效益和低延遲等目標進行了優化。"
},
"gemini-2.0-flash-preview-image-generation": {
"description": "Gemini 2.0 Flash 預覽模型,支持圖像生成"
},
"gemini-2.5-flash": {
"description": "Gemini 2.5 Flash 是 Google 性價比最高的模型,提供全面的功能。"
},
@@ -1511,9 +1508,6 @@
"gemini-2.5-flash-preview-04-17": {
"description": "Gemini 2.5 Flash Preview 是 Google 性價比最高的模型,提供全面的功能。"
},
"gemini-2.5-flash-preview-05-20": {
"description": "Gemini 2.5 Flash Preview 是 Google 性價比最高的模型,提供全面的功能。"
},
"gemini-2.5-flash-preview-09-2025": {
"description": "預覽版本(2025年9月25日)之 Gemini 2.5 Flash"
},
@@ -1529,6 +1523,9 @@
"gemini-2.5-pro-preview-06-05": {
"description": "Gemini 2.5 Pro Preview 是 Google 最先進的思維模型,能夠對程式碼、數學和STEM領域的複雜問題進行推理,以及使用長上下文分析大型資料集、程式碼庫和文件。"
},
"gemini-3-pro-preview": {
"description": "Gemini 3 Pro 是 Google 最智慧的模型,具備最先進的推理與多模態理解能力,以及強大的代理與情境編碼功能。"
},
"gemini-flash-latest": {
"description": "Gemini Flash 最新版本"
},
-1
View File
@@ -42,7 +42,6 @@ const nextConfig: NextConfig = {
// so we need to disable it
// refs: https://github.com/lobehub/lobe-chat/pull/7430
serverMinification: false,
turbopackFileSystemCacheForDev: true,
webVitalsAttribution: ['CLS', 'LCP'],
webpackBuildWorker: true,
webpackMemoryOptimizations: true,
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@lobehub/lobehub",
"version": "2.0.0-next.86",
"version": "2.0.0-next.92",
"description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
"keywords": [
"framework",
@@ -141,7 +141,7 @@
"@emotion/react": "^11.14.0",
"@fal-ai/client": "^1.7.2",
"@formkit/auto-animate": "^0.9.0",
"@google/genai": "^1.29.1",
"@google/genai": "^1.30.0",
"@huggingface/inference": "^4.13.3",
"@icons-pack/react-simple-icons": "^13.8.0",
"@khmyznikov/pwa-install": "0.3.9",
@@ -130,6 +130,7 @@ export class ToolCallProcessor extends BaseProcessor {
: `${tool.identifier}.${tool.apiName}`,
},
id: tool.id,
thoughtSignature: tool.thoughtSignature,
type: 'function',
}),
);
@@ -72,6 +72,65 @@ describe('ToolCallProcessor', () => {
]);
});
it('should pass through thoughtSignature when present', async () => {
const processor = new ToolCallProcessor(defaultConfig);
const context = createContext([
{
content: '',
id: 'msg1',
role: 'assistant',
tools: [
{
apiName: 'search',
arguments: '{"query":"test"}',
id: 'call_1',
identifier: 'web',
thoughtSignature: 'Let me search for this information',
type: 'builtin',
},
],
},
]);
const result = await processor.process(context);
expect(result.messages[0].tool_calls).toEqual([
{
function: {
arguments: '{"query":"test"}',
name: 'web.search',
},
id: 'call_1',
thoughtSignature: 'Let me search for this information',
type: 'function',
},
]);
});
it('should handle missing thoughtSignature', async () => {
const processor = new ToolCallProcessor(defaultConfig);
const context = createContext([
{
content: '',
id: 'msg1',
role: 'assistant',
tools: [
{
apiName: 'search',
arguments: '{"query":"test"}',
id: 'call_1',
identifier: 'web',
type: 'builtin',
},
],
},
]);
const result = await processor.process(context);
expect(result.messages[0].tool_calls[0].thoughtSignature).toBeUndefined();
});
it('should use custom genToolCallingName function', async () => {
const genToolCallingName = vi.fn(
(identifier, apiName, type) => `custom_${identifier}_${apiName}_${type}`,
@@ -82,6 +82,7 @@ export class ToolNameResolver {
arguments: toolCall.function.arguments,
id: toolCall.id,
identifier,
thoughtSignature: toolCall.thoughtSignature,
type: (type ?? 'default') as any,
};
@@ -455,6 +455,63 @@ describe('ToolNameResolver', () => {
});
});
describe('resolve - thoughtSignature', () => {
it('should pass through thoughtSignature when present', () => {
const toolCalls = [
{
function: {
arguments: '{"query": "test"}',
name: 'test-plugin____myAction____builtin',
},
id: 'call_1',
thoughtSignature: 'thinking about this...',
type: 'function',
},
];
const manifests = {
'test-plugin': {
api: [{ description: 'My action', name: 'myAction', parameters: {} }],
identifier: 'test-plugin',
meta: {},
type: 'builtin' as const,
},
};
const result = resolver.resolve(toolCalls, manifests);
expect(result).toHaveLength(1);
expect(result[0].thoughtSignature).toBe('thinking about this...');
});
it('should handle missing thoughtSignature', () => {
const toolCalls = [
{
function: {
arguments: '{"query": "test"}',
name: 'test-plugin____myAction____builtin',
},
id: 'call_1',
type: 'function',
},
];
const manifests = {
'test-plugin': {
api: [{ description: 'My action', name: 'myAction', parameters: {} }],
identifier: 'test-plugin',
meta: {},
type: 'builtin' as const,
},
};
const result = resolver.resolve(toolCalls, manifests);
expect(result).toHaveLength(1);
expect(result[0].thoughtSignature).toBeUndefined();
});
});
describe('resolve - edge cases', () => {
it('should filter out invalid tool calls with missing apiName', () => {
const toolCalls = [
+1
View File
@@ -30,6 +30,7 @@ export interface MessageToolCall {
name: string;
};
id: string;
thoughtSignature?: string;
type: 'function';
}
export interface Message {
+12 -2
View File
@@ -17,7 +17,7 @@ import { nanoid } from '@lobechat/utils/uuid';
import { getMessageError } from './parseError';
type SSEFinishType = 'done' | 'error' | 'abort';
type SSEFinishType = 'done' | 'error' | 'abort' | string;
export type OnFinishHandler = (
text: string,
@@ -48,6 +48,10 @@ export interface MessageTextChunk {
text: string;
type: 'text';
}
export interface MessageStopChunk {
reason: string;
type: 'stop';
}
export interface MessageBase64ImageChunk {
id: string;
@@ -86,7 +90,8 @@ export interface FetchSSEOptions {
| MessageGroundingChunk
| MessageUsageChunk
| MessageBase64ImageChunk
| MessageSpeedChunk,
| MessageSpeedChunk
| MessageStopChunk,
) => void;
responseAnimation?: ResponseAnimation;
}
@@ -387,6 +392,11 @@ export const fetchSSE = async (url: string, options: RequestInit & FetchSSEOptio
break;
}
case 'stop': {
options.onMessageHandle?.({ reason: data, type: 'stop' });
break;
}
case 'reasoning': {
if (textSmoothing) {
thinkingController.pushToQueue(data);
+60 -1
View File
@@ -986,6 +986,66 @@ const aihubmixModels: AIChatModelCard[] = [
},
type: 'chat',
},
{
abilities: {
functionCall: true,
reasoning: true,
search: true,
video: true,
vision: true,
},
contextWindowTokens: 1_048_576 + 65_536,
description:
'Gemini 3 Pro 是 Google 最智能的模型,具有 SOTA 推理和多模式理解,以及强大的代理和氛围编码功能。',
displayName: 'Gemini 3 Pro Preview',
enabled: true,
id: 'gemini-3-pro-preview',
maxOutput: 65_536,
pricing: {
units: [
{
name: 'textInput_cacheRead',
strategy: 'tiered',
tiers: [
{ rate: 0.2, upTo: 200_000 },
{ rate: 0.4, upTo: 'infinity' },
],
unit: 'millionTokens',
},
{
name: 'textInput',
strategy: 'tiered',
tiers: [
{ rate: 2, upTo: 200_000 },
{ rate: 4, upTo: 'infinity' },
],
unit: 'millionTokens',
},
{
name: 'textOutput',
strategy: 'tiered',
tiers: [
{ rate: 12, upTo: 200_000 },
{ rate: 18, upTo: 'infinity' },
],
unit: 'millionTokens',
},
{
lookup: { prices: { '1h': 4.5 }, pricingParams: ['ttl'] },
name: 'textInput_cacheWrite',
strategy: 'lookup',
unit: 'millionTokens',
},
],
},
releasedAt: '2025-11-18',
settings: {
extendParams: ['thinkingLevel', 'urlContext'],
searchImpl: 'params',
searchProvider: 'google',
},
type: 'chat',
},
{
abilities: {
functionCall: true,
@@ -998,7 +1058,6 @@ const aihubmixModels: AIChatModelCard[] = [
description:
'Gemini 2.5 Pro 是 Google 最先进的思维模型,能够对代码、数学和STEM领域的复杂问题进行推理,以及使用长上下文分析大型数据集、代码库和文档。',
displayName: 'Gemini 2.5 Pro',
enabled: true,
id: 'gemini-2.5-pro',
maxOutput: 65_536,
pricing: {
+21 -86
View File
@@ -123,8 +123,8 @@ const googleChatModels: AIChatModelCard[] = [
},
contextWindowTokens: 1_048_576 + 65_536,
description:
'Gemini 3.0 Pro Preview 是 Google 最先进的思维模型,能够对代码、数学和STEM领域的复杂问题进行推理,以及使用长上下文分析大型数据集、代码库和文档。',
displayName: 'Gemini 3.0 Pro Preview',
'Gemini 3 Pro 是 全球最佳的多模态理解模型,也是 Google 迄今为止最强大的智能体和氛围编程模型,提供更丰富的视觉效果和更深层次的交互性,所有这些都建立在最先进的推理能力基础之上。',
displayName: 'Gemini 3 Pro Preview',
enabled: true,
id: 'gemini-3-pro-preview',
maxOutput: 65_536,
@@ -134,8 +134,8 @@ const googleChatModels: AIChatModelCard[] = [
name: 'textInput_cacheRead',
strategy: 'tiered',
tiers: [
{ rate: 0.20, upTo: 200_000 },
{ rate: 0.40, upTo: 'infinity' },
{ rate: 0.2, upTo: 200_000 },
{ rate: 0.4, upTo: 'infinity' },
],
unit: 'millionTokens',
},
@@ -143,8 +143,8 @@ const googleChatModels: AIChatModelCard[] = [
name: 'textInput',
strategy: 'tiered',
tiers: [
{ rate: 2.0, upTo: 200_000 },
{ rate: 4.0, upTo: 'infinity' },
{ rate: 2, upTo: 200_000 },
{ rate: 4, upTo: 'infinity' },
],
unit: 'millionTokens',
},
@@ -152,16 +152,22 @@ const googleChatModels: AIChatModelCard[] = [
name: 'textOutput',
strategy: 'tiered',
tiers: [
{ rate: 12.0, upTo: 200_000 },
{ rate: 18.0, upTo: 'infinity' },
{ rate: 12, upTo: 200_000 },
{ rate: 18, upTo: 'infinity' },
],
unit: 'millionTokens',
},
{
lookup: { prices: { '1h': 4.5 }, pricingParams: ['ttl'] },
name: 'textInput_cacheWrite',
strategy: 'lookup',
unit: 'millionTokens',
},
],
},
releasedAt: '2025-11-18',
settings: {
extendParams: ['thinkingBudget', 'urlContext'],
extendParams: ['thinkingLevel', 'urlContext'],
searchImpl: 'params',
searchProvider: 'google',
},
@@ -211,6 +217,12 @@ const googleChatModels: AIChatModelCard[] = [
],
unit: 'millionTokens',
},
{
lookup: { prices: { '1h': 4.5 }, pricingParams: ['ttl'] },
name: 'textInput_cacheWrite',
strategy: 'lookup',
unit: 'millionTokens',
},
],
},
releasedAt: '2025-06-17',
@@ -383,34 +395,6 @@ const googleChatModels: AIChatModelCard[] = [
},
type: 'chat',
},
{
abilities: {
functionCall: true,
reasoning: true,
search: true,
video: true,
vision: true,
},
contextWindowTokens: 1_048_576 + 65_536,
description: 'Gemini 2.5 Flash Preview 是 Google 性价比最高的模型,提供全面的功能。',
displayName: 'Gemini 2.5 Flash Preview 05-20',
id: 'gemini-2.5-flash-preview-05-20',
maxOutput: 65_536,
pricing: {
units: [
{ name: 'textInput_cacheRead', rate: 0.0375, strategy: 'fixed', unit: 'millionTokens' },
{ name: 'textInput', rate: 0.15, strategy: 'fixed', unit: 'millionTokens' },
{ name: 'textOutput', rate: 3.5, strategy: 'fixed', unit: 'millionTokens' },
],
},
releasedAt: '2025-05-20',
settings: {
extendParams: ['thinkingBudget', 'urlContext'],
searchImpl: 'params',
searchProvider: 'google',
},
type: 'chat',
},
{
abilities: {
imageOutput: true,
@@ -514,35 +498,6 @@ const googleChatModels: AIChatModelCard[] = [
},
type: 'chat',
},
{
abilities: {
functionCall: true,
reasoning: true,
search: true,
video: true,
vision: true,
},
contextWindowTokens: 1_048_576 + 65_536,
description:
'Gemini 2.5 Flash-Lite Preview 是 Google 最小、性价比最高的模型,专为大规模使用而设计。',
displayName: 'Gemini 2.5 Flash-Lite Preview 06-17',
id: 'gemini-2.5-flash-lite-preview-06-17',
maxOutput: 65_536,
pricing: {
units: [
{ name: 'textInput_cacheRead', rate: 0.025, strategy: 'fixed', unit: 'millionTokens' },
{ name: 'textInput', rate: 0.1, strategy: 'fixed', unit: 'millionTokens' },
{ name: 'textOutput', rate: 0.4, strategy: 'fixed', unit: 'millionTokens' },
],
},
releasedAt: '2025-06-11',
settings: {
extendParams: ['thinkingBudget', 'urlContext'],
searchImpl: 'params',
searchProvider: 'google',
},
type: 'chat',
},
{
abilities: {
functionCall: true,
@@ -597,26 +552,6 @@ const googleChatModels: AIChatModelCard[] = [
},
type: 'chat',
},
{
abilities: {
imageOutput: true,
vision: true,
},
contextWindowTokens: 32_768 + 8192,
description: 'Gemini 2.0 Flash 预览模型,支持图像生成',
displayName: 'Gemini 2.0 Flash Preview Image Generation',
id: 'gemini-2.0-flash-preview-image-generation',
maxOutput: 8192,
pricing: {
units: [
{ name: 'textInput', rate: 0.1, strategy: 'fixed', unit: 'millionTokens' },
{ name: 'textOutput', rate: 0.4, strategy: 'fixed', unit: 'millionTokens' },
{ name: 'imageGeneration', rate: 0.039, strategy: 'fixed', unit: 'image' },
],
},
releasedAt: '2025-05-07',
type: 'chat',
},
{
abilities: {
imageOutput: true,
@@ -5,12 +5,14 @@ const ollamaCloudModels: AIChatModelCard[] = [
abilities: {
functionCall: true,
reasoning: true,
vision: true,
},
contextWindowTokens: 200_000,
description: 'MiniMax M2 是专为编码和代理工作流程构建的高效大型语言模型。',
displayName: 'MiniMax M2',
contextWindowTokens: 1_048_576,
description:
'Gemini 3 Pro 是 Google 最智能的模型,具有 SOTA 推理和多模式理解,以及强大的代理和氛围编码功能。',
displayName: 'Gemini 3 Pro Preview',
enabled: true,
id: 'minimax-m2',
id: 'gemini-3-pro-preview',
type: 'chat',
},
{
@@ -19,8 +21,7 @@ const ollamaCloudModels: AIChatModelCard[] = [
reasoning: true,
},
contextWindowTokens: 200_000,
description:
'MiniMax M2 是专为编码和代理工作流程构建的高效大型语言模型。',
description: 'MiniMax M2 是专为编码和代理工作流程构建的高效大型语言模型。',
displayName: 'MiniMax M2',
enabled: true,
id: 'minimax-m2',
+2
View File
@@ -242,6 +242,7 @@ export type ExtendParamsType =
| 'textVerbosity'
| 'thinking'
| 'thinkingBudget'
| 'thinkingLevel'
| 'urlContext';
export interface AiModelSettings {
@@ -422,6 +423,7 @@ export interface EnabledAiModel {
id: string;
parameters?: ModelParamsSchema;
providerId: string;
releasedAt?: string;
settings?: AiModelSettings;
sort?: number;
type: AiModelType;
@@ -6,6 +6,7 @@ import { describe, expect, it, vi } from 'vitest';
import { ChatCompletionTool, OpenAIChatMessage, UserMessageContentPart } from '../../types';
import { parseDataUri } from '../../utils/uriParser';
import {
GEMINI_MAGIC_THOUGHT_SIGNATURE,
buildGoogleMessage,
buildGoogleMessages,
buildGooglePart,
@@ -232,6 +233,415 @@ describe('google contextBuilders', () => {
});
});
it('should correctly convert function call message with thoughtSignature', async () => {
const message = {
role: 'assistant',
tool_calls: [
{
function: {
arguments: JSON.stringify({
language: ['JSON'],
path: 'package.json',
query: '"version":',
repo: 'lobehub/lobe-chat',
}),
name: 'grep____searchGitHub____mcp',
},
id: 'grep____searchGitHub____mcp_0_6RnOMTF0',
thoughtSignature:
'EsUHCsIHAdHtim9/MrjP+pnhM8DVkvulyfWQVf+isXQxEAbF32gbflE1hl6Te80qtp77Ywn8opB2uhQOIH/l6SStsj3+XRy1U1DTeKtqZxDBoLP2rNK6pi3/nk0ZOQIc8f6rxB70G/zOhk7d/1XQFqhmw5H+yDVRQjGD1cNPY5ctWGxQLAIk/HMWNovUJzz2c81jGWoXu7k2vtpuur2hcAL+J79BEVUTfvU3mSiXqJFTClmFPB6Fe79i0y3TwM2XdIBxzPgVgf8B+Pnv1S6YDxHNSm46jTlXKcSw30r3ixs5xEOzerbOUW5WG9BGukw/YQVvHiuoGLIALRa2Ig7dlOMH8+o+f0mKJtyYj8yF6wyBMol+G4mhSHvQSKJLj/Z5kFHvDZKeVUEOZed6vZivYLrVezjQPXgLHJMOmbp6QrZGxqW45QxDKY5X5F8giIOM8VgsUYhDQUBown+3vvwkIBA24icDsOwdhJ/roe9GabbGfxpkSzARIFh7rSI01cRKbh6cEaVFXf2WQftPeD7dBseQLiCdUYoy4ytECrjTpknrWnVUG6Ly4SKW6uN/IJXpm9JT9GgnGLIddFtEQzm9sIKWNpGEz6++lZpiCFS6LsYSnTP3vPj/7oSABRmwWywxA8EmLh+sv+jiK5aMjFi1sTuJ0Ujsvza3/SHZKewNi9WKQUDOa9Mqtjs2YGDnJxto4l5GMUzI5vhf6/+/A5eHALfVabaFP97v8FEPrXQU94dognwx4EnNqy/KWmGIlYZYqIfjaSAy7Z74viwl+oTtL9gyyBDc/FrQvXfyrYIq8N0pkLKAEh33fa/+YVocLL1LKI9rb2bg/RRr+Ee4NyIQKhIdEJaEh74d1COd/4r06J92ThkfVo5PEVTSsr8tBKiJ5wSmX9vyhbLWzxmXoq1xfGrs8kg7NMW53XEWGlQrIVOQmUtjjjBQKj6b4rBTAO6EKk63cGFbkSPohifiUBPHbxUUPy/hf0tQpeOo3jA01AuCFLOIZ5IYJ+Rm5+aZTU3Panv+Q7Yl1w5t5swhbNZfg7MlU/sxwLijLuWDDNfw+2Zw/aa3VDPgVw6Nv2vKkHi4tUU0XlgfiQgQYUMPxpGRV837uUxvZFNep2QUlAMog5h4sMYJWIAX1kK1pzsyR/KxuCn6nUq4ovWNBQHLC4aW2ZcGgW/6CbF81F1cewUz+vWNMMkJrL0d9celGEbFuY0Q709UipaDbCg49twlnLV9XUwqC5wYTFBiJbynBDqiZAvXn2YOxNIs8CCzuu2GSCQDo09ksJy5g/o=',
type: 'function',
},
],
} as OpenAIChatMessage;
const converted = await buildGoogleMessage(message);
expect(converted).toEqual({
parts: [
{
functionCall: {
args: {
language: ['JSON'],
path: 'package.json',
query: '"version":',
repo: 'lobehub/lobe-chat',
},
name: 'grep____searchGitHub____mcp',
},
thoughtSignature:
'EsUHCsIHAdHtim9/MrjP+pnhM8DVkvulyfWQVf+isXQxEAbF32gbflE1hl6Te80qtp77Ywn8opB2uhQOIH/l6SStsj3+XRy1U1DTeKtqZxDBoLP2rNK6pi3/nk0ZOQIc8f6rxB70G/zOhk7d/1XQFqhmw5H+yDVRQjGD1cNPY5ctWGxQLAIk/HMWNovUJzz2c81jGWoXu7k2vtpuur2hcAL+J79BEVUTfvU3mSiXqJFTClmFPB6Fe79i0y3TwM2XdIBxzPgVgf8B+Pnv1S6YDxHNSm46jTlXKcSw30r3ixs5xEOzerbOUW5WG9BGukw/YQVvHiuoGLIALRa2Ig7dlOMH8+o+f0mKJtyYj8yF6wyBMol+G4mhSHvQSKJLj/Z5kFHvDZKeVUEOZed6vZivYLrVezjQPXgLHJMOmbp6QrZGxqW45QxDKY5X5F8giIOM8VgsUYhDQUBown+3vvwkIBA24icDsOwdhJ/roe9GabbGfxpkSzARIFh7rSI01cRKbh6cEaVFXf2WQftPeD7dBseQLiCdUYoy4ytECrjTpknrWnVUG6Ly4SKW6uN/IJXpm9JT9GgnGLIddFtEQzm9sIKWNpGEz6++lZpiCFS6LsYSnTP3vPj/7oSABRmwWywxA8EmLh+sv+jiK5aMjFi1sTuJ0Ujsvza3/SHZKewNi9WKQUDOa9Mqtjs2YGDnJxto4l5GMUzI5vhf6/+/A5eHALfVabaFP97v8FEPrXQU94dognwx4EnNqy/KWmGIlYZYqIfjaSAy7Z74viwl+oTtL9gyyBDc/FrQvXfyrYIq8N0pkLKAEh33fa/+YVocLL1LKI9rb2bg/RRr+Ee4NyIQKhIdEJaEh74d1COd/4r06J92ThkfVo5PEVTSsr8tBKiJ5wSmX9vyhbLWzxmXoq1xfGrs8kg7NMW53XEWGlQrIVOQmUtjjjBQKj6b4rBTAO6EKk63cGFbkSPohifiUBPHbxUUPy/hf0tQpeOo3jA01AuCFLOIZ5IYJ+Rm5+aZTU3Panv+Q7Yl1w5t5swhbNZfg7MlU/sxwLijLuWDDNfw+2Zw/aa3VDPgVw6Nv2vKkHi4tUU0XlgfiQgQYUMPxpGRV837uUxvZFNep2QUlAMog5h4sMYJWIAX1kK1pzsyR/KxuCn6nUq4ovWNBQHLC4aW2ZcGgW/6CbF81F1cewUz+vWNMMkJrL0d9celGEbFuY0Q709UipaDbCg49twlnLV9XUwqC5wYTFBiJbynBDqiZAvXn2YOxNIs8CCzuu2GSCQDo09ksJy5g/o=',
},
],
role: 'model',
});
});
describe('should correctly convert function call message without thoughtSignature', () => {
it('should add magic signature when last message is tool message', async () => {
const messages: OpenAIChatMessage[] = [
{
content: '<plugins>Web Browsing plugin available</plugins>',
role: 'system',
},
{
content: '杭州天气如何',
role: 'user',
},
{
content: '',
role: 'assistant',
tool_calls: [
{
function: {
arguments: '{"query":"杭州天气","searchEngines":["google"]}',
name: 'lobe-web-browsing____search____builtin',
},
id: 'call_001',
type: 'function',
},
],
},
{
content: 'Tool execution was aborted by user.',
name: 'lobe-web-browsing____search____builtin',
role: 'tool',
tool_call_id: 'call_001',
},
{
content: '',
role: 'assistant',
tool_calls: [
{
function: {
arguments: '{"query":"杭州 天气","searchEngines":["bing"]}',
name: 'lobe-web-browsing____search____builtin',
},
id: 'call_002',
type: 'function',
},
],
},
{
content: 'no result',
name: 'lobe-web-browsing____search____builtin',
role: 'tool',
tool_call_id: 'call_002',
},
];
const contents = await buildGoogleMessages(messages);
expect(contents).toEqual([
{
parts: [{ text: '<plugins>Web Browsing plugin available</plugins>' }],
role: 'user',
},
{ parts: [{ text: '杭州天气如何' }], role: 'user' },
{
parts: [
{
functionCall: {
args: { query: '杭州天气', searchEngines: ['google'] },
name: 'lobe-web-browsing____search____builtin',
},
thoughtSignature: GEMINI_MAGIC_THOUGHT_SIGNATURE,
},
],
role: 'model',
},
{
parts: [
{
functionResponse: {
name: 'lobe-web-browsing____search____builtin',
response: { result: 'Tool execution was aborted by user.' },
},
},
],
role: 'user',
},
{
parts: [
{
functionCall: {
args: { query: '杭州 天气', searchEngines: ['bing'] },
name: 'lobe-web-browsing____search____builtin',
},
thoughtSignature: GEMINI_MAGIC_THOUGHT_SIGNATURE,
},
],
role: 'model',
},
{
parts: [
{
functionResponse: {
name: 'lobe-web-browsing____search____builtin',
response: { result: 'no result' },
},
},
],
role: 'user',
},
]);
});
it('should NOT add magic signature when thoughtSignature already exists', async () => {
const existingSignature = 'existing_signature_from_model';
const messages: OpenAIChatMessage[] = [
{
content: '杭州天气如何',
role: 'user',
},
{
content: '',
role: 'assistant',
tool_calls: [
{
function: {
arguments: '{"query":"杭州天气","searchEngines":["google"]}',
name: 'lobe-web-browsing____search____builtin',
},
id: 'call_001',
thoughtSignature: existingSignature,
type: 'function',
},
],
},
{
content: 'Tool result',
name: 'lobe-web-browsing____search____builtin',
role: 'tool',
tool_call_id: 'call_001',
},
];
const contents = await buildGoogleMessages(messages);
expect(contents).toEqual([
{
parts: [{ text: '杭州天气如何' }],
role: 'user',
},
{
parts: [
{
functionCall: {
args: { query: '杭州天气', searchEngines: ['google'] },
name: 'lobe-web-browsing____search____builtin',
},
// Should keep existing thoughtSignature, not add magic signature
thoughtSignature: existingSignature,
},
],
role: 'model',
},
{
parts: [
{
functionResponse: {
name: 'lobe-web-browsing____search____builtin',
response: { result: 'Tool result' },
},
},
],
role: 'user',
},
]);
});
it('should add magic signature only after last user message in multi-turn scenario', async () => {
const messages: OpenAIChatMessage[] = [
{
content: 'First question',
role: 'user',
},
{
content: '',
role: 'assistant',
tool_calls: [
{
function: {
arguments: '{"query":"first"}',
name: 'search',
},
id: 'call_001',
type: 'function',
},
],
},
{
content: 'First result',
name: 'search',
role: 'tool',
tool_call_id: 'call_001',
},
{
content: 'Second question',
role: 'user',
},
{
content: '',
role: 'assistant',
tool_calls: [
{
function: {
arguments: '{"query":"second"}',
name: 'search',
},
id: 'call_002',
type: 'function',
},
],
},
{
content: 'Second result',
name: 'search',
role: 'tool',
tool_call_id: 'call_002',
},
];
const contents = await buildGoogleMessages(messages);
expect(contents).toEqual([
{
parts: [{ text: 'First question' }],
role: 'user',
},
{
parts: [
{
functionCall: {
args: { query: 'first' },
name: 'search',
},
// No magic signature for this one (before last user message)
},
],
role: 'model',
},
{
parts: [
{
functionResponse: {
name: 'search',
response: { result: 'First result' },
},
},
],
role: 'user',
},
{
parts: [{ text: 'Second question' }],
role: 'user',
},
{
parts: [
{
functionCall: {
args: { query: 'second' },
name: 'search',
},
// Magic signature added (after last user message)
thoughtSignature: GEMINI_MAGIC_THOUGHT_SIGNATURE,
},
],
role: 'model',
},
{
parts: [
{
functionResponse: {
name: 'search',
response: { result: 'Second result' },
},
},
],
role: 'user',
},
]);
});
it('should NOT add magic signature when last message is user text message', async () => {
const messages: OpenAIChatMessage[] = [
{
content: '<plugins>Web Browsing plugin available</plugins>',
role: 'system',
},
{
content: '杭州天气如何',
role: 'user',
},
{
content: '',
role: 'assistant',
tool_calls: [
{
function: {
arguments: '{"query":"杭州天气","searchEngines":["google"]}',
name: 'lobe-web-browsing____search____builtin',
},
id: 'call_001',
type: 'function',
},
],
},
{
content: 'Tool execution was aborted by user.',
name: 'lobe-web-browsing____search____builtin',
role: 'tool',
tool_call_id: 'call_001',
},
{
content: 'Please try again',
role: 'user',
},
];
const contents = await buildGoogleMessages(messages);
expect(contents).toEqual([
{
parts: [{ text: '<plugins>Web Browsing plugin available</plugins>' }],
role: 'user',
},
{
parts: [{ text: '杭州天气如何' }],
role: 'user',
},
{
parts: [
{
functionCall: {
args: { query: '杭州天气', searchEngines: ['google'] },
name: 'lobe-web-browsing____search____builtin',
},
// No thoughtSignature should be added when last message is user text
},
],
role: 'model',
},
{
parts: [
{
functionResponse: {
name: 'lobe-web-browsing____search____builtin',
response: { result: 'Tool execution was aborted by user.' },
},
},
],
role: 'user',
},
{
parts: [{ text: 'Please try again' }],
role: 'user',
},
]);
});
});
it('should correctly handle empty content', async () => {
const message: OpenAIChatMessage = {
content: '' as any, // explicitly set as empty string
@@ -361,6 +771,7 @@ describe('google contextBuilders', () => {
args: { location: 'London', unit: 'celsius' },
name: 'get_current_weather',
},
thoughtSignature: GEMINI_MAGIC_THOUGHT_SIGNATURE,
},
],
role: 'model',
@@ -410,6 +821,74 @@ describe('google contextBuilders', () => {
{ parts: [{ text: 'Hi' }], role: 'model' },
]);
});
it('should correctly convert full conversation with thoughtSignature', async () => {
const messages: OpenAIChatMessage[] = [
{ content: 'system prompt', role: 'system' },
{ content: 'LobeChat 最新版本', role: 'user' },
{
content: '',
role: 'assistant',
tool_calls: [
{
function: {
arguments: JSON.stringify({
language: ['JSON'],
path: 'package.json',
query: '"version":',
repo: 'lobehub/lobe-chat',
}),
name: 'grep____searchGitHub____mcp',
},
id: 'grep____searchGitHub____mcp_0_6RnOMTF0',
thoughtSignature: 'test-signature',
type: 'function',
},
],
},
{
content: '',
name: 'grep____searchGitHub____mcp',
role: 'tool',
tool_call_id: 'grep____searchGitHub____mcp_0_6RnOMTF0',
},
];
const contents = await buildGoogleMessages(messages);
expect(contents).toEqual([
{ parts: [{ text: 'system prompt' }], role: 'user' },
{ parts: [{ text: 'LobeChat 最新版本' }], role: 'user' },
{
parts: [
{
functionCall: {
args: {
language: ['JSON'],
path: 'package.json',
query: '"version":',
repo: 'lobehub/lobe-chat',
},
name: 'grep____searchGitHub____mcp',
},
thoughtSignature: 'test-signature',
},
],
role: 'model',
},
{
parts: [
{
functionResponse: {
name: 'grep____searchGitHub____mcp',
response: { result: '' },
},
},
],
role: 'user',
},
]);
});
});
describe('buildGoogleTool', () => {
@@ -11,6 +11,12 @@ import { ChatCompletionTool, OpenAIChatMessage, UserMessageContentPart } from '.
import { safeParseJSON } from '../../utils/safeParseJSON';
import { parseDataUri } from '../../utils/uriParser';
/**
* Magic thoughtSignature
* @see https://ai.google.dev/gemini-api/docs/thought-signatures#model-behavior:~:text=context_engineering_is_the_way_to_go
*/
export const GEMINI_MAGIC_THOUGHT_SIGNATURE = 'context_engineering_is_the_way_to_go';
/**
* Convert OpenAI content part to Google Part format
*/
@@ -95,6 +101,7 @@ export const buildGoogleMessage = async (
args: safeParseJSON(tool.function.arguments)!,
name: tool.function.name,
},
thoughtSignature: tool.thoughtSignature,
})),
role: 'model',
};
@@ -155,7 +162,43 @@ export const buildGoogleMessages = async (messages: OpenAIChatMessage[]): Promis
const contents = await Promise.all(pools);
// Filter out empty messages: contents.parts must not be empty.
return contents.filter((content: Content) => content.parts && content.parts.length > 0);
const filteredContents = contents.filter(
(content: Content) => content.parts && content.parts.length > 0,
);
// Check if the last message is a tool message
const lastMessage = messages.at(-1);
const shouldAddMagicSignature = lastMessage?.role === 'tool';
if (shouldAddMagicSignature) {
// Find the last user message index in filtered contents
let lastUserIndex = -1;
for (let i = filteredContents.length - 1; i >= 0; i--) {
if (filteredContents[i].role === 'user') {
// Skip if it's a functionResponse (tool result)
const hasFunctionResponse = filteredContents[i].parts?.some((p) => p.functionResponse);
if (!hasFunctionResponse) {
lastUserIndex = i;
break;
}
}
}
// Add magic signature to all function calls after last user message that don't have thoughtSignature
for (let i = lastUserIndex + 1; i < filteredContents.length; i++) {
const content = filteredContents[i];
if (content.role === 'model' && content.parts) {
for (const part of content.parts) {
if (part.functionCall && !part.thoughtSignature) {
// Only add magic signature if thoughtSignature doesn't exist
part.thoughtSignature = GEMINI_MAGIC_THOUGHT_SIGNATURE;
}
}
}
}
}
return filteredContents;
};
/**
File diff suppressed because it is too large Load Diff
@@ -1,4 +1,4 @@
import { GenerateContentResponse } from '@google/genai';
import { GenerateContentResponse, Part } from '@google/genai';
import { GroundingSearch } from '@lobechat/types';
import { ChatStreamCallbacks } from '../../../types';
@@ -74,19 +74,27 @@ const transformGoogleGenerativeAIStream = (
}
}
const functionCalls = chunk.functionCalls;
// Parse function calls from candidate.content.parts
const functionCalls =
candidate?.content?.parts
?.filter((part: any) => part.functionCall)
.map((part: Part) => ({
...part.functionCall,
thoughtSignature: part.thoughtSignature,
})) || [];
if (functionCalls) {
if (functionCalls.length > 0) {
return [
{
data: functionCalls.map(
(value, index): StreamToolCallChunkData => ({
(value, index: number): StreamToolCallChunkData => ({
function: {
arguments: JSON.stringify(value.args),
name: value.name,
},
id: generateToolCallId(index, value.name),
index: index,
thoughtSignature: value.thoughtSignature,
type: 'function',
}),
),
@@ -97,7 +105,13 @@ const transformGoogleGenerativeAIStream = (
];
}
const text = chunk.text;
// Parse text from candidate.content.parts
// Filter out thought content (thought: true) and thoughtSignature
const text =
candidate?.content?.parts
?.filter((part: any) => part.text && !part.thought && !part.thoughtSignature)
.map((part: any) => part.text)
.join('') || '';
if (candidate) {
// 首先检查是否为 reasoning 内容 (thought: true)
@@ -98,6 +98,7 @@ export interface StreamToolCallChunkData {
};
id?: string;
index: number;
thoughtSignature?: string;
type: 'function' | string;
}
@@ -1,5 +1,5 @@
// @vitest-environment node
import { GenerateContentResponse, Tool } from '@google/genai';
import { GenerateContentResponse } from '@google/genai';
import OpenAI from 'openai';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
@@ -194,7 +194,7 @@ export class LobeGoogleAI implements LobeRuntimeAI {
async chat(rawPayload: ChatStreamPayload, options?: ChatMethodOptions) {
try {
const payload = this.buildPayload(rawPayload);
const { model, thinkingBudget } = payload;
const { model, thinkingBudget, thinkingLevel } = payload;
// https://ai.google.dev/gemini-api/docs/thinking#set-budget
const resolvedThinkingBudget = resolveModelThinkingBudget(model, thinkingBudget);
@@ -209,6 +209,11 @@ export class LobeGoogleAI implements LobeRuntimeAI {
thinkingBudget: resolvedThinkingBudget,
};
// Add thinkingLevel for 3.0 models
if (model?.toLowerCase().includes('-3-') && thinkingLevel) {
(thinkingConfig as any).thinkingLevel = thinkingLevel;
}
const contents = await buildGoogleMessages(payload.messages);
const controller = new AbortController();
@@ -262,19 +267,21 @@ export class LobeGoogleAI implements LobeRuntimeAI {
const inputStartAt = Date.now();
const geminiStreamResponse = await this.client.models.generateContentStream({
config,
contents,
model,
});
const googleStream = this.createEnhancedStream(geminiStreamResponse, controller.signal);
const [prod, useForDebug] = googleStream.tee();
const finalPayload = { config, contents, model };
const key = this.isVertexAi
? 'DEBUG_VERTEX_AI_CHAT_COMPLETION'
: 'DEBUG_GOOGLE_CHAT_COMPLETION';
if (process.env[key] === '1') {
console.log('[requestPayload]');
console.log(JSON.stringify(finalPayload), '\n');
}
const geminiStreamResponse = await this.client.models.generateContentStream(finalPayload);
const googleStream = this.createEnhancedStream(geminiStreamResponse, controller.signal);
const [prod, useForDebug] = googleStream.tee();
if (process.env[key] === '1') {
debugStream(useForDebug).catch();
}
+4
View File
@@ -124,6 +124,10 @@ export interface ChatStreamPayload {
type: 'enabled' | 'disabled';
};
thinkingBudget?: number;
/**
* Thinking level for Gemini models (e.g., gemini-3.0-pro)
*/
thinkingLevel?: 'low' | 'high';
tool_choice?: string;
tools?: ChatCompletionTool[];
/**
@@ -1,5 +1,5 @@
import { z } from 'zod';
import type { PartialDeep } from 'type-fest';
import { z } from 'zod';
/**
* The function that the model called.
@@ -30,6 +30,7 @@ export interface MessageToolCall {
*/
id: string;
thoughtSignature?: string;
/**
* The type of the tool. Currently, only `function` is supported.
*/
@@ -42,6 +43,7 @@ export const MessageToolCallSchema = z.object({
name: z.string(),
}),
id: z.string(),
thoughtSignature: z.string().optional(),
type: z.string(),
});
+2
View File
@@ -38,6 +38,7 @@ export interface LobeAgentChatConfig {
*/
textVerbosity?: 'low' | 'medium' | 'high';
thinking?: 'disabled' | 'auto' | 'enabled';
thinkingLevel?: 'low' | 'high';
thinkingBudget?: number;
/**
* Disable context caching
@@ -91,6 +92,7 @@ export const AgentChatConfigSchema = z.object({
textVerbosity: z.enum(['low', 'medium', 'high']).optional(),
thinking: z.enum(['disabled', 'auto', 'enabled']).optional(),
thinkingBudget: z.number().optional(),
thinkingLevel: z.enum(['low', 'high']).optional(),
urlContext: z.boolean().optional(),
useModelBuiltinSearch: z.boolean().optional(),
});
@@ -77,6 +77,7 @@ export const ModelPerformanceSchema = z.object({
export const MessageMetadataSchema = ModelUsageSchema.merge(ModelPerformanceSchema).extend({
collapsed: z.boolean().optional(),
inspectExpanded: z.boolean().optional(),
});
export interface ModelUsage extends ModelTokensUsage {
@@ -114,6 +115,11 @@ export interface MessageMetadata extends ModelUsage, ModelPerformance {
* true: collapsed, false/undefined: expanded
*/
collapsed?: boolean;
/**
* Tool inspect expanded state
* true: expanded, false/undefined: collapsed
*/
inspectExpanded?: boolean;
compare?: boolean;
usage?: ModelUsage;
performance?: ModelPerformance;
@@ -30,6 +30,7 @@ export interface ChatToolPayload {
identifier: string;
intervention?: ToolIntervention;
result_msg_id?: string;
thoughtSignature?: string;
type: LobeToolRenderType;
}
@@ -84,6 +85,7 @@ export interface MessageToolCall {
*/
id: string;
thoughtSignature?: string;
/**
* The type of the tool. Currently, only `function` is supported.
*/
@@ -108,6 +110,7 @@ export const ChatToolPayloadSchema = z.object({
identifier: z.string(),
intervention: ToolInterventionSchema.optional(),
result_msg_id: z.string().optional(),
thoughtSignature: z.string().optional(),
type: z.string(),
});
+4
View File
@@ -39,3 +39,7 @@ export const lastMonth = () => monthsAgo(1).endOf('month');
export function getYYYYmmddHHMMss(date: Date) {
return dayjs(date).format('YYYYMMDD_HHmmss');
}
export const isNewReleaseDate = (date: string, days = 14) => {
return dayjs().diff(dayjs(date), 'day') < days;
};
+1 -2
View File
@@ -21,12 +21,11 @@ import { createErrorResponse } from '@/utils/errorResponse';
import { checkAuthMethod } from './utils';
type CreateRuntime = (jwtPayload: ClientSecretPayload) => ModelRuntime;
type RequestOptions = { createRuntime?: CreateRuntime; params: Promise<{ provider: string }> };
type RequestOptions = { createRuntime?: CreateRuntime; params: Promise<{ provider?: string }> };
export type RequestHandler = (
req: Request,
options: RequestOptions & {
createRuntime?: CreateRuntime;
jwtPayload: ClientSecretPayload;
},
) => Promise<Response>;
@@ -14,7 +14,7 @@ import { getTracePayload } from '@/utils/trace';
export const maxDuration = 300;
export const POST = checkAuth(async (req: Request, { params, jwtPayload, createRuntime }) => {
const { provider } = await params;
const provider = (await params)!.provider!;
try {
// ============ 1. init chat model ============ //
@@ -6,7 +6,7 @@ import { initModelRuntimeWithUserPayload } from '@/server/modules/ModelRuntime';
import { createErrorResponse } from '@/utils/errorResponse';
export const POST = checkAuth(async (req, { params, jwtPayload }) => {
const { provider } = await params;
const provider = (await params)!.provider!;
try {
const agentRuntime = await initModelRuntimeWithUserPayload(provider, jwtPayload);
@@ -10,7 +10,7 @@ import { createErrorResponse } from '@/utils/errorResponse';
const noNeedAPIKey = (provider: string) => [ModelProvider.OpenRouter].includes(provider as any);
export const GET = checkAuth(async (req, { params, jwtPayload }) => {
const { provider } = await params;
const provider = (await params)!.provider!;
try {
const hasDefaultApiKey = jwtPayload.apiKey || 'dont-need-api-key-for-model-list';
@@ -45,7 +45,7 @@ export const preferredRegion = [
// );
export const POST = checkAuth(async (req: Request, { params, jwtPayload }) => {
const { provider } = await params;
const provider = (await params)!.provider!;
try {
// ============ 1. init chat model ============ //
@@ -4,7 +4,7 @@ import { App, Switch } from 'antd';
import { createStyles, useTheme } from 'antd-style';
import { LucidePencil, TrashIcon } from 'lucide-react';
import { AiModelSourceEnum, AiProviderModelListItem } from 'model-bank';
import { memo, use, useState } from 'react';
import React, { memo, use, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
@@ -17,6 +17,7 @@ import {
getTextInputUnitRate,
getTextOutputUnitRate,
} from '@/utils/pricing';
import { isNewReleaseDate } from '@/utils/time';
import ModelConfigModal from './ModelConfigModal';
import { ProviderSettingsContext } from './ProviderSettingsContext';
@@ -162,6 +163,70 @@ const ModelItem = memo<ModelItemProps>(
const isMobile = useIsMobile();
const NewTag =
releasedAt && isNewReleaseDate(releasedAt) ? (
<Tag color="blue" style={{ marginLeft: 8 }}>
{t('new', { ns: 'common' })}
</Tag>
) : null;
const ModelIdTag = (
<Tag onClick={copyModelId} style={{ cursor: 'pointer', marginRight: 0 }}>
{id}
</Tag>
);
const EnableSwitch = (
<Switch
checked={checked}
loading={isModelLoading}
onChange={async (e) => {
setChecked(e);
await toggleModelEnabled({ enabled: e, id, source, type });
}}
size={'small'}
/>
);
const Actions =
modelEditable &&
((style?: React.CSSProperties) => (
<Flexbox className={styles.config} horizontal style={style}>
<ActionIcon
icon={LucidePencil}
onClick={(e) => {
e.stopPropagation();
setShowConfig(true);
}}
size={'small'}
title={t('providerModels.item.config')}
/>
{source !== AiModelSourceEnum.Builtin && (
<ActionIcon
icon={TrashIcon}
onClick={() => {
modal.confirm({
centered: true,
okButtonProps: {
danger: true,
type: 'primary',
},
onOk: async () => {
await removeAiModel(id, activeAiProvider!);
message.success(t('providerModels.item.delete.success'));
},
title: t('providerModels.item.delete.confirm', {
displayName: displayName || id,
}),
});
}}
size={'small'}
title={t('providerModels.item.delete.title')}
/>
)}
</Flexbox>
));
const dom = isMobile ? (
<Flexbox
align={'center'}
@@ -195,58 +260,14 @@ const ModelItem = memo<ModelItemProps>(
</Flexbox>
</Flexbox>
<div>
<Tag onClick={copyModelId} style={{ cursor: 'pointer', marginRight: 0 }}>
{id}
</Tag>
{ModelIdTag}
{NewTag}
</div>
</Flexbox>
</Flexbox>
<Flexbox align={'center'} gap={4} horizontal>
{modelEditable && (
<Flexbox className={styles.config} horizontal style={{ opacity: 1 }}>
<ActionIcon
icon={LucidePencil}
onClick={(e) => {
e.stopPropagation();
setShowConfig(true);
}}
size={'small'}
title={t('providerModels.item.config')}
/>
{source !== AiModelSourceEnum.Builtin && (
<ActionIcon
icon={TrashIcon}
onClick={() => {
modal.confirm({
centered: true,
okButtonProps: {
danger: true,
type: 'primary',
},
onOk: async () => {
await removeAiModel(id, activeAiProvider!);
message.success(t('providerModels.item.delete.success'));
},
title: t('providerModels.item.delete.confirm', {
displayName: displayName || id,
}),
});
}}
size={'small'}
title={t('providerModels.item.delete.title')}
/>
)}
</Flexbox>
)}
<Switch
checked={checked}
loading={isModelLoading}
onChange={async (e) => {
setChecked(e);
await toggleModelEnabled({ enabled: e, id, source, type });
}}
size={'small'}
/>
{Actions && Actions({ opacity: 1 })}
{EnableSwitch}
</Flexbox>
</Flexbox>
) : (
@@ -264,45 +285,9 @@ const ModelItem = memo<ModelItemProps>(
<Flexbox flex={1} gap={2} style={{ minWidth: 0 }}>
<Flexbox align={'center'} gap={8} horizontal>
{displayName || id}
<Tag onClick={copyModelId} style={{ cursor: 'pointer', marginRight: 0 }}>
{id}
</Tag>
{modelEditable && (
<Flexbox className={styles.config} horizontal>
<ActionIcon
icon={LucidePencil}
onClick={(e) => {
e.stopPropagation();
setShowConfig(true);
}}
size={'small'}
title={t('providerModels.item.config')}
/>
{source !== AiModelSourceEnum.Builtin && (
<ActionIcon
icon={TrashIcon}
onClick={() => {
modal.confirm({
centered: true,
okButtonProps: {
danger: true,
type: 'primary',
},
onOk: async () => {
await removeAiModel(id, activeAiProvider!);
message.success(t('providerModels.item.delete.success'));
},
title: t('providerModels.item.delete.confirm', {
displayName: displayName || id,
}),
});
}}
size={'small'}
title={t('providerModels.item.delete.title')}
/>
)}
</Flexbox>
)}
{ModelIdTag}
{NewTag}
{Actions && Actions()}
</Flexbox>
<Flexbox align={'baseline'} gap={8} horizontal>
{content.length > 0 && (
@@ -329,15 +314,7 @@ const ModelItem = memo<ModelItemProps>(
{/* <ActionIcon icon={Recycle} style={{ color: theme.colorWarning }} />*/}
{/* </Tooltip>*/}
{/*)}*/}
<Switch
checked={checked}
loading={isModelLoading}
onChange={async (e) => {
setChecked(e);
await toggleModelEnabled({ enabled: e, id, source, type });
}}
size={'small'}
/>
{EnableSwitch}
</Flexbox>
</Flexbox>
);
@@ -0,0 +1,20 @@
'use client';
import { RouterProvider } from 'react-router-dom';
import type { Locales } from '@/types/locale';
import { createDesktopRouter } from './desktopRouter.config';
interface ClientRouterProps {
locale: Locales;
}
const ClientRouter = ({ locale }: ClientRouterProps) => {
const router = createDesktopRouter(locale);
return <RouterProvider router={router} />;
};
ClientRouter.displayName = 'ClientRouter';
export default ClientRouter;
+1 -21
View File
@@ -1,31 +1,11 @@
'use client';
import dynamic from 'next/dynamic';
import { memo, useMemo } from 'react';
import { RouterProvider } from 'react-router-dom';
import BootErrorBoundary from '@/components/BootErrorBoundary';
import Loading from '@/components/Loading/BrandTextLoading';
import type { Locales } from '@/types/locale';
import { createDesktopRouter } from './desktopRouter.config';
interface ClientRouterProps {
locale: Locales;
}
const ClientRouter = memo<ClientRouterProps>(({ locale }) => {
const router = useMemo(() => createDesktopRouter(locale), [locale]);
return (
<BootErrorBoundary fallback={<Loading />}>
<RouterProvider router={router} />
</BootErrorBoundary>
);
});
ClientRouter.displayName = 'ClientRouter';
const DesktopRouterClient = dynamic(() => Promise.resolve(ClientRouter), {
const DesktopRouterClient = dynamic(() => import('./DesktopClientRouter'), {
loading: () => <Loading />,
ssr: false,
});
+20
View File
@@ -0,0 +1,20 @@
'use client';
import { RouterProvider } from 'react-router-dom';
import type { Locales } from '@/types/locale';
import { createMobileRouter } from './mobileRouter.config';
interface ClientRouterProps {
locale: Locales;
}
const ClientRouter = ({ locale }: ClientRouterProps) => {
const router = createMobileRouter(locale);
return <RouterProvider router={router} />;
};
ClientRouter.displayName = 'ClientRouter';
export default ClientRouter;
+1 -22
View File
@@ -1,32 +1,11 @@
'use client';
import dynamic from 'next/dynamic';
import { memo, useMemo } from 'react';
import { RouterProvider } from 'react-router-dom';
import BootErrorBoundary from '@/components/BootErrorBoundary';
import Loading from '@/components/Loading/BrandTextLoading';
import type { Locales } from '@/types/locale';
import { createMobileRouter } from './mobileRouter.config';
interface ClientRouterProps {
locale: Locales;
}
const ClientRouter = memo<ClientRouterProps>(({ locale }) => {
const router = useMemo(() => createMobileRouter(locale), [locale]);
return (
<BootErrorBoundary fallback={<Loading />}>
<RouterProvider router={router} />
</BootErrorBoundary>
);
});
ClientRouter.displayName = 'ClientRouter';
const MobileRouterClient = dynamic(() => Promise.resolve(ClientRouter), {
const MobileRouterClient = dynamic(() => import('./MobileClientRouter'), {
loading: () => <Loading />,
ssr: false,
});
+280 -152
View File
@@ -1,8 +1,10 @@
'use client';
import dynamic from 'next/dynamic';
import { useEffect } from 'react';
import { type LoaderFunction, createBrowserRouter, redirect, useNavigate } from 'react-router-dom';
import { createBrowserRouter, redirect, useNavigate, useRouteError } from 'react-router-dom';
import ErrorCapture from '@/components/Error';
import Loading from '@/components/Loading/BrandTextLoading';
import { useGlobalStore } from '@/store/global';
import type { Locales } from '@/types/locale';
@@ -10,6 +12,213 @@ import type { Locales } from '@/types/locale';
import DesktopMainLayout from './(main)/layouts/desktop';
import { idLoader, slugLoader } from './loaders/routeParams';
/**
* Desktop Router Configuration - Pure CSR Mode
*
* IMPORTANT: This router runs ONLY in the browser (client-side).
*
* Key characteristics:
* - createBrowserRouter uses window.history API (client-only)
* - All loaders execute in the browser during navigation
* - No server-side rendering or hydration involved
* - Route data fetching happens on-demand during client navigation
*
* The entire router tree is wrapped with Next.js dynamic import (ssr: false),
* ensuring this code never executes on the server.
*/
// Chat components
const DesktopChatPage = dynamic(
() => import('./(main)/chat/index').then((m) => m.DesktopChatPage),
{
loading: () => <Loading />,
ssr: false,
},
);
const ChatLayout = dynamic(() => import('./(main)/chat/_layout/Desktop'), {
loading: () => <Loading />,
ssr: false,
});
// Discover List components
const DesktopHomePage = dynamic(
() => import('./(main)/discover/(list)/(home)/index').then((m) => m.DesktopHomePage),
{
loading: () => <Loading />,
ssr: false,
},
);
const DesktopAssistantPage = dynamic(
() => import('./(main)/discover/(list)/assistant/index').then((m) => m.DesktopAssistantPage),
{
loading: () => <Loading />,
ssr: false,
},
);
const DiscoverAssistantLayout = dynamic(
() => import('./(main)/discover/(list)/assistant/_layout/Desktop'),
{
loading: () => <Loading />,
ssr: false,
},
);
const DiscoverListMcpPage = dynamic(
() => import('./(main)/discover/(list)/mcp/index').then((m) => m.DesktopMcpPage),
{
loading: () => <Loading />,
ssr: false,
},
);
const DiscoverMcpLayout = dynamic(() => import('./(main)/discover/(list)/mcp/_layout/Desktop'), {
loading: () => <Loading />,
ssr: false,
});
const DiscoverListModelPage = dynamic(
() => import('./(main)/discover/(list)/model/index').then((m) => m.DesktopModelPage),
{
loading: () => <Loading />,
ssr: false,
},
);
const DiscoverModelLayout = dynamic(
() => import('./(main)/discover/(list)/model/_layout/Desktop'),
{
loading: () => <Loading />,
ssr: false,
},
);
const DiscoverListProviderPage = dynamic(
() => import('./(main)/discover/(list)/provider/index').then((m) => m.DesktopProviderPage),
{
loading: () => <Loading />,
ssr: false,
},
);
const DiscoverListLayout = dynamic(() => import('./(main)/discover/(list)/_layout/Desktop/index'), {
loading: () => <Loading />,
ssr: false,
});
// Discover Detail components
const DesktopDiscoverAssistantDetailPage = dynamic(
() =>
import('./(main)/discover/(detail)/assistant/index').then(
(m) => m.DesktopDiscoverAssistantDetailPage,
),
{
loading: () => <Loading />,
ssr: false,
},
);
const DiscoverDetailMcpPage = dynamic(
() => import('./(main)/discover/(detail)/mcp/index').then((m) => m.DesktopMcpPage),
{
loading: () => <Loading />,
ssr: false,
},
);
const DiscoverDetailModelPage = dynamic(
() => import('./(main)/discover/(detail)/model/index').then((m) => m.DesktopModelPage),
{
loading: () => <Loading />,
ssr: false,
},
);
const DiscoverDetailProviderPage = dynamic(
() => import('./(main)/discover/(detail)/provider/index').then((m) => m.DesktopProviderPage),
{
loading: () => <Loading />,
ssr: false,
},
);
const DiscoverDetailLayout = dynamic(() => import('./(main)/discover/(detail)/_layout/Desktop'), {
loading: () => <Loading />,
ssr: false,
});
const DiscoverLayout = dynamic(() => import('./(main)/discover/_layout/Desktop/index'), {
loading: () => <Loading />,
ssr: false,
});
// Knowledge components
const KnowledgeHome = dynamic(() => import('./(main)/knowledge/routes/KnowledgeHome'), {
loading: () => <Loading />,
ssr: false,
});
const KnowledgeBasesList = dynamic(() => import('./(main)/knowledge/routes/KnowledgeBasesList'), {
loading: () => <Loading />,
ssr: false,
});
const KnowledgeBaseDetail = dynamic(() => import('./(main)/knowledge/routes/KnowledgeBaseDetail'), {
loading: () => <Loading />,
ssr: false,
});
const KnowledgeLayout = dynamic(() => import('./(main)/knowledge/_layout/Desktop'), {
loading: () => <Loading />,
ssr: false,
});
// Settings components
const SettingsLayout = dynamic(() => import('./(main)/settings/_layout/Desktop'), {
loading: () => <Loading />,
ssr: false,
});
const SettingsLayoutWrapper = dynamic(() => import('./(main)/settings/_layout/DesktopWrapper'), {
loading: () => <Loading />,
ssr: false,
});
// Image components
const ImagePage = dynamic(() => import('./(main)/image'), {
loading: () => <Loading />,
ssr: false,
});
const ImageLayoutWrapper = dynamic(() => import('./(main)/image/_layout/DesktopWrapper'), {
loading: () => <Loading />,
ssr: false,
});
// Labs components
const LabsPage = dynamic(() => import('./(main)/labs'), {
loading: () => <Loading />,
ssr: false,
});
// Profile components
const ProfileHomePage = dynamic(() => import('./(main)/profile/(home)/desktop'), {
loading: () => <Loading />,
ssr: false,
});
const ProfileApikeyPage = dynamic(() => import('./(main)/profile/apikey/index'), {
loading: () => <Loading />,
ssr: false,
});
const DesktopProfileSecurityPage = dynamic(
() => import('./(main)/profile/security/index').then((m) => m.DesktopProfileSecurityPage),
{
loading: () => <Loading />,
ssr: false,
},
);
const DesktopProfileStatsPage = dynamic(
() => import('./(main)/profile/stats/index').then((m) => m.DesktopProfileStatsPage),
{
loading: () => <Loading />,
ssr: false,
},
);
const DesktopProfileUsagePage = dynamic(
() => import('./(main)/profile/usage/index').then((m) => m.DesktopProfileUsagePage),
{
loading: () => <Loading />,
ssr: false,
},
);
const ProfileLayoutWrapper = dynamic(() => import('./(main)/profile/_layout/DesktopWrapper'), {
loading: () => <Loading />,
ssr: false,
});
// Component to register navigate function in global store
const NavigatorRegistrar = () => {
const navigate = useNavigate();
@@ -25,50 +234,57 @@ const NavigatorRegistrar = () => {
return null;
};
// Root layout wrapper component - just registers navigator and renders outlet
// Note: Desktop layout is provided by individual route components
const RootLayout = (props: { locale: Locales }) => {
return (
<>
<NavigatorRegistrar />
<DesktopMainLayout locale={props.locale} />
</>
);
// Error boundary factory for React Router errorElement
const createErrorBoundary = (resetPath: string) => {
const ErrorBoundary = () => {
const error = useRouteError() as Error;
const navigate = useNavigate();
const reset = () => {
navigate(resetPath);
};
return <ErrorCapture error={error} reset={reset} />;
};
return ErrorBoundary;
};
// Hydration gate loader -always return true to bypass hydration gate
const hydrationGateLoader: LoaderFunction = () => {
return true
};
// Create error boundaries for each route
const ChatErrorBoundary = createErrorBoundary('/chat');
const DiscoverErrorBoundary = createErrorBoundary('/discover');
const KnowledgeErrorBoundary = createErrorBoundary('/knowledge');
const SettingsErrorBoundary = createErrorBoundary('/settings');
const ImageErrorBoundary = createErrorBoundary('/image');
const ProfileErrorBoundary = createErrorBoundary('/profile');
const RootErrorBoundary = createErrorBoundary('/chat'); // Root level falls back to chat
// Root layout wrapper component
const RootLayout = (props: { locale: Locales }) => (
<>
<NavigatorRegistrar />
<DesktopMainLayout locale={props.locale} />
</>
);
// Create desktop router configuration
export const createDesktopRouter = (locale: Locales) =>
createBrowserRouter([
{
HydrateFallback: () => <Loading />,
children: [
// Chat routes
{
children: [
{
element: <DesktopChatPage />,
index: true,
lazy: () =>
import('./(main)/chat/index').then((m) => ({
Component: m.DesktopChatPage,
})),
},
{
lazy: () =>
import('./(main)/chat/index').then((m) => ({
Component: m.DesktopChatPage,
})),
element: <DesktopChatPage />,
path: '*',
},
],
lazy: () =>
import('./(main)/chat/_layout/Desktop').then((m) => ({
Component: m.default,
})),
element: <ChatLayout />,
errorElement: <ChatErrorBoundary />,
path: 'chat',
},
@@ -81,117 +297,73 @@ export const createDesktopRouter = (locale: Locales) =>
{
children: [
{
element: <DesktopAssistantPage />,
index: true,
lazy: () =>
import('./(main)/discover/(list)/assistant/index').then((m) => ({
Component: m.DesktopAssistantPage,
})),
},
],
lazy: () =>
import('./(main)/discover/(list)/assistant/_layout/Desktop').then((m) => ({
Component: m.default,
})),
element: <DiscoverAssistantLayout />,
path: 'assistant',
},
{
children: [
{
element: <DiscoverListModelPage />,
index: true,
lazy: () =>
import('./(main)/discover/(list)/model/index').then((m) => ({
Component: m.DesktopModelPage,
})),
},
],
lazy: () =>
import('./(main)/discover/(list)/model/_layout/Desktop').then((m) => ({
Component: m.default,
})),
element: <DiscoverModelLayout />,
path: 'model',
},
{
lazy: () =>
import('./(main)/discover/(list)/provider/index').then((m) => ({
Component: m.DesktopProviderPage,
})),
element: <DiscoverListProviderPage />,
path: 'provider',
},
{
children: [
{
element: <DiscoverListMcpPage />,
index: true,
lazy: () =>
import('./(main)/discover/(list)/mcp/index').then((m) => ({
Component: m.DesktopMcpPage,
})),
},
],
lazy: () =>
import('./(main)/discover/(list)/mcp/_layout/Desktop').then((m) => ({
Component: m.default,
})),
element: <DiscoverMcpLayout />,
path: 'mcp',
},
{
element: <DesktopHomePage />,
index: true,
lazy: () =>
import('./(main)/discover/(list)/(home)/index').then((m) => ({
Component: m.DesktopHomePage,
})),
},
],
lazy: () =>
import('./(main)/discover/(list)/_layout/Desktop/index').then((m) => ({
Component: m.default,
})),
element: <DiscoverListLayout />,
},
// Detail routes (with DetailLayout)
{
children: [
{
lazy: () =>
import('./(main)/discover/(detail)/assistant/index').then((m) => ({
Component: m.DesktopDiscoverAssistantDetailPage,
})),
element: <DesktopDiscoverAssistantDetailPage />,
loader: slugLoader,
path: 'assistant/:slug',
},
{
lazy: () =>
import('./(main)/discover/(detail)/model/index').then((m) => ({
Component: m.DesktopModelPage,
})),
element: <DiscoverDetailModelPage />,
loader: slugLoader,
path: 'model/:slug',
},
{
lazy: () =>
import('./(main)/discover/(detail)/provider/index').then((m) => ({
Component: m.DesktopProviderPage,
})),
element: <DiscoverDetailProviderPage />,
loader: slugLoader,
path: 'provider/:slug',
},
{
lazy: () =>
import('./(main)/discover/(detail)/mcp/index').then((m) => ({
Component: m.DesktopMcpPage,
})),
element: <DiscoverDetailMcpPage />,
loader: slugLoader,
path: 'mcp/:slug',
},
],
lazy: () =>
import('./(main)/discover/(detail)/_layout/Desktop').then((m) => ({
Component: m.default,
})),
element: <DiscoverDetailLayout />,
},
],
lazy: () =>
import('./(main)/discover/_layout/Desktop/index').then((m) => ({
Component: m.default,
})),
element: <DiscoverLayout />,
errorElement: <DiscoverErrorBoundary />,
path: 'discover',
},
@@ -199,40 +371,26 @@ export const createDesktopRouter = (locale: Locales) =>
{
children: [
{
element: <KnowledgeHome />,
index: true,
lazy: () =>
import('./(main)/knowledge/routes/KnowledgeHome').then((m) => ({
Component: m.default,
})),
},
{
lazy: () =>
import('./(main)/knowledge/routes/KnowledgeBasesList').then((m) => ({
Component: m.default,
})),
element: <KnowledgeBasesList />,
path: 'bases',
},
{
lazy: () =>
import('./(main)/knowledge/routes/KnowledgeBaseDetail').then((m) => ({
Component: m.default,
})),
element: <KnowledgeBaseDetail />,
loader: idLoader,
path: 'bases/:id',
},
{
lazy: () =>
import('./(main)/knowledge/routes/KnowledgeBaseDetail').then((m) => ({
Component: m.default,
})),
element: <KnowledgeBaseDetail />,
loader: idLoader,
path: '*',
},
],
lazy: () =>
import('./(main)/knowledge/_layout/Desktop').then((m) => ({
Component: m.default,
})),
element: <KnowledgeLayout />,
errorElement: <KnowledgeErrorBoundary />,
path: 'knowledge',
},
@@ -240,17 +398,12 @@ export const createDesktopRouter = (locale: Locales) =>
{
children: [
{
element: <SettingsLayout />,
index: true,
lazy: () =>
import('./(main)/settings/_layout/Desktop').then((m) => ({
Component: m.default,
})),
},
],
lazy: () =>
import('./(main)/settings/_layout/DesktopWrapper').then((m) => ({
Component: m.default,
})),
element: <SettingsLayoutWrapper />,
errorElement: <SettingsErrorBoundary />,
path: 'settings',
},
@@ -258,26 +411,18 @@ export const createDesktopRouter = (locale: Locales) =>
{
children: [
{
element: <ImagePage />,
index: true,
lazy: () =>
import('./(main)/image').then((m) => ({
Component: m.default,
})),
},
],
lazy: () =>
import('./(main)/image/_layout/DesktopWrapper').then((m) => ({
Component: m.default,
})),
element: <ImageLayoutWrapper />,
errorElement: <ImageErrorBoundary />,
path: 'image',
},
// Labs routes
{
lazy: () =>
import('./(main)/labs').then((m) => ({
Component: m.default,
})),
element: <LabsPage />,
path: 'labs',
},
@@ -285,45 +430,28 @@ export const createDesktopRouter = (locale: Locales) =>
{
children: [
{
element: <ProfileHomePage />,
index: true,
lazy: () =>
import('./(main)/profile/(home)/desktop').then((m) => ({
Component: m.default,
})),
},
{
lazy: () =>
import('./(main)/profile/apikey/index').then((m) => ({
Component: m.default,
})),
element: <ProfileApikeyPage />,
path: 'apikey',
},
{
lazy: () =>
import('./(main)/profile/security/index').then((m) => ({
Component: m.DesktopProfileSecurityPage,
})),
element: <DesktopProfileSecurityPage />,
path: 'security',
},
{
lazy: () =>
import('./(main)/profile/stats/index').then((m) => ({
Component: m.DesktopProfileStatsPage,
})),
element: <DesktopProfileStatsPage />,
path: 'stats',
},
{
lazy: () =>
import('./(main)/profile/usage/index').then((m) => ({
Component: m.DesktopProfileUsagePage,
})),
element: <DesktopProfileUsagePage />,
path: 'usage',
},
],
lazy: () =>
import('./(main)/profile/_layout/DesktopWrapper').then((m) => ({
Component: m.default,
})),
element: <ProfileLayoutWrapper />,
errorElement: <ProfileErrorBoundary />,
path: 'profile',
},
@@ -340,7 +468,7 @@ export const createDesktopRouter = (locale: Locales) =>
},
],
element: <RootLayout locale={locale} />,
loader: hydrationGateLoader,
errorElement: <RootErrorBoundary />,
path: '/',
},
]);
+3
View File
@@ -0,0 +1,3 @@
import Loading from '@/components/Loading/BrandTextLoading';
export default () => <Loading />;
+302 -162
View File
@@ -1,8 +1,10 @@
'use client';
import dynamic from 'next/dynamic';
import { useEffect } from 'react';
import { type LoaderFunction, createBrowserRouter, redirect, useNavigate } from 'react-router-dom';
import { createBrowserRouter, redirect, useNavigate, useRouteError } from 'react-router-dom';
import ErrorCapture from '@/components/Error';
import Loading from '@/components/Loading/BrandTextLoading';
import { useGlobalStore } from '@/store/global';
import type { Locales } from '@/types/locale';
@@ -10,6 +12,233 @@ import type { Locales } from '@/types/locale';
import { MobileMainLayout } from './(main)/layouts/mobile';
import { idLoader, slugLoader } from './loaders/routeParams';
/**
* Mobile Router Configuration - Pure CSR Mode
*
* IMPORTANT: This router runs ONLY in the browser (client-side).
*
* Key characteristics:
* - createBrowserRouter uses window.history API (client-only)
* - All loaders execute in the browser during navigation
* - No server-side rendering or hydration involved
* - Route data fetching happens on-demand during client navigation
*
* The entire router tree is wrapped with Next.js dynamic import (ssr: false),
* ensuring this code never executes on the server.
*/
// Chat components
const MobileChatPage = dynamic(() => import('./(main)/chat/index').then((m) => m.MobileChatPage), {
loading: () => <Loading />,
ssr: false,
});
const ChatSettings = dynamic(() => import('./(main)/chat/settings'), {
loading: () => <Loading />,
ssr: false,
});
const ChatLayout = dynamic(() => import('./(main)/chat/_layout/Mobile'), {
loading: () => <Loading />,
ssr: false,
});
// Discover List components
const MobileHomePage = dynamic(
() => import('./(main)/discover/(list)/(home)/index').then((m) => m.MobileHomePage),
{
loading: () => <Loading />,
ssr: false,
},
);
const MobileAssistantPage = dynamic(
() => import('./(main)/discover/(list)/assistant/index').then((m) => m.MobileAssistantPage),
{
loading: () => <Loading />,
ssr: false,
},
);
const DiscoverAssistantLayout = dynamic(
() => import('./(main)/discover/(list)/assistant/_layout/Mobile'),
{
loading: () => <Loading />,
ssr: false,
},
);
const DiscoverListMobileModelPage = dynamic(
() => import('./(main)/discover/(list)/model/index').then((m) => m.MobileModelPage),
{
loading: () => <Loading />,
ssr: false,
},
);
const DiscoverModelLayout = dynamic(() => import('./(main)/discover/(list)/model/_layout/Mobile'), {
loading: () => <Loading />,
ssr: false,
});
const DiscoverListMobileProviderPage = dynamic(
() => import('./(main)/discover/(list)/provider/index').then((m) => m.MobileProviderPage),
{
loading: () => <Loading />,
ssr: false,
},
);
const DiscoverListMobileMcpPage = dynamic(
() => import('./(main)/discover/(list)/mcp/index').then((m) => m.MobileMcpPage),
{
loading: () => <Loading />,
ssr: false,
},
);
const DiscoverMcpLayout = dynamic(() => import('./(main)/discover/(list)/mcp/_layout/Mobile'), {
loading: () => <Loading />,
ssr: false,
});
const DiscoverListLayout = dynamic(() => import('./(main)/discover/(list)/_layout/Mobile/index'), {
loading: () => <Loading />,
ssr: false,
});
// Discover Detail components
const MobileDiscoverAssistantDetailPage = dynamic(
() =>
import('./(main)/discover/(detail)/assistant/index').then(
(m) => m.MobileDiscoverAssistantDetailPage,
),
{
loading: () => <Loading />,
ssr: false,
},
);
const DiscoverDetailMobileModelPage = dynamic(
() => import('./(main)/discover/(detail)/model/index').then((m) => m.MobileModelPage),
{
loading: () => <Loading />,
ssr: false,
},
);
const DiscoverDetailMobileProviderPage = dynamic(
() => import('./(main)/discover/(detail)/provider/index').then((m) => m.MobileProviderPage),
{
loading: () => <Loading />,
ssr: false,
},
);
const DiscoverDetailMobileMcpPage = dynamic(
() => import('./(main)/discover/(detail)/mcp/index').then((m) => m.MobileMcpPage),
{
loading: () => <Loading />,
ssr: false,
},
);
const DiscoverDetailLayout = dynamic(
() => import('./(main)/discover/(detail)/_layout/Mobile/index'),
{
loading: () => <Loading />,
ssr: false,
},
);
const DiscoverLayout = dynamic(() => import('./(main)/discover/_layout/Mobile/index'), {
loading: () => <Loading />,
ssr: false,
});
// Knowledge components
const KnowledgeHome = dynamic(() => import('./(main)/knowledge/routes/KnowledgeHome'), {
loading: () => <Loading />,
ssr: false,
});
const KnowledgeBasesList = dynamic(() => import('./(main)/knowledge/routes/KnowledgeBasesList'), {
loading: () => <Loading />,
ssr: false,
});
const KnowledgeBaseDetail = dynamic(() => import('./(main)/knowledge/routes/KnowledgeBaseDetail'), {
loading: () => <Loading />,
ssr: false,
});
const KnowledgeLayout = dynamic(() => import('./(main)/knowledge/_layout/Mobile'), {
loading: () => <Loading />,
ssr: false,
});
// Settings components
const SettingsLayout = dynamic(() => import('./(main)/settings/_layout/Mobile'), {
loading: () => <Loading />,
ssr: false,
});
const SettingsLayoutWrapper = dynamic(() => import('./(main)/settings/_layout/MobileWrapper'), {
loading: () => <Loading />,
ssr: false,
});
// Image components
const ImageComingSoon = dynamic(() => import('./(main)/image/ComingSoon'), {
loading: () => <Loading />,
ssr: false,
});
const ImageLayoutMobile = dynamic(() => import('./(main)/image/_layout/Mobile'), {
loading: () => <Loading />,
ssr: false,
});
// Labs components
const LabsPage = dynamic(() => import('./(main)/labs'), {
loading: () => <Loading />,
ssr: false,
});
// Profile components
const ProfileHomePage = dynamic(() => import('./(main)/profile/(home)'), {
loading: () => <Loading />,
ssr: false,
});
const ProfileApikeyPage = dynamic(() => import('./(main)/profile/apikey/index'), {
loading: () => <Loading />,
ssr: false,
});
const MobileProfileSecurityPage = dynamic(
() => import('./(main)/profile/security').then((m) => m.MobileProfileSecurityPage),
{
loading: () => <Loading />,
ssr: false,
},
);
const MobileProfileStatsPage = dynamic(
() => import('./(main)/profile/stats').then((m) => m.MobileProfileStatsPage),
{
loading: () => <Loading />,
ssr: false,
},
);
const ProfileLayoutMobile = dynamic(() => import('./(main)/profile/_layout/Mobile'), {
loading: () => <Loading />,
ssr: false,
});
// Me (mobile personal center) components
const MeHomePage = dynamic(() => import('./(main)/(mobile)/me/(home)'), {
loading: () => <Loading />,
ssr: false,
});
const MeHomeLayout = dynamic(() => import('./(main)/(mobile)/me/(home)/layout'), {
loading: () => <Loading />,
ssr: false,
});
const MeProfilePage = dynamic(() => import('./(main)/(mobile)/me/profile'), {
loading: () => <Loading />,
ssr: false,
});
const MeProfileLayout = dynamic(() => import('./(main)/(mobile)/me/profile/layout'), {
loading: () => <Loading />,
ssr: false,
});
const MeSettingsPage = dynamic(() => import('./(main)/(mobile)/me/settings'), {
loading: () => <Loading />,
ssr: false,
});
const MeSettingsLayout = dynamic(() => import('./(main)/(mobile)/me/settings/layout'), {
loading: () => <Loading />,
ssr: false,
});
// Component to register navigate function in global store
const NavigatorRegistrar = () => {
const navigate = useNavigate();
@@ -25,8 +254,32 @@ const NavigatorRegistrar = () => {
return null;
};
// Root layout wrapper component - just registers navigator and renders outlet
// Note: Mobile layout is provided by individual route components
// Error boundary factory for React Router errorElement
const createErrorBoundary = (resetPath: string) => {
const ErrorBoundary = () => {
const error = useRouteError() as Error;
const navigate = useNavigate();
const reset = () => {
navigate(resetPath);
};
return <ErrorCapture error={error} reset={reset} />;
};
return ErrorBoundary;
};
// Create error boundaries for each route
const ChatErrorBoundary = createErrorBoundary('/chat');
const DiscoverErrorBoundary = createErrorBoundary('/discover');
const KnowledgeErrorBoundary = createErrorBoundary('/knowledge');
const SettingsErrorBoundary = createErrorBoundary('/settings');
const ImageErrorBoundary = createErrorBoundary('/image');
const ProfileErrorBoundary = createErrorBoundary('/profile');
const MeErrorBoundary = createErrorBoundary('/me'); // Mobile only
const RootErrorBoundary = createErrorBoundary('/chat'); // Root level falls back to chat
// Root layout wrapper component
const RootLayout = (props: { locale: Locales }) => (
<>
<NavigatorRegistrar />
@@ -34,39 +287,25 @@ const RootLayout = (props: { locale: Locales }) => (
</>
);
// Hydration gate loader -always return true to bypass hydration gate
const hydrationGateLoader: LoaderFunction = () => {
return true
};
// Create mobile router configuration
export const createMobileRouter = (locale: Locales) =>
createBrowserRouter([
{
HydrateFallback: () => <Loading />,
children: [
// Chat routes
{
children: [
{
element: <MobileChatPage />,
index: true,
lazy: () =>
import('./(main)/chat/index').then((m) => ({
Component: m.MobileChatPage,
})),
},
{
lazy: () =>
import('./(main)/chat/settings').then((m) => ({
Component: m.default,
})),
element: <ChatSettings />,
path: 'settings',
},
],
lazy: () =>
import('./(main)/chat/_layout/Mobile').then((m) => ({
Component: m.default,
})),
element: <ChatLayout />,
errorElement: <ChatErrorBoundary />,
path: 'chat',
},
@@ -77,116 +316,72 @@ export const createMobileRouter = (locale: Locales) =>
{
children: [
{
element: <MobileHomePage />,
index: true,
lazy: () =>
import('./(main)/discover/(list)/(home)/index').then((m) => ({
Component: m.MobileHomePage,
})),
},
{
children: [
{
lazy: () =>
import('./(main)/discover/(list)/assistant/index').then((m) => ({
Component: m.MobileAssistantPage,
})),
element: <MobileAssistantPage />,
path: 'assistant',
},
],
lazy: () =>
import('./(main)/discover/(list)/assistant/_layout/Mobile').then((m) => ({
Component: m.default,
})),
element: <DiscoverAssistantLayout />,
},
{
children: [
{
lazy: () =>
import('./(main)/discover/(list)/model/index').then((m) => ({
Component: m.MobileModelPage,
})),
element: <DiscoverListMobileModelPage />,
path: 'model',
},
],
lazy: () =>
import('./(main)/discover/(list)/model/_layout/Mobile').then((m) => ({
Component: m.default,
})),
element: <DiscoverModelLayout />,
},
{
lazy: () =>
import('./(main)/discover/(list)/provider/index').then((m) => ({
Component: m.MobileProviderPage,
})),
element: <DiscoverListMobileProviderPage />,
path: 'provider',
},
{
children: [
{
lazy: () =>
import('./(main)/discover/(list)/mcp/index').then((m) => ({
Component: m.MobileMcpPage,
})),
element: <DiscoverListMobileMcpPage />,
path: 'mcp',
},
],
lazy: () =>
import('./(main)/discover/(list)/mcp/_layout/Mobile').then((m) => ({
Component: m.default,
})),
element: <DiscoverMcpLayout />,
},
],
lazy: () =>
import('./(main)/discover/(list)/_layout/Mobile/index').then((m) => ({
Component: m.default,
})),
element: <DiscoverListLayout />,
},
// Detail routes (with DetailLayout)
{
children: [
{
lazy: () =>
import('./(main)/discover/(detail)/assistant/index').then((m) => ({
Component: m.MobileDiscoverAssistantDetailPage,
})),
element: <MobileDiscoverAssistantDetailPage />,
loader: slugLoader,
path: 'assistant/:slug',
},
{
lazy: () =>
import('./(main)/discover/(detail)/model/index').then((m) => ({
Component: m.MobileModelPage,
})),
element: <DiscoverDetailMobileModelPage />,
loader: slugLoader,
path: 'model/:slug',
},
{
lazy: () =>
import('./(main)/discover/(detail)/provider/index').then((m) => ({
Component: m.MobileProviderPage,
})),
element: <DiscoverDetailMobileProviderPage />,
loader: slugLoader,
path: 'provider/:slug',
},
{
lazy: () =>
import('./(main)/discover/(detail)/mcp/index').then((m) => ({
Component: m.MobileMcpPage,
})),
element: <DiscoverDetailMobileMcpPage />,
loader: slugLoader,
path: 'mcp/:slug',
},
],
lazy: () =>
import('./(main)/discover/(detail)/_layout/Mobile/index').then((m) => ({
Component: m.default,
})),
element: <DiscoverDetailLayout />,
},
],
lazy: () =>
import('./(main)/discover/_layout/Mobile/index').then((m) => ({
Component: m.default,
})),
element: <DiscoverLayout />,
errorElement: <DiscoverErrorBoundary />,
path: 'discover',
},
@@ -194,32 +389,21 @@ export const createMobileRouter = (locale: Locales) =>
{
children: [
{
element: <KnowledgeHome />,
index: true,
lazy: () =>
import('./(main)/knowledge/routes/KnowledgeHome').then((m) => ({
Component: m.default,
})),
},
{
lazy: () =>
import('./(main)/knowledge/routes/KnowledgeBasesList').then((m) => ({
Component: m.default,
})),
element: <KnowledgeBasesList />,
path: 'bases',
},
{
lazy: () =>
import('./(main)/knowledge/routes/KnowledgeBaseDetail').then((m) => ({
Component: m.default,
})),
element: <KnowledgeBaseDetail />,
loader: idLoader,
path: 'bases/:id',
},
],
lazy: () =>
import('./(main)/knowledge/_layout/Mobile').then((m) => ({
Component: m.default,
})),
element: <KnowledgeLayout />,
errorElement: <KnowledgeErrorBoundary />,
path: 'knowledge',
},
@@ -227,17 +411,12 @@ export const createMobileRouter = (locale: Locales) =>
{
children: [
{
element: <SettingsLayout />,
index: true,
lazy: () =>
import('./(main)/settings/_layout/Mobile').then((m) => ({
Component: m.default,
})),
},
],
lazy: () =>
import('./(main)/settings/_layout/MobileWrapper').then((m) => ({
Component: m.default,
})),
element: <SettingsLayoutWrapper />,
errorElement: <SettingsErrorBoundary />,
path: 'settings',
},
@@ -245,26 +424,18 @@ export const createMobileRouter = (locale: Locales) =>
{
children: [
{
element: <ImageComingSoon />,
index: true,
lazy: () =>
import('./(main)/image/ComingSoon').then((m) => ({
Component: m.default,
})),
},
],
lazy: () =>
import('./(main)/image/_layout/Mobile').then((m) => ({
Component: m.default,
})),
element: <ImageLayoutMobile />,
errorElement: <ImageErrorBoundary />,
path: 'image',
},
// Labs routes
{
lazy: () =>
import('./(main)/labs').then((m) => ({
Component: m.default,
})),
element: <LabsPage />,
path: 'labs',
},
@@ -272,38 +443,24 @@ export const createMobileRouter = (locale: Locales) =>
{
children: [
{
element: <ProfileHomePage />,
index: true,
lazy: () =>
import('./(main)/profile/(home)').then((m) => ({
Component: m.default,
})),
},
{
lazy: () =>
import('./(main)/profile/apikey/index').then((m) => ({
Component: m.default,
})),
element: <ProfileApikeyPage />,
path: 'apikey',
},
{
lazy: () =>
import('./(main)/profile/security').then((m) => ({
Component: m.MobileProfileSecurityPage,
})),
element: <MobileProfileSecurityPage />,
path: 'security',
},
{
lazy: () =>
import('./(main)/profile/stats').then((m) => ({
Component: m.MobileProfileStatsPage,
})),
element: <MobileProfileStatsPage />,
path: 'stats',
},
],
lazy: () =>
import('./(main)/profile/_layout/Mobile').then((m) => ({
Component: m.default,
})),
element: <ProfileLayoutMobile />,
errorElement: <ProfileErrorBoundary />,
path: 'profile',
},
@@ -313,49 +470,32 @@ export const createMobileRouter = (locale: Locales) =>
{
children: [
{
element: <MeHomePage />,
index: true,
lazy: () =>
import('./(main)/(mobile)/me/(home)').then((m) => ({
Component: m.default,
})),
},
],
lazy: () =>
import('./(main)/(mobile)/me/(home)/layout').then((m) => ({
Component: m.default,
})),
element: <MeHomeLayout />,
},
{
children: [
{
lazy: () =>
import('./(main)/(mobile)/me/profile').then((m) => ({
Component: m.default,
})),
element: <MeProfilePage />,
path: 'profile',
},
],
lazy: () =>
import('./(main)/(mobile)/me/profile/layout').then((m) => ({
Component: m.default,
})),
element: <MeProfileLayout />,
},
{
children: [
{
lazy: () =>
import('./(main)/(mobile)/me/settings').then((m) => ({
Component: m.default,
})),
element: <MeSettingsPage />,
path: 'settings',
},
],
lazy: () =>
import('./(main)/(mobile)/me/settings/layout').then((m) => ({
Component: m.default,
})),
element: <MeSettingsLayout />,
},
],
errorElement: <MeErrorBoundary />,
path: 'me',
},
@@ -372,7 +512,7 @@ export const createMobileRouter = (locale: Locales) =>
},
],
element: <RootLayout locale={locale} />,
loader: hydrationGateLoader,
errorElement: <RootErrorBoundary />,
path: '/',
},
]);
+1
View File
@@ -12,6 +12,7 @@ export default async (props: DynamicLayoutProps) => {
// Conditionally load and render based on device type
// Using native dynamic import ensures complete code splitting
// Mobile and Desktop bundles will be completely separate
if (isMobile) {
return <MobileRouter locale={locale} />;
}
@@ -17,6 +17,7 @@ import ReasoningEffortSlider from './ReasoningEffortSlider';
import ReasoningTokenSlider from './ReasoningTokenSlider';
import TextVerbositySlider from './TextVerbositySlider';
import ThinkingBudgetSlider from './ThinkingBudgetSlider';
import ThinkingLevelSlider from './ThinkingLevelSlider';
import ThinkingSlider from './ThinkingSlider';
const ControlsForm = memo(() => {
@@ -177,6 +178,16 @@ const ControlsForm = memo(() => {
paddingBottom: 0,
},
},
{
children: <ThinkingLevelSlider />,
label: t('extendParams.thinkingLevel.title'),
layout: 'horizontal',
minWidth: undefined,
name: 'thinkingLevel',
style: {
paddingBottom: 0,
},
},
].filter(Boolean) as FormItemProps[];
return (
@@ -0,0 +1,56 @@
import { Slider } from 'antd';
import { memo, useCallback } from 'react';
import { Flexbox } from 'react-layout-kit';
import { useAgentStore } from '@/store/agent';
import { agentChatConfigSelectors } from '@/store/agent/selectors';
const ThinkingLevelSlider = memo(() => {
const [config, updateAgentChatConfig] = useAgentStore((s) => [
agentChatConfigSelectors.currentChatConfig(s),
s.updateAgentChatConfig,
]);
const thinkingLevel = config.thinkingLevel || 'high'; // Default to 'high' if not set
const marks = {
0: 'low',
1: 'high',
};
const levelValues = ['low', 'high'];
const indexValue = levelValues.indexOf(thinkingLevel);
const currentValue = indexValue === -1 ? 1 : indexValue;
const updateThinkingLevel = useCallback(
(value: number) => {
const level = levelValues[value] as 'low' | 'high';
updateAgentChatConfig({ thinkingLevel: level });
},
[updateAgentChatConfig],
);
return (
<Flexbox
align={'center'}
gap={12}
horizontal
paddingInline={'0 20px'}
style={{ minWidth: 130, width: '100%' }} // 三项时宽度需改回 200
>
<Flexbox flex={1}>
<Slider
marks={marks}
max={1}
min={0}
onChange={updateThinkingLevel}
step={1}
tooltip={{ open: false }}
value={currentValue}
/>
</Flexbox>
</Flexbox>
);
});
export default ThinkingLevelSlider;
@@ -15,12 +15,13 @@ export interface ErrorContentProps {
const ErrorContent = memo<ErrorContentProps>(({ error, id }) => {
const { t } = useTranslation('common');
const errorProps = useErrorContent(error);
const [deleteMessage] = useChatStore((s) => [s.deleteDBMessage]);
const message = <ErrorMessageExtra block data={{ error, id }} />;
if (!error?.message) {
const errorProps = useErrorContent(error);
if (!errorProps?.message) {
if (!message) return null;
return <Flexbox>{message}</Flexbox>;
}
@@ -30,10 +30,10 @@ const GroupItem = memo<GroupItemProps>(
});
}}
>
<ContentBlock index={index} {...item} />
<ContentBlock index={index} {...item} error={error} />
</Flexbox>
) : (
<ContentBlock index={index} {...item} />
<ContentBlock index={index} {...item} error={error} />
);
},
isEqual,
@@ -18,6 +18,9 @@ export const useStyles = createStyles(({ css, token }) => ({
text-overflow: ellipsis;
`,
expand: css`
color: ${token.colorText};
`,
shinyText: shinyTextStylish(token),
}));
@@ -26,22 +29,30 @@ interface BuiltinPluginTitleProps {
icon?: ReactNode;
identifier: string;
index: number;
isExpanded?: boolean;
isLoading?: boolean;
messageId: string;
title: string;
toolCallId: string;
}
const BuiltinPluginTitle = memo<BuiltinPluginTitleProps>(({ apiName, title, isLoading }) => {
const { styles } = useStyles();
const BuiltinPluginTitle = memo<BuiltinPluginTitleProps>(
({ apiName, title, isLoading, isExpanded }) => {
const { styles, cx } = useStyles();
return (
<Flexbox align={'center'} className={isLoading ? styles.shinyText : ''} gap={4} horizontal>
<div>{title}</div>
<Icon icon={ChevronRight} />
<span className={styles.apiName}>{apiName}</span>
</Flexbox>
);
});
return (
<Flexbox
align={'center'}
className={cx(isLoading && styles.shinyText, isExpanded && styles.expand)}
gap={4}
horizontal
>
<div>{title}</div>
<Icon icon={ChevronRight} />
<span className={styles.apiName}>{apiName}</span>
</Flexbox>
);
},
);
export default BuiltinPluginTitle;
@@ -0,0 +1,41 @@
import { ToolIntervention } from '@lobechat/types';
import { Icon, Tooltip } from '@lobehub/ui';
import { useTheme } from 'antd-style';
import { Ban, Check, CircleStop, X } from 'lucide-react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
interface StatusIndicatorProps {
intervention?: ToolIntervention;
result?: { content: string | null; error?: any; state?: any };
}
const StatusIndicator = memo<StatusIndicatorProps>(({ intervention, result }) => {
const { t } = useTranslation('chat');
const theme = useTheme();
const hasError = !!result?.error;
const isReject = intervention?.status === 'rejected';
const isAbort = intervention?.status === 'aborted';
return (
<Flexbox align={'center'} gap={4} horizontal style={{ fontSize: 12 }}>
{isAbort ? (
<Tooltip title={t('tool.intervention.toolAbort')}>
<Icon color={theme.colorTextTertiary} icon={CircleStop} />
</Tooltip>
) : isReject ? (
<Tooltip title={t('tool.intervention.toolRejected')}>
<Icon color={theme.colorTextTertiary} icon={Ban} />
</Tooltip>
) : hasError ? (
<Icon color={theme.colorError} icon={X} />
) : (
<Icon color={theme.colorSuccess} icon={Check} />
)}
</Flexbox>
);
});
export default StatusIndicator;
@@ -26,6 +26,9 @@ export const useStyles = createStyles(({ css, token }) => ({
text-overflow: ellipsis;
`,
expand: css`
color: ${token.colorText};
`,
shinyText: shinyTextStylish(token),
}));
@@ -33,15 +36,16 @@ interface ToolTitleProps {
apiName: string;
identifier: string;
index: number;
isExpanded?: boolean;
isLoading?: boolean;
messageId: string;
toolCallId: string;
}
const ToolTitle = memo<ToolTitleProps>(
({ identifier, apiName, isLoading, index, toolCallId, messageId }) => {
({ identifier, apiName, isLoading, index, toolCallId, messageId, isExpanded }) => {
const { t } = useTranslation('plugin');
const { styles } = useStyles();
const { styles, cx } = useStyles();
const pluginMeta = useToolStore(toolSelectors.getMetaById(identifier), isEqual);
@@ -69,6 +73,7 @@ const ToolTitle = memo<ToolTitleProps>(
{...builtinPluginTitle}
identifier={identifier}
index={index}
isExpanded={isExpanded}
isLoading={isLoading}
messageId={messageId}
toolCallId={toolCallId}
@@ -79,7 +84,12 @@ const ToolTitle = memo<ToolTitleProps>(
const pluginTitle = pluginHelpers.getPluginTitle(pluginMeta) ?? t('unknownPlugin');
return (
<Flexbox align={'center'} className={isLoading ? styles.shinyText : ''} gap={6} horizontal>
<Flexbox
align={'center'}
className={cx(isLoading && styles.shinyText, isExpanded && styles.expand)}
gap={6}
horizontal
>
<div>{pluginTitle}</div> <Icon icon={ChevronRight} />
<span className={styles.apiName}>{apiName}</span>
</Flexbox>
@@ -1,26 +1,19 @@
import { ToolIntervention } from '@lobechat/types';
import { ActionIcon, Icon, Tooltip } from '@lobehub/ui';
import { ActionIcon } from '@lobehub/ui';
import { createStyles } from 'antd-style';
import {
Ban,
Check,
LayoutPanelTop,
LogsIcon,
LucideBug,
LucideBugOff,
Trash2,
X,
} from 'lucide-react';
import { LayoutPanelTop, LogsIcon, LucideBug, LucideBugOff, Trash2 } from 'lucide-react';
import { CSSProperties, memo, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import { LOADING_FLAT } from '@/const/message';
import { useChatStore } from '@/store/chat';
import { dbMessageSelectors } from '@/store/chat/slices/message/selectors';
import { shinyTextStylish } from '@/styles/loading';
import Debug from './Debug';
import Settings from './Settings';
import StatusIndicator from './StatusIndicator';
import ToolTitle from './ToolTitle';
export const useStyles = createStyles(({ css, token, cx }) => ({
@@ -86,6 +79,7 @@ interface InspectorProps {
showPortal?: boolean;
showRender: boolean;
style?: CSSProperties;
toolMessageId?: string;
type?: string;
}
@@ -103,15 +97,18 @@ const Inspectors = memo<InspectorProps>(
setShowPluginRender,
type,
intervention,
toolMessageId,
}) => {
const { t } = useTranslation('plugin');
const { styles, theme } = useStyles();
const { styles } = useStyles();
const [showDebug, setShowDebug] = useState(false);
const [isPinned, setIsPinned] = useState(false);
const [isHovered, setIsHovered] = useState(false);
const [deleteAssistantMessage] = useChatStore((s) => [s.deleteAssistantMessage]);
const [deleteAssistantMessage, toggleInspectExpanded] = useChatStore((s) => [
s.deleteAssistantMessage,
s.toggleInspectExpanded,
]);
const hasError = !!result?.error;
const hasSuccessResult = !!result?.content && result.content !== LOADING_FLAT;
@@ -119,18 +116,26 @@ const Inspectors = memo<InspectorProps>(
const hasResult = hasSuccessResult || hasError;
const isPending = intervention?.status === 'pending';
const isReject = intervention?.status === 'rejected' || intervention?.status === 'aborted';
const isReject = intervention?.status === 'rejected';
const isAbort = intervention?.status === 'aborted';
const isTitleLoading = !hasResult && !isPending;
// Compute actual render state based on pinned or hovered
const shouldShowRender = isPinned || isHovered;
const isPersistentExpanded = useChatStore((s) => {
if (!toolMessageId) return undefined;
const message = dbMessageSelectors.getDbMessageById(toolMessageId)(s);
return message?.metadata?.inspectExpanded;
});
// Compute actual render state based on persistent expanded or hovered
const shouldShowRender = isPersistentExpanded || isHovered;
// Sync with parent state
useEffect(() => {
setShowRender(shouldShowRender);
}, [shouldShowRender, setShowRender]);
const showCustomPluginRender = shouldShowRender && !isPending && !isReject;
const showCustomPluginRender = shouldShowRender && !isPending && !isReject && !isAbort;
return (
<Flexbox className={styles.container} gap={4}>
<Flexbox align={'center'} distribution={'space-between'} gap={8} horizontal>
@@ -140,15 +145,15 @@ const Inspectors = memo<InspectorProps>(
gap={8}
horizontal
onClick={() => {
setIsPinned(!isPinned);
if (toolMessageId) toggleInspectExpanded(toolMessageId);
}}
onMouseEnter={() => {
if (!isPinned) {
if (!isPersistentExpanded) {
setIsHovered(true);
}
}}
onMouseLeave={() => {
if (!isPinned) {
if (!isPersistentExpanded) {
setIsHovered(false);
}
}}
@@ -158,6 +163,7 @@ const Inspectors = memo<InspectorProps>(
apiName={apiName}
identifier={identifier}
index={index}
isExpanded={isPersistentExpanded}
isLoading={isTitleLoading}
messageId={assistantMessageId}
toolCallId={id}
@@ -194,19 +200,7 @@ const Inspectors = memo<InspectorProps>(
<Settings id={identifier} />
</Flexbox>
{hasResult && (
<Flexbox align={'center'} gap={4} horizontal style={{ fontSize: 12 }}>
{isReject ? (
<Tooltip title={t('tool.intervention.toolRejected', { ns: 'chat' })}>
<Icon color={theme.colorTextTertiary} icon={Ban} />
</Tooltip>
) : hasError ? (
<Icon color={theme.colorError} icon={X} />
) : (
<Icon color={theme.colorSuccess} icon={Check} />
)}
</Flexbox>
)}
{hasResult && <StatusIndicator intervention={intervention} result={result} />}
</Flexbox>
</Flexbox>
{showDebug && (
@@ -0,0 +1,37 @@
import { Icon } from '@lobehub/ui';
import { createStyles } from 'antd-style';
import { BanIcon } from 'lucide-react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
const useStyles = createStyles(({ css, token }) => ({
container: css`
padding-block: 8px;
padding-inline: 6px;
`,
reason: css`
font-size: 12px;
color: ${token.colorTextTertiary};
`,
title: css`
font-size: 14px;
color: ${token.colorTextSecondary};
`,
}));
const AbortResponse = memo(() => {
const { t } = useTranslation('chat');
const { styles, theme } = useStyles();
return (
<Flexbox className={styles.container} gap={8}>
<Flexbox align={'center'} gap={8} horizontal>
<Icon color={theme.colorTextTertiary} icon={BanIcon} size={16} />
<div className={styles.title}>{t('tool.intervention.toolAbort')}</div>
</Flexbox>
</Flexbox>
);
});
export default AbortResponse;
@@ -3,6 +3,8 @@ import { ChatToolResult, ToolIntervention } from '@lobechat/types';
import { Suspense, memo } from 'react';
import { Flexbox } from 'react-layout-kit';
import AbortResponse from '@/features/Conversation/Messages/Group/Tool/Render/AbortResponse';
import CustomRender from './CustomRender';
import ErrorResponse from './ErrorResponse';
import Intervention from './Intervention';
@@ -63,6 +65,10 @@ const Render = memo<RenderProps>(
return <RejectedResponse reason={intervention.rejectedReason} />;
}
if (intervention?.status === 'aborted') {
return <AbortResponse />;
}
if (!result) return null;
// Handle error state
@@ -70,6 +70,7 @@ const Tool = memo<GroupToolProps>(
setShowRender={setShowToolDetail}
showPluginRender={showCustomPluginUI}
showRender={showToolContent}
toolMessageId={toolMessageId}
type={type}
/>
<AnimatedCollapsed open={showToolContent} width={{ collapsed: 'auto' }}>
@@ -63,16 +63,7 @@ const StoreInitialization = memo(() => {
// init user state
useInitUserState(isLoginOnInit, serverConfig, {
onError: () => {
// 即使失败也要设置标志,避免应用卡住
useGlobalStore.setState({ isAppHydrated: true });
console.warn('[Hydration] Client state initialization failed.');
},
onSuccess: (state) => {
// 设置水合完成标志
useGlobalStore.setState({ isAppHydrated: true });
console.log('[Hydration] Client state initialized successfully.');
if (state.isOnboard === false) {
router.push('/onboard');
}
+4
View File
@@ -66,6 +66,9 @@ export default {
thinking: {
title: '深度思考开关',
},
thinkingLevel: {
title: '思考水平',
},
title: '模型扩展功能',
urlContext: {
desc: '开启后将自动解析网页链接,以获取实际网页上下文内容',
@@ -427,6 +430,7 @@ export default {
rejectReasonPlaceholder: '输入拒绝原因将帮助 Agent 理解并优化后续行动',
rejectTitle: '拒绝本次工具调用',
rejectedWithReason: '本次工具调用被主动拒绝:{{reason}}',
toolAbort: '本次工具调用被用户取消',
toolRejected: '本次工具调用被主动拒绝',
},
},
+1
View File
@@ -286,6 +286,7 @@ export default {
business: '商务合作',
support: '邮件支持',
},
new: '新',
oauth: 'SSO 登录',
officialSite: '官方网站',
ok: '确定',
-6
View File
@@ -220,12 +220,6 @@ export const fileRouter = router({
embeddingStatus: null,
finishEmbedding: false,
} as FileListItem;
console.log('[API getKnowledgeItems] Processing document:', {
editorDataPreview: item.editorData ? JSON.stringify(item.editorData).slice(0, 100) : null,
hasEditorData: !!item.editorData,
id: item.id,
name: item.name,
});
resultItems.push(documentItem);
}
}
+4
View File
@@ -196,6 +196,10 @@ class ChatService {
extendParams.thinkingBudget = chatConfig.thinkingBudget;
}
if (modelExtendParams!.includes('thinkingLevel') && chatConfig.thinkingLevel) {
extendParams.thinkingLevel = chatConfig.thinkingLevel;
}
if (modelExtendParams!.includes('urlContext') && chatConfig.urlContext) {
extendParams.urlContext = chatConfig.urlContext;
}
@@ -76,7 +76,7 @@ export interface StreamingExecutorAction {
tool_calls?: MessageToolCall[];
content: string;
traceId?: string;
finishType?: 'done' | 'error' | 'abort';
finishType?: string;
usage?: ModelUsage;
}>;
/**
@@ -283,13 +283,13 @@ export const streamingExecutor: StateCreator<
let thinkingStartAt: number;
let duration: number | undefined;
let reasoningOperationId: string | undefined;
let finishType: 'done' | 'error' | 'abort' | undefined;
let finishType: string | undefined;
// to upload image
const uploadTasks: Map<string, Promise<{ id?: string; url?: string }>> = new Map();
// Throttle tool_calls updates to prevent excessive re-renders (max once per 300ms)
const throttledUpdateToolCalls = throttle(
(toolCalls: any[]) => {
(toolCalls: MessageToolCall[]) => {
internal_dispatchMessage(
{
id: messageId,
@@ -366,7 +366,6 @@ export const streamingExecutor: StateCreator<
throttledUpdateToolCalls.flush();
internal_toggleToolCallingStreaming(messageId, undefined);
tools = get().internal_transformToolCalls(parsedToolCalls);
tool_calls = toolCalls;
parsedToolCalls = parsedToolCalls.map((item) => ({
@@ -377,6 +376,8 @@ export const streamingExecutor: StateCreator<
},
}));
tools = get().internal_transformToolCalls(parsedToolCalls);
isFunctionCall = true;
}
@@ -395,7 +396,7 @@ export const streamingExecutor: StateCreator<
messageId,
content,
{
toolCalls: parsedToolCalls,
tools,
reasoning: !!reasoning
? { ...reasoning, duration: duration && !isNaN(duration) ? duration : undefined }
: undefined,
@@ -3,11 +3,11 @@ import {
ChatImageItem,
ChatMessageError,
ChatMessagePluginError,
ChatToolPayload,
CreateMessageParams,
GroundingSearch,
MessageMetadata,
MessagePluginItem,
MessageToolCall,
ModelReasoning,
UIChatMessage,
UpdateMessageRAGParams,
@@ -69,7 +69,7 @@ export interface MessageOptimisticUpdateAction {
provider?: string;
reasoning?: ModelReasoning;
search?: GroundingSearch;
toolCalls?: MessageToolCall[];
tools?: ChatToolPayload[];
},
context?: OptimisticUpdateContext,
) => Promise<void>;
@@ -204,22 +204,17 @@ export const messageOptimisticUpdate: StateCreator<
},
optimisticUpdateMessageContent: async (id, content, extra, context) => {
const {
internal_dispatchMessage,
refreshMessages,
internal_transformToolCalls,
replaceMessages,
} = get();
const { internal_dispatchMessage, refreshMessages, replaceMessages } = get();
// Due to the async update method and refresh need about 100ms
// we need to update the message content at the frontend to avoid the update flick
// refs: https://medium.com/@kyledeguzmanx/what-are-optimistic-updates-483662c3e171
if (extra?.toolCalls) {
if (extra?.tools) {
internal_dispatchMessage(
{
id,
type: 'updateMessage',
value: { tools: internal_transformToolCalls(extra?.toolCalls) },
value: { tools: extra?.tools },
},
context,
);
@@ -246,7 +241,7 @@ export const messageOptimisticUpdate: StateCreator<
provider: extra?.provider,
reasoning: extra?.reasoning,
search: extra?.search,
tools: extra?.toolCalls ? internal_transformToolCalls(extra?.toolCalls) : undefined,
tools: extra?.tools,
},
{ sessionId, topicId },
);
@@ -47,6 +47,10 @@ export interface MessagePublicApiAction {
* Toggle message collapsed state
*/
toggleMessageCollapsed: (id: string, collapsed?: boolean) => Promise<void>;
/**
* Toggle tool inspect expanded state
*/
toggleInspectExpanded: (id: string, expanded?: boolean) => Promise<void>;
// ===== Others ===== //
copyMessage: (id: string, content: string) => Promise<void>;
@@ -258,4 +262,15 @@ export const messagePublicApi: StateCreator<
collapsed: nextCollapsed,
});
},
toggleInspectExpanded: async (id, expanded) => {
const message = dbMessageSelectors.getDbMessageById(id)(get());
if (!message) return;
// 如果没有传入 expanded,则取反当前状态
const nextExpanded = expanded ?? !message.metadata?.inspectExpanded;
// 直接调用现有的 optimisticUpdateMessageMetadata
await get().optimisticUpdateMessageMetadata(id, { inspectExpanded: nextExpanded });
},
});
@@ -1,6 +1,6 @@
/* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
import { ToolNameResolver } from '@lobechat/context-engine';
import { MessageToolCall, ToolsCallingContext } from '@lobechat/types';
import { ChatToolPayload, MessageToolCall, ToolsCallingContext } from '@lobechat/types';
import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
import { StateCreator } from 'zustand/vanilla';
@@ -19,7 +19,7 @@ export interface PluginInternalsAction {
/**
* Transform tool calls from runtime format to storage format
*/
internal_transformToolCalls: (toolCalls: MessageToolCall[]) => any[];
internal_transformToolCalls: (toolCalls: MessageToolCall[]) => ChatToolPayload[];
/**
* Construct tools calling context for plugin invocation

Some files were not shown because too many files have changed in this diff Show More