@lobehub/lobehub 2.0.0-next.240 → 2.0.0-next.242

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/.github/workflows/test.yml +13 -5
  2. package/CHANGELOG.md +50 -0
  3. package/apps/desktop/build/Icon-beta.Assets.car +0 -0
  4. package/apps/desktop/build/Icon-beta.icns +0 -0
  5. package/apps/desktop/build/icon-beta.ico +0 -0
  6. package/apps/desktop/build/icon-beta.png +0 -0
  7. package/apps/desktop/resources/locales/ar/menu.json +5 -1
  8. package/apps/desktop/resources/locales/bg-BG/menu.json +5 -1
  9. package/apps/desktop/resources/locales/de-DE/menu.json +5 -1
  10. package/apps/desktop/resources/locales/es-ES/menu.json +5 -1
  11. package/apps/desktop/resources/locales/fa-IR/menu.json +5 -1
  12. package/apps/desktop/resources/locales/fr-FR/menu.json +5 -1
  13. package/apps/desktop/resources/locales/it-IT/menu.json +5 -1
  14. package/apps/desktop/resources/locales/ja-JP/menu.json +5 -1
  15. package/apps/desktop/resources/locales/ko-KR/menu.json +5 -1
  16. package/apps/desktop/resources/locales/nl-NL/menu.json +5 -1
  17. package/apps/desktop/resources/locales/pl-PL/menu.json +5 -1
  18. package/apps/desktop/resources/locales/pt-BR/menu.json +5 -1
  19. package/apps/desktop/resources/locales/ru-RU/menu.json +5 -1
  20. package/apps/desktop/resources/locales/tr-TR/menu.json +5 -1
  21. package/apps/desktop/resources/locales/vi-VN/menu.json +5 -1
  22. package/apps/desktop/resources/locales/zh-CN/menu.json +5 -1
  23. package/apps/desktop/resources/locales/zh-TW/menu.json +5 -1
  24. package/apps/desktop/src/main/locales/default/menu.ts +5 -1
  25. package/apps/desktop/src/main/menus/impls/linux.ts +30 -0
  26. package/apps/desktop/src/main/menus/impls/macOS.test.ts +17 -0
  27. package/apps/desktop/src/main/menus/impls/macOS.ts +33 -0
  28. package/apps/desktop/src/main/menus/impls/windows.ts +30 -0
  29. package/changelog/v1.json +10 -0
  30. package/locales/ar/electron.json +24 -0
  31. package/locales/ar/models.json +48 -7
  32. package/locales/ar/plugin.json +9 -0
  33. package/locales/ar/providers.json +1 -0
  34. package/locales/bg-BG/electron.json +24 -0
  35. package/locales/bg-BG/models.json +35 -7
  36. package/locales/bg-BG/plugin.json +9 -0
  37. package/locales/bg-BG/providers.json +1 -0
  38. package/locales/de-DE/electron.json +24 -0
  39. package/locales/de-DE/models.json +26 -6
  40. package/locales/de-DE/plugin.json +9 -0
  41. package/locales/de-DE/providers.json +1 -0
  42. package/locales/en-US/electron.json +24 -0
  43. package/locales/en-US/models.json +10 -10
  44. package/locales/en-US/oauth.json +0 -1
  45. package/locales/en-US/providers.json +1 -0
  46. package/locales/en-US/subscription.json +2 -2
  47. package/locales/es-ES/electron.json +24 -0
  48. package/locales/es-ES/models.json +42 -7
  49. package/locales/es-ES/plugin.json +9 -0
  50. package/locales/es-ES/providers.json +1 -0
  51. package/locales/fa-IR/electron.json +24 -0
  52. package/locales/fa-IR/models.json +52 -10
  53. package/locales/fa-IR/plugin.json +9 -0
  54. package/locales/fa-IR/providers.json +1 -0
  55. package/locales/fr-FR/electron.json +24 -0
  56. package/locales/fr-FR/models.json +36 -7
  57. package/locales/fr-FR/plugin.json +9 -0
  58. package/locales/fr-FR/providers.json +1 -0
  59. package/locales/it-IT/electron.json +24 -0
  60. package/locales/it-IT/models.json +42 -7
  61. package/locales/it-IT/plugin.json +9 -0
  62. package/locales/it-IT/providers.json +1 -0
  63. package/locales/ja-JP/electron.json +24 -0
  64. package/locales/ja-JP/models.json +35 -6
  65. package/locales/ja-JP/plugin.json +9 -0
  66. package/locales/ja-JP/providers.json +1 -0
  67. package/locales/ko-KR/electron.json +24 -0
  68. package/locales/ko-KR/models.json +28 -7
  69. package/locales/ko-KR/plugin.json +9 -0
  70. package/locales/ko-KR/providers.json +1 -0
  71. package/locales/nl-NL/electron.json +24 -0
  72. package/locales/nl-NL/models.json +35 -6
  73. package/locales/nl-NL/plugin.json +9 -0
  74. package/locales/nl-NL/providers.json +1 -0
  75. package/locales/pl-PL/electron.json +24 -0
  76. package/locales/pl-PL/models.json +36 -7
  77. package/locales/pl-PL/plugin.json +9 -0
  78. package/locales/pl-PL/providers.json +1 -0
  79. package/locales/pt-BR/electron.json +24 -0
  80. package/locales/pt-BR/models.json +35 -6
  81. package/locales/pt-BR/plugin.json +9 -0
  82. package/locales/pt-BR/providers.json +1 -0
  83. package/locales/ru-RU/electron.json +24 -0
  84. package/locales/ru-RU/models.json +35 -7
  85. package/locales/ru-RU/plugin.json +9 -0
  86. package/locales/ru-RU/providers.json +1 -0
  87. package/locales/tr-TR/electron.json +24 -0
  88. package/locales/tr-TR/models.json +5 -7
  89. package/locales/tr-TR/plugin.json +9 -0
  90. package/locales/tr-TR/providers.json +1 -0
  91. package/locales/vi-VN/electron.json +24 -0
  92. package/locales/vi-VN/models.json +5 -5
  93. package/locales/vi-VN/plugin.json +9 -0
  94. package/locales/vi-VN/providers.json +1 -0
  95. package/locales/zh-CN/electron.json +24 -0
  96. package/locales/zh-CN/models.json +48 -6
  97. package/locales/zh-CN/oauth.json +0 -1
  98. package/locales/zh-CN/providers.json +1 -0
  99. package/locales/zh-CN/subscription.json +1 -1
  100. package/locales/zh-TW/electron.json +24 -0
  101. package/locales/zh-TW/models.json +10 -10
  102. package/locales/zh-TW/plugin.json +9 -0
  103. package/locales/zh-TW/providers.json +1 -0
  104. package/package.json +1 -1
  105. package/packages/electron-client-ipc/src/events/navigation.ts +12 -0
  106. package/src/components/PageTitle/index.tsx +11 -1
  107. package/src/features/ElectronTitlebar/NavigationBar/RecentlyViewed.tsx +137 -0
  108. package/src/features/ElectronTitlebar/NavigationBar/index.tsx +86 -0
  109. package/src/features/ElectronTitlebar/helpers/routeMetadata.ts +214 -0
  110. package/src/features/ElectronTitlebar/hooks/useNavigationHistory.ts +152 -0
  111. package/src/features/ElectronTitlebar/index.tsx +13 -5
  112. package/src/features/NavHeader/index.tsx +4 -2
  113. package/src/features/NavPanel/components/NavPanelDraggable.tsx +174 -0
  114. package/src/features/NavPanel/hooks/useNavPanel.ts +11 -35
  115. package/src/features/NavPanel/index.tsx +2 -126
  116. package/src/hooks/useTypeScriptHappyCallback.ts +7 -0
  117. package/src/locales/default/electron.ts +24 -0
  118. package/src/locales/default/subscription.ts +2 -3
  119. package/src/server/services/memory/userMemory/extract.ts +46 -6
  120. package/src/store/electron/actions/navigationHistory.ts +247 -0
  121. package/src/store/electron/initialState.ts +7 -1
  122. package/src/store/electron/store.ts +9 -2
  123. package/src/store/global/selectors/systemStatus.ts +4 -1
@@ -271,20 +271,20 @@
271
271
  "chatgpt-4o-latest.description": "ChatGPT-4o 是一款即時更新的動態模型,結合強大的理解與生成能力,適用於客服、教育與技術支援等大規模應用場景。",
272
272
  "claude-2.0.description": "Claude 2 提供企業級關鍵改進,包括領先的 20 萬 token 上下文、降低幻覺、系統提示與新測試功能:工具調用。",
273
273
  "claude-2.1.description": "Claude 2 提供企業級關鍵改進,包括領先的 20 萬 token 上下文、降低幻覺、系統提示與新測試功能:工具調用。",
274
- "claude-3-5-haiku-20241022.description": "Claude 3.5 Haiku 是 Anthropic 最新一代中速度最快的模型。相較於 Claude 3 Haiku,其在多項技能上皆有提升,並在多項智能基準測試中超越先前最大模型 Claude 3 Opus。",
274
+ "claude-3-5-haiku-20241022.description": "Claude 3.5 Haiku 是 Anthropic 推出的次世代最快模型,在多項技能上全面提升,並在多項基準測試中超越前一代旗艦 Claude 3 Opus。",
275
275
  "claude-3-5-haiku-latest.description": "Claude 3.5 Haiku 提供快速回應,適用於輕量任務。",
276
- "claude-3-7-sonnet-20250219.description": "Claude 3.7 Sonnet 是 Anthropic 最具智慧的模型,也是市場上首個混合推理模型。它能夠即時回應,或進行可視化的逐步推理。Sonnet 尤其擅長程式設計、資料科學、視覺處理與智能代理任務。",
276
+ "claude-3-7-sonnet-20250219.description": "Claude Sonnet 3.7 是 Anthropic 最智慧的模型,也是市場上首個混合推理模型,支援即時回應與延伸思考,並提供細緻的控制能力。",
277
277
  "claude-3-7-sonnet-latest.description": "Claude 3.7 Sonnet 是 Anthropic 最新且最強大的模型,適用於高度複雜任務,具備卓越的效能、智慧、流暢度與理解力。",
278
278
  "claude-3-haiku-20240307.description": "Claude 3 Haiku 是 Anthropic 推出的最快速且最精簡的模型,設計用於即時回應,具備快速且準確的表現。",
279
279
  "claude-3-opus-20240229.description": "Claude 3 Opus 是 Anthropic 最強大的模型,適用於高度複雜任務,具備卓越的效能、智慧、流暢度與理解力。",
280
280
  "claude-3-sonnet-20240229.description": "Claude 3 Sonnet 在智慧與速度之間取得平衡,適合企業工作負載,提供高效能與低成本的大規模部署。",
281
- "claude-haiku-4-5-20251001.description": "Claude Haiku 4.5 是 Anthropic 最快且最聰明的 Haiku 模型,具備閃電般的速度與延伸推理能力。",
281
+ "claude-haiku-4-5-20251001.description": "Claude Haiku 4.5 是 Anthropic 最快且最智慧的 Haiku 模型,具備閃電般的速度與延伸思考能力。",
282
282
  "claude-opus-4-1-20250805-thinking.description": "Claude Opus 4.1 Thinking 是一個進階版本,能夠揭示其推理過程。",
283
283
  "claude-opus-4-1-20250805.description": "Claude Opus 4.1 是 Anthropic 最新且最強大的模型,適用於高度複雜任務,具備卓越的效能、智慧、流暢度與理解力。",
284
- "claude-opus-4-20250514.description": "Claude Opus 4 是 Anthropic 最強大的模型,專為處理高度複雜任務而設計,表現出色,具備卓越的智能、流暢度與理解力。",
284
+ "claude-opus-4-20250514.description": "Claude Opus 4 是 Anthropic 最強大的模型,專為處理高度複雜任務而設,表現卓越,具備高智慧、流暢性與理解力。",
285
285
  "claude-opus-4-5-20251101.description": "Claude Opus 4.5 是 Anthropic 的旗艦模型,結合卓越智慧與可擴展效能,適合需要最高品質回應與推理的複雜任務。",
286
286
  "claude-sonnet-4-20250514-thinking.description": "Claude Sonnet 4 Thinking 可產生即時回應或延伸的逐步思考,並顯示其推理過程。",
287
- "claude-sonnet-4-20250514.description": "Claude Sonnet 4 能夠即時回應,或進行可視化的逐步思考過程。",
287
+ "claude-sonnet-4-20250514.description": "Claude Sonnet 4 是 Anthropic 迄今最智慧的模型,為 API 使用者提供即時回應或逐步思考的能力,並具備細緻控制功能。",
288
288
  "claude-sonnet-4-5-20250929.description": "Claude Sonnet 4.5 是 Anthropic 迄今最智慧的模型。",
289
289
  "codegeex-4.description": "CodeGeeX-4 是一款強大的 AI 程式輔助工具,支援多語言問答與程式碼補全,能有效提升開發者的生產力。",
290
290
  "codegeex4-all-9b.description": "CodeGeeX4-ALL-9B 是一個多語言程式碼生成模型,支援程式碼補全與生成、程式碼解釋器、網頁搜尋、函式呼叫與倉庫層級的程式碼問答,涵蓋多種軟體開發場景。它是參數數量低於 100 億的頂尖程式碼模型之一。",
@@ -355,7 +355,7 @@
355
355
  "deepseek-ai/deepseek-v3.1-terminus.description": "DeepSeek V3.1 是新一代推理模型,具備更強的複雜推理與思維鏈能力,適用於深度分析任務。",
356
356
  "deepseek-ai/deepseek-v3.1.description": "DeepSeek V3.1 是新一代推理模型,具備更強的複雜推理與思維鏈能力,適用於深度分析任務。",
357
357
  "deepseek-ai/deepseek-vl2.description": "DeepSeek-VL2 是一款基於 DeepSeekMoE-27B 的 MoE 視覺語言模型,採用稀疏激活,僅使用 4.5B 活躍參數即可達到強大表現。擅長視覺問答、OCR、文件/表格/圖表理解與視覺對齊。",
358
- "deepseek-chat.description": "一款結合通用與程式能力的開源模型。保留聊天模型的對話能力與程式模型的強大編碼能力,並提升偏好對齊。DeepSeek-V2.5 也在寫作與指令遵循方面有所進步。",
358
+ "deepseek-chat.description": "DeepSeek V3.2 在推理與輸出長度之間取得平衡,適用於日常問答與智能體任務。其公開基準表現達 GPT-5 水準,並首創將思考整合至工具使用,在開源智能體評測中表現領先。",
359
359
  "deepseek-coder-33B-instruct.description": "DeepSeek Coder 33B 是一款程式語言模型,訓練於 2T token(87% 程式碼,13% 中英文文本),支援 16K 上下文視窗與中間填充任務,提供專案級程式補全與片段填充功能。",
360
360
  "deepseek-coder-v2.description": "DeepSeek Coder V2 是一款開源 MoE 程式模型,在程式任務中表現強勁,媲美 GPT-4 Turbo。",
361
361
  "deepseek-coder-v2:236b.description": "DeepSeek Coder V2 是一款開源 MoE 程式模型,在程式任務中表現強勁,媲美 GPT-4 Turbo。",
@@ -378,7 +378,7 @@
378
378
  "deepseek-r1-fast-online.description": "DeepSeek R1 快速全量版,支援即時網頁搜尋,結合 671B 規模能力與快速回應。",
379
379
  "deepseek-r1-online.description": "DeepSeek R1 全量版擁有 671B 參數與即時網頁搜尋功能,提供更強的理解與生成能力。",
380
380
  "deepseek-r1.description": "DeepSeek-R1 在強化學習前使用冷啟動資料,於數學、程式碼與推理任務中表現可媲美 OpenAI-o1。",
381
- "deepseek-reasoner.description": "DeepSeek V3.2 推理模式會在最終答案前輸出思考鏈,以提升準確性。",
381
+ "deepseek-reasoner.description": "DeepSeek V3.2 Thinking 是一款深度推理模型,能在輸出前生成思考鏈以提升準確性,在競賽中表現優異,推理能力媲美 Gemini-3.0-Pro。",
382
382
  "deepseek-v2.description": "DeepSeek V2 是一款高效的 MoE 模型,適用於具成本效益的處理任務。",
383
383
  "deepseek-v2:236b.description": "DeepSeek V2 236B 是 DeepSeek 專注於程式碼生成的模型,具備強大能力。",
384
384
  "deepseek-v3-0324.description": "DeepSeek-V3-0324 是一款擁有 671B 參數的 MoE 模型,在程式設計、技術能力、語境理解與長文本處理方面表現出色。",
@@ -471,7 +471,7 @@
471
471
  "ernie-tiny-8k.description": "ERNIE Tiny 8K 是一款超輕量模型,適合簡單問答、分類與低成本推理。",
472
472
  "ernie-x1-turbo-32k.description": "ERNIE X1 Turbo 32K 是一款快速思考模型,具備 32K 上下文,適合複雜推理與多輪對話。",
473
473
  "ernie-x1.1-preview.description": "ERNIE X1.1 預覽版是一款思考模型預覽,用於評估與測試。",
474
- "fal-ai/bytedance/seedream/v4.description": "Seedream 4.0 是字節跳動 Seed 團隊推出的圖像生成模型,支援文字與圖像輸入,實現高度可控與高品質的圖像生成,能根據文字提示生成圖像。",
474
+ "fal-ai/bytedance/seedream/v4.description": "Seedream 4.0 由字節跳動 Seed 團隊打造,支援文字與圖像輸入,能從提示中生成高品質、可控性強的圖像。",
475
475
  "fal-ai/flux-kontext/dev.description": "FLUX.1 模型專注於圖像編輯,支援文字與圖像輸入。",
476
476
  "fal-ai/flux-pro/kontext.description": "FLUX.1 Kontext [pro] 接受文字與參考圖像輸入,實現目標區域編輯與複雜場景轉換。",
477
477
  "fal-ai/flux/krea.description": "Flux Krea [dev] 是一款圖像生成模型,偏好更真實自然的美學風格。",
@@ -479,8 +479,8 @@
479
479
  "fal-ai/hunyuan-image/v3.description": "一款強大的原生多模態圖像生成模型。",
480
480
  "fal-ai/imagen4/preview.description": "來自 Google 的高品質圖像生成模型。",
481
481
  "fal-ai/nano-banana.description": "Nano Banana 是 Google 最新、最快且最高效的原生多模態模型,支援透過對話進行圖像生成與編輯。",
482
- "fal-ai/qwen-image-edit.description": "來自 Qwen 團隊的專業圖像編輯模型,支援語義與外觀編輯,能精準處理中英文文字,並實現風格轉換、物體旋轉等高品質編輯。",
483
- "fal-ai/qwen-image.description": "來自 Qwen 團隊的強大圖像生成模型,具備優秀的中文文字渲染與多樣化視覺風格。",
482
+ "fal-ai/qwen-image-edit.description": "來自 Qwen 團隊的專業圖像編輯模型,支援語義與外觀編輯、中英文文字精準修改、風格轉換、旋轉等功能。",
483
+ "fal-ai/qwen-image.description": "Qwen 團隊推出的強大圖像生成模型,具備優秀的中文文字渲染能力與多樣化視覺風格。",
484
484
  "flux-1-schnell.description": "來自黑森林實驗室的 12B 參數文字轉圖像模型,透過潛在對抗擴散蒸餾技術,在 1 至 4 步內生成高品質圖像。其表現媲美封閉式替代方案,並以 Apache-2.0 授權釋出,供個人、研究與商業用途。",
485
485
  "flux-dev.description": "FLUX.1 [dev] 是一款開放權重的蒸餾模型,僅限非商業用途。它保有接近專業水準的圖像品質與指令遵循能力,同時運行更高效,資源使用優於同等大小的標準模型。",
486
486
  "flux-kontext-max.description": "最先進的語境圖像生成與編輯技術,結合文字與圖像輸入,實現精準且一致的結果。",
@@ -92,6 +92,15 @@
92
92
  "builtins.lobe-local-system.inspector.noResults": "沒有結果",
93
93
  "builtins.lobe-local-system.inspector.rename.result": "<old>{{oldName}}</old> → <new>{{newName}}</new>",
94
94
  "builtins.lobe-local-system.title": "本機系統",
95
+ "builtins.lobe-notebook.actions.copy": "複製",
96
+ "builtins.lobe-notebook.actions.creating": "正在建立文件...",
97
+ "builtins.lobe-notebook.actions.edit": "編輯",
98
+ "builtins.lobe-notebook.actions.expand": "展開",
99
+ "builtins.lobe-notebook.apiName.createDocument": "建立文件",
100
+ "builtins.lobe-notebook.apiName.deleteDocument": "刪除文件",
101
+ "builtins.lobe-notebook.apiName.getDocument": "取得文件",
102
+ "builtins.lobe-notebook.apiName.updateDocument": "更新文件",
103
+ "builtins.lobe-notebook.title": "筆記本",
95
104
  "builtins.lobe-page-agent.apiName.batchUpdate": "批次更新節點",
96
105
  "builtins.lobe-page-agent.apiName.compareSnapshots": "比較快照",
97
106
  "builtins.lobe-page-agent.apiName.convertToList": "轉換為清單",
@@ -29,6 +29,7 @@
29
29
  "internlm.description": "一個專注於大型模型研究與工具的開源組織,提供高效、易用的平台,讓尖端模型與演算法更易於取得。",
30
30
  "jina.description": "Jina AI 成立於 2020 年,是領先的搜尋 AI 公司。其搜尋技術堆疊包含向量模型、重排序器與小型語言模型,打造可靠且高品質的生成式與多模態搜尋應用。",
31
31
  "lmstudio.description": "LM Studio 是一款桌面應用程式,可在本機開發與實驗大型語言模型。",
32
+ "lobehub.description": "LobeHub Cloud 使用官方 API 存取 AI 模型,並以與模型代幣相關的點數(Credits)來計算使用量。",
32
33
  "minimax.description": "MiniMax 成立於 2021 年,致力於打造通用 AI,擁有多模態基礎模型,包括兆級參數的 MoE 文本模型、語音模型與視覺模型,並推出如海螺 AI 等應用。",
33
34
  "mistral.description": "Mistral 提供先進的通用、專業與研究模型,支援複雜推理、多語言任務與程式碼生成,並支援函式呼叫以實現自訂整合。",
34
35
  "modelscope.description": "ModelScope 是阿里雲的模型即服務平台,提供多樣化的 AI 模型與推理服務。",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.240",
3
+ "version": "2.0.0-next.242",
4
4
  "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.",
5
5
  "keywords": [
6
6
  "framework",
@@ -1,4 +1,16 @@
1
1
  export interface NavigationBroadcastEvents {
2
+ /**
3
+ * Ask renderer to go back in navigation history.
4
+ * Triggered from the main process menu.
5
+ */
6
+ historyGoBack: () => void;
7
+
8
+ /**
9
+ * Ask renderer to go forward in navigation history.
10
+ * Triggered from the main process menu.
11
+ */
12
+ historyGoForward: () => void;
13
+
2
14
  /**
3
15
  * Ask renderer to navigate within the SPA without reloading the whole page.
4
16
  */
@@ -1,10 +1,20 @@
1
1
  import { BRANDING_NAME } from '@lobechat/business-const';
2
2
  import { memo, useEffect } from 'react';
3
3
 
4
+ import { isDesktop } from '@/const/version';
5
+ import { useElectronStore } from '@/store/electron';
6
+
4
7
  const PageTitle = memo<{ title: string }>(({ title }) => {
8
+ const setCurrentPageTitle = useElectronStore((s) => s.setCurrentPageTitle);
9
+
5
10
  useEffect(() => {
6
11
  document.title = title ? `${title} · ${BRANDING_NAME}` : BRANDING_NAME;
7
- }, [title]);
12
+
13
+ // Sync title to electron store for navigation history
14
+ if (isDesktop) {
15
+ setCurrentPageTitle(title);
16
+ }
17
+ }, [title, setCurrentPageTitle]);
8
18
 
9
19
  return null;
10
20
  });
@@ -0,0 +1,137 @@
1
+ 'use client';
2
+
3
+ import { Flexbox, Icon } from '@lobehub/ui';
4
+ import { createStaticStyles } from 'antd-style';
5
+ import { memo } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { useNavigate } from 'react-router-dom';
8
+
9
+ import { useElectronStore } from '@/store/electron';
10
+ import type { HistoryEntry } from '@/store/electron/actions/navigationHistory';
11
+
12
+ import { getRouteIcon } from '../helpers/routeMetadata';
13
+
14
+ const styles = createStaticStyles(({ css, cssVar }) => ({
15
+ container: css`
16
+ overflow-y: auto;
17
+ width: 260px;
18
+ max-height: 320px;
19
+ padding: 4px;
20
+ `,
21
+ empty: css`
22
+ padding-block: 16px;
23
+ padding-inline: 12px;
24
+
25
+ font-size: 12px;
26
+ color: ${cssVar.colorTextTertiary};
27
+ text-align: center;
28
+ `,
29
+ icon: css`
30
+ flex-shrink: 0;
31
+ color: ${cssVar.colorTextSecondary};
32
+ `,
33
+ item: css`
34
+ cursor: pointer;
35
+
36
+ overflow: hidden;
37
+
38
+ padding-block: 6px;
39
+ padding-inline: 8px;
40
+ border-radius: ${cssVar.borderRadiusSM};
41
+
42
+ transition: background-color 0.15s ${cssVar.motionEaseInOut};
43
+
44
+ &:hover {
45
+ background-color: ${cssVar.colorFillSecondary};
46
+ }
47
+ `,
48
+ itemActive: css`
49
+ background-color: ${cssVar.colorFillTertiary};
50
+ `,
51
+ itemTitle: css`
52
+ overflow: hidden;
53
+ flex: 1;
54
+
55
+ font-size: 12px;
56
+ color: ${cssVar.colorText};
57
+ text-overflow: ellipsis;
58
+ white-space: nowrap;
59
+ `,
60
+ title: css`
61
+ padding-block: 4px;
62
+ padding-inline: 8px;
63
+
64
+ font-size: 11px;
65
+ font-weight: 500;
66
+ color: ${cssVar.colorTextTertiary};
67
+ text-transform: uppercase;
68
+ letter-spacing: 0.5px;
69
+ `,
70
+ }));
71
+
72
+ interface RecentlyViewedProps {
73
+ onClose: () => void;
74
+ }
75
+
76
+ const RecentlyViewed = memo<RecentlyViewedProps>(({ onClose }) => {
77
+ const { t } = useTranslation('electron');
78
+ const navigate = useNavigate();
79
+ const historyEntries = useElectronStore((s) => s.historyEntries);
80
+ const historyCurrentIndex = useElectronStore((s) => s.historyCurrentIndex);
81
+ const setIsNavigatingHistory = useElectronStore((s) => s.setIsNavigatingHistory);
82
+
83
+ const handleClick = (entry: HistoryEntry, index: number) => {
84
+ // Set flag to prevent adding duplicate history entry
85
+ setIsNavigatingHistory(true);
86
+
87
+ // Update the current index in store
88
+ useElectronStore.setState({ historyCurrentIndex: index });
89
+
90
+ // Navigate to the selected entry
91
+ navigate(entry.url);
92
+
93
+ // Close the popover
94
+ onClose();
95
+ };
96
+
97
+ // Show entries in reverse order (most recent first), excluding current
98
+ const recentEntries = [...historyEntries]
99
+ .map((entry, index) => ({ entry, originalIndex: index }))
100
+ .reverse();
101
+
102
+ if (recentEntries.length === 0) {
103
+ return (
104
+ <div className={styles.container}>
105
+ <div className={styles.empty}>{t('navigation.recentView')}</div>
106
+ </div>
107
+ );
108
+ }
109
+
110
+ return (
111
+ <Flexbox className={styles.container}>
112
+ <div className={styles.title}>{t('navigation.recentView')}</div>
113
+ {recentEntries.map(({ entry, originalIndex }) => {
114
+ const isActive = originalIndex === historyCurrentIndex;
115
+ const RouteIcon = getRouteIcon(entry.url);
116
+
117
+ return (
118
+ <Flexbox
119
+ align="center"
120
+ className={`${styles.item} ${isActive ? styles.itemActive : ''}`}
121
+ gap={8}
122
+ horizontal
123
+ key={`${entry.url}-${originalIndex}`}
124
+ onClick={() => handleClick(entry, originalIndex)}
125
+ >
126
+ {RouteIcon && <Icon className={styles.icon} icon={RouteIcon} size="small" />}
127
+ <span className={styles.itemTitle}>{entry.title}</span>
128
+ </Flexbox>
129
+ );
130
+ })}
131
+ </Flexbox>
132
+ );
133
+ });
134
+
135
+ RecentlyViewed.displayName = 'RecentlyViewed';
136
+
137
+ export default RecentlyViewed;
@@ -0,0 +1,86 @@
1
+ 'use client';
2
+
3
+ import { ActionIcon, Flexbox, Popover, Tooltip } from '@lobehub/ui';
4
+ import { ArrowLeft, ArrowRight, Clock } from 'lucide-react';
5
+ import { memo, useCallback, useEffect, useState } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+
8
+ import { useGlobalStore } from '@/store/global';
9
+ import { systemStatusSelectors } from '@/store/global/selectors';
10
+ import { electronStylish } from '@/styles/electron';
11
+ import { isMacOS } from '@/utils/platform';
12
+
13
+ import { useNavigationHistory } from '../hooks/useNavigationHistory';
14
+ import RecentlyViewed from './RecentlyViewed';
15
+
16
+ const isMac = isMacOS();
17
+
18
+ const useNavPanelWidth = () => {
19
+ return useGlobalStore(systemStatusSelectors.leftPanelWidth);
20
+ };
21
+
22
+ const NavigationBar = memo(() => {
23
+ const { t } = useTranslation('electron');
24
+ const { canGoBack, canGoForward, goBack, goForward } = useNavigationHistory();
25
+ const [historyOpen, setHistoryOpen] = useState(false);
26
+ // Use ResizeObserver for real-time width updates during resize
27
+ const leftPanelWidth = useNavPanelWidth();
28
+
29
+ // Toggle history popover
30
+ const toggleHistoryOpen = useCallback(() => {
31
+ setHistoryOpen((prev) => !prev);
32
+ }, []);
33
+
34
+ // Listen for keyboard shortcut ⌘Y / Ctrl+Y
35
+ useEffect(() => {
36
+ const handleKeyDown = (event: KeyboardEvent) => {
37
+ const isCmdOrCtrl = isMac ? event.metaKey : event.ctrlKey;
38
+ if (isCmdOrCtrl && event.key.toLowerCase() === 'y') {
39
+ event.preventDefault();
40
+ toggleHistoryOpen();
41
+ }
42
+ };
43
+
44
+ window.addEventListener('keydown', handleKeyDown);
45
+ return () => {
46
+ window.removeEventListener('keydown', handleKeyDown);
47
+ };
48
+ }, [toggleHistoryOpen]);
49
+
50
+ // Tooltip content for the clock button
51
+ const tooltipContent = t('navigation.recentView');
52
+
53
+ return (
54
+ <Flexbox
55
+ align="center"
56
+ className={electronStylish.nodrag}
57
+ data-width={leftPanelWidth}
58
+ horizontal
59
+ justify="end"
60
+ style={{ width: `${leftPanelWidth - 12}px` }}
61
+ >
62
+ <Flexbox align="center" gap={2} horizontal>
63
+ <ActionIcon disabled={!canGoBack} icon={ArrowLeft} onClick={goBack} size="small" />
64
+ <ActionIcon disabled={!canGoForward} icon={ArrowRight} onClick={goForward} size="small" />
65
+ <Popover
66
+ content={<RecentlyViewed onClose={() => setHistoryOpen(false)} />}
67
+ onOpenChange={setHistoryOpen}
68
+ open={historyOpen}
69
+ placement="bottomLeft"
70
+ styles={{ content: { padding: 0 } }}
71
+ trigger="click"
72
+ >
73
+ <div>
74
+ <Tooltip open={historyOpen ? false : undefined} title={tooltipContent}>
75
+ <ActionIcon icon={Clock} size="small" />
76
+ </Tooltip>
77
+ </div>
78
+ </Popover>
79
+ </Flexbox>
80
+ </Flexbox>
81
+ );
82
+ });
83
+
84
+ NavigationBar.displayName = 'NavigationBar';
85
+
86
+ export default NavigationBar;
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Route metadata mapping for navigation history
3
+ * Provides title and icon information based on route path
4
+ */
5
+ import {
6
+ Brain,
7
+ Circle,
8
+ Compass,
9
+ Database,
10
+ FileText,
11
+ Home,
12
+ Image,
13
+ type LucideIcon,
14
+ MessageSquare,
15
+ Rocket,
16
+ Settings,
17
+ Users,
18
+ } from 'lucide-react';
19
+
20
+ export interface RouteMetadata {
21
+ icon?: LucideIcon;
22
+ /** i18n key for the title (namespace: electron) */
23
+ titleKey: string;
24
+ /** Whether this route should use document.title for more specific title */
25
+ useDynamicTitle?: boolean;
26
+ }
27
+
28
+ interface RoutePattern {
29
+ icon?: LucideIcon;
30
+ test: (pathname: string) => boolean;
31
+ /** i18n key for the title (namespace: electron) */
32
+ titleKey: string;
33
+ /** Whether this route should use document.title for more specific title */
34
+ useDynamicTitle?: boolean;
35
+ }
36
+
37
+ /**
38
+ * Route patterns ordered by specificity (most specific first)
39
+ */
40
+ const routePatterns: RoutePattern[] = [
41
+ // Settings routes
42
+ {
43
+ icon: Settings,
44
+ test: (p) => p.startsWith('/settings/provider'),
45
+ titleKey: 'navigation.provider',
46
+ },
47
+ {
48
+ icon: Settings,
49
+ test: (p) => p.startsWith('/settings'),
50
+ titleKey: 'navigation.settings',
51
+ },
52
+
53
+ // Agent/Chat routes - use dynamic title for specific chat names
54
+ {
55
+ icon: MessageSquare,
56
+ test: (p) => p.startsWith('/agent/'),
57
+ titleKey: 'navigation.chat',
58
+ useDynamicTitle: true,
59
+ },
60
+ {
61
+ icon: MessageSquare,
62
+ test: (p) => p === '/agent',
63
+ titleKey: 'navigation.chat',
64
+ },
65
+
66
+ // Group routes - use dynamic title for specific group names
67
+ {
68
+ icon: Users,
69
+ test: (p) => p.startsWith('/group/'),
70
+ titleKey: 'navigation.groupChat',
71
+ useDynamicTitle: true,
72
+ },
73
+ {
74
+ icon: Users,
75
+ test: (p) => p === '/group',
76
+ titleKey: 'navigation.group',
77
+ },
78
+
79
+ // Community/Discover routes
80
+ {
81
+ icon: Compass,
82
+ test: (p) => p.startsWith('/community/assistant'),
83
+ titleKey: 'navigation.discoverAssistants',
84
+ },
85
+ {
86
+ icon: Compass,
87
+ test: (p) => p.startsWith('/community/model'),
88
+ titleKey: 'navigation.discoverModels',
89
+ },
90
+ {
91
+ icon: Compass,
92
+ test: (p) => p.startsWith('/community/provider'),
93
+ titleKey: 'navigation.discoverProviders',
94
+ },
95
+ {
96
+ icon: Compass,
97
+ test: (p) => p.startsWith('/community/mcp'),
98
+ titleKey: 'navigation.discoverMcp',
99
+ },
100
+ {
101
+ icon: Compass,
102
+ test: (p) => p.startsWith('/community'),
103
+ titleKey: 'navigation.discover',
104
+ },
105
+
106
+ // Resource/Knowledge routes
107
+ {
108
+ icon: Database,
109
+ test: (p) => p.startsWith('/resource/library'),
110
+ titleKey: 'navigation.knowledgeBase',
111
+ },
112
+ {
113
+ icon: Database,
114
+ test: (p) => p.startsWith('/resource'),
115
+ titleKey: 'navigation.resources',
116
+ },
117
+
118
+ // Memory routes
119
+ {
120
+ icon: Brain,
121
+ test: (p) => p.startsWith('/memory/identities'),
122
+ titleKey: 'navigation.memoryIdentities',
123
+ },
124
+ {
125
+ icon: Brain,
126
+ test: (p) => p.startsWith('/memory/contexts'),
127
+ titleKey: 'navigation.memoryContexts',
128
+ },
129
+ {
130
+ icon: Brain,
131
+ test: (p) => p.startsWith('/memory/preferences'),
132
+ titleKey: 'navigation.memoryPreferences',
133
+ },
134
+ {
135
+ icon: Brain,
136
+ test: (p) => p.startsWith('/memory/experiences'),
137
+ titleKey: 'navigation.memoryExperiences',
138
+ },
139
+ {
140
+ icon: Brain,
141
+ test: (p) => p.startsWith('/memory'),
142
+ titleKey: 'navigation.memory',
143
+ },
144
+
145
+ // Image routes
146
+ {
147
+ icon: Image,
148
+ test: (p) => p.startsWith('/image'),
149
+ titleKey: 'navigation.image',
150
+ },
151
+
152
+ // Page routes - use dynamic title for specific page names
153
+ {
154
+ icon: FileText,
155
+ test: (p) => p.startsWith('/page/'),
156
+ titleKey: 'navigation.page',
157
+ useDynamicTitle: true,
158
+ },
159
+ {
160
+ icon: FileText,
161
+ test: (p) => p === '/page',
162
+ titleKey: 'navigation.pages',
163
+ },
164
+
165
+ // Onboarding
166
+ {
167
+ icon: Rocket,
168
+ test: (p) => p.startsWith('/desktop-onboarding') || p.startsWith('/onboarding'),
169
+ titleKey: 'navigation.onboarding',
170
+ },
171
+
172
+ // Home (default)
173
+ {
174
+ icon: Home,
175
+ test: (p) => p === '/' || p === '',
176
+ titleKey: 'navigation.home',
177
+ },
178
+ ];
179
+
180
+ /**
181
+ * Get route metadata based on pathname
182
+ * @param pathname - The current route pathname
183
+ * @returns Route metadata with titleKey, icon, and useDynamicTitle flag
184
+ */
185
+ export const getRouteMetadata = (pathname: string): RouteMetadata => {
186
+ // Find the first matching pattern
187
+ for (const pattern of routePatterns) {
188
+ if (pattern.test(pathname)) {
189
+ return {
190
+ icon: pattern.icon,
191
+ titleKey: pattern.titleKey,
192
+ useDynamicTitle: pattern.useDynamicTitle,
193
+ };
194
+ }
195
+ }
196
+
197
+ // Default fallback
198
+ return {
199
+ icon: Circle,
200
+ titleKey: 'navigation.lobehub',
201
+ };
202
+ };
203
+
204
+ /**
205
+ * Get route icon based on pathname or URL
206
+ * @param url - The route URL (may include query string)
207
+ * @returns LucideIcon component or undefined
208
+ */
209
+ export const getRouteIcon = (url: string): LucideIcon | undefined => {
210
+ // Extract pathname from URL
211
+ const pathname = url.split('?')[0];
212
+ const metadata = getRouteMetadata(pathname);
213
+ return metadata.icon;
214
+ };