mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-16 20:46:08 +00:00
Compare commits
89 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b8b1ab6616 | |||
| 3891015a3d | |||
| 5babb7d826 | |||
| a7504b696a | |||
| 9dc4308942 | |||
| 082117998d | |||
| 9a74d6c045 | |||
| b1a4f24dc9 | |||
| c47551775b | |||
| 2d83300795 | |||
| 0915538da8 | |||
| b76e3c85b9 | |||
| 29ce0225b2 | |||
| 06878829c9 | |||
| ae613c7c35 | |||
| 8184f9d097 | |||
| 7fac37b983 | |||
| d3570879da | |||
| 1ad80809cf | |||
| 2c97a9e920 | |||
| 246cce28db | |||
| 9ffb6891e4 | |||
| 766ca942b3 | |||
| 147975ae46 | |||
| a6c3317192 | |||
| 4cd6347d7e | |||
| cd7d955e3d | |||
| 61901ddb07 | |||
| 77ed938cfb | |||
| 4c3ac3bce7 | |||
| a142b3384f | |||
| ee80f613df | |||
| 7d05d0270c | |||
| 53fc0642e0 | |||
| a8c725abd5 | |||
| b8a7f6e9eb | |||
| bb594f87e2 | |||
| b0ee9b434e | |||
| cf2c5a1d37 | |||
| 0511e43a48 | |||
| 1f128f407f | |||
| f258a2e042 | |||
| 7996e1c431 | |||
| 93dddfc2e5 | |||
| 5e4186559b | |||
| 9bfd9bb4a5 | |||
| 9ca54135b5 | |||
| f162556607 | |||
| 3292ed83f9 | |||
| 561a38f788 | |||
| 71aaf0fac5 | |||
| 287601f8ec | |||
| b36f8781e6 | |||
| 705450a571 | |||
| 5272c7373f | |||
| fb24b6f1b7 | |||
| 2fd65fe8a3 | |||
| 35d5a2c937 | |||
| 42f40d2717 | |||
| ef8a644d8c | |||
| 81c84348bc | |||
| 8d7a0467db | |||
| e9522729c5 | |||
| cf01894077 | |||
| b5d945b1fd | |||
| cbee964582 | |||
| 87a38ad0c4 | |||
| f2d4745ad3 | |||
| 0167ac8e28 | |||
| b480227fd0 | |||
| 97ff98cada | |||
| 845d3ef58a | |||
| 906917362f | |||
| c69049d6da | |||
| 4f7356ffab | |||
| d20c82c115 | |||
| d617a6cd97 | |||
| 408391eeb6 | |||
| 4a2e671f55 | |||
| 695a261df1 | |||
| 39b723eff4 | |||
| 68937d842c | |||
| b66bc66260 | |||
| 4d06279abd | |||
| 1a8d33fbf4 | |||
| 2c086373cc | |||
| c7d49258f8 | |||
| 2280fd6ff9 | |||
| 8eb901c401 |
@@ -21,6 +21,7 @@ jobs:
|
||||
(github.event_name == 'pull_request_review' && github.event.sender.type != 'Bot') ||
|
||||
(github.event_name == 'pull_request_review_comment' && github.event.sender.type != 'Bot')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
contents: read
|
||||
# update issues/comments
|
||||
|
||||
+100
@@ -2,6 +2,106 @@
|
||||
|
||||
# Changelog
|
||||
|
||||
## [Version 2.0.0-next.69](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.68...v2.0.0-next.69)
|
||||
|
||||
<sup>Released on **2025-11-17**</sup>
|
||||
|
||||
#### ♻ Code Refactoring
|
||||
|
||||
- **misc**: Remove `language_model_settings` and remove isDeprecatedEdition.
|
||||
|
||||
<br/>
|
||||
|
||||
<details>
|
||||
<summary><kbd>Improvements and Fixes</kbd></summary>
|
||||
|
||||
#### Code refactoring
|
||||
|
||||
- **misc**: Remove `language_model_settings` and remove isDeprecatedEdition, closes [#10264](https://github.com/lobehub/lobe-chat/issues/10264) ([ae613c7](https://github.com/lobehub/lobe-chat/commit/ae613c7))
|
||||
|
||||
</details>
|
||||
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
## [Version 2.0.0-next.68](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.67...v2.0.0-next.68)
|
||||
|
||||
<sup>Released on **2025-11-16**</sup>
|
||||
|
||||
#### 🐛 Bug Fixes
|
||||
|
||||
- **misc**: The tool to fail execution on ollama when a message contains b….
|
||||
|
||||
<br/>
|
||||
|
||||
<details>
|
||||
<summary><kbd>Improvements and Fixes</kbd></summary>
|
||||
|
||||
#### What's fixed
|
||||
|
||||
- **misc**: The tool to fail execution on ollama when a message contains b…, closes [#10259](https://github.com/lobehub/lobe-chat/issues/10259) ([1ad8080](https://github.com/lobehub/lobe-chat/commit/1ad8080))
|
||||
|
||||
</details>
|
||||
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
## [Version 2.0.0-next.67](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.66...v2.0.0-next.67)
|
||||
|
||||
<sup>Released on **2025-11-16**</sup>
|
||||
|
||||
#### ♻ Code Refactoring
|
||||
|
||||
- **misc**: Refactor to virtua.
|
||||
|
||||
<br/>
|
||||
|
||||
<details>
|
||||
<summary><kbd>Improvements and Fixes</kbd></summary>
|
||||
|
||||
#### Code refactoring
|
||||
|
||||
- **misc**: Refactor to virtua, closes [#10151](https://github.com/lobehub/lobe-chat/issues/10151) ([9ffb689](https://github.com/lobehub/lobe-chat/commit/9ffb689))
|
||||
|
||||
</details>
|
||||
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
## [Version 2.0.0-next.66](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.65...v2.0.0-next.66)
|
||||
|
||||
<sup>Released on **2025-11-16**</sup>
|
||||
|
||||
#### ✨ Features
|
||||
|
||||
- **misc**: Support to collapse message.
|
||||
|
||||
<br/>
|
||||
|
||||
<details>
|
||||
<summary><kbd>Improvements and Fixes</kbd></summary>
|
||||
|
||||
#### What's improved
|
||||
|
||||
- **misc**: Support to collapse message, closes [#10234](https://github.com/lobehub/lobe-chat/issues/10234) ([4cd6347](https://github.com/lobehub/lobe-chat/commit/4cd6347))
|
||||
|
||||
</details>
|
||||
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
## [Version 2.0.0-next.65](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.64...v2.0.0-next.65)
|
||||
|
||||
<sup>Released on **2025-11-16**</sup>
|
||||
|
||||
+18
-18
@@ -32,33 +32,33 @@
|
||||
"electron-updater": "^6.6.2",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"fetch-socks": "^1.3.2",
|
||||
"get-port-please": "^3.1.2",
|
||||
"get-port-please": "^3.2.0",
|
||||
"pdfjs-dist": "4.10.38"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron-toolkit/eslint-config-prettier": "^3.0.0",
|
||||
"@electron-toolkit/eslint-config-ts": "^3.0.0",
|
||||
"@electron-toolkit/preload": "^3.0.1",
|
||||
"@electron-toolkit/eslint-config-ts": "^3.1.0",
|
||||
"@electron-toolkit/preload": "^3.0.2",
|
||||
"@electron-toolkit/tsconfig": "^2.0.0",
|
||||
"@electron-toolkit/utils": "^4.0.0",
|
||||
"@lobechat/electron-client-ipc": "workspace:*",
|
||||
"@lobechat/electron-server-ipc": "workspace:*",
|
||||
"@lobechat/file-loaders": "workspace:*",
|
||||
"@lobehub/i18n-cli": "^1.20.3",
|
||||
"@types/lodash": "^4.17.0",
|
||||
"@lobehub/i18n-cli": "^1.25.1",
|
||||
"@types/lodash": "^4.17.20",
|
||||
"@types/resolve": "^1.20.6",
|
||||
"@types/semver": "^7.7.0",
|
||||
"@types/semver": "^7.7.1",
|
||||
"@types/set-cookie-parser": "^2.4.10",
|
||||
"@typescript/native-preview": "7.0.0-dev.20250711.1",
|
||||
"consola": "^3.1.0",
|
||||
"consola": "^3.4.2",
|
||||
"cookie": "^1.0.2",
|
||||
"electron": "^38.6.0",
|
||||
"electron": "^38.7.0",
|
||||
"electron-builder": "^26.0.12",
|
||||
"electron-is": "^3.0.0",
|
||||
"electron-log": "^5.3.3",
|
||||
"electron-log": "^5.4.3",
|
||||
"electron-store": "^8.2.0",
|
||||
"electron-vite": "^3.0.0",
|
||||
"execa": "^9.5.2",
|
||||
"electron-vite": "^3.1.0",
|
||||
"execa": "^9.6.0",
|
||||
"fast-glob": "^3.3.3",
|
||||
"fix-path": "^5.0.0",
|
||||
"http-proxy-agent": "^7.0.2",
|
||||
@@ -66,13 +66,13 @@
|
||||
"just-diff": "^6.0.2",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.21",
|
||||
"resolve": "^1.22.8",
|
||||
"semver": "^7.5.4",
|
||||
"set-cookie-parser": "^2.7.1",
|
||||
"tsx": "^4.19.3",
|
||||
"typescript": "^5.7.3",
|
||||
"undici": "^7.9.0",
|
||||
"vite": "^6.3.5",
|
||||
"resolve": "^1.22.11",
|
||||
"semver": "^7.7.3",
|
||||
"set-cookie-parser": "^2.7.2",
|
||||
"tsx": "^4.20.6",
|
||||
"typescript": "^5.9.3",
|
||||
"undici": "^7.16.0",
|
||||
"vite": "^6.4.1",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"pnpm": {
|
||||
|
||||
@@ -1,4 +1,39 @@
|
||||
[
|
||||
{
|
||||
"children": {
|
||||
"improvements": ["Remove language_model_settings and remove isDeprecatedEdition."]
|
||||
},
|
||||
"date": "2025-11-17",
|
||||
"version": "2.0.0-next.69"
|
||||
},
|
||||
{
|
||||
"children": {
|
||||
"fixes": ["The tool to fail execution on ollama when a message contains b…."]
|
||||
},
|
||||
"date": "2025-11-16",
|
||||
"version": "2.0.0-next.68"
|
||||
},
|
||||
{
|
||||
"children": {
|
||||
"improvements": ["Refactor to virtua."]
|
||||
},
|
||||
"date": "2025-11-16",
|
||||
"version": "2.0.0-next.67"
|
||||
},
|
||||
{
|
||||
"children": {
|
||||
"features": ["Support to collapse message."]
|
||||
},
|
||||
"date": "2025-11-16",
|
||||
"version": "2.0.0-next.66"
|
||||
},
|
||||
{
|
||||
"children": {
|
||||
"improvements": ["Update i18n."]
|
||||
},
|
||||
"date": "2025-11-16",
|
||||
"version": "2.0.0-next.65"
|
||||
},
|
||||
{
|
||||
"children": {
|
||||
"improvements": ["Refactor package types."]
|
||||
|
||||
+2
-2
@@ -17,8 +17,8 @@
|
||||
"playwright": "^1.56.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.10.5",
|
||||
"@types/node": "^22.19.1",
|
||||
"tsx": "^4.20.6",
|
||||
"typescript": "^5.7.3"
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"availableAgents": "المساعدون المتاحون",
|
||||
"backToBottom": "العودة إلى الأسفل",
|
||||
"chatList": {
|
||||
"expandMessage": "عرض الرسائل",
|
||||
"longMessageDetail": "عرض التفاصيل"
|
||||
},
|
||||
"clearCurrentMessages": "مسح رسائل الجلسة الحالية",
|
||||
@@ -173,9 +174,11 @@
|
||||
"title": "الإشارة إلى الأعضاء"
|
||||
},
|
||||
"messageAction": {
|
||||
"collapse": "إخفاء الرسائل",
|
||||
"continueGeneration": "متابعة التوليد",
|
||||
"delAndRegenerate": "حذف وإعادة الإنشاء",
|
||||
"deleteDisabledByThreads": "يوجد موضوعات فرعية، لا يمكن الحذف",
|
||||
"expand": "عرض الرسائل",
|
||||
"regenerate": "إعادة الإنشاء"
|
||||
},
|
||||
"messages": {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"availableAgents": "Налични асистенти",
|
||||
"backToBottom": "Върни се в началото",
|
||||
"chatList": {
|
||||
"expandMessage": "Разгъни съобщението",
|
||||
"longMessageDetail": "Вижте детайлите"
|
||||
},
|
||||
"clearCurrentMessages": "Изчисти съобщенията от текущата сесия",
|
||||
@@ -173,9 +174,11 @@
|
||||
"title": "Споменаване на членове"
|
||||
},
|
||||
"messageAction": {
|
||||
"collapse": "Скрий съобщението",
|
||||
"continueGeneration": "Продължи генерирането",
|
||||
"delAndRegenerate": "Изтрий и прегенерирай",
|
||||
"deleteDisabledByThreads": "Съществуват подтеми, не можете да изтриете.",
|
||||
"expand": "Разгъни съобщението",
|
||||
"regenerate": "Прегенерирай"
|
||||
},
|
||||
"messages": {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"availableAgents": "Verfügbare Assistenten",
|
||||
"backToBottom": "Zurück zum Ende",
|
||||
"chatList": {
|
||||
"expandMessage": "Nachricht anzeigen",
|
||||
"longMessageDetail": "Details anzeigen"
|
||||
},
|
||||
"clearCurrentMessages": "Aktuelle Nachrichten löschen",
|
||||
@@ -173,9 +174,11 @@
|
||||
"title": "Mitglieder erwähnen"
|
||||
},
|
||||
"messageAction": {
|
||||
"collapse": "Nachricht ausblenden",
|
||||
"continueGeneration": "Generierung fortsetzen",
|
||||
"delAndRegenerate": "Löschen und neu generieren",
|
||||
"deleteDisabledByThreads": "Es gibt Unterthemen, die Löschung ist nicht möglich.",
|
||||
"expand": "Nachricht anzeigen",
|
||||
"regenerate": "Neu generieren"
|
||||
},
|
||||
"messages": {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"availableAgents": "Available assistants",
|
||||
"backToBottom": "Back to bottom",
|
||||
"chatList": {
|
||||
"expandMessage": "Expand Message",
|
||||
"longMessageDetail": "View Details"
|
||||
},
|
||||
"clearCurrentMessages": "Clear current session messages",
|
||||
@@ -173,9 +174,11 @@
|
||||
"title": "Mention Members"
|
||||
},
|
||||
"messageAction": {
|
||||
"collapse": "Collapse Message",
|
||||
"continueGeneration": "Continue Generating",
|
||||
"delAndRegenerate": "Delete and Regenerate",
|
||||
"deleteDisabledByThreads": "There are subtopics, deletion is not allowed",
|
||||
"expand": "Expand Message",
|
||||
"regenerate": "Regenerate"
|
||||
},
|
||||
"messages": {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"availableAgents": "Agentes disponibles",
|
||||
"backToBottom": "Volver al fondo",
|
||||
"chatList": {
|
||||
"expandMessage": "Expandir mensaje",
|
||||
"longMessageDetail": "Ver detalles"
|
||||
},
|
||||
"clearCurrentMessages": "Borrar mensajes actuales",
|
||||
@@ -173,9 +174,11 @@
|
||||
"title": "Mencionar miembros"
|
||||
},
|
||||
"messageAction": {
|
||||
"collapse": "Ocultar mensaje",
|
||||
"continueGeneration": "Continuar generando",
|
||||
"delAndRegenerate": "Eliminar y Regenerar",
|
||||
"deleteDisabledByThreads": "Existen subtemas, no se puede eliminar",
|
||||
"expand": "Expandir mensaje",
|
||||
"regenerate": "Regenerar"
|
||||
},
|
||||
"messages": {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"availableAgents": "دستیاران در دسترس",
|
||||
"backToBottom": "بازگشت به پایین",
|
||||
"chatList": {
|
||||
"expandMessage": "گسترش پیام",
|
||||
"longMessageDetail": "مشاهده جزئیات"
|
||||
},
|
||||
"clearCurrentMessages": "پاک کردن پیامهای جلسه فعلی",
|
||||
@@ -173,9 +174,11 @@
|
||||
"title": "ذکر اعضا"
|
||||
},
|
||||
"messageAction": {
|
||||
"collapse": "بستن پیام",
|
||||
"continueGeneration": "ادامه تولید",
|
||||
"delAndRegenerate": "حذف و بازتولید",
|
||||
"deleteDisabledByThreads": "زیرموضوع وجود دارد، نمیتوان حذف کرد",
|
||||
"expand": "گسترش پیام",
|
||||
"regenerate": "بازتولید"
|
||||
},
|
||||
"messages": {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"availableAgents": "Assistants disponibles",
|
||||
"backToBottom": "Retour en bas",
|
||||
"chatList": {
|
||||
"expandMessage": "Développer le message",
|
||||
"longMessageDetail": "Voir les détails"
|
||||
},
|
||||
"clearCurrentMessages": "Effacer les messages actuels",
|
||||
@@ -173,9 +174,11 @@
|
||||
"title": "Mentionner un membre"
|
||||
},
|
||||
"messageAction": {
|
||||
"collapse": "Réduire le message",
|
||||
"continueGeneration": "Continuer la génération",
|
||||
"delAndRegenerate": "Supprimer et régénérer",
|
||||
"deleteDisabledByThreads": "Il existe des sous-sujets, la suppression n'est pas possible.",
|
||||
"expand": "Développer le message",
|
||||
"regenerate": "Régénérer"
|
||||
},
|
||||
"messages": {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"availableAgents": "Assistenti disponibili",
|
||||
"backToBottom": "Torna in fondo",
|
||||
"chatList": {
|
||||
"expandMessage": "Espandi messaggio",
|
||||
"longMessageDetail": "Visualizza dettagli"
|
||||
},
|
||||
"clearCurrentMessages": "Cancella messaggi attuali",
|
||||
@@ -173,9 +174,11 @@
|
||||
"title": "Menziona membri"
|
||||
},
|
||||
"messageAction": {
|
||||
"collapse": "Comprimi messaggio",
|
||||
"continueGeneration": "Continua a generare",
|
||||
"delAndRegenerate": "Elimina e rigenera",
|
||||
"deleteDisabledByThreads": "Esistono sottoargomenti, non è possibile eliminare",
|
||||
"expand": "Espandi messaggio",
|
||||
"regenerate": "Rigenera"
|
||||
},
|
||||
"messages": {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"availableAgents": "利用可能なアシスタント",
|
||||
"backToBottom": "現在に戻る",
|
||||
"chatList": {
|
||||
"expandMessage": "メッセージを展開",
|
||||
"longMessageDetail": "詳細を見る"
|
||||
},
|
||||
"clearCurrentMessages": "現在の会話をクリア",
|
||||
@@ -173,9 +174,11 @@
|
||||
"title": "メンバーをメンション"
|
||||
},
|
||||
"messageAction": {
|
||||
"collapse": "メッセージを折りたたむ",
|
||||
"continueGeneration": "生成を続ける",
|
||||
"delAndRegenerate": "削除して再生成",
|
||||
"deleteDisabledByThreads": "サブトピックが存在するため、削除できません。",
|
||||
"expand": "メッセージを展開",
|
||||
"regenerate": "再生成"
|
||||
},
|
||||
"messages": {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"availableAgents": "사용 가능한 도우미",
|
||||
"backToBottom": "하단으로 이동",
|
||||
"chatList": {
|
||||
"expandMessage": "메시지 펼치기",
|
||||
"longMessageDetail": "자세히 보기"
|
||||
},
|
||||
"clearCurrentMessages": "현재 대화 지우기",
|
||||
@@ -173,9 +174,11 @@
|
||||
"title": "멤버 언급"
|
||||
},
|
||||
"messageAction": {
|
||||
"collapse": "메시지 접기",
|
||||
"continueGeneration": "계속 생성하기",
|
||||
"delAndRegenerate": "삭제하고 다시 생성",
|
||||
"deleteDisabledByThreads": "하위 주제가 존재하여 삭제할 수 없습니다.",
|
||||
"expand": "메시지 펼치기",
|
||||
"regenerate": "다시 생성"
|
||||
},
|
||||
"messages": {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"availableAgents": "Beschikbare assistenten",
|
||||
"backToBottom": "Terug naar onderen",
|
||||
"chatList": {
|
||||
"expandMessage": "Bericht uitvouwen",
|
||||
"longMessageDetail": "Bekijk details"
|
||||
},
|
||||
"clearCurrentMessages": "Huidige berichten wissen",
|
||||
@@ -173,9 +174,11 @@
|
||||
"title": "Leden vermelden"
|
||||
},
|
||||
"messageAction": {
|
||||
"collapse": "Bericht samenvouwen",
|
||||
"continueGeneration": "Doorgaan met genereren",
|
||||
"delAndRegenerate": "Verwijderen en opnieuw genereren",
|
||||
"deleteDisabledByThreads": "Er zijn subonderwerpen, verwijderen is niet mogelijk.",
|
||||
"expand": "Bericht uitvouwen",
|
||||
"regenerate": "Opnieuw genereren"
|
||||
},
|
||||
"messages": {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"availableAgents": "Dostępni asystenci",
|
||||
"backToBottom": "Przewiń na dół",
|
||||
"chatList": {
|
||||
"expandMessage": "Rozwiń wiadomość",
|
||||
"longMessageDetail": "Zobacz szczegóły"
|
||||
},
|
||||
"clearCurrentMessages": "Wyczyść bieżącą rozmowę",
|
||||
@@ -173,9 +174,11 @@
|
||||
"title": "Wzmianka o członkach"
|
||||
},
|
||||
"messageAction": {
|
||||
"collapse": "Zwiń wiadomość",
|
||||
"continueGeneration": "Kontynuuj generowanie",
|
||||
"delAndRegenerate": "Usuń i wygeneruj ponownie",
|
||||
"deleteDisabledByThreads": "Istnieją podwątki, nie można usunąć",
|
||||
"expand": "Rozwiń wiadomość",
|
||||
"regenerate": "Wygeneruj ponownie"
|
||||
},
|
||||
"messages": {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"availableAgents": "Assistentes disponíveis",
|
||||
"backToBottom": "Voltar para o início",
|
||||
"chatList": {
|
||||
"expandMessage": "Expandir mensagem",
|
||||
"longMessageDetail": "Ver detalhes"
|
||||
},
|
||||
"clearCurrentMessages": "Limpar mensagens atuais",
|
||||
@@ -173,9 +174,11 @@
|
||||
"title": "Mencionar membros"
|
||||
},
|
||||
"messageAction": {
|
||||
"collapse": "Recolher mensagem",
|
||||
"continueGeneration": "Continuar gerando",
|
||||
"delAndRegenerate": "Excluir e Regenerar",
|
||||
"deleteDisabledByThreads": "Existem subtópicos, não é possível deletar.",
|
||||
"expand": "Expandir mensagem",
|
||||
"regenerate": "Regenerar"
|
||||
},
|
||||
"messages": {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"availableAgents": "Доступные помощники",
|
||||
"backToBottom": "Вернуться вниз",
|
||||
"chatList": {
|
||||
"expandMessage": "Развернуть сообщение",
|
||||
"longMessageDetail": "Посмотреть детали"
|
||||
},
|
||||
"clearCurrentMessages": "Очистить текущий разговор",
|
||||
@@ -173,9 +174,11 @@
|
||||
"title": "Упомянуть участника"
|
||||
},
|
||||
"messageAction": {
|
||||
"collapse": "Свернуть сообщение",
|
||||
"continueGeneration": "Продолжить генерацию",
|
||||
"delAndRegenerate": "Удалить и пересоздать",
|
||||
"deleteDisabledByThreads": "Существуют подтемы, удаление невозможно",
|
||||
"expand": "Развернуть сообщение",
|
||||
"regenerate": "Пересоздать"
|
||||
},
|
||||
"messages": {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"availableAgents": "Kullanılabilir asistanlar",
|
||||
"backToBottom": "En alta git",
|
||||
"chatList": {
|
||||
"expandMessage": "Mesajı Genişlet",
|
||||
"longMessageDetail": "Detayları görüntüle"
|
||||
},
|
||||
"clearCurrentMessages": "Mevcut oturum mesajlarını temizle",
|
||||
@@ -173,9 +174,11 @@
|
||||
"title": "Üyeleri Etiketle"
|
||||
},
|
||||
"messageAction": {
|
||||
"collapse": "Mesajı Daralt",
|
||||
"continueGeneration": "Oluşturmaya devam et",
|
||||
"delAndRegenerate": "Sil ve Yeniden Oluştur",
|
||||
"deleteDisabledByThreads": "Alt konular mevcut, silinemez",
|
||||
"expand": "Mesajı Genişlet",
|
||||
"regenerate": "Yeniden Oluştur"
|
||||
},
|
||||
"messages": {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"availableAgents": "Trợ lý có sẵn",
|
||||
"backToBottom": "Quay về dưới cùng",
|
||||
"chatList": {
|
||||
"expandMessage": "Mở rộng tin nhắn",
|
||||
"longMessageDetail": "Xem chi tiết"
|
||||
},
|
||||
"clearCurrentMessages": "Xóa tin nhắn hiện tại",
|
||||
@@ -173,9 +174,11 @@
|
||||
"title": "Nhắc đến thành viên"
|
||||
},
|
||||
"messageAction": {
|
||||
"collapse": "Thu gọn tin nhắn",
|
||||
"continueGeneration": "Tiếp tục tạo",
|
||||
"delAndRegenerate": "Xóa và tạo lại",
|
||||
"deleteDisabledByThreads": "Có chủ đề con, không thể xóa",
|
||||
"expand": "Mở rộng tin nhắn",
|
||||
"regenerate": "Tạo lại"
|
||||
},
|
||||
"messages": {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"availableAgents": "可用助手",
|
||||
"backToBottom": "跳转至当前",
|
||||
"chatList": {
|
||||
"expandMessage": "展开消息",
|
||||
"longMessageDetail": "查看详情"
|
||||
},
|
||||
"clearCurrentMessages": "清空当前会话消息",
|
||||
@@ -173,9 +174,11 @@
|
||||
"title": "提及成员"
|
||||
},
|
||||
"messageAction": {
|
||||
"collapse": "收起消息",
|
||||
"continueGeneration": "继续生成",
|
||||
"delAndRegenerate": "删除并重新生成",
|
||||
"deleteDisabledByThreads": "存在子话题,不能删除",
|
||||
"expand": "展开消息",
|
||||
"regenerate": "重新生成"
|
||||
},
|
||||
"messages": {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"availableAgents": "可用助理",
|
||||
"backToBottom": "返回底部",
|
||||
"chatList": {
|
||||
"expandMessage": "展開訊息",
|
||||
"longMessageDetail": "查看詳情"
|
||||
},
|
||||
"clearCurrentMessages": "清空當前對話",
|
||||
@@ -173,9 +174,11 @@
|
||||
"title": "提及成員"
|
||||
},
|
||||
"messageAction": {
|
||||
"collapse": "收起訊息",
|
||||
"continueGeneration": "繼續生成",
|
||||
"delAndRegenerate": "刪除並重新生成",
|
||||
"deleteDisabledByThreads": "存在子話題,無法刪除",
|
||||
"expand": "展開訊息",
|
||||
"regenerate": "重新生成"
|
||||
},
|
||||
"messages": {
|
||||
|
||||
+5
-5
@@ -249,11 +249,11 @@ const nextConfig: NextConfig = {
|
||||
// permanent: true,
|
||||
// source: '/settings',
|
||||
// },
|
||||
{
|
||||
destination: '/chat',
|
||||
permanent: false,
|
||||
source: '/',
|
||||
},
|
||||
// {
|
||||
// destination: '/chat',
|
||||
// permanent: false,
|
||||
// source: '/',
|
||||
// },
|
||||
{
|
||||
destination: '/chat',
|
||||
permanent: true,
|
||||
|
||||
+23
-28
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@lobehub/lobehub",
|
||||
"version": "2.0.0-next.65",
|
||||
"version": "2.0.0-next.69",
|
||||
"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",
|
||||
@@ -122,30 +122,27 @@
|
||||
"eslint --fix"
|
||||
]
|
||||
},
|
||||
"overrides": {
|
||||
"eta": "4.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^5.6.1",
|
||||
"@ant-design/pro-components": "^2.8.10",
|
||||
"@anthropic-ai/sdk": "^0.67.1",
|
||||
"@auth/core": "^0.40.0",
|
||||
"@aws-sdk/client-s3": "~3.893.0",
|
||||
"@aws-sdk/s3-request-presigner": "~3.893.0",
|
||||
"@aws-sdk/client-s3": "~3.932.0",
|
||||
"@aws-sdk/s3-request-presigner": "~3.932.0",
|
||||
"@azure-rest/ai-inference": "1.0.0-beta.5",
|
||||
"@azure/core-auth": "^1.10.1",
|
||||
"@cfworker/json-schema": "^4.1.1",
|
||||
"@clerk/localizations": "^3.27.2",
|
||||
"@clerk/nextjs": "^6.35.0",
|
||||
"@clerk/themes": "^2.4.35",
|
||||
"@clerk/localizations": "^3.28.0",
|
||||
"@clerk/nextjs": "^6.35.1",
|
||||
"@clerk/themes": "^2.4.36",
|
||||
"@codesandbox/sandpack-react": "^2.20.0",
|
||||
"@cyntler/react-doc-viewer": "^1.17.1",
|
||||
"@electric-sql/pglite": "0.2.17",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@fal-ai/client": "^1.7.2",
|
||||
"@formkit/auto-animate": "^0.9.0",
|
||||
"@google/genai": "^1.29.0",
|
||||
"@huggingface/inference": "^4.13.2",
|
||||
"@google/genai": "^1.29.1",
|
||||
"@huggingface/inference": "^4.13.3",
|
||||
"@icons-pack/react-simple-icons": "^13.8.0",
|
||||
"@khmyznikov/pwa-install": "0.3.9",
|
||||
"@langchain/community": "^0.3.57",
|
||||
@@ -168,14 +165,14 @@
|
||||
"@lobehub/charts": "^2.1.2",
|
||||
"@lobehub/chat-plugin-sdk": "^1.32.4",
|
||||
"@lobehub/chat-plugins-gateway": "^1.9.0",
|
||||
"@lobehub/editor": "^1.22.0",
|
||||
"@lobehub/editor": "^1.23.1",
|
||||
"@lobehub/icons": "^2.43.1",
|
||||
"@lobehub/market-sdk": "^0.23.0",
|
||||
"@lobehub/tts": "^2.0.1",
|
||||
"@lobehub/ui": "^2.13.8",
|
||||
"@lobehub/ui": "^2.16.2",
|
||||
"@modelcontextprotocol/sdk": "^1.22.0",
|
||||
"@neondatabase/serverless": "^1.0.2",
|
||||
"@next/third-parties": "^16.0.1",
|
||||
"@next/third-parties": "^16.0.3",
|
||||
"@opentelemetry/exporter-jaeger": "^2.2.0",
|
||||
"@opentelemetry/winston-transport": "^0.18.0",
|
||||
"@react-pdf/renderer": "^4.3.1",
|
||||
@@ -183,14 +180,14 @@
|
||||
"@saintno/comfyui-sdk": "^0.2.49",
|
||||
"@serwist/next": "^9.2.1",
|
||||
"@t3-oss/env-nextjs": "^0.13.8",
|
||||
"@tanstack/react-query": "^5.90.7",
|
||||
"@tanstack/react-query": "^5.90.10",
|
||||
"@trpc/client": "^11.7.1",
|
||||
"@trpc/next": "^11.7.1",
|
||||
"@trpc/react-query": "^11.7.1",
|
||||
"@trpc/server": "^11.7.1",
|
||||
"@vercel/analytics": "^1.5.0",
|
||||
"@vercel/edge-config": "^1.4.3",
|
||||
"@vercel/functions": "^3.3.0",
|
||||
"@vercel/functions": "^3.3.2",
|
||||
"@vercel/speed-insights": "^1.2.0",
|
||||
"@virtuoso.dev/masonry": "^1.3.5",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
@@ -225,7 +222,7 @@
|
||||
"langfuse": "^3.38.6",
|
||||
"langfuse-core": "^3.38.6",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lucide-react": "^0.548.0",
|
||||
"lucide-react": "^0.553.0",
|
||||
"mammoth": "^1.11.0",
|
||||
"markdown-to-txt": "^2.0.1",
|
||||
"marked": "^16.4.2",
|
||||
@@ -233,7 +230,7 @@
|
||||
"model-bank": "workspace:*",
|
||||
"modern-screenshot": "^4.6.6",
|
||||
"nanoid": "^5.1.6",
|
||||
"next": "^16.0.1",
|
||||
"next": "^16.0.3",
|
||||
"next-auth": "5.0.0-beta.30",
|
||||
"next-mdx-remote": "^5.0.0",
|
||||
"nextjs-toploader": "^3.9.17",
|
||||
@@ -242,7 +239,7 @@
|
||||
"nuqs": "^2.7.3",
|
||||
"officeparser": "5.1.1",
|
||||
"oidc-provider": "^9.5.2",
|
||||
"ollama": "^0.6.2",
|
||||
"ollama": "^0.6.3",
|
||||
"openai": "^4.104.0",
|
||||
"openapi-fetch": "^0.14.1",
|
||||
"partial-json": "^0.1.7",
|
||||
@@ -265,19 +262,19 @@
|
||||
"react-fast-marquee": "^1.6.5",
|
||||
"react-hotkeys-hook": "^5.2.1",
|
||||
"react-i18next": "^15.7.4",
|
||||
"react-layout-kit": "^2.0.0",
|
||||
"react-layout-kit": "^2.0.1",
|
||||
"react-lazy-load": "^4.0.1",
|
||||
"react-pdf": "^9.2.1",
|
||||
"react-responsive": "^10.0.1",
|
||||
"react-rnd": "^10.5.2",
|
||||
"react-router-dom": "^7.9.5",
|
||||
"react-router-dom": "^7.9.6",
|
||||
"react-scan": "^0.4.3",
|
||||
"react-virtuoso": "^4.14.1",
|
||||
"react-wrap-balancer": "^1.1.1",
|
||||
"remark": "^15.0.1",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"remark-html": "^16.0.1",
|
||||
"resolve-accept-language": "^3.1.13",
|
||||
"resolve-accept-language": "^3.1.14",
|
||||
"rtl-detect": "^1.1.2",
|
||||
"semver": "^7.7.3",
|
||||
"sharp": "^0.34.5",
|
||||
@@ -295,6 +292,7 @@
|
||||
"url-join": "^5.0.0",
|
||||
"use-merge-value": "^1.2.0",
|
||||
"uuid": "^11.1.0",
|
||||
"virtua": "^0.47.0",
|
||||
"word-extractor": "^1.0.4",
|
||||
"ws": "^8.18.3",
|
||||
"yaml": "^2.8.1",
|
||||
@@ -311,7 +309,7 @@
|
||||
"@lobehub/lint": "^1.26.2",
|
||||
"@lobehub/market-types": "^1.11.4",
|
||||
"@lobehub/seo-cli": "^1.7.0",
|
||||
"@next/bundle-analyzer": "^16.0.1",
|
||||
"@next/bundle-analyzer": "^16.0.3",
|
||||
"@next/eslint-plugin-next": "^15.5.6",
|
||||
"@peculiar/webcrypto": "^1.5.0",
|
||||
"@playwright/test": "^1.56.1",
|
||||
@@ -397,9 +395,6 @@
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@vercel/speed-insights"
|
||||
],
|
||||
"overrides": {
|
||||
"eta": "4.0.1"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,6 @@
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"openai": "^4.0.0"
|
||||
"openai": "^4.104.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"main": "./src/index.ts",
|
||||
"dependencies": {
|
||||
"model-bank": "workspace:*",
|
||||
"query-string": "^9.2.2",
|
||||
"query-string": "^9.3.1",
|
||||
"url-join": "^5.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,8 @@ import { BRANDING_NAME, ORG_NAME } from './branding';
|
||||
|
||||
export const CURRENT_VERSION = pkg.version;
|
||||
|
||||
export const isServerMode = true;
|
||||
export const isUsePgliteDB = false;
|
||||
|
||||
export const isDesktop = process.env.NEXT_PUBLIC_IS_DESKTOP_APP === '1';
|
||||
|
||||
export const isDeprecatedEdition = false;
|
||||
|
||||
// @ts-ignore
|
||||
export const isCustomBranding = BRANDING_NAME !== 'LobeHub';
|
||||
// @ts-ignore
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
"@lobechat/prompts": "workspace:*",
|
||||
"@lobechat/types": "workspace:*",
|
||||
"@lobechat/utils": "workspace:*",
|
||||
"debug": "^4.3.4",
|
||||
"immer": "^10.0.3",
|
||||
"debug": "^4.4.3",
|
||||
"immer": "^10.2.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"ts-md5": "^2.0.1"
|
||||
},
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import type { Message, ParseResult } from '../../types';
|
||||
// Input fixtures
|
||||
import assistantChainWithFollowupInput from './inputs/assistant-chain-with-followup.json';
|
||||
import assistantWithToolsInput from './inputs/assistant-with-tools.json';
|
||||
import { assistantGroup as assistantGroupInputs } from './inputs/assistantGroup';
|
||||
import { branch as branchInputs } from './inputs/branch';
|
||||
import { compare as compareInputs } from './inputs/compare';
|
||||
import complexScenarioInput from './inputs/complex-scenario.json';
|
||||
import linearConversationInput from './inputs/linear-conversation.json';
|
||||
// Output fixtures
|
||||
import assistantChainWithFollowupOutput from './outputs/assistant-chain-with-followup.json';
|
||||
import assistantWithToolsOutput from './outputs/assistant-with-tools.json';
|
||||
import { assistantGroup as assistantGroupOutputs } from './outputs/assistantGroup';
|
||||
import { branch as branchOutputs } from './outputs/branch';
|
||||
import { compare as compareOutputs } from './outputs/compare';
|
||||
import complexScenarioOutput from './outputs/complex-scenario.json';
|
||||
import linearConversationOutput from './outputs/linear-conversation.json';
|
||||
|
||||
/**
|
||||
@@ -28,10 +26,9 @@ export interface SerializedParseResult {
|
||||
*/
|
||||
export const inputs = {
|
||||
assistantChainWithFollowup: assistantChainWithFollowupInput as Message[],
|
||||
assistantWithTools: assistantWithToolsInput as Message[],
|
||||
assistantGroup: assistantGroupInputs,
|
||||
branch: branchInputs,
|
||||
compare: compareInputs,
|
||||
complexScenario: complexScenarioInput as Message[],
|
||||
linearConversation: linearConversationInput as Message[],
|
||||
};
|
||||
|
||||
@@ -40,9 +37,8 @@ export const inputs = {
|
||||
*/
|
||||
export const outputs = {
|
||||
assistantChainWithFollowup: assistantChainWithFollowupOutput as unknown as SerializedParseResult,
|
||||
assistantWithTools: assistantWithToolsOutput as unknown as SerializedParseResult,
|
||||
assistantGroup: assistantGroupOutputs,
|
||||
branch: branchOutputs,
|
||||
compare: compareOutputs,
|
||||
complexScenario: complexScenarioOutput as unknown as SerializedParseResult,
|
||||
linearConversation: linearConversationOutput as unknown as SerializedParseResult,
|
||||
};
|
||||
|
||||
+2
-1
@@ -97,7 +97,8 @@
|
||||
"tps": 48,
|
||||
"ttft": 178,
|
||||
"duration": 875,
|
||||
"latency": 1053
|
||||
"latency": 1053,
|
||||
"collapsed": true
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { Message } from '../../../../types';
|
||||
import assistantWithTools from './assistant-with-tools.json';
|
||||
import toolsWithBranches from './tools-with-branches.json';
|
||||
|
||||
export const assistantGroup = {
|
||||
assistantWithTools: assistantWithTools as Message[],
|
||||
toolsWithBranches: toolsWithBranches as Message[],
|
||||
};
|
||||
@@ -1,14 +1,12 @@
|
||||
import type { Message } from '../../../types';
|
||||
import assistantWithTools from './assistant-with-tools.json';
|
||||
import { assistantGroup } from './assistantGroup';
|
||||
import { branch } from './branch';
|
||||
import { compare } from './compare';
|
||||
import complexScenario from './complex-scenario.json';
|
||||
import linearConversation from './linear-conversation.json';
|
||||
|
||||
export const inputs = {
|
||||
assistantWithTools: assistantWithTools as Message[],
|
||||
assistantGroup,
|
||||
branch,
|
||||
compare,
|
||||
complexScenario: complexScenario as Message[],
|
||||
linearConversation: linearConversation as Message[],
|
||||
};
|
||||
|
||||
+8
-8
@@ -9,18 +9,12 @@
|
||||
{
|
||||
"id": "msg-102",
|
||||
"type": "message",
|
||||
"tools": [
|
||||
"msg-103",
|
||||
"msg-104"
|
||||
]
|
||||
"tools": ["msg-103", "msg-104"]
|
||||
},
|
||||
{
|
||||
"id": "msg-105",
|
||||
"type": "message",
|
||||
"tools": [
|
||||
"msg-106",
|
||||
"msg-107"
|
||||
]
|
||||
"tools": ["msg-106", "msg-107"]
|
||||
},
|
||||
{
|
||||
"id": "msg-108",
|
||||
@@ -98,6 +92,9 @@
|
||||
"totalInputTokens": 28,
|
||||
"totalOutputTokens": 45,
|
||||
"totalTokens": 73
|
||||
},
|
||||
"metadata": {
|
||||
"collapsed": true
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -178,6 +175,9 @@
|
||||
"totalOutputTokens": 199,
|
||||
"totalTokens": 628,
|
||||
"cost": 0.0018839999999999998
|
||||
},
|
||||
"metadata": {
|
||||
"collapsed": true
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { SerializedParseResult } from '../..';
|
||||
import assistantWithTools from './assistant-with-tools.json';
|
||||
import toolsWithBranches from './tools-with-branches.json';
|
||||
|
||||
export const assistantGroup = {
|
||||
assistantWithTools: assistantWithTools as unknown as SerializedParseResult,
|
||||
toolsWithBranches: toolsWithBranches as unknown as SerializedParseResult,
|
||||
};
|
||||
@@ -22,9 +22,9 @@ describe('parse', () => {
|
||||
|
||||
describe('Tool Usage', () => {
|
||||
it('should parse assistant with tools correctly', () => {
|
||||
const result = parse(inputs.assistantWithTools);
|
||||
const result = parse(inputs.assistantGroup.assistantWithTools);
|
||||
|
||||
expect(serializeParseResult(result)).toEqual(outputs.assistantWithTools);
|
||||
expect(serializeParseResult(result)).toEqual(outputs.assistantGroup.assistantWithTools);
|
||||
});
|
||||
|
||||
it('should include follow-up messages after assistant chain', () => {
|
||||
@@ -99,11 +99,11 @@ describe('parse', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Complex Scenarios', () => {
|
||||
it('should handle complex mixed scenarios correctly', () => {
|
||||
const result = parse(inputs.complexScenario);
|
||||
describe('Assistant Group Scenarios', () => {
|
||||
it('should handle tools with assistant branches correctly', () => {
|
||||
const result = parse(inputs.assistantGroup.toolsWithBranches);
|
||||
|
||||
expect(serializeParseResult(result)).toEqual(outputs.complexScenario);
|
||||
expect(serializeParseResult(result)).toEqual(outputs.assistantGroup.toolsWithBranches);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -40,9 +40,53 @@ export function parse(messages: Message[], messageGroups?: MessageGroupMetadata[
|
||||
const flatList = transformer.flatten(messages);
|
||||
|
||||
// Convert messageMap from Map to plain object for serialization
|
||||
// Clean up metadata for assistant messages with tools
|
||||
const messageMapObj: Record<string, Message> = {};
|
||||
const usagePerformanceFields = new Set([
|
||||
'acceptedPredictionTokens',
|
||||
'cost',
|
||||
'duration',
|
||||
'inputAudioTokens',
|
||||
'inputCacheMissTokens',
|
||||
'inputCachedTokens',
|
||||
'inputCitationTokens',
|
||||
'inputImageTokens',
|
||||
'inputTextTokens',
|
||||
'inputWriteCacheTokens',
|
||||
'latency',
|
||||
'outputAudioTokens',
|
||||
'outputImageTokens',
|
||||
'outputReasoningTokens',
|
||||
'outputTextTokens',
|
||||
'rejectedPredictionTokens',
|
||||
'totalInputTokens',
|
||||
'totalOutputTokens',
|
||||
'totalTokens',
|
||||
'tps',
|
||||
'ttft',
|
||||
]);
|
||||
|
||||
helperMaps.messageMap.forEach((message, id) => {
|
||||
messageMapObj[id] = message;
|
||||
// For assistant messages with tools, clean metadata to keep only usage/performance fields
|
||||
if (
|
||||
message.role === 'assistant' &&
|
||||
message.tools &&
|
||||
message.tools.length > 0 &&
|
||||
message.metadata
|
||||
) {
|
||||
const cleanedMetadata: Record<string, any> = {};
|
||||
Object.entries(message.metadata).forEach(([key, value]) => {
|
||||
if (usagePerformanceFields.has(key)) {
|
||||
cleanedMetadata[key] = value;
|
||||
}
|
||||
});
|
||||
messageMapObj[id] = {
|
||||
...message,
|
||||
metadata: Object.keys(cleanedMetadata).length > 0 ? cleanedMetadata : undefined,
|
||||
};
|
||||
} else {
|
||||
messageMapObj[id] = message;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -445,6 +445,40 @@ export class FlatListBuilder {
|
||||
const msgUsage = assistant.usage || metaUsage;
|
||||
const msgPerformance = assistant.performance || metaPerformance;
|
||||
|
||||
// Extract non-usage/performance metadata fields
|
||||
const otherMetadata: Record<string, any> = {};
|
||||
if (assistant.metadata) {
|
||||
const usagePerformanceFields = new Set([
|
||||
'acceptedPredictionTokens',
|
||||
'cost',
|
||||
'duration',
|
||||
'inputAudioTokens',
|
||||
'inputCacheMissTokens',
|
||||
'inputCachedTokens',
|
||||
'inputCitationTokens',
|
||||
'inputImageTokens',
|
||||
'inputTextTokens',
|
||||
'inputWriteCacheTokens',
|
||||
'latency',
|
||||
'outputAudioTokens',
|
||||
'outputImageTokens',
|
||||
'outputReasoningTokens',
|
||||
'outputTextTokens',
|
||||
'rejectedPredictionTokens',
|
||||
'totalInputTokens',
|
||||
'totalOutputTokens',
|
||||
'totalTokens',
|
||||
'tps',
|
||||
'ttft',
|
||||
]);
|
||||
|
||||
Object.entries(assistant.metadata).forEach(([key, value]) => {
|
||||
if (!usagePerformanceFields.has(key)) {
|
||||
otherMetadata[key] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const childBlock: AssistantContentBlock = {
|
||||
content: assistant.content || '',
|
||||
id: assistant.id,
|
||||
@@ -457,12 +491,37 @@ export class FlatListBuilder {
|
||||
if (assistant.reasoning) childBlock.reasoning = assistant.reasoning;
|
||||
if (toolsWithResults.length > 0) childBlock.tools = toolsWithResults;
|
||||
if (msgUsage) childBlock.usage = msgUsage;
|
||||
if (Object.keys(otherMetadata).length > 0) {
|
||||
childBlock.metadata = otherMetadata;
|
||||
}
|
||||
|
||||
children.push(childBlock);
|
||||
}
|
||||
|
||||
const aggregated = this.messageTransformer.aggregateMetadata(children);
|
||||
|
||||
// Collect all non-usage/performance metadata from all children
|
||||
const groupMetadata: Record<string, any> = {};
|
||||
children.forEach((child) => {
|
||||
if ((child as any).metadata) {
|
||||
Object.assign(groupMetadata, (child as any).metadata);
|
||||
}
|
||||
});
|
||||
|
||||
// If there's group-level metadata, apply it to first child and remove from others
|
||||
if (Object.keys(groupMetadata).length > 0 && children.length > 0) {
|
||||
// Ensure first child has the group metadata
|
||||
if (!(children[0] as any).metadata) {
|
||||
(children[0] as any).metadata = {};
|
||||
}
|
||||
Object.assign((children[0] as any).metadata, groupMetadata);
|
||||
|
||||
// Remove metadata from subsequent children (keep only in first child)
|
||||
for (let i = 1; i < children.length; i++) {
|
||||
delete (children[i] as any).metadata;
|
||||
}
|
||||
}
|
||||
|
||||
const result: Message = {
|
||||
...firstAssistant,
|
||||
children,
|
||||
@@ -480,6 +539,11 @@ export class FlatListBuilder {
|
||||
if (aggregated.performance) result.performance = aggregated.performance;
|
||||
if (aggregated.usage) result.usage = aggregated.usage;
|
||||
|
||||
// Add group-level metadata if it exists
|
||||
if (Object.keys(groupMetadata).length > 0) {
|
||||
result.metadata = groupMetadata;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@electric-sql/pglite": "^0.2.17",
|
||||
"dayjs": ">=1.11.18",
|
||||
"dayjs": ">=1.11.19",
|
||||
"drizzle-orm": ">=0.44.7",
|
||||
"nanoid": ">=5.1.5",
|
||||
"nanoid": ">=5.1.6",
|
||||
"pg": ">=8.16.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,6 +287,44 @@ describe('ChunkModel', () => {
|
||||
expect(result[1].index).toBe(1);
|
||||
expect(result[2].index).toBe(2);
|
||||
});
|
||||
|
||||
it('should handle chunks with null metadata and return undefined pageNumber', async () => {
|
||||
const fileId = '1';
|
||||
const [chunk] = await serverDB
|
||||
.insert(chunks)
|
||||
.values([{ text: 'Chunk with null metadata', userId, index: 0, metadata: null }])
|
||||
.returning();
|
||||
|
||||
await serverDB.insert(fileChunks).values([{ fileId, chunkId: chunk.id, userId }]);
|
||||
|
||||
const result = await chunkModel.findByFileId(fileId, 0);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].metadata).toBeNull();
|
||||
expect(result[0].pageNumber).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle chunks with metadata containing pageNumber', async () => {
|
||||
const fileId = '1';
|
||||
const [chunk] = await serverDB
|
||||
.insert(chunks)
|
||||
.values([
|
||||
{
|
||||
text: 'Chunk with pageNumber',
|
||||
userId,
|
||||
index: 0,
|
||||
metadata: { pageNumber: 5 } as any,
|
||||
},
|
||||
])
|
||||
.returning();
|
||||
|
||||
await serverDB.insert(fileChunks).values([{ fileId, chunkId: chunk.id, userId }]);
|
||||
|
||||
const result = await chunkModel.findByFileId(fileId, 0);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].pageNumber).toBe(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getChunksTextByFileId', () => {
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"peerDependencies": {
|
||||
"react": "^19"
|
||||
"react": "^19.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
"test:coverage": "vitest --coverage --silent='passed-only'"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4"
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/debug": "^4.1.7"
|
||||
"@types/debug": "^4.1.12"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,6 @@
|
||||
"@lobechat/model-runtime": "workspace:*",
|
||||
"@lobechat/types": "workspace:*",
|
||||
"@lobechat/utils": "workspace:*",
|
||||
"i18next": "^24.2.1"
|
||||
"i18next": "^24.2.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
"@napi-rs/canvas": "^0.1.70",
|
||||
"@xmldom/xmldom": "^0.9.8",
|
||||
"concat-stream": "^2.0.0",
|
||||
"debug": "^4.3.4",
|
||||
"mammoth": "^1.8.0",
|
||||
"debug": "^4.4.3",
|
||||
"mammoth": "^1.11.0",
|
||||
"officeparser": "5.1.1",
|
||||
"pdfjs-dist": "4.10.38",
|
||||
"word-extractor": "^1.0.4",
|
||||
@@ -39,7 +39,7 @@
|
||||
"devDependencies": {
|
||||
"@types/concat-stream": "^2.0.3",
|
||||
"@types/yauzl": "^2.10.3",
|
||||
"typescript": "^5"
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5"
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
"ora": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsx": "^4.19.2"
|
||||
"tsx": "^4.20.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3"
|
||||
"zod": "^3.25.76"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,12 @@
|
||||
"test:update": "vitest -u"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-bedrock-runtime": "^3.862.0",
|
||||
"@huggingface/inference": "^4.11.3",
|
||||
"@aws-sdk/client-bedrock-runtime": "^3.932.0",
|
||||
"@huggingface/inference": "^4.13.3",
|
||||
"@lobechat/const": "workspace:*",
|
||||
"@lobechat/types": "workspace:*",
|
||||
"@lobechat/utils": "workspace:*",
|
||||
"debug": "^4.4.1",
|
||||
"debug": "^4.4.3",
|
||||
"model-bank": "workspace:*",
|
||||
"openai": "^4.104.0"
|
||||
}
|
||||
|
||||
@@ -235,6 +235,73 @@ describe('OllamaStream', () => {
|
||||
expect(onToolCall).toHaveBeenCalledTimes(1);
|
||||
expect(onCompletionMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('tools use with a done', async () => {
|
||||
vi.spyOn(uuidModule, 'nanoid').mockReturnValueOnce('1').mockReturnValueOnce('abcd1234');
|
||||
|
||||
const mockOllamaStream = new ReadableStream<ChatResponse>({
|
||||
start(controller) {
|
||||
controller.enqueue({
|
||||
model: 'qwen2.5',
|
||||
created_at: new Date('2024-12-01T03:34:55.166692Z'),
|
||||
message: {
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
tool_calls: [
|
||||
{
|
||||
function: {
|
||||
name: 'realtime-weather____fetchCurrentWeather',
|
||||
arguments: { city: '杭州' },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
done_reason: 'stop',
|
||||
done: true,
|
||||
total_duration: 1122415333,
|
||||
load_duration: 26178333,
|
||||
prompt_eval_count: 221,
|
||||
prompt_eval_duration: 507000000,
|
||||
eval_count: 26,
|
||||
eval_duration: 583000000,
|
||||
} as unknown as ChatResponse);
|
||||
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
const onStartMock = vi.fn();
|
||||
const onTextMock = vi.fn();
|
||||
const onToolCall = vi.fn();
|
||||
const onCompletionMock = vi.fn();
|
||||
|
||||
const protocolStream = OllamaStream(mockOllamaStream, {
|
||||
onStart: onStartMock,
|
||||
onText: onTextMock,
|
||||
onCompletion: onCompletionMock,
|
||||
onToolsCalling: onToolCall,
|
||||
});
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
const chunks = [];
|
||||
|
||||
// @ts-ignore
|
||||
for await (const chunk of protocolStream) {
|
||||
chunks.push(decoder.decode(chunk, { stream: true }));
|
||||
}
|
||||
|
||||
expect(chunks).toEqual(
|
||||
[
|
||||
'id: chat_1',
|
||||
'event: tool_calls',
|
||||
`data: [{"function":{"arguments":"{\\"city\\":\\"杭州\\"}","name":"realtime-weather____fetchCurrentWeather"},"id":"realtime-weather____fetchCurrentWeather_0_abcd1234","index":0,"type":"function"}]\n`,
|
||||
].map((i) => `${i}\n`),
|
||||
);
|
||||
|
||||
expect(onTextMock).toHaveBeenCalledTimes(0);
|
||||
expect(onStartMock).toHaveBeenCalledTimes(1);
|
||||
expect(onToolCall).toHaveBeenCalledTimes(1);
|
||||
expect(onCompletionMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle empty stream', async () => {
|
||||
|
||||
@@ -11,11 +11,6 @@ import {
|
||||
} from './protocol';
|
||||
|
||||
const transformOllamaStream = (chunk: ChatResponse, stack: StreamContext): StreamProtocolChunk => {
|
||||
// maybe need another structure to add support for multiple choices
|
||||
if (chunk.done && !chunk.message.content) {
|
||||
return { data: 'finished', id: stack.id, type: 'stop' };
|
||||
}
|
||||
|
||||
if (chunk.message.thinking) {
|
||||
return { data: chunk.message.thinking, id: stack.id, type: 'reasoning' };
|
||||
}
|
||||
@@ -36,6 +31,11 @@ const transformOllamaStream = (chunk: ChatResponse, stack: StreamContext): Strea
|
||||
};
|
||||
}
|
||||
|
||||
// maybe need another structure to add support for multiple choices
|
||||
if (chunk.done && !chunk.message.content) {
|
||||
return { data: 'finished', id: stack.id, type: 'stop' };
|
||||
}
|
||||
|
||||
// 判断是否有 <think> 或 </think> 标签,更新 thinkingInContent 状态
|
||||
if (chunk.message.content.includes('<think>')) {
|
||||
stack.thinkingInContent = true;
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
"@opentelemetry/instrumentation": "^0.207.0",
|
||||
"@opentelemetry/instrumentation-http": "^0.207.0",
|
||||
"@opentelemetry/instrumentation-pg": "^0.60.0",
|
||||
"@opentelemetry/resources": "^2.0.1",
|
||||
"@opentelemetry/sdk-metrics": "^2.0.1",
|
||||
"@opentelemetry/resources": "^2.2.0",
|
||||
"@opentelemetry/sdk-metrics": "^2.2.0",
|
||||
"@opentelemetry/sdk-node": "^0.207.0",
|
||||
"@opentelemetry/sdk-trace-node": "^2.0.1",
|
||||
"@opentelemetry/semantic-conventions": "^1.36.0",
|
||||
"@vercel/otel": "^1.13.0"
|
||||
"@opentelemetry/sdk-trace-node": "^2.2.0",
|
||||
"@opentelemetry/semantic-conventions": "^1.38.0",
|
||||
"@vercel/otel": "^2.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"promptfoo": "^0.118.17",
|
||||
"tsx": "^4.20.4"
|
||||
"tsx": "^4.20.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"node-fetch": "^3.3.2",
|
||||
"request-filtering-agent": "^3"
|
||||
"request-filtering-agent": "^3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitest": "^3.2.4"
|
||||
|
||||
@@ -75,7 +75,9 @@ export const ModelPerformanceSchema = z.object({
|
||||
latency: z.number().optional(),
|
||||
});
|
||||
|
||||
export const MessageMetadataSchema = ModelUsageSchema.merge(ModelPerformanceSchema);
|
||||
export const MessageMetadataSchema = ModelUsageSchema.merge(ModelPerformanceSchema).extend({
|
||||
collapsed: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export interface ModelUsage extends ModelTokensUsage {
|
||||
/**
|
||||
@@ -106,5 +108,10 @@ export interface ModelPerformance {
|
||||
export interface MessageMetadata extends ModelUsage, ModelPerformance {
|
||||
activeBranchIndex?: number;
|
||||
activeColumn?: boolean;
|
||||
/**
|
||||
* 消息折叠状态
|
||||
* true: 折叠, false/undefined: 展开
|
||||
*/
|
||||
collapsed?: boolean;
|
||||
compare?: boolean;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ export interface AssistantContentBlock {
|
||||
error?: ChatMessageError | null;
|
||||
id: string;
|
||||
imageList?: ChatImageItem[];
|
||||
metadata?: Record<string, any>;
|
||||
performance?: ModelPerformance;
|
||||
reasoning?: ModelReasoning;
|
||||
tools?: ChatToolPayloadWithResult[];
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
"dependencies": {
|
||||
"@lobechat/const": "workspace:*",
|
||||
"@lobechat/types": "workspace:*",
|
||||
"dayjs": "^1.11.18",
|
||||
"dompurify": "^3.2.7"
|
||||
"dayjs": "^1.11.19",
|
||||
"dompurify": "^3.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitest-canvas-mock": "^0.3.3"
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@mozilla/readability": "^0.6.0",
|
||||
"happy-dom": "^20.0.0",
|
||||
"happy-dom": "^20.0.10",
|
||||
"node-html-markdown": "^1.3.0",
|
||||
"ssrf-safe-fetch": "workspace:*",
|
||||
"query-string": "^9.1.1",
|
||||
"query-string": "^9.3.1",
|
||||
"url-join": "^5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ const partialBuildPages = [
|
||||
{
|
||||
name: 'changelog',
|
||||
disabled: isDesktop,
|
||||
paths: ['src/app/[variants]/@modal/(.)changelog', 'src/app/[variants]/(main)/changelog'],
|
||||
paths: ['src/app/[variants]/(main)/changelog'],
|
||||
},
|
||||
{
|
||||
name: 'auth',
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
import { isServerMode } from '@/const/version';
|
||||
import { serverDB } from '@/database/server';
|
||||
import { authEnv } from '@/envs/auth';
|
||||
import { pino } from '@/libs/logger';
|
||||
@@ -8,7 +7,7 @@ import { UserService } from '@/server/services/user';
|
||||
|
||||
import { validateRequest } from './validateRequest';
|
||||
|
||||
if (authEnv.NEXT_PUBLIC_ENABLE_CLERK_AUTH && isServerMode && !authEnv.CLERK_WEBHOOK_SECRET) {
|
||||
if (authEnv.NEXT_PUBLIC_ENABLE_CLERK_AUTH && !authEnv.CLERK_WEBHOOK_SECRET) {
|
||||
throw new Error('`CLERK_WEBHOOK_SECRET` environment variable is missing');
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ type RouteContext = {
|
||||
}>;
|
||||
};
|
||||
|
||||
const MARKET_BASE_URL = process.env.NEXT_PUBLIC_MARKET_BASE_URL || 'http://127.0.0.1:8787';
|
||||
const MARKET_BASE_URL = process.env.NEXT_PUBLIC_MARKET_BASE_URL || 'https://market.lobehub.com';
|
||||
|
||||
const extractAccessToken = (req: NextRequest) => {
|
||||
const authorization = req.headers.get('authorization');
|
||||
|
||||
@@ -7,7 +7,7 @@ type RouteContext = {
|
||||
}>;
|
||||
};
|
||||
|
||||
const MARKET_BASE_URL = process.env.NEXT_PUBLIC_MARKET_BASE_URL || 'http://127.0.0.1:8787';
|
||||
const MARKET_BASE_URL = process.env.NEXT_PUBLIC_MARKET_BASE_URL || 'https://market.lobehub.com';
|
||||
const ALLOWED_ENDPOINTS = new Set(['handoff', 'token', 'userinfo']);
|
||||
|
||||
const ensureEndpoint = (segments?: string[]) => {
|
||||
|
||||
@@ -17,12 +17,12 @@ const handler = (req: NextRequest) => {
|
||||
*/
|
||||
createContext: () => createLambdaContext(req),
|
||||
|
||||
endpoint: '/trpc/desktop',
|
||||
endpoint: '/trpc/desktop',
|
||||
|
||||
onError: ({ error, path, type }) => {
|
||||
pino.info(`Error in tRPC handler (desktop) on path: ${path}, type: ${type}`);
|
||||
console.error(error);
|
||||
},
|
||||
onError: ({ error, path, type }) => {
|
||||
pino.info(`Error in tRPC handler (desktop) on path: ${path}, type: ${type}`);
|
||||
console.error(error);
|
||||
},
|
||||
|
||||
req: preparedReq,
|
||||
responseMeta({ ctx }) {
|
||||
@@ -34,4 +34,4 @@ const handler = (req: NextRequest) => {
|
||||
});
|
||||
};
|
||||
|
||||
export { handler as GET, handler as POST };
|
||||
export { handler as GET, handler as POST };
|
||||
@@ -6,10 +6,12 @@ import { useUserStore } from '@/store/user';
|
||||
import UserBanner from '../features/UserBanner';
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock('next/navigation', () => ({
|
||||
useRouter: vi.fn(() => ({
|
||||
push: vi.fn(),
|
||||
})),
|
||||
const mockNavigate = vi.fn();
|
||||
vi.mock('react-router-dom', () => ({
|
||||
Link: ({ to, children }: { to: string; children: React.ReactNode }) => (
|
||||
<a href={to}>{children}</a>
|
||||
),
|
||||
useNavigate: () => mockNavigate,
|
||||
}));
|
||||
|
||||
vi.mock('@/features/User/UserInfo', () => ({
|
||||
@@ -45,6 +47,7 @@ vi.mock('@/const/auth', () => ({
|
||||
|
||||
afterEach(() => {
|
||||
enableAuth = true;
|
||||
mockNavigate.mockReset();
|
||||
});
|
||||
|
||||
describe('UserBanner', () => {
|
||||
|
||||
@@ -11,10 +11,9 @@ const wrapper: React.JSXElementConstructor<{ children: React.ReactNode }> = ({ c
|
||||
);
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock('next/navigation', () => ({
|
||||
useRouter: vi.fn(() => ({
|
||||
push: vi.fn(),
|
||||
})),
|
||||
const mockNavigate = vi.fn();
|
||||
vi.mock('react-router-dom', () => ({
|
||||
useNavigate: () => mockNavigate,
|
||||
}));
|
||||
|
||||
vi.mock('react-i18next', () => ({
|
||||
@@ -23,10 +22,6 @@ vi.mock('react-i18next', () => ({
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock('../../settings/features/useCategory', () => ({
|
||||
useCategory: vi.fn(() => [{ key: 'extraSetting', label: 'Extra Setting' }]),
|
||||
}));
|
||||
|
||||
// 定义一个变量来存储 enableAuth 的值
|
||||
let enableAuth = true;
|
||||
let enableClerk = true;
|
||||
@@ -52,6 +47,7 @@ vi.mock('@/const/version', async (importOriginal) => {
|
||||
afterEach(() => {
|
||||
enableAuth = true;
|
||||
enableClerk = true;
|
||||
mockNavigate.mockReset();
|
||||
});
|
||||
|
||||
describe('useCategory', () => {
|
||||
@@ -68,7 +64,6 @@ describe('useCategory', () => {
|
||||
const items = result.current;
|
||||
expect(items.some((item) => item.key === 'profile')).toBe(true);
|
||||
expect(items.some((item) => item.key === 'setting')).toBe(true);
|
||||
expect(items.some((item) => item.key === 'data')).toBe(true);
|
||||
expect(items.some((item) => item.key === 'docs')).toBe(true);
|
||||
expect(items.some((item) => item.key === 'feedback')).toBe(true);
|
||||
expect(items.some((item) => item.key === 'changelog')).toBe(true);
|
||||
@@ -93,19 +88,4 @@ describe('useCategory', () => {
|
||||
expect(items.some((item) => item.key === 'changelog')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle settings for non-authenticated users', () => {
|
||||
act(() => {
|
||||
useUserStore.setState({ isSignedIn: false });
|
||||
});
|
||||
enableClerk = false;
|
||||
enableAuth = false;
|
||||
|
||||
const { result } = renderHook(() => useCategory(), { wrapper });
|
||||
|
||||
act(() => {
|
||||
const items = result.current;
|
||||
expect(items.some((item) => item.key === 'extraSetting')).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { memo } from 'react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
||||
import { enableAuth, enableNextAuth } from '@/const/auth';
|
||||
@@ -13,7 +12,7 @@ import { useUserStore } from '@/store/user';
|
||||
import { authSelectors } from '@/store/user/selectors';
|
||||
|
||||
const UserBanner = memo(() => {
|
||||
const router = useRouter();
|
||||
const navigate = useNavigate();
|
||||
const isLoginWithAuth = useUserStore(authSelectors.isLoginWithAuth);
|
||||
const [signIn] = useUserStore((s) => [s.openLogin]);
|
||||
|
||||
@@ -21,10 +20,10 @@ const UserBanner = memo(() => {
|
||||
<Flexbox gap={12} paddingBlock={8}>
|
||||
{!enableAuth || (enableAuth && isLoginWithAuth) ? (
|
||||
<>
|
||||
<Link href={'/profile'} style={{ color: 'inherit' }}>
|
||||
<Link style={{ color: 'inherit' }} to="/profile">
|
||||
<UserInfo />
|
||||
</Link>
|
||||
<Link href={'/profile/stats'} style={{ color: 'inherit' }}>
|
||||
<Link style={{ color: 'inherit' }} to="/profile/stats">
|
||||
<DataStatistics paddingInline={12} />
|
||||
</Link>
|
||||
</>
|
||||
@@ -36,7 +35,7 @@ const UserBanner = memo(() => {
|
||||
signIn();
|
||||
return;
|
||||
}
|
||||
router.push('/login');
|
||||
navigate('/login');
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,44 +1,35 @@
|
||||
import { DOCUMENTS, FEEDBACK, LOBE_CHAT_CLOUD, OFFICIAL_URL, UTM_SOURCE } from '@lobechat/const';
|
||||
import {
|
||||
Book,
|
||||
CircleUserRound,
|
||||
Cloudy,
|
||||
Database,
|
||||
Download,
|
||||
Feather,
|
||||
FileClockIcon,
|
||||
Settings2,
|
||||
} from 'lucide-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { CellProps } from '@/components/Cell';
|
||||
import { enableAuth } from '@/const/auth';
|
||||
import { LOBE_CHAT_CLOUD } from '@/const/branding';
|
||||
import { DOCUMENTS, FEEDBACK, OFFICIAL_URL, UTM_SOURCE } from '@/const/url';
|
||||
import { isServerMode } from '@/const/version';
|
||||
import { usePWAInstall } from '@/hooks/usePWAInstall';
|
||||
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
|
||||
import { useUserStore } from '@/store/user';
|
||||
import { authSelectors } from '@/store/user/selectors';
|
||||
|
||||
import { useCategory as useSettingsCategory } from '../../settings/features/useCategory';
|
||||
|
||||
export const useCategory = () => {
|
||||
const router = useRouter();
|
||||
const navigate = useNavigate();
|
||||
const { canInstall, install } = usePWAInstall();
|
||||
const { t } = useTranslation(['common', 'setting', 'auth']);
|
||||
const { showCloudPromotion, hideDocs } = useServerConfigStore(featureFlagsSelectors);
|
||||
const [isLogin, isLoginWithAuth] = useUserStore((s) => [
|
||||
authSelectors.isLogin(s),
|
||||
authSelectors.isLoginWithAuth(s),
|
||||
]);
|
||||
const [isLoginWithAuth] = useUserStore((s) => [authSelectors.isLoginWithAuth(s)]);
|
||||
|
||||
const profile: CellProps[] = [
|
||||
{
|
||||
icon: CircleUserRound,
|
||||
key: 'profile',
|
||||
label: t('userPanel.profile'),
|
||||
onClick: () => router.push('/me/profile'),
|
||||
onClick: () => navigate('/me/profile'),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -47,7 +38,7 @@ export const useCategory = () => {
|
||||
icon: Settings2,
|
||||
key: 'setting',
|
||||
label: t('userPanel.setting'),
|
||||
onClick: () => router.push('/me/settings'),
|
||||
onClick: () => navigate('/me/settings'),
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
@@ -66,29 +57,7 @@ export const useCategory = () => {
|
||||
},
|
||||
];
|
||||
|
||||
const settingsWithoutAuth = [
|
||||
...useSettingsCategory(),
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
];
|
||||
|
||||
/* ↓ cloud slot ↓ */
|
||||
|
||||
/* ↑ cloud slot ↑ */
|
||||
|
||||
const data: CellProps[] = [
|
||||
{
|
||||
icon: Database,
|
||||
key: 'data',
|
||||
label: t('userPanel.data'),
|
||||
onClick: () => router.push('/me/data'),
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
];
|
||||
|
||||
const helps: CellProps[] = [
|
||||
showCloudPromotion && {
|
||||
icon: Cloudy,
|
||||
@@ -112,7 +81,7 @@ export const useCategory = () => {
|
||||
icon: FileClockIcon,
|
||||
key: 'changelog',
|
||||
label: t('changelog'),
|
||||
onClick: () => router.push('/changelog'),
|
||||
onClick: () => navigate('/changelog'),
|
||||
},
|
||||
].filter(Boolean) as CellProps[];
|
||||
|
||||
@@ -120,13 +89,12 @@ export const useCategory = () => {
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
...(!enableAuth || (enableAuth && isLoginWithAuth) ? profile : []),
|
||||
...(enableAuth ? (isLoginWithAuth ? settings : []) : settingsWithoutAuth),
|
||||
...(isLoginWithAuth ? profile : []),
|
||||
...(isLoginWithAuth ? settings : []),
|
||||
/* ↓ cloud slot ↓ */
|
||||
|
||||
/* ↑ cloud slot ↑ */
|
||||
...(canInstall ? pwa : []),
|
||||
...(isLogin && !isServerMode ? data : []),
|
||||
...(!hideDocs ? helps : []),
|
||||
].filter(Boolean) as CellProps[];
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
'use client';
|
||||
|
||||
import { memo } from 'react';
|
||||
import { Center } from 'react-layout-kit';
|
||||
|
||||
import BrandWatermark from '@/components/BrandWatermark';
|
||||
|
||||
import Category from './features/Category';
|
||||
import UserBanner from './features/UserBanner';
|
||||
|
||||
const MeHomePage = memo(() => {
|
||||
return (
|
||||
<>
|
||||
<UserBanner />
|
||||
<Category />
|
||||
<Center padding={16}>
|
||||
<BrandWatermark />
|
||||
</Center>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
MeHomePage.displayName = 'MeHomePage';
|
||||
|
||||
export default MeHomePage;
|
||||
@@ -1,18 +1,15 @@
|
||||
import { PropsWithChildren, Suspense } from 'react';
|
||||
import MobileContentLayout from "@/components/server/MobileNavLayout";
|
||||
import Loading from "@/components/Loading/BrandTextLoading";
|
||||
import { Outlet } from "react-router-dom";
|
||||
import Header from "./features/Header";
|
||||
import { Suspense } from "react";
|
||||
|
||||
import Loading from '@/components/Loading/BrandTextLoading';
|
||||
import MobileContentLayout from '@/components/server/MobileNavLayout';
|
||||
|
||||
import Header from './features/Header';
|
||||
|
||||
const Layout = ({ children }: PropsWithChildren) => {
|
||||
return (
|
||||
<MobileContentLayout header={<Header />} withNav>
|
||||
<Suspense fallback={<Loading />}>{children}</Suspense>
|
||||
const Layout = () => {
|
||||
return <MobileContentLayout header={<Header />} withNav>
|
||||
<Suspense fallback={<Loading />}>
|
||||
<Outlet />
|
||||
</Suspense>
|
||||
</MobileContentLayout>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
Layout.displayName = 'MeLayout';
|
||||
|
||||
export default Layout;
|
||||
export default Layout;
|
||||
@@ -1,38 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { Skeleton } from 'antd';
|
||||
import { memo } from 'react';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
||||
import Divider from '@/components/Cell/Divider';
|
||||
import SkeletonLoading from '@/components/Loading/SkeletonLoading';
|
||||
|
||||
const Loading = memo(() => {
|
||||
return (
|
||||
<>
|
||||
<Flexbox align={'center'} gap={12} horizontal paddingBlock={12} paddingInline={12}>
|
||||
<Skeleton.Avatar active shape={'circle'} size={48} />
|
||||
<Skeleton.Button active block />
|
||||
</Flexbox>
|
||||
<Flexbox gap={4} horizontal paddingBlock={12} paddingInline={16}>
|
||||
<Skeleton.Button active block />
|
||||
<Skeleton.Button active block />
|
||||
<Skeleton.Button active block />
|
||||
</Flexbox>
|
||||
<Divider />
|
||||
<SkeletonLoading
|
||||
active
|
||||
paragraph={{ rows: 6, style: { marginBottom: 0 }, width: '100%' }}
|
||||
title={false}
|
||||
/>
|
||||
<Divider />
|
||||
<SkeletonLoading
|
||||
active
|
||||
paragraph={{ rows: 3, style: { marginBottom: 0 }, width: '100%' }}
|
||||
title={false}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default Loading;
|
||||
@@ -1,40 +0,0 @@
|
||||
import { redirect } from 'next/navigation';
|
||||
import { Center } from 'react-layout-kit';
|
||||
|
||||
import BrandWatermark from '@/components/BrandWatermark';
|
||||
import { metadataModule } from '@/server/metadata';
|
||||
import { translation } from '@/server/translation';
|
||||
import { DynamicLayoutProps } from '@/types/next';
|
||||
import { RouteVariants } from '@/utils/server/routeVariants';
|
||||
|
||||
import Category from './features/Category';
|
||||
import UserBanner from './features/UserBanner';
|
||||
|
||||
export const generateMetadata = async (props: DynamicLayoutProps) => {
|
||||
const locale = await RouteVariants.getLocale(props);
|
||||
const { t } = await translation('common', locale);
|
||||
return metadataModule.generate({
|
||||
title: t('tab.me'),
|
||||
url: '/me',
|
||||
});
|
||||
};
|
||||
|
||||
const Page = async (props: DynamicLayoutProps) => {
|
||||
const isMobile = await RouteVariants.getIsMobile(props);
|
||||
|
||||
if (!isMobile) return redirect('/chat');
|
||||
|
||||
return (
|
||||
<>
|
||||
<UserBanner />
|
||||
<Category />
|
||||
<Center padding={16}>
|
||||
<BrandWatermark />
|
||||
</Center>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Page.displayName = 'Me';
|
||||
|
||||
export default Page;
|
||||
@@ -1,13 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { ChartColumnBigIcon, LogOut, ShieldCheck, UserCircle } from 'lucide-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import Cell, { CellProps } from '@/components/Cell';
|
||||
import { enableAuth } from '@/const/auth';
|
||||
import { isDeprecatedEdition } from '@/const/version';
|
||||
import { ProfileTabs } from '@/store/global/initialState';
|
||||
import { useUserStore } from '@/store/user';
|
||||
import { authSelectors } from '@/store/user/selectors';
|
||||
@@ -18,42 +16,39 @@ const Category = memo(() => {
|
||||
authSelectors.isLoginWithClerk(s),
|
||||
s.logout,
|
||||
]);
|
||||
const router = useRouter();
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation('auth');
|
||||
const items: CellProps[] = [
|
||||
{
|
||||
icon: UserCircle,
|
||||
key: ProfileTabs.Profile,
|
||||
label: t('tab.profile'),
|
||||
onClick: () => router.push('/profile'),
|
||||
onClick: () => navigate('/profile'),
|
||||
},
|
||||
enableAuth &&
|
||||
isLoginWithClerk && {
|
||||
icon: ShieldCheck,
|
||||
key: ProfileTabs.Security,
|
||||
label: t('tab.security'),
|
||||
onClick: () => router.push('/profile/security'),
|
||||
},
|
||||
!isDeprecatedEdition && {
|
||||
isLoginWithClerk && {
|
||||
icon: ShieldCheck,
|
||||
key: ProfileTabs.Security,
|
||||
label: t('tab.security'),
|
||||
onClick: () => navigate('/profile/security'),
|
||||
},
|
||||
{
|
||||
icon: ChartColumnBigIcon,
|
||||
key: ProfileTabs.Stats,
|
||||
label: t('tab.stats'),
|
||||
onClick: () => router.push('/profile/stats'),
|
||||
onClick: () => navigate('/profile/stats'),
|
||||
},
|
||||
enableAuth &&
|
||||
isLogin && {
|
||||
type: 'divider',
|
||||
},
|
||||
enableAuth &&
|
||||
isLogin && {
|
||||
icon: LogOut,
|
||||
key: 'logout',
|
||||
label: t('signout', { ns: 'auth' }),
|
||||
onClick: () => {
|
||||
signOut();
|
||||
router.push('/login');
|
||||
},
|
||||
isLogin && {
|
||||
type: 'divider',
|
||||
},
|
||||
isLogin && {
|
||||
icon: LogOut,
|
||||
key: 'logout',
|
||||
label: t('signout', { ns: 'auth' }),
|
||||
onClick: () => {
|
||||
signOut();
|
||||
navigate('/login');
|
||||
},
|
||||
},
|
||||
].filter(Boolean) as CellProps[];
|
||||
|
||||
return items?.map(({ key, ...item }, index) => <Cell key={key || index} {...item} />);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
'use client';
|
||||
|
||||
import { ChatHeader } from '@lobehub/ui/mobile';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
||||
import { mobileHeaderSticky } from '@/styles/mobileHeader';
|
||||
@@ -11,7 +11,7 @@ import { mobileHeaderSticky } from '@/styles/mobileHeader';
|
||||
const Header = memo(() => {
|
||||
const { t } = useTranslation('common');
|
||||
|
||||
const router = useRouter();
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<ChatHeader
|
||||
center={
|
||||
@@ -23,7 +23,7 @@ const Header = memo(() => {
|
||||
}
|
||||
/>
|
||||
}
|
||||
onBackClick={() => router.push('/me')}
|
||||
onBackClick={() => navigate('/me')}
|
||||
showBackButton
|
||||
style={mobileHeaderSticky}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
'use client';
|
||||
|
||||
import { memo } from 'react';
|
||||
|
||||
|
||||
import Category from './features/Category';
|
||||
|
||||
const MeProfilePage = memo(() => {
|
||||
return (
|
||||
<Category />
|
||||
);
|
||||
});
|
||||
|
||||
MeProfilePage.displayName = 'MeProfilePage';
|
||||
|
||||
export default MeProfilePage;
|
||||
@@ -1,13 +1,11 @@
|
||||
import { PropsWithChildren } from 'react';
|
||||
import MobileContentLayout from "@/components/server/MobileNavLayout";
|
||||
import { Outlet } from "react-router-dom";
|
||||
import Header from "./features/Header";
|
||||
|
||||
import MobileContentLayout from '@/components/server/MobileNavLayout';
|
||||
const Layout = () => {
|
||||
return <MobileContentLayout header={<Header />}>
|
||||
<Outlet />
|
||||
</MobileContentLayout>
|
||||
}
|
||||
|
||||
import Header from './features/Header';
|
||||
|
||||
const Layout = ({ children }: PropsWithChildren) => {
|
||||
return <MobileContentLayout header={<Header />}>{children}</MobileContentLayout>;
|
||||
};
|
||||
|
||||
Layout.displayName = 'MeProfileLayout';
|
||||
|
||||
export default Layout;
|
||||
export default Layout;
|
||||
@@ -1,5 +0,0 @@
|
||||
import SkeletonLoading from '@/components/Loading/SkeletonLoading';
|
||||
|
||||
export default () => {
|
||||
return <SkeletonLoading paragraph={{ rows: 8 }} />;
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
import { metadataModule } from '@/server/metadata';
|
||||
import { translation } from '@/server/translation';
|
||||
import { DynamicLayoutProps } from '@/types/next';
|
||||
import { RouteVariants } from '@/utils/server/routeVariants';
|
||||
|
||||
import Category from './features/Category';
|
||||
|
||||
export const generateMetadata = async (props: DynamicLayoutProps) => {
|
||||
const locale = await RouteVariants.getLocale(props);
|
||||
const { t } = await translation('auth', locale);
|
||||
return metadataModule.generate({
|
||||
description: t('header.desc'),
|
||||
title: t('header.title'),
|
||||
url: '/me/profile',
|
||||
});
|
||||
};
|
||||
|
||||
const Page = async (props: DynamicLayoutProps) => {
|
||||
const isMobile = await RouteVariants.getIsMobile(props);
|
||||
|
||||
if (!isMobile) return redirect('/profile');
|
||||
|
||||
return <Category />;
|
||||
};
|
||||
|
||||
Page.displayName = 'MeProfile';
|
||||
|
||||
export default Page;
|
||||
@@ -1,9 +1,9 @@
|
||||
'use client';
|
||||
|
||||
import { ChatHeader } from '@lobehub/ui/mobile';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
||||
import { mobileHeaderSticky } from '@/styles/mobileHeader';
|
||||
@@ -11,7 +11,7 @@ import { mobileHeaderSticky } from '@/styles/mobileHeader';
|
||||
const Header = memo(() => {
|
||||
const { t } = useTranslation('common');
|
||||
|
||||
const router = useRouter();
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<ChatHeader
|
||||
center={
|
||||
@@ -23,7 +23,7 @@ const Header = memo(() => {
|
||||
}
|
||||
/>
|
||||
}
|
||||
onBackClick={() => router.push('/me')}
|
||||
onBackClick={() => navigate('/me')}
|
||||
showBackButton
|
||||
style={mobileHeaderSticky}
|
||||
/>
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
import { Bot, Brain, Info, Mic2, Settings2, Sparkles } from 'lucide-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { CellProps } from '@/components/Cell';
|
||||
import { isDeprecatedEdition } from '@/const/version';
|
||||
import { SettingsTabs } from '@/store/global/initialState';
|
||||
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
|
||||
|
||||
export const useCategory = () => {
|
||||
const router = useRouter();
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation('setting');
|
||||
const { showLLM } = useServerConfigStore(featureFlagsSelectors);
|
||||
|
||||
const items: CellProps[] = [
|
||||
{
|
||||
@@ -23,18 +19,11 @@ export const useCategory = () => {
|
||||
key: SettingsTabs.SystemAgent,
|
||||
label: t('tab.system-agent'),
|
||||
},
|
||||
showLLM &&
|
||||
(isDeprecatedEdition
|
||||
? {
|
||||
icon: Brain,
|
||||
key: SettingsTabs.LLM,
|
||||
label: t('tab.llm'),
|
||||
}
|
||||
: {
|
||||
icon: Brain,
|
||||
key: SettingsTabs.Provider,
|
||||
label: t('tab.provider'),
|
||||
}),
|
||||
{
|
||||
icon: Brain,
|
||||
key: SettingsTabs.Provider,
|
||||
label: t('tab.provider'),
|
||||
},
|
||||
{ icon: Mic2, key: SettingsTabs.TTS, label: t('tab.tts') },
|
||||
{
|
||||
icon: Bot,
|
||||
@@ -50,6 +39,6 @@ export const useCategory = () => {
|
||||
|
||||
return items.map((item) => ({
|
||||
...item,
|
||||
onClick: () => router.push(`/settings?active=${item.key}`),
|
||||
onClick: () => navigate(`/settings?active=${item.key}`),
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
'use client';
|
||||
|
||||
import { memo } from 'react';
|
||||
|
||||
|
||||
import Category from './features/Category';
|
||||
|
||||
const MeSettingsPage = memo(() => {
|
||||
return (
|
||||
<Category />
|
||||
);
|
||||
});
|
||||
|
||||
MeSettingsPage.displayName = 'MeSettingsPage';
|
||||
|
||||
export default MeSettingsPage;
|
||||
@@ -1,13 +1,15 @@
|
||||
import { PropsWithChildren } from 'react';
|
||||
import MobileContentLayout from "@/components/server/MobileNavLayout";
|
||||
import Loading from "@/components/Loading/BrandTextLoading";
|
||||
import { Outlet } from "react-router-dom";
|
||||
import Header from "./features/Header";
|
||||
import { Suspense } from "react";
|
||||
|
||||
import MobileContentLayout from '@/components/server/MobileNavLayout';
|
||||
const Layout = () => {
|
||||
return <MobileContentLayout header={<Header />} withNav>
|
||||
<Suspense fallback={<Loading />}>
|
||||
<Outlet />
|
||||
</Suspense>
|
||||
</MobileContentLayout>
|
||||
}
|
||||
|
||||
import Header from './features/Header';
|
||||
|
||||
const Layout = ({ children }: PropsWithChildren) => {
|
||||
return <MobileContentLayout header={<Header />}>{children}</MobileContentLayout>;
|
||||
};
|
||||
|
||||
Layout.displayName = 'MeSettingsLayout';
|
||||
|
||||
export default Layout;
|
||||
export default Layout;
|
||||
@@ -1,5 +0,0 @@
|
||||
import SkeletonLoading from '@/components/Loading/SkeletonLoading';
|
||||
|
||||
export default () => {
|
||||
return <SkeletonLoading paragraph={{ rows: 8 }} />;
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
import { metadataModule } from '@/server/metadata';
|
||||
import { translation } from '@/server/translation';
|
||||
import { DynamicLayoutProps } from '@/types/next';
|
||||
import { RouteVariants } from '@/utils/server/routeVariants';
|
||||
|
||||
import Category from './features/Category';
|
||||
|
||||
export const generateMetadata = async (props: DynamicLayoutProps) => {
|
||||
const locale = await RouteVariants.getLocale(props);
|
||||
const { t } = await translation('setting', locale);
|
||||
return metadataModule.generate({
|
||||
description: t('header.desc'),
|
||||
title: t('header.title'),
|
||||
url: '/me/settings',
|
||||
});
|
||||
};
|
||||
|
||||
const Page = async (props: DynamicLayoutProps) => {
|
||||
const isMobile = await RouteVariants.getIsMobile(props);
|
||||
|
||||
if (!isMobile) return redirect('/settings');
|
||||
|
||||
return <Category />;
|
||||
};
|
||||
|
||||
Page.displayName = 'MeSettings';
|
||||
|
||||
export default Page;
|
||||
@@ -1,106 +0,0 @@
|
||||
import { ActionIcon, ActionIconProps, Hotkey } from '@lobehub/ui';
|
||||
import { Compass, FolderClosed, MessageSquare, Palette } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
import { SidebarTabKey } from '@/store/global/initialState';
|
||||
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
|
||||
import { useSessionStore } from '@/store/session';
|
||||
import { useUserStore } from '@/store/user';
|
||||
import { settingsSelectors } from '@/store/user/selectors';
|
||||
import { HotkeyEnum } from '@/types/hotkey';
|
||||
|
||||
const ICON_SIZE: ActionIconProps['size'] = {
|
||||
blockSize: 40,
|
||||
size: 24,
|
||||
strokeWidth: 2,
|
||||
};
|
||||
|
||||
export interface TopActionProps {
|
||||
isPinned?: boolean | null;
|
||||
tab?: SidebarTabKey;
|
||||
}
|
||||
|
||||
// TODO Change icons
|
||||
const TopActions = memo<TopActionProps>(({ tab, isPinned }) => {
|
||||
const { t } = useTranslation('common');
|
||||
const switchBackToChat = useGlobalStore((s) => s.switchBackToChat);
|
||||
const { showMarket, enableKnowledgeBase, showAiImage } =
|
||||
useServerConfigStore(featureFlagsSelectors);
|
||||
const hotkey = useUserStore(settingsSelectors.getHotkeyById(HotkeyEnum.NavigateToChat));
|
||||
|
||||
const isChatActive = tab === SidebarTabKey.Chat && !isPinned;
|
||||
const isFilesActive = tab === SidebarTabKey.Files;
|
||||
const isDiscoverActive = tab === SidebarTabKey.Discover;
|
||||
const isImageActive = tab === SidebarTabKey.Image;
|
||||
|
||||
return (
|
||||
<Flexbox gap={8}>
|
||||
<Link
|
||||
aria-label={t('tab.chat')}
|
||||
href={'/chat'}
|
||||
onClick={(e) => {
|
||||
// If Cmd key is pressed, let the default link behavior happen (open in new tab)
|
||||
if (e.metaKey || e.ctrlKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, prevent default and switch session within the current tab
|
||||
e.preventDefault();
|
||||
switchBackToChat(useSessionStore.getState().activeId);
|
||||
}}
|
||||
>
|
||||
<ActionIcon
|
||||
active={isChatActive}
|
||||
icon={MessageSquare}
|
||||
size={ICON_SIZE}
|
||||
title={
|
||||
<Flexbox align={'center'} gap={8} horizontal justify={'space-between'}>
|
||||
<span>{t('tab.chat')}</span>
|
||||
<Hotkey inverseTheme keys={hotkey} />
|
||||
</Flexbox>
|
||||
}
|
||||
tooltipProps={{ placement: 'right' }}
|
||||
/>
|
||||
</Link>
|
||||
{enableKnowledgeBase && (
|
||||
<Link aria-label={t('tab.knowledgeBase')} href={'/knowledge'}>
|
||||
<ActionIcon
|
||||
active={isFilesActive}
|
||||
icon={FolderClosed}
|
||||
size={ICON_SIZE}
|
||||
title={t('tab.knowledgeBase')}
|
||||
tooltipProps={{ placement: 'right' }}
|
||||
/>
|
||||
</Link>
|
||||
)}
|
||||
{showAiImage && (
|
||||
<Link aria-label={t('tab.aiImage')} href={'/image'}>
|
||||
<ActionIcon
|
||||
active={isImageActive}
|
||||
icon={Palette}
|
||||
size={ICON_SIZE}
|
||||
title={t('tab.aiImage')}
|
||||
tooltipProps={{ placement: 'right' }}
|
||||
/>
|
||||
</Link>
|
||||
)}
|
||||
{showMarket && (
|
||||
<Link aria-label={t('tab.discover')} href={'/discover'}>
|
||||
<ActionIcon
|
||||
active={isDiscoverActive}
|
||||
icon={Compass}
|
||||
size={ICON_SIZE}
|
||||
title={t('tab.discover')}
|
||||
tooltipProps={{ placement: 'right' }}
|
||||
/>
|
||||
</Link>
|
||||
)}
|
||||
</Flexbox>
|
||||
);
|
||||
});
|
||||
|
||||
export default TopActions;
|
||||
@@ -1,15 +1,16 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
|
||||
import { Locales } from '@/locales/resources';
|
||||
|
||||
import Hero from '../../features/Hero';
|
||||
import Container from './Container';
|
||||
|
||||
type Props = { children: ReactNode };
|
||||
|
||||
const Layout = ({ children }: Props) => {
|
||||
const Layout = (props: { locale: Locales }) => {
|
||||
const { locale } = props;
|
||||
return (
|
||||
<Container>
|
||||
<Hero />
|
||||
{children}
|
||||
<Outlet context={{ locale }} />
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
'use client';
|
||||
|
||||
import { ChatHeader } from '@lobehub/ui/mobile';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { mobileHeaderSticky } from '@/styles/mobileHeader';
|
||||
|
||||
const Header = memo(() => {
|
||||
const { t } = useTranslation('changelog');
|
||||
|
||||
const router = useRouter();
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<ChatHeader
|
||||
center={
|
||||
@@ -23,7 +23,7 @@ const Header = memo(() => {
|
||||
}
|
||||
/>
|
||||
}
|
||||
onBackClick={() => router.back()}
|
||||
onBackClick={() => navigate(-1)}
|
||||
showBackButton
|
||||
style={mobileHeaderSticky}
|
||||
/>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
|
||||
import MobileContentLayout from '@/components/server/MobileNavLayout';
|
||||
import { Locales } from '@/locales/resources';
|
||||
|
||||
import Hero from '../../features/Hero';
|
||||
import Header from './Header';
|
||||
|
||||
type Props = { children: ReactNode };
|
||||
|
||||
const Layout = ({ children }: Props) => {
|
||||
const Layout = (props: { locale: Locales }) => {
|
||||
const { locale } = props;
|
||||
return (
|
||||
<MobileContentLayout header={<Header />} padding={16}>
|
||||
<Hero />
|
||||
{children}
|
||||
<Outlet context={{ locale }} />
|
||||
</MobileContentLayout>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Typography } from '@lobehub/ui';
|
||||
import { Image } from '@lobehub/ui/mdx';
|
||||
import { Divider } from 'antd';
|
||||
import Link from 'next/link';
|
||||
import useSWR from 'swr';
|
||||
import urlJoin from 'url-join';
|
||||
|
||||
import { CustomMDX } from '@/components/mdx';
|
||||
import Image from '@/components/mdx/Image';
|
||||
import { OFFICIAL_SITE } from '@/const/url';
|
||||
import { Locales } from '@/locales/resources';
|
||||
import { ChangelogService } from '@/server/services/changelog';
|
||||
@@ -14,14 +15,16 @@ import GridLayout from './GridLayout';
|
||||
import PublishedTime from './PublishedTime';
|
||||
import VersionTag from './VersionTag';
|
||||
|
||||
const Post = async ({
|
||||
const Post = ({
|
||||
id,
|
||||
mobile,
|
||||
versionRange,
|
||||
locale,
|
||||
}: ChangelogIndexItem & { branch?: string; locale: Locales; mobile?: boolean }) => {
|
||||
const changelogService = new ChangelogService();
|
||||
const data = await changelogService.getPostById(id, { locale });
|
||||
const { data } = useSWR([`changelog-post-${id}`, locale], async () => {
|
||||
const changelogService = new ChangelogService();
|
||||
return await changelogService.getPostById(id, { locale });
|
||||
});
|
||||
|
||||
if (!data || !data.title) return null;
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { Fragment } from 'react';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
import { useOutletContext } from 'react-router-dom';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import NotFound from '@/components/404';
|
||||
import { Locales } from '@/locales/resources';
|
||||
import { ChangelogService } from '@/server/services/changelog';
|
||||
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
|
||||
|
||||
import GridLayout from './features/GridLayout';
|
||||
import Pagination from './features/Pagination';
|
||||
import Post from './features/Post';
|
||||
import UpdateChangelogStatus from './features/UpdateChangelogStatus';
|
||||
|
||||
const Page = (props: { isMobile: boolean }) => {
|
||||
const { locale } = useOutletContext<{ locale: Locales }>();
|
||||
const { isMobile } = props;
|
||||
const { hideDocs } = useServerConfigStore(featureFlagsSelectors);
|
||||
|
||||
const { data } = useSWR('changelog-index', async () => {
|
||||
const changelogService = new ChangelogService();
|
||||
return await changelogService.getChangelogIndex();
|
||||
});
|
||||
|
||||
if (hideDocs) return <NotFound />;
|
||||
|
||||
if (!data) return <NotFound />;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flexbox gap={isMobile ? 16 : 48}>
|
||||
{data?.map((item) => (
|
||||
<Fragment key={item.id}>
|
||||
<Post locale={locale} mobile={isMobile} {...item} />
|
||||
</Fragment>
|
||||
))}
|
||||
</Flexbox>
|
||||
<GridLayout>
|
||||
<Pagination />
|
||||
</GridLayout>
|
||||
<UpdateChangelogStatus currentId={data[0]?.id} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const DesktopPage = () => {
|
||||
return <Page isMobile={false} />;
|
||||
};
|
||||
|
||||
const MobilePage = () => {
|
||||
return <Page isMobile={true} />;
|
||||
};
|
||||
|
||||
export { DesktopPage, MobilePage };
|
||||
@@ -1,10 +0,0 @@
|
||||
import ServerLayout from '@/components/server/ServerLayout';
|
||||
|
||||
import Desktop from './_layout/Desktop';
|
||||
import Mobile from './_layout/Mobile';
|
||||
|
||||
const MainLayout = ServerLayout({ Desktop, Mobile });
|
||||
|
||||
MainLayout.displayName = 'ChangelogLayout';
|
||||
|
||||
export default MainLayout;
|
||||
@@ -1,23 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { useQueryRoute } from '@/hooks/useQueryRoute';
|
||||
|
||||
/**
|
||||
* @description: Changelog Modal (intercepting routes fallback when hard refresh)
|
||||
* @example: /changelog/modal => /changelog
|
||||
* @refs: https://github.com/lobehub/lobe-chat/discussions/2295#discussioncomment-9290942
|
||||
*/
|
||||
|
||||
const ChangelogModal = () => {
|
||||
const router = useQueryRoute();
|
||||
|
||||
useEffect(() => {
|
||||
router.replace('/changelog');
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default ChangelogModal;
|
||||
@@ -1,78 +0,0 @@
|
||||
import { Divider, Skeleton } from 'antd';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { Fragment, Suspense } from 'react';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
import urlJoin from 'url-join';
|
||||
|
||||
import Pagination from '@/app/[variants]/@modal/(.)changelog/modal/features/Pagination';
|
||||
import UpdateChangelogStatus from '@/app/[variants]/@modal/(.)changelog/modal/features/UpdateChangelogStatus';
|
||||
import StructuredData from '@/components/StructuredData';
|
||||
import { serverFeatureFlags } from '@/config/featureFlags';
|
||||
import { BRANDING_NAME } from '@/const/branding';
|
||||
import { OFFICIAL_SITE } from '@/const/url';
|
||||
import { ldModule } from '@/server/ld';
|
||||
import { metadataModule } from '@/server/metadata';
|
||||
import { ChangelogService } from '@/server/services/changelog';
|
||||
import { translation } from '@/server/translation';
|
||||
import { DynamicLayoutProps } from '@/types/next';
|
||||
import { RouteVariants } from '@/utils/server/routeVariants';
|
||||
|
||||
import GridLayout from './features/GridLayout';
|
||||
import Post from './features/Post';
|
||||
|
||||
export const generateMetadata = async (props: DynamicLayoutProps) => {
|
||||
const locale = await RouteVariants.getLocale(props);
|
||||
const { t } = await translation('metadata', locale);
|
||||
return metadataModule.generate({
|
||||
canonical: urlJoin(OFFICIAL_SITE, 'changelog'),
|
||||
description: t('changelog.description', { appName: BRANDING_NAME }),
|
||||
title: t('changelog.title'),
|
||||
url: '/changelog',
|
||||
});
|
||||
};
|
||||
|
||||
const Page = async (props: DynamicLayoutProps) => {
|
||||
const hideDocs = serverFeatureFlags().hideDocs;
|
||||
if (hideDocs) return notFound();
|
||||
|
||||
const { isMobile, locale } = await RouteVariants.getVariantsFromProps(props);
|
||||
const { t } = await translation('metadata', locale);
|
||||
const changelogService = new ChangelogService();
|
||||
const data = await changelogService.getChangelogIndex();
|
||||
|
||||
if (!data) return notFound();
|
||||
|
||||
const ld = ldModule.generate({
|
||||
description: t('changelog.description', { appName: BRANDING_NAME }),
|
||||
title: t('changelog.title', { appName: BRANDING_NAME }),
|
||||
url: '/changelog',
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<StructuredData ld={ld} />
|
||||
<Flexbox gap={isMobile ? 16 : 48}>
|
||||
{data?.map((item) => (
|
||||
<Fragment key={item.id}>
|
||||
<Suspense
|
||||
fallback={
|
||||
<GridLayout>
|
||||
<Divider />
|
||||
<Skeleton active paragraph={{ rows: 5 }} />
|
||||
</GridLayout>
|
||||
}
|
||||
>
|
||||
<Post locale={locale} mobile={isMobile} {...item} />
|
||||
</Suspense>
|
||||
</Fragment>
|
||||
))}
|
||||
</Flexbox>
|
||||
<GridLayout>
|
||||
<Pagination />
|
||||
</GridLayout>
|
||||
<UpdateChangelogStatus currentId={data[0]?.id} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
@@ -1,83 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { memo, useEffect } from 'react';
|
||||
import { useMediaQuery } from 'react-responsive';
|
||||
import { MemoryRouter, Navigate, Route, Routes, useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
import MainChatPage from './components/MainChatPage';
|
||||
import SettingsPage from './components/SettingsPage';
|
||||
|
||||
// Get initial path from URL
|
||||
const getInitialPath = () => {
|
||||
if (typeof window === 'undefined') return '/';
|
||||
const fullPath = window.location.pathname;
|
||||
const searchParams = window.location.search;
|
||||
const chatIndex = fullPath.indexOf('/chat');
|
||||
|
||||
if (chatIndex !== -1) {
|
||||
const pathAfterChat = fullPath.slice(chatIndex + '/chat'.length) || '/';
|
||||
return pathAfterChat + searchParams;
|
||||
}
|
||||
return '/';
|
||||
};
|
||||
|
||||
// Helper component to sync URL with MemoryRouter
|
||||
const UrlSynchronizer = () => {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Sync initial URL
|
||||
useEffect(() => {
|
||||
const fullPath = window.location.pathname;
|
||||
const searchParams = window.location.search;
|
||||
const chatIndex = fullPath.indexOf('/chat');
|
||||
|
||||
if (chatIndex !== -1) {
|
||||
const pathAfterChat = fullPath.slice(chatIndex + '/chat'.length) || '/';
|
||||
const targetPath = pathAfterChat + searchParams;
|
||||
|
||||
if (location.pathname + location.search !== targetPath) {
|
||||
navigate(targetPath, { replace: true });
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Update browser URL when location changes
|
||||
useEffect(() => {
|
||||
const normalizedPath = location.pathname === '/' ? '' : location.pathname;
|
||||
const newUrl = `/chat${normalizedPath}${location.search}`;
|
||||
if (window.location.pathname + window.location.search !== newUrl) {
|
||||
window.history.replaceState({}, '', newUrl);
|
||||
}
|
||||
}, [location.pathname, location.search]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const ChatRouter = memo(() => {
|
||||
const mobile = useMediaQuery({ maxWidth: 768 });
|
||||
const routes = (
|
||||
<Routes>
|
||||
<Route element={<MainChatPage mobile={true} />} path="/" />
|
||||
<Route element={<SettingsPage mobile={true} />} path="/settings" />
|
||||
<Route element={<Navigate replace to="/" />} path="*" />
|
||||
</Routes>
|
||||
);
|
||||
|
||||
return (
|
||||
<MemoryRouter initialEntries={[getInitialPath()]} initialIndex={0}>
|
||||
<UrlSynchronizer />
|
||||
{mobile ? (
|
||||
// Mobile Layout
|
||||
routes
|
||||
) : (
|
||||
// Desktop Layout
|
||||
<MainChatPage mobile={false} />
|
||||
)}
|
||||
</MemoryRouter>
|
||||
);
|
||||
});
|
||||
|
||||
ChatRouter.displayName = 'ChatRouter';
|
||||
|
||||
export default ChatRouter;
|
||||
@@ -1,22 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { PropsWithChildren, memo } from 'react';
|
||||
|
||||
import Desktop from './Desktop';
|
||||
import Mobile from './Mobile';
|
||||
|
||||
interface ChatLayoutProps extends PropsWithChildren {
|
||||
mobile?: boolean;
|
||||
}
|
||||
|
||||
const ChatLayout = memo<ChatLayoutProps>(({ children, mobile }) => {
|
||||
if (mobile) {
|
||||
return <Mobile>{children}</Mobile>;
|
||||
}
|
||||
|
||||
return <Desktop>{children}</Desktop>;
|
||||
});
|
||||
|
||||
ChatLayout.displayName = 'ChatLayout';
|
||||
|
||||
export default ChatLayout;
|
||||
@@ -1,15 +1,14 @@
|
||||
import { Suspense } from 'react';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
|
||||
import { isDesktop } from '@/const/version';
|
||||
import ProtocolUrlHandler from '@/features/ProtocolUrlHandler';
|
||||
|
||||
import { LayoutProps } from '../type';
|
||||
import RegisterHotkeys from './RegisterHotkeys';
|
||||
import SessionPanel from './SessionPanel';
|
||||
import Workspace from './Workspace';
|
||||
|
||||
const Layout = ({ children }: LayoutProps) => {
|
||||
const Layout = () => {
|
||||
return (
|
||||
<>
|
||||
<Flexbox
|
||||
@@ -19,14 +18,14 @@ const Layout = ({ children }: LayoutProps) => {
|
||||
width={'100%'}
|
||||
>
|
||||
<SessionPanel />
|
||||
<Workspace>{children}</Workspace>
|
||||
<Workspace>
|
||||
<Outlet />
|
||||
</Workspace>
|
||||
</Flexbox>
|
||||
{/* ↓ cloud slot ↓ */}
|
||||
|
||||
{/* ↑ cloud slot ↑ */}
|
||||
<Suspense>
|
||||
<RegisterHotkeys />
|
||||
</Suspense>
|
||||
<RegisterHotkeys />
|
||||
{isDesktop && <ProtocolUrlHandler />}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -8,7 +8,7 @@ import { withSuspense } from '@/components/withSuspense';
|
||||
import { useShowMobileWorkspace } from '@/hooks/useShowMobileWorkspace';
|
||||
|
||||
import SessionPanelContent from '../components/SessionPanel';
|
||||
import { LayoutProps } from './type';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
|
||||
const useStyles = createStyles(({ css, token }) => ({
|
||||
main: css`
|
||||
@@ -18,7 +18,7 @@ const useStyles = createStyles(({ css, token }) => ({
|
||||
`,
|
||||
}));
|
||||
|
||||
const Layout = memo<LayoutProps>(({ children }) => {
|
||||
const Layout = memo(( ) => {
|
||||
const showMobileWorkspace = useShowMobileWorkspace();
|
||||
const { styles } = useStyles();
|
||||
|
||||
@@ -38,7 +38,7 @@ const Layout = memo<LayoutProps>(({ children }) => {
|
||||
style={showMobileWorkspace ? undefined : { display: 'none' }}
|
||||
width="100%"
|
||||
>
|
||||
{children}
|
||||
<Outlet />
|
||||
</Flexbox>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { memo } from 'react';
|
||||
|
||||
import TelemetryNotification from '../components/features/TelemetryNotification';
|
||||
import PageTitle from '../features/PageTitle';
|
||||
import WorkspaceLayout from './WorkspaceLayout';
|
||||
|
||||
interface MainChatPageProps {
|
||||
mobile?: boolean;
|
||||
}
|
||||
|
||||
const MainChatPage = memo<MainChatPageProps>(({ mobile }) => {
|
||||
return (
|
||||
<>
|
||||
<PageTitle />
|
||||
<WorkspaceLayout mobile={mobile} />
|
||||
<TelemetryNotification mobile={mobile} />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
MainChatPage.displayName = 'MainChatPage';
|
||||
|
||||
export default MainChatPage;
|
||||
@@ -14,9 +14,6 @@ import ConversationArea from './ConversationArea';
|
||||
import PortalPanel from './PortalPanel';
|
||||
import TopicSidebar from './TopicSidebar';
|
||||
|
||||
interface WorkspaceLayoutProps {
|
||||
mobile?: boolean;
|
||||
}
|
||||
|
||||
const DesktopWorkspace = memo(() => (
|
||||
<>
|
||||
@@ -60,14 +57,4 @@ const MobileWorkspace = memo(() => (
|
||||
|
||||
MobileWorkspace.displayName = 'MobileWorkspace';
|
||||
|
||||
const WorkspaceLayout = memo<WorkspaceLayoutProps>(({ mobile }) => {
|
||||
if (mobile) {
|
||||
return <MobileWorkspace />;
|
||||
}
|
||||
|
||||
return <DesktopWorkspace />;
|
||||
});
|
||||
|
||||
WorkspaceLayout.displayName = 'WorkspaceLayout';
|
||||
|
||||
export default WorkspaceLayout;
|
||||
export { DesktopWorkspace, MobileWorkspace };
|
||||
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
'use client';
|
||||
|
||||
import { useQueryState } from 'nuqs';
|
||||
import { memo, useLayoutEffect } from 'react';
|
||||
import { createStoreUpdater } from 'zustand-utils';
|
||||
|
||||
import { useQueryState } from '@/hooks/useQueryParam';
|
||||
import { useChatStore } from '@/store/chat';
|
||||
|
||||
// sync outside state to useChatStore
|
||||
@@ -34,7 +34,7 @@ const ChatHydration = memo(() => {
|
||||
unsubscribeTopic();
|
||||
unsubscribeThread();
|
||||
};
|
||||
}, []);
|
||||
}, [setTopic, setThread]); // ✅ 现在 setValue 是稳定的,可以安全地添加到依赖数组
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user