Compare commits

...

155 Commits

Author SHA1 Message Date
ONLY-yours ebd4bbaf0f feat: add exportAndUploadFile in sandbox servers 2026-01-19 18:41:10 +08:00
arvinxx 83ff13680e fix memory schema 2026-01-19 18:28:38 +08:00
arvinxx d2bb7d1ad8 fix batch async tasks 2026-01-19 17:09:02 +08:00
arvinxx 26c1d09411 fix 2026-01-19 17:05:39 +08:00
arvinxx a24cb4f9c9 fix 2026-01-19 17:01:16 +08:00
arvinxx 5975a45666 fix cloud sandbox in server agent runtime 2026-01-19 14:51:22 +08:00
arvinxx ea6900b44c fix cloud sandbox in server agent runtime 2026-01-19 14:09:33 +08:00
arvinxx 78a7274172 improve task message issue 2026-01-19 13:55:33 +08:00
arvinxx 63fc0f3ed4 refactor the cloud sandbox in client mode 2026-01-19 13:55:32 +08:00
arvinxx 3c8c478846 fix server agent task run with headless 2026-01-19 13:55:32 +08:00
lobehubbot 37e59245d0 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-19 05:47:25 +00:00
semantic-release-bot f0af810849 🔖 chore(release): v2.0.0-next.311 [skip ci]
## [Version 2.0.0-next.311](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.310...v2.0.0-next.311)
<sup>Released on **2026-01-19**</sup>

#### ♻ Code Refactoring

- **misc**: Refactor market sdk into market servers.

<br/>

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

#### Code refactoring

* **misc**: Refactor market sdk into market servers, closes [#11604](https://github.com/lobehub/lobe-chat/issues/11604) ([858cc20](https://github.com/lobehub/lobe-chat/commit/858cc20))

</details>

<div align="right">

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

</div>
2026-01-19 05:45:32 +00:00
Shinji-Li 858cc20a5b ♻️ refactor: refactor market sdk into market servers (#11604)
* refactor: refactor market sdk into market servers

* fix: fixed the test failed problem
2026-01-19 13:25:54 +08:00
YuTengjing 2c1af8a728 feat(router-runtime): add fallback options (#11531) 2026-01-19 11:28:54 +08:00
René Wang 475905f622 fix: Chat minimap not working (#11598)
* fix: chat minimap

* fix: chat minimap
2026-01-19 11:04:00 +08:00
lobehubbot dd6a9f94f0 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-19 02:18:23 +00:00
semantic-release-bot dad9eaf023 🔖 chore(release): v2.0.0-next.310 [skip ci]
## [Version&nbsp;2.0.0-next.310](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.309...v2.0.0-next.310)
<sup>Released on **2026-01-19**</sup>

#### 💄 Styles

- **misc**: Update i18n.

<br/>

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

#### Styles

* **misc**: Update i18n, closes [#11596](https://github.com/lobehub/lobe-chat/issues/11596) ([b02d26c](https://github.com/lobehub/lobe-chat/commit/b02d26c))

</details>

<div align="right">

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

</div>
2026-01-19 02:16:34 +00:00
LobeHub Bot b02d26c14e 🤖 style: update i18n (#11596)
💄 style: update i18n

Co-authored-by: canisminor1990 <17870709+canisminor1990@users.noreply.github.com>
2026-01-19 09:56:54 +08:00
Arvin Xu ba34e92523 test: fix pages e2e test (#11594)
fix pages
2026-01-19 09:56:38 +08:00
lobehubbot f08e6aec2b 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-18 19:37:03 +00:00
semantic-release-bot d4e9db9859 🔖 chore(release): v2.0.0-next.309 [skip ci]
## [Version&nbsp;2.0.0-next.309](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.308...v2.0.0-next.309)
<sup>Released on **2026-01-18**</sup>

#### 🐛 Bug Fixes

- **misc**: Fix group sub task execution.

<br/>

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

#### What's fixed

* **misc**: Fix group sub task execution, closes [#11595](https://github.com/lobehub/lobe-chat/issues/11595) ([32be2b2](https://github.com/lobehub/lobe-chat/commit/32be2b2))

</details>

<div align="right">

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

</div>
2026-01-18 19:35:18 +00:00
Arvin Xu 32be2b2882 🐛 fix: fix group sub task execution (#11595)
* update memory manifest

* support console error in the server with subagent task

*  test(agent-service): add unit tests for getAgentConfig method

Add 7 test cases for the new getAgentConfig(idOrSlug) method:
- Return null if agent does not exist
- Support lookup by agent id
- Support lookup by slug
- Merge DEFAULT_AGENT_CONFIG and serverDefaultAgentConfig
- Use default model/provider when agent has none
- Prioritize agent model/provider over defaults
- Merge user default agent config

Relates to: LOBE-3514

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

*  feat(group-agent-builder): add GetAgentInfo Inspector

Add Inspector component for getAgentInfo API to display agent avatar
and name in the tool call UI.

Changes:
- Add GetAgentInfoInspector component with avatar and title display
- Register inspector in GroupAgentBuilderInspectors registry
- Add i18n translations for en-US and zh-CN

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix lobehub manifest temporarily

* fix twitter calling

* 🔧 chore: remove unused serializeError function

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* 🐛 fix(test): fix execAgent.threadId test mock for AgentService

Add AgentService mock and use importOriginal for model-bank mock
to fix test failures after refactoring to use AgentService.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 03:16:02 +08:00
lobehubbot ef27ed0824 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-18 17:52:31 +00:00
semantic-release-bot 69755cfd18 🔖 chore(release): v2.0.0-next.308 [skip ci]
## [Version&nbsp;2.0.0-next.308](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.307...v2.0.0-next.308)
<sup>Released on **2026-01-18**</sup>

#### 🐛 Bug Fixes

- **misc**: Fix group subagent task issue.

#### 💄 Styles

- **misc**: Update i18n.

<br/>

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

#### What's fixed

* **misc**: Fix group subagent task issue, closes [#11589](https://github.com/lobehub/lobe-chat/issues/11589) ([9ad468b](https://github.com/lobehub/lobe-chat/commit/9ad468b))

#### Styles

* **misc**: Update i18n, closes [#11482](https://github.com/lobehub/lobe-chat/issues/11482) ([676611e](https://github.com/lobehub/lobe-chat/commit/676611e))

</details>

<div align="right">

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

</div>
2026-01-18 17:50:36 +00:00
Arvin Xu 9ad468be06 🐛 fix: fix group subagent task issue (#11589)
* improve WriteFile feeling

* refactor exector

* improve task title

* fix flick

* improve i18n

* fix tests
2026-01-19 01:30:59 +08:00
yliu7949 b4d103b438 💄style: add deepseek-math-v2 for Qiniu provider (#10823)
* style(): add deepseek-math-v2 for Qiniu provider

* style(): rewrite description of deepseek-math-v2 in English
2026-01-19 01:25:06 +08:00
LobeHub Bot 676611e5bd 🤖 style: update i18n (#11482)
💄 style: update i18n

Co-authored-by: canisminor1990 <17870709+canisminor1990@users.noreply.github.com>
2026-01-19 01:13:02 +08:00
Innei 69c038a36b 🔧 chore: cleanup redundant displayName properties (#11591) 2026-01-19 00:01:12 +08:00
lobehubbot 7d151c51d2 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-18 15:57:47 +00:00
semantic-release-bot b733e35f58 🔖 chore(release): v2.0.0-next.307 [skip ci]
## [Version&nbsp;2.0.0-next.307](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.306...v2.0.0-next.307)
<sup>Released on **2026-01-18**</sup>

#### 🐛 Bug Fixes

- **upload**: Resolve file upload button unresponsive issue.
- **misc**: Fixed the createGroupWithSupervisor function test, slove when use copy & install group from market, the member system Role is lost.

<br/>

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

#### What's fixed

* **upload**: Resolve file upload button unresponsive issue, closes [#11588](https://github.com/lobehub/lobe-chat/issues/11588) ([76fd478](https://github.com/lobehub/lobe-chat/commit/76fd478))
* **misc**: Fixed the createGroupWithSupervisor function test, closes [#11590](https://github.com/lobehub/lobe-chat/issues/11590) ([83bb343](https://github.com/lobehub/lobe-chat/commit/83bb343))
* **misc**: Slove when use copy & install group from market, the member system Role is lost, closes [#11585](https://github.com/lobehub/lobe-chat/issues/11585) ([9b73ad7](https://github.com/lobehub/lobe-chat/commit/9b73ad7))

</details>

<div align="right">

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

</div>
2026-01-18 15:55:51 +00:00
Innei 76fd478752 🐛 fix(upload): resolve file upload button unresponsive issue (#11588)
* 🐛 fix(upload): resolve file upload button unresponsive issue

The file upload dropdown menu was not properly handling the interaction
between the dropdown and the Upload component, causing the menu to block
file selection events.

Changes:
- Add controlled open state for upload dropdown
- Mark upload menu items with closeOnClick: false to prevent premature closing
- Manually close dropdown after file selection completes
- Enhance ActionDropdown to support interactive elements with proper event handling
- Add scheduleClose functionality for delayed menu closing

Closes LOBE-3503

* 🔧 chore: update package dependencies and enhance VSCode settings

- Bump version of @lobehub/ui to ^4.22.0 in package.json.
- Update VSCode settings to exclude additional locale directories from search, improving performance and relevance.

Signed-off-by: Innei <tukon479@gmail.com>

---------

Signed-off-by: Innei <tukon479@gmail.com>
2026-01-18 23:36:15 +08:00
Shinji-Li 83bb343066 🐛 fix: fixed the createGroupWithSupervisor function test (#11590)
fix: fixed the createGroupWithSupervisor function test
2026-01-18 23:26:52 +08:00
Arvin Xu 51d2eae0dc 🐛 fix: fix group manage tools and supervisor config resolve (#11586)
* fix group management and agent builder

* try to fix group agent config id

* refactor local-system prompt
2026-01-18 23:01:01 +08:00
Shinji-Li 9b73ad78f9 🐛 fix: slove when use copy & install group from market, the member system Role is lost (#11585)
fix: slove when use copy & install group from market, the member systemRole not found
2026-01-18 22:24:22 +08:00
lobehubbot a6754c38a5 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-18 14:13:30 +00:00
semantic-release-bot aedfdff8c6 🔖 chore(release): v2.0.0-next.306 [skip ci]
## [Version&nbsp;2.0.0-next.306](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.305...v2.0.0-next.306)
<sup>Released on **2026-01-18**</sup>

#### 🐛 Bug Fixes

- **misc**: Fix supervisor id issue.

<br/>

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

#### What's fixed

* **misc**: Fix supervisor id issue, closes [#11584](https://github.com/lobehub/lobe-chat/issues/11584) ([c097584](https://github.com/lobehub/lobe-chat/commit/c097584))

</details>

<div align="right">

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

</div>
2026-01-18 14:11:35 +00:00
Arvin Xu c09758409f 🐛 fix: fix supervisor id issue (#11584)
* fix group supervisor issue

* fix identity memory id

* fix supervisor message

* fix bug

* fix loading issue

* add debug mode
2026-01-18 21:52:07 +08:00
LobeHub Bot a9e320b893 test: add unit tests for parameter precedence in modelParamsResolver (#11581)
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-18 21:31:25 +08:00
Arvin Xu 171f328ff1 test: improve community e2e testing (#11534)
* improve community e2e testing

* fix steps
2026-01-18 21:31:05 +08:00
lobehubbot 14b9bf7353 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-18 11:17:11 +00:00
semantic-release-bot 1b403bd9f5 🔖 chore(release): v2.0.0-next.305 [skip ci]
## [Version&nbsp;2.0.0-next.305](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.304...v2.0.0-next.305)
<sup>Released on **2026-01-18**</sup>

#### 🐛 Bug Fixes

- **desktop**: Add auth required modal and improve error handling.

<br/>

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

#### What's fixed

* **desktop**: Add auth required modal and improve error handling, closes [#11574](https://github.com/lobehub/lobe-chat/issues/11574) ([4e5a516](https://github.com/lobehub/lobe-chat/commit/4e5a516))

</details>

<div align="right">

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

</div>
2026-01-18 11:15:18 +00:00
Innei 4e5a516865 🐛 fix(desktop): add auth required modal and improve error handling (#11574)
* 🐛 fix(desktop): add auth required modal and improve error handling

- Add AuthRequiredModal component to handle authentication expiration
- Improve backend proxy protocol error handling for auth errors
- Add updater manager authentication header support
- Add i18n strings for auth error messages

* 🔧 fix(desktop): update UpdaterManager to leave channel unset for GitHub prerelease matching

- Modify UpdaterManager to leave the channel unset, allowing GitHub to use version tags for prerelease matching.
- Update logging to reflect the new behavior when the channel is unset or kept as is.

Signed-off-by: Innei <tukon479@gmail.com>

* 🔧 fix(desktop): clarify UpdaterManager behavior for GitHub provider

- Update comments and logging in UpdaterManager to clarify that the channel is left unset for beta/nightly, allowing GitHub to use version tags for prerelease matching.
- Ensure logging accurately reflects the new behavior when the channel is unset.

Signed-off-by: Innei <tukon479@gmail.com>

*  feat(desktop): add desktop build channel script and update documentation

- Introduced a new script for building desktop applications for specific release channels (stable, beta, nightly).
- Updated package.json to include a new npm command for the build channel.
- Enhanced README documentation to guide users on simulating CI channel builds and retaining changes.

Signed-off-by: Innei <tukon479@gmail.com>

* 🔧 fix(desktop): streamline NODE_ENV usage in logger and config

- Removed redundant process.env.NODE_ENV definition from electron.vite.config.ts.
- Simplified logger implementation by directly using process.env.NODE_ENV for environment checks.
- Improved readability and maintainability of logging behavior based on the environment.

Signed-off-by: Innei <tukon479@gmail.com>

* 🔧 fix(desktop): enhance logging configuration to support debug mode

- Updated logger configuration to allow for debug level logging when DEBUG environment variable is set.
- Simplified the logic for console logging levels based on the environment, improving clarity and maintainability.

Signed-off-by: Innei <tukon479@gmail.com>

* 🔧 fix(desktop): enhance version generation and logging in UpdaterManager

- Updated version generation logic in manual-build-desktop.yml to handle channel suffixes more effectively.
- Added inferredChannel logging in UpdaterManager to improve clarity on the current update channel being used.

Signed-off-by: Innei <tukon479@gmail.com>

* 🔧 fix(desktop): update localization files and set default entry locale to English

- Changed default entry locale from Chinese (zh-CN) to English (en) in .i18nrc.js.
- Added full disk access messages in multiple languages (Arabic, Bulgarian, German, Spanish, French, Italian, Japanese, Korean, Dutch, Polish, Portuguese, Russian, Turkish, Vietnamese, Traditional Chinese).
- Enhanced menu localization with new settings and permissions options across various languages.

Signed-off-by: Innei <tukon479@gmail.com>

---------

Signed-off-by: Innei <tukon479@gmail.com>
2026-01-18 18:55:18 +08:00
Shinji-Li dd4cbcce7a feat agent builder add more skill, improve auto install plugins oauth or market install (#11582)
* fix: add the lost lobehub skill & market mcp install in agent builder install tools call

* fix: imporve when call install tools render ui
2026-01-18 16:10:37 +08:00
lobehubbot 34f96dac4b 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-18 04:24:28 +00:00
semantic-release-bot 7c9eafe3db 🔖 chore(release): v2.0.0-next.304 [skip ci]
## [Version&nbsp;2.0.0-next.304](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.303...v2.0.0-next.304)
<sup>Released on **2026-01-18**</sup>

#### 💄 Styles

- **misc**: Improve auto scroll and loading hint.

<br/>

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

#### Styles

* **misc**: Improve auto scroll and loading hint, closes [#11579](https://github.com/lobehub/lobe-chat/issues/11579) ([277b42d](https://github.com/lobehub/lobe-chat/commit/277b42d))

</details>

<div align="right">

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

</div>
2026-01-18 04:22:40 +00:00
LobeHub Bot d33d374a11 🌐 chore: translate non-English comments to English in database & desktop modules (#11578)
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-18 12:02:52 +08:00
Arvin Xu 277b42d3d9 💄 style: improve auto scroll and loading hint (#11579)
* improve operation hint

* improve i18n

* try to fix auto scroll
2026-01-18 12:02:08 +08:00
lobehubbot 9417652c73 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-18 03:11:23 +00:00
semantic-release-bot 749177c588 🔖 chore(release): v2.0.0-next.303 [skip ci]
## [Version&nbsp;2.0.0-next.303](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.302...v2.0.0-next.303)
<sup>Released on **2026-01-18**</sup>

#### 💄 Styles

- **misc**: Improve operation hint and fix scroll issue.

<br/>

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

#### Styles

* **misc**: Improve operation hint and fix scroll issue, closes [#11573](https://github.com/lobehub/lobe-chat/issues/11573) ([8505d14](https://github.com/lobehub/lobe-chat/commit/8505d14))

</details>

<div align="right">

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

</div>
2026-01-18 03:09:38 +00:00
Arvin Xu 8505d14a0d 💄 style: improve operation hint and fix scroll issue (#11573)
improve operation hint
2026-01-18 10:49:50 +08:00
lobehubbot 013a643752 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-17 17:39:46 +00:00
semantic-release-bot 271d56ec9e 🔖 chore(release): v2.0.0-next.302 [skip ci]
## [Version&nbsp;2.0.0-next.302](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.301...v2.0.0-next.302)
<sup>Released on **2026-01-17**</sup>

#### 🐛 Bug Fixes

- **misc**: Try to fix group supervisor id not sync successful.

#### 💄 Styles

- **misc**: Fix left panel on group page.

<br/>

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

#### What's fixed

* **misc**: Try to fix group supervisor id not sync successful, closes [#11570](https://github.com/lobehub/lobe-chat/issues/11570) ([ef51c17](https://github.com/lobehub/lobe-chat/commit/ef51c17))

#### Styles

* **misc**: Fix left panel on group page, closes [#11571](https://github.com/lobehub/lobe-chat/issues/11571) ([de81a42](https://github.com/lobehub/lobe-chat/commit/de81a42))

</details>

<div align="right">

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

</div>
2026-01-17 17:37:45 +00:00
Arvin Xu de81a42213 💄 style: fix left panel on group page (#11571)
fix left panel
2026-01-18 01:15:21 +08:00
Arvin Xu ef51c17f59 🐛 fix: try to fix group supervisor id not sync successful (#11570)
* improve code

* fix share button suspense

* update streaming issue

* improve

* fix share button issue

* fix store sync
2026-01-18 01:10:43 +08:00
LobeHub Bot dea1b25e2d 🌐 chore: translate non-English comments to English in model-runtime/src/core/streams (#11506)
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-17 19:07:07 +08:00
Arvin Xu 4c1cff9fc8 🔧 chore: use pnpm for Vercel install command (#11568)
update
2026-01-17 18:48:19 +08:00
LobeHub Bot 311d4fec6d 🌐 chore: translate non-English comments to English in model-bank (#11544)
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-17 18:35:56 +08:00
lobehubbot 7fdd47bbfe 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-17 09:20:48 +00:00
semantic-release-bot 78cf897467 🔖 chore(release): v2.0.0-next.301 [skip ci]
## [Version&nbsp;2.0.0-next.301](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.300...v2.0.0-next.301)
<sup>Released on **2026-01-17**</sup>

#### 🐛 Bug Fixes

- **desktop**: Ensure allowPrerelease is set correctly for updater.

<br/>

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

#### What's fixed

* **desktop**: Ensure allowPrerelease is set correctly for updater, closes [#11566](https://github.com/lobehub/lobe-chat/issues/11566) ([9383c6b](https://github.com/lobehub/lobe-chat/commit/9383c6b))

</details>

<div align="right">

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

</div>
2026-01-17 09:19:09 +00:00
Innei 9383c6be44 🐛 fix(desktop): ensure allowPrerelease is set correctly for updater (#11566)
* 🐛 fix(desktop): ensure allowPrerelease is set correctly for updater

- Add explicit allowPrerelease check before each update check
- Ensure allowPrerelease is re-applied after setFeedURL call
- Guard against potential internal state resets by electron-updater

*  feat(updater): Enhance prerelease handling for update checks

- Ensure `allowPrerelease` is set correctly before and after update checks to accommodate internal state resets by `electron-updater`.
- Added logging to indicate the configuration of the GitHub update URL and the `allowPrerelease` status.

Signed-off-by: Innei <tukon479@gmail.com>

---------

Signed-off-by: Innei <tukon479@gmail.com>
2026-01-17 16:59:51 +08:00
lobehubbot 23fa8a2745 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-17 05:00:55 +00:00
semantic-release-bot fdfd224402 🔖 chore(release): v2.0.0-next.300 [skip ci]
## [Version&nbsp;2.0.0-next.300](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.299...v2.0.0-next.300)
<sup>Released on **2026-01-17**</sup>

#### 🐛 Bug Fixes

- **misc**: Fix supervisor group prompt.

<br/>

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

#### What's fixed

* **misc**: Fix supervisor group prompt, closes [#11543](https://github.com/lobehub/lobe-chat/issues/11543) ([3a6efbc](https://github.com/lobehub/lobe-chat/commit/3a6efbc))

</details>

<div align="right">

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

</div>
2026-01-17 04:59:08 +00:00
Arvin Xu 3a6efbcbad 🐛 fix: fix supervisor group prompt (#11543)
* update content

* improve i18n

* update i18n

* fix group supervisor prompts

* update
2026-01-17 12:39:30 +08:00
lobehubbot ac0a99f18d 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-17 02:27:19 +00:00
semantic-release-bot 7fb22c7315 🔖 chore(release): v2.0.0-next.299 [skip ci]
## [Version&nbsp;2.0.0-next.299](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.298...v2.0.0-next.299)
<sup>Released on **2026-01-17**</sup>

#### ♻ Code Refactoring

- **ui**: Migrate from Dropdown to DropdownMenu/ContextMenuTrigger components.

#### 🐛 Bug Fixes

- **misc**: Fix topic messages display error when switch topic quickly.

<br/>

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

#### Code refactoring

* **ui**: Migrate from Dropdown to DropdownMenu/ContextMenuTrigger components, closes [#11539](https://github.com/lobehub/lobe-chat/issues/11539) ([9c9d4b1](https://github.com/lobehub/lobe-chat/commit/9c9d4b1))

#### What's fixed

* **misc**: Fix topic messages display error when switch topic quickly, closes [#11542](https://github.com/lobehub/lobe-chat/issues/11542) ([371d91e](https://github.com/lobehub/lobe-chat/commit/371d91e))

</details>

<div align="right">

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

</div>
2026-01-17 02:25:30 +00:00
Arvin Xu 371d91e091 🐛 fix: fix topic messages display error when switch topic quickly (#11542)
* fix Switch topic issues

* clean document

* add abortable mode
2026-01-17 10:05:51 +08:00
Innei 9c9d4b17a9 ♻️ refactor(ui): migrate from Dropdown to DropdownMenu/ContextMenuTrigger components (#11539)
*  feat(mcp): improve system dependency checks and error handling

- Refactored checkSystemDependency method to streamline command execution and error handling.
- Removed unnecessary npx command for package version checks, simplifying the installation verification process.
- Enhanced error handling in InstallError component for better user feedback during MCP installation failures.
- Updated UpdaterManager to prevent auto-update checks in development mode.

Signed-off-by: Innei <tukon479@gmail.com>

*  feat: update ChatWithModel component to use DropdownMenu

- Replaced the Dropdown component with DropdownMenu for better integration with UI library.
- Added type definitions for DropdownMenuProps to support new properties.

Signed-off-by: Innei <tukon479@gmail.com>

---------

Signed-off-by: Innei <tukon479@gmail.com>
2026-01-17 00:41:39 +08:00
lobehubbot e97a99d835 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-16 14:37:34 +00:00
semantic-release-bot dd0304f5b1 🔖 chore(release): v2.0.0-next.298 [skip ci]
## [Version&nbsp;2.0.0-next.298](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.297...v2.0.0-next.298)
<sup>Released on **2026-01-16**</sup>

#### 🐛 Bug Fixes

- **misc**: Fix switch skill in home.

<br/>

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

#### What's fixed

* **misc**: Fix switch skill in home, closes [#11537](https://github.com/lobehub/lobe-chat/issues/11537) ([d5561f3](https://github.com/lobehub/lobe-chat/commit/d5561f3))

</details>

<div align="right">

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

</div>
2026-01-16 14:35:50 +00:00
Arvin Xu d5561f3b70 🐛 fix: fix switch skill in home (#11537)
* fix switch skill in home

* clean
2026-01-16 22:16:33 +08:00
lobehubbot 67bb9f64aa 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-16 12:27:35 +00:00
semantic-release-bot cc459167f0 🔖 chore(release): v2.0.0-next.297 [skip ci]
## [Version&nbsp;2.0.0-next.297](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.296...v2.0.0-next.297)
<sup>Released on **2026-01-16**</sup>

####  Features

- **misc**: Add agent group publish into market & use market group agents in lobehub.

<br/>

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

#### What's improved

* **misc**: Add agent group publish into market & use market group agents in lobehub, closes [#11535](https://github.com/lobehub/lobe-chat/issues/11535) ([02b9e76](https://github.com/lobehub/lobe-chat/commit/02b9e76))

</details>

<div align="right">

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

</div>
2026-01-16 12:25:46 +00:00
Shinji-Li 02b9e76bb9 feat: add agent group publish into market & use market group agents in lobehub (#11535)
* feat: add the publihs group button into group profiles

* feat: add agent group detail page

* feat: the /community/group_agent pages inital

* feat: upload the agent group detail get fixed

* feat: add the agent group add it to user way

* feat: add agent group in agents list & item update

* feat: update the market-sdk

* feat: add group active tab as overview default
2026-01-16 20:06:18 +08:00
lobehubbot 5bf204b112 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-16 10:51:23 +00:00
semantic-release-bot bcf219f106 🔖 chore(release): v2.0.0-next.296 [skip ci]
## [Version&nbsp;2.0.0-next.296](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.295...v2.0.0-next.296)
<sup>Released on **2026-01-16**</sup>

#### 💄 Styles

- **misc**: Improve todo list.

<br/>

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

#### Styles

* **misc**: Improve todo list, closes [#11533](https://github.com/lobehub/lobe-chat/issues/11533) ([a4b71e9](https://github.com/lobehub/lobe-chat/commit/a4b71e9))

</details>

<div align="right">

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

</div>
2026-01-16 10:49:35 +00:00
Arvin Xu a4b71e97cd 💄 style: improve todo list (#11533)
* fix todo with wip

* update

* fix tests

* fix url
2026-01-16 18:30:12 +08:00
lobehubbot 212d8e3630 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-16 08:33:17 +00:00
René Wang ad7961f9be fix: resource problem (#11519)
* fix: Cannot reach end

* fix: Cannot reach end

* fix: Cannot move item to root

* fix: Cannot move item to root

* fix: Cannot move item to root

* style: Droppable area style

* style: library head
2026-01-16 16:13:56 +08:00
Arvin Xu 488175a143 feat: improve memory tool display and refactor tool render (#11525)
* fix memory display

* refactor assisant group tool

* rename render to detail

* refactor

* feat support display message tool ui

* fix memory issue
2026-01-16 10:05:31 +08:00
lobehubbot b04faaf525 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-15 16:49:13 +00:00
semantic-release-bot 108c153d3f 🔖 chore(release): v2.0.0-next.295 [skip ci]
## [Version&nbsp;2.0.0-next.295](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.294...v2.0.0-next.295)
<sup>Released on **2026-01-15**</sup>

#### ♻ Code Refactoring

- **misc**: Migrate Next.js navigation APIs to React Router for SPA.

<br/>

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

#### Code refactoring

* **misc**: Migrate Next.js navigation APIs to React Router for SPA, closes [#11394](https://github.com/lobehub/lobe-chat/issues/11394) ([2253d46](https://github.com/lobehub/lobe-chat/commit/2253d46))

</details>

<div align="right">

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

</div>
2026-01-15 16:47:34 +00:00
Innei 2253d46ee0 ♻️ refactor: migrate Next.js navigation APIs to React Router for SPA (#11394)
This implements Phase 3 of LOBE-2850, establishing a dual routing architecture
for the Next.js to Vite React Router SPA migration.

## Changes

### New wrapper modules
- `src/libs/next/` - Next.js wrappers for auth pages (Link, Image, navigation, dynamic)
- `src/libs/router/` - React Router wrappers for SPA routes (Link, navigation hooks)

### Link component updates
- External links (`http://`, `https://`) → native `<a>` tags
- SPA internal routes → React Router `<Link>`
- Auth pages → Next.js `<Link>` (preserved)
- Global `src/components/Link.tsx` → smart component for ConfigProvider

### Navigation hook updates
- SPA routes use `@/libs/router/navigation` (useRouter, usePathname, useSearchParams)
- Auth pages use `@/libs/next/navigation`
- GlobalProvider uses `window.location` (outside Router context)

### Architecture
```
GlobalProvider (no Router context)
└── AppTheme + ConfigProvider
    ├── Auth pages (Next.js routing)
    └── SPA Router (BrowserRouter)
        └── SPA pages (React Router)
```

Resolves LOBE-2850
2026-01-16 00:28:15 +08:00
lobehubbot 85c793b1d0 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-15 15:54:13 +00:00
semantic-release-bot 6d35c4abbd 🔖 chore(release): v2.0.0-next.294 [skip ci]
## [Version&nbsp;2.0.0-next.294](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.293...v2.0.0-next.294)
<sup>Released on **2026-01-15**</sup>

#### 🐛 Bug Fixes

- **chat**: Reset activeTopicId when switching agent/group.
- **mcp**: Fix installation check hanging issue in desktop app.

<br/>

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

#### What's fixed

* **chat**: Reset activeTopicId when switching agent/group, closes [#11523](https://github.com/lobehub/lobe-chat/issues/11523) ([fde54b0](https://github.com/lobehub/lobe-chat/commit/fde54b0))
* **mcp**: Fix installation check hanging issue in desktop app, closes [#11524](https://github.com/lobehub/lobe-chat/issues/11524) ([b9341c3](https://github.com/lobehub/lobe-chat/commit/b9341c3))

</details>

<div align="right">

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

</div>
2026-01-15 15:52:23 +00:00
Arvin Xu fde54b064e 🐛 fix(chat): reset activeTopicId when switching agent/group (#11523)
When switching agents via dropdown menu, the activeTopicId was not being
reset, causing messages to be saved to the wrong topic bucket. This fix
adds useEffect hooks to AgentIdSync and GroupIdSync components to detect
agent/group changes and synchronously reset activeTopicId.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-15 23:32:39 +08:00
Innei b9341c3183 🐛 fix(mcp): fix installation check hanging issue in desktop app (#11524)
*  feat(mcp): enhance error handling and logging for MCP connections

- Introduced MCPConnectionError class to capture and log stderr output during MCP connections.
- Updated McpCtr to handle MCPConnectionError and provide enhanced error messages with stderr logs.
- Modified MCPClient to collect stderr logs and throw enhanced errors when connection issues occur.
- Improved error display in MCPManifestForm to show detailed error information when connection tests fail.
- Added utility functions to parse and extract STDIO process output from error messages.

Signed-off-by: Innei <tukon479@gmail.com>

* 🐛 fix(mcp): remove npx check to prevent hanging during installation check

- Remove `npx -y` package check that could download packages or start MCP servers
- This was causing the UI to hang on "Checking installation environment"
- npm packages don't require pre-installation, npx handles on-demand download

---------

Signed-off-by: Innei <tukon479@gmail.com>
2026-01-15 23:30:05 +08:00
Innei 4dad2ec5ad 🔧 ci(release): improve stable/beta release detection logic (#11522)
- Add 'next' version support to beta release workflow
- Simplify stable version detection by checking for '-' suffix instead of enumerating prerelease identifiers
- Fix manual trigger flow in stable release workflow
2026-01-15 20:50:14 +08:00
semantic-release-bot e4e93f86f3 🔖 chore(release): v2.0.0-next.293 [skip ci]
## [Version&nbsp;2.0.0-next.293](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.292...v2.0.0-next.293)
<sup>Released on **2026-01-15**</sup>

####  Features

- **desktop**: Add desktop release service and API endpoint.

<br/>

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

#### What's improved

* **desktop**: Add desktop release service and API endpoint, closes [#11520](https://github.com/lobehub/lobe-chat/issues/11520) ([e3dc5be](https://github.com/lobehub/lobe-chat/commit/e3dc5be))

</details>

<div align="right">

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

</div>
2026-01-15 12:48:53 +00:00
Innei e3dc5bede9 feat(desktop): add desktop release service and API endpoint (#11520)
feat(desktop): add desktop release service and API endpoint

ci: add macOS Intel build option to release workflow
test: add tests for desktop release service
refactor: create validation middleware for API routes
2026-01-15 20:30:44 +08:00
lobehubbot 33088ee0c7 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-15 09:56:20 +00:00
semantic-release-bot 8712767b8b 🔖 chore(release): v2.0.0-next.292 [skip ci]
## [Version&nbsp;2.0.0-next.292](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.291...v2.0.0-next.292)
<sup>Released on **2026-01-15**</sup>

#### ♻ Code Refactoring

- **misc**: Use fallbackData to prevent useActionSWR auto-fetch.

####  Features

- **desktop**: Add local update testing scripts and stable channel API version check.

<br/>

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

#### Code refactoring

* **misc**: Use fallbackData to prevent useActionSWR auto-fetch, closes [#11514](https://github.com/lobehub/lobe-chat/issues/11514) ([d446163](https://github.com/lobehub/lobe-chat/commit/d446163))

#### What's improved

* **desktop**: Add local update testing scripts and stable channel API version check, closes [#11474](https://github.com/lobehub/lobe-chat/issues/11474) [#11513](https://github.com/lobehub/lobe-chat/issues/11513) ([959c210](https://github.com/lobehub/lobe-chat/commit/959c210))

</details>

<div align="right">

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

</div>
2026-01-15 09:54:40 +00:00
YuTengjing d44616354e ♻️ refactor: use fallbackData to prevent useActionSWR auto-fetch (#11514) 2026-01-15 17:34:49 +08:00
Innei 959c210e86 feat(desktop): add local update testing scripts and stable channel API version check (#11474)
* chore: stable updater

*  feat: add local update testing scripts and configuration

- Introduced scripts for local update testing, including setup, server management, and manifest generation.
- Added `dev-app-update.local.yml` for local server configuration.
- Implemented `generate-manifest.sh` to create update manifests.
- Created `run-test.sh` for streamlined testing process.
- Updated `README.md` with instructions for local testing setup and usage.
- Enhanced `UpdaterManager` to allow forced use of dev update configuration in packaged apps.

Signed-off-by: Innei <tukon479@gmail.com>

* 🐛 fix(desktop): update UpdaterManager test mocks for new exports

Add missing mock exports for @/modules/updater/configs:
- isStableChannel
- githubConfig
- UPDATE_SERVER_URL

Add mock for @/env with getDesktopEnv
Add setFeedURL method to autoUpdater mock

*  feat: add Conductor setup scripts and configuration

*  feat: enhance update modal functionality and refactor modal hooks

- Added `useUpdateModal` for managing update modal state and behavior.
- Refactored `UpdateModal` to utilize new modal management approach.
- Improved `useWatchBroadcast` integration for handling update events.
- Removed deprecated `createModalHooks` and related components from `FunctionModal`.
- Updated `AddFilesToKnowledgeBase` and `CreateNew` modals to use new modal context for closing behavior.

This refactor streamlines modal management and enhances the user experience during update processes.

Signed-off-by: Innei <tukon479@gmail.com>

* update flow (#11513)

* ci: simplify desktop release workflow and add renderer tarball

* 👷 ci: fix s3 upload credentials for desktop release

* 🐛 fix(ci): use compact jq output for GitHub Actions matrix

Add -c flag to jq commands to produce single-line JSON output,
fixing "Invalid format" error when setting GITHUB_OUTPUT.

* 🐛 fix(ci): add administration permission to detect self-hosted runner

The /actions/runners API requires administration:read permission
to list repository runners.

* 🔧 refactor(ci): use workflow input for self-hosted runner selection

Replace API-based runner detection with workflow input parameter since
GITHUB_TOKEN lacks permission to call /actions/runners API.

- Add `use_self_hosted_mac` input (default: true)
- Release events always use self-hosted runner
- Manual dispatch can toggle via input

* feat(updater): add stable channel support with fallback mechanism

- Configure electron-builder to generate stable-mac.yml for stable channel
- Update CI workflow to handle both stable and latest manifest files
- Implement fallback to GitHub provider when primary S3 provider fails
- Reset to primary provider after successful update check

* 🐛 fix(updater): remove invalid channel config from electron-builder

- Remove unsupported 'channel' property from electron-builder config
- Create stable*.yml files from latest*.yml in workflow instead
- This ensures electron-updater finds correct manifest for stable channel

* 🐛 fix(updater): use correct channel based on provider type

- S3 provider: channel='stable' → looks for stable-mac.yml
- GitHub provider: channel='latest' → looks for latest-mac.yml

This fixes the 404 error when falling back to GitHub releases,
which only have latest-mac.yml files.

* refactor(env): remove unused OFFICIAL_CLOUD_SERVER and update env defaults

Update environment variable handling by removing unused OFFICIAL_CLOUD_SERVER and setting defaults for UPDATE_CHANNEL and UPDATE_SERVER_URL from process.env during build stage.

* 🐛 fix(ci): add version prefix to stable manifest URLs for S3

S3 directory structure: stable/{version}/xxx.dmg
So stable-mac.yml URLs need version prefix:
  url: LobeHub-2.1.0-arm64.dmg → url: 2.1.1/LobeHub-2.1.0-arm64.dmg

*  feat(ci): add renderer tar manifest for integrity verification

Creates stable-renderer.yml with SHA512 checksum for lobehub-renderer.tar.gz
This allows the desktop app to verify renderer tarball integrity before extraction.

* 🐛 fix(ci): fix YAML syntax error in renderer manifest generation

*  feat(ci): archive manifest files in version directory

* refactor(ci): update desktop release workflows to streamline build process

- Removed unnecessary dependencies in the build job for the desktop beta workflow.
- Introduced a new gate job to conditionally proceed with publishing based on the success of previous jobs.
- Updated macOS file merging to depend on the new gate job instead of the build job.
- Simplified macOS runner selection logic in the stable workflow by using GitHub-hosted runners exclusively.

Signed-off-by: Innei <tukon479@gmail.com>

* refactor(electron): reorganize titlebar components and update imports

- Moved titlebar components to a new directory structure for better organization.
- Updated import paths for `SimpleTitleBar`, `TitleBar`, and related constants.
- Introduced new components for connection management and navigation within the titlebar.
- Added constants for title bar height to maintain consistency across components.

This refactor enhances the maintainability of the titlebar code and improves the overall structure of the Electron application.

Signed-off-by: Innei <tukon479@gmail.com>

* feat(ci): add release notes handling to desktop stable workflow

- Enhanced the desktop stable release workflow to include release notes.
- Updated output variables to capture release notes from the GitHub event.
- Adjusted environment variables in subsequent jobs to utilize the new release notes data.

This addition improves the clarity and documentation of releases by ensuring that release notes are included in the workflow process.

Signed-off-by: Innei <tukon479@gmail.com>

* 🐛 fix: call onClose after knowledge base modal closes

* 🧪 test: fix UpdaterManager update channel mocks

---------

Signed-off-by: Innei <tukon479@gmail.com>
2026-01-15 17:26:19 +08:00
lobehubbot 41801649b6 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-15 07:26:21 +00:00
semantic-release-bot 074af06588 🔖 chore(release): v2.0.0-next.291 [skip ci]
## [Version&nbsp;2.0.0-next.291](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.290...v2.0.0-next.291)
<sup>Released on **2026-01-15**</sup>

#### 🐛 Bug Fixes

- **settings**: Add instant UI feedback for provider config switches.
- **misc**: Click lobe ai topic trigger create new agent.

#### 💄 Styles

- **misc**: Improve agent loading state.

<br/>

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

#### What's fixed

* **settings**: Add instant UI feedback for provider config switches, closes [#11362](https://github.com/lobehub/lobe-chat/issues/11362) ([a758d01](https://github.com/lobehub/lobe-chat/commit/a758d01))
* **misc**: Click lobe ai topic trigger create new agent, closes [#11508](https://github.com/lobehub/lobe-chat/issues/11508) ([2443189](https://github.com/lobehub/lobe-chat/commit/2443189))

#### Styles

* **misc**: Improve agent loading state, closes [#11511](https://github.com/lobehub/lobe-chat/issues/11511) ([3bb7f33](https://github.com/lobehub/lobe-chat/commit/3bb7f33))

</details>

<div align="right">

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

</div>
2026-01-15 07:24:26 +00:00
sxjeru a758d012ed 🐛 fix(settings): add instant UI feedback for provider config switches (#11362)
*  feat(provider): Enhance Checker and ProviderConfig components for improved state management and UI responsiveness

* 🐛 fix(provider): Enhance credential watching in ProviderConfig for better authentication handling
2026-01-15 15:04:27 +08:00
YuTengjing 244318983b 🐛 fix: click lobe ai topic trigger create new agent (#11508) 2026-01-15 14:56:34 +08:00
Arvin Xu 3bb7f331f2 💄 style: improve agent loading state (#11511)
* improve loading state

* improve loading state

* improve loading state
2026-01-15 14:44:16 +08:00
lobehubbot 26ef2ff025 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-15 05:37:25 +00:00
semantic-release-bot 740e047aa8 🔖 chore(release): v2.0.0-next.290 [skip ci]
## [Version&nbsp;2.0.0-next.290](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.289...v2.0.0-next.290)
<sup>Released on **2026-01-15**</sup>

#### 🐛 Bug Fixes

- **misc**: Fix internal editor onTextChange issue and add test case.

<br/>

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

#### What's fixed

* **misc**: Fix internal editor onTextChange issue and add test case, closes [#11509](https://github.com/lobehub/lobe-chat/issues/11509) ([e5eb03e](https://github.com/lobehub/lobe-chat/commit/e5eb03e))

</details>

<div align="right">

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

</div>
2026-01-15 05:35:38 +00:00
Arvin Xu e5eb03ee01 🐛 fix: fix internal editor onTextChange issue and add test case (#11509)
* fix internal editor onTextChange issue and add test case

* fix tests
2026-01-15 13:15:43 +08:00
lobehubbot d4561af381 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-15 03:13:06 +00:00
semantic-release-bot b5143900b3 🔖 chore(release): v2.0.0-next.289 [skip ci]
## [Version&nbsp;2.0.0-next.289](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.288...v2.0.0-next.289)
<sup>Released on **2026-01-15**</sup>

#### 🐛 Bug Fixes

- **misc**: Fix page content mismatch when switch quickly.

<br/>

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

#### What's fixed

* **misc**: Fix page content mismatch when switch quickly, closes [#11505](https://github.com/lobehub/lobe-chat/issues/11505) ([0cb1374](https://github.com/lobehub/lobe-chat/commit/0cb1374))

</details>

<div align="right">

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

</div>
2026-01-15 03:11:21 +00:00
Arvin Xu 0cb1374cf7 🐛 fix: fix page content mismatch when switch quickly (#11505)
* filter GroupOrchestration action

* fix page issue
2026-01-15 10:51:59 +08:00
lobehubbot b2f44bee8a 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-15 02:03:36 +00:00
semantic-release-bot e949bd97eb 🔖 chore(release): v2.0.0-next.288 [skip ci]
## [Version&nbsp;2.0.0-next.288](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.287...v2.0.0-next.288)
<sup>Released on **2026-01-15**</sup>

####  Features

- **misc**: Improve group prompt context engine and fix group supervisor response issue.

<br/>

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

#### What's improved

* **misc**: Improve group prompt context engine and fix group supervisor response issue, closes [#11490](https://github.com/lobehub/lobe-chat/issues/11490) ([7d066eb](https://github.com/lobehub/lobe-chat/commit/7d066eb))

</details>

<div align="right">

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

</div>
2026-01-15 02:01:52 +00:00
Arvin Xu 7d066eb7d1 feat: improve group prompt context engine and fix group supervisor response issue (#11490)
* improve group prompt context engine

* fix tests

* fix send group messages

* add group management

* fix metadata

* fix delete multi messages

* update sandbox ui

* update
2026-01-15 09:44:34 +08:00
lobehubbot 7de5164010 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-14 14:26:34 +00:00
semantic-release-bot 6aadea4f89 🔖 chore(release): v2.0.0-next.287 [skip ci]
## [Version&nbsp;2.0.0-next.287](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.286...v2.0.0-next.287)
<sup>Released on **2026-01-14**</sup>

#### ♻ Code Refactoring

- **desktop**: Unify TITLE_BAR_HEIGHT constant to desktop-bridge.

#### 🐛 Bug Fixes

- **desktop**: Return OFFICIAL_URL in cloud mode for remoteServerUrl selector.

<br/>

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

#### Code refactoring

* **desktop**: Unify TITLE_BAR_HEIGHT constant to desktop-bridge, closes [#11496](https://github.com/lobehub/lobe-chat/issues/11496) ([e7739e5](https://github.com/lobehub/lobe-chat/commit/e7739e5))

#### What's fixed

* **desktop**: Return OFFICIAL_URL in cloud mode for remoteServerUrl selector, closes [#11502](https://github.com/lobehub/lobe-chat/issues/11502) ([1d11fac](https://github.com/lobehub/lobe-chat/commit/1d11fac))

</details>

<div align="right">

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

</div>
2026-01-14 14:24:51 +00:00
Innei e7739e5c64 ♻️ refactor(desktop): unify TITLE_BAR_HEIGHT constant to desktop-bridge (#11496)
* ♻️ refactor(desktop): unify TITLE_BAR_HEIGHT constant to desktop-bridge

- Move TITLE_BAR_HEIGHT constant (38) to @lobechat/desktop-bridge package
- Update all references to import directly from the shared package
- Remove duplicate const.ts file from ElectronTitlebar
- Ensure consistency between main process and renderer process

*  test(desktop): update WindowThemeManager test for new TITLE_BAR_HEIGHT

- Update mock to use @lobechat/desktop-bridge instead of @/const/theme
- Update expected height values from 32 to 38

* 🔧 fix(desktop): adjust TITLE_BAR_HEIGHT to prevent container border blocking

- Decrease TITLE_BAR_HEIGHT by 2px to avoid blocking the container border edge in WindowThemeManager.
- Update related test to reflect the new height adjustment.

Signed-off-by: Innei <tukon479@gmail.com>

* 🔧 fix(desktop): further adjust TITLE_BAR_HEIGHT in tests

- Decrease TITLE_BAR_HEIGHT in WindowThemeManager tests from 38px to 36px to maintain consistency with recent changes.
- Update all relevant test cases to reflect the new height.

Signed-off-by: Innei <tukon479@gmail.com>

---------

Signed-off-by: Innei <tukon479@gmail.com>
2026-01-14 22:05:28 +08:00
Innei 1d11fac4c6 🐛 fix(desktop): return OFFICIAL_URL in cloud mode for remoteServerUrl selector (#11502)
The remoteServerUrl selector was returning an empty string in cloud mode,
causing avatar URLs with relative paths to not be properly prefixed with
the remote server URL in desktop environment.

Changes:
- Update remoteServerUrl selector to return OFFICIAL_URL in cloud mode
- Add rawRemoteServerUrl selector for forms that need the original config value
- Fix avatar URL handling in UserAvatar and useCategory
- Update tests to reflect new selector behavior

Fixes LOBE-3197
2026-01-14 22:05:15 +08:00
lobehubbot f397b7f944 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-14 13:34:50 +00:00
semantic-release-bot c78795d034 🔖 chore(release): v2.0.0-next.286 [skip ci]
## [Version&nbsp;2.0.0-next.286](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.285...v2.0.0-next.286)
<sup>Released on **2026-01-14**</sup>

#### 🐛 Bug Fixes

- **misc**: Prevent auto navigation to profile when clicking topic.

<br/>

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

#### What's fixed

* **misc**: Prevent auto navigation to profile when clicking topic, closes [#11500](https://github.com/lobehub/lobe-chat/issues/11500) ([1e03005](https://github.com/lobehub/lobe-chat/commit/1e03005))

</details>

<div align="right">

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

</div>
2026-01-14 13:33:12 +00:00
YuTengjing 1e03005e0e 🐛 fix: prevent auto navigation to profile when clicking topic (#11500) 2026-01-14 21:14:11 +08:00
lobehubbot ba58756cd0 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-14 11:29:45 +00:00
semantic-release-bot 830a7afa96 🔖 chore(release): v2.0.0-next.285 [skip ci]
## [Version&nbsp;2.0.0-next.285](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.284...v2.0.0-next.285)
<sup>Released on **2026-01-14**</sup>

####  Features

- **misc**: Update the agent profiles tools check & agentbuilder tools & publish to market button.

<br/>

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

#### What's improved

* **misc**: Update the agent profiles tools check & agentbuilder tools & publish to market button, closes [#11501](https://github.com/lobehub/lobe-chat/issues/11501) ([85277fa](https://github.com/lobehub/lobe-chat/commit/85277fa))

</details>

<div align="right">

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

</div>
2026-01-14 11:27:54 +00:00
Shinji-Li 85277fa5c4 feat: update the agent profiles tools check & agentbuilder tools & publish to market button (#11501)
* feat: add lobehubskill into profiles tools

* feat: agent builder support the lobehub skill in profiles

* fix: slove the agent builder controll agent tools not work problem

* feat: add the publish button to profiles main page
2026-01-14 19:08:37 +08:00
René Wang 27ec25ca10 fix: Ask agent in CMDK not working (#11494)
* fix: cannot ask Lobe Ai in CMDK

* fix: CMDK jump to agent not working
2026-01-14 18:36:19 +08:00
lobehubbot 1452df02ee 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-14 10:25:55 +00:00
YuTengjing cbdce6a29c chore: add budget source i18n and Seedream 4.5 model (#11499) 2026-01-14 18:06:51 +08:00
lobehubbot 6154f7eb23 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-14 09:15:49 +00:00
semantic-release-bot 8aeb3bd832 🔖 chore(release): v2.0.0-next.284 [skip ci]
## [Version&nbsp;2.0.0-next.284](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.283...v2.0.0-next.284)
<sup>Released on **2026-01-14**</sup>

#### ♻ Code Refactoring

- **misc**: Remove the old lobehub plugins.

#### 🐛 Bug Fixes

- **misc**: Slove the settings/profile change but not refresh the profiles.

<br/>

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

#### Code refactoring

* **misc**: Remove the old lobehub plugins, closes [#11498](https://github.com/lobehub/lobe-chat/issues/11498) ([e5b47df](https://github.com/lobehub/lobe-chat/commit/e5b47df))

#### What's fixed

* **misc**: Slove the settings/profile change but not refresh the profiles, closes [#11497](https://github.com/lobehub/lobe-chat/issues/11497) ([f1e2111](https://github.com/lobehub/lobe-chat/commit/f1e2111))

</details>

<div align="right">

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

</div>
2026-01-14 09:14:00 +00:00
Shinji-Li e5b47df334 ♻️ refactor: remove the old lobehub plugins (#11498)
feat: unuse old lobehub plugins
2026-01-14 16:53:38 +08:00
Shinji-Li f1e2111bf3 🐛 fix: slove the settings/profile change but not refresh the profiles (#11497)
fix: slove the settings/profile change but not refresh the profiles
2026-01-14 16:43:06 +08:00
lobehubbot a778ab16d3 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-14 08:36:07 +00:00
semantic-release-bot 4db689d26e 🔖 chore(release): v2.0.0-next.283 [skip ci]
## [Version&nbsp;2.0.0-next.283](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.282...v2.0.0-next.283)
<sup>Released on **2026-01-14**</sup>

#### 💄 Styles

- **misc**: Fix UI issues with tooltip wrapping and dropdown type.

<br/>

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

#### Styles

* **misc**: Fix UI issues with tooltip wrapping and dropdown type, closes [#11495](https://github.com/lobehub/lobe-chat/issues/11495) ([9d90eba](https://github.com/lobehub/lobe-chat/commit/9d90eba))

</details>

<div align="right">

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

</div>
2026-01-14 08:34:32 +00:00
Innei 9d90eba310 💄 style: fix UI issues with tooltip wrapping and dropdown type (#11495)
- Upgrade @lobehub/ui to ^4.19.0
- Fix DropdownItem import path
- Change dropdown checkbox type to switch type
- Add showSorterTooltip: false to prevent double tooltip
- Wrap tooltip children with span to fix React warning
2026-01-14 16:15:14 +08:00
lobehubbot 5837816009 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-14 04:26:45 +00:00
semantic-release-bot 200f9f14a8 🔖 chore(release): v2.0.0-next.282 [skip ci]
## [Version&nbsp;2.0.0-next.282](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.281...v2.0.0-next.282)
<sup>Released on **2026-01-14**</sup>

#### 💄 Styles

- **misc**: Update readFile content.

<br/>

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

#### Styles

* **misc**: Update readFile content, closes [#11485](https://github.com/lobehub/lobe-chat/issues/11485) ([050499b](https://github.com/lobehub/lobe-chat/commit/050499b))

</details>

<div align="right">

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

</div>
2026-01-14 04:24:57 +00:00
Arvin Xu 909f4d8648 🐛 fix: revert editor modal (#11484)
revert update editor modal
2026-01-14 12:05:13 +08:00
Arvin Xu 050499b8a9 💄 style: update readFile content (#11485)
update content
2026-01-14 11:59:36 +08:00
Neko bc9144a6d7 🔨 chore(observability-otel): support to use OTEL_JS_LOBEHUB_DIAG to toggle on diag debug for OTEL (#11471) 2026-01-14 11:44:45 +08:00
lobehubbot 9e05dd0fbf 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-14 03:23:16 +00:00
semantic-release-bot 97ef8dc8e0 🔖 chore(release): v2.0.0-next.281 [skip ci]
## [Version&nbsp;2.0.0-next.281](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.280...v2.0.0-next.281)
<sup>Released on **2026-01-14**</sup>

#### 🐛 Bug Fixes

- **misc**: Fix group ux and memory retriever.

<br/>

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

#### What's fixed

* **misc**: Fix group ux and memory retriever, closes [#11481](https://github.com/lobehub/lobe-chat/issues/11481) ([033ca92](https://github.com/lobehub/lobe-chat/commit/033ca92))

</details>

<div align="right">

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

</div>
2026-01-14 03:21:34 +00:00
Arvin Xu 033ca92011 🐛 fix: fix group ux and memory retriever (#11481)
* update topic

* update topic

* memory not block

* remove incorrectly memory fetch

* refactor group switch topic

* improve streaming style for Agent Tool display

* fix group tab active issue

* 🐛 fix: E2E test for switching conversations and TypeScript type errors

- Fix E2E test 'AGENT-CONV-002' that was failing when switching between conversations
  - Add detection for home page vs agent page state
  - Use correct selectors for Recent Topics cards on home page
  - Add proper wait time for messages to load after topic switch
  - Use '.message-wrapper' selector for message verification

- Fix TypeScript type errors in MakedownRender.tsx
  - Add explicit type annotations for a and img component props
  - Import ReactNode type

- Remove unused @ts-expect-error directive in useMarkdown.tsx

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 11:02:09 +08:00
lobehubbot c093cd8ca9 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-13 17:08:54 +00:00
semantic-release-bot b061774a91 🔖 chore(release): v2.0.0-next.280 [skip ci]
## [Version&nbsp;2.0.0-next.280](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.279...v2.0.0-next.280)
<sup>Released on **2026-01-13**</sup>

#### 💄 Styles

- **misc**: Add MiniMax-M2.1 and GLM-4.7 for Qiniu provider.

<br/>

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

#### Styles

* **misc**: Add MiniMax-M2.1 and GLM-4.7 for Qiniu provider, closes [#10982](https://github.com/lobehub/lobe-chat/issues/10982) ([695784d](https://github.com/lobehub/lobe-chat/commit/695784d))

</details>

<div align="right">

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

</div>
2026-01-13 17:07:15 +00:00
yliu7949 695784daab 💄 style(): add MiniMax-M2.1 and GLM-4.7 for Qiniu provider (#10982)
style(): add MiniMax-M2.1 and GLM-4.7 for Qiniu provider
2026-01-14 00:45:28 +08:00
lobehubbot ef0ecd571e 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-13 16:37:33 +00:00
semantic-release-bot e9957445c5 🔖 chore(release): v2.0.0-next.279 [skip ci]
## [Version&nbsp;2.0.0-next.279](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.278...v2.0.0-next.279)
<sup>Released on **2026-01-13**</sup>

#### 🐛 Bug Fixes

- **misc**: Fix new topic flick issue, fix thread portal not open correctly.

<br/>

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

#### What's fixed

* **misc**: Fix new topic flick issue, closes [#11473](https://github.com/lobehub/lobe-chat/issues/11473) ([c53d372](https://github.com/lobehub/lobe-chat/commit/c53d372))
* **misc**: Fix thread portal not open correctly, closes [#11475](https://github.com/lobehub/lobe-chat/issues/11475) ([e6ff90b](https://github.com/lobehub/lobe-chat/commit/e6ff90b))

</details>

<div align="right">

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

</div>
2026-01-13 16:35:21 +00:00
Arvin Xu c53d372696 🐛 fix: fix new topic flick issue (#11473)
* fix agent tool display

* fix agent tool display

* don't fetch messages when topic is null

* fix welcome
2026-01-14 00:15:11 +08:00
Arvin Xu e6ff90b108 🐛 fix: fix thread portal not open correctly (#11475)
fix thread portal
2026-01-14 00:14:48 +08:00
lobehubbot c5df7d1f2d 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-13 14:30:02 +00:00
semantic-release-bot 8b37bcf306 🔖 chore(release): v2.0.0-next.278 [skip ci]
## [Version&nbsp;2.0.0-next.278](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.277...v2.0.0-next.278)
<sup>Released on **2026-01-13**</sup>

####  Features

- **share**: Add topic sharing functionality.

<br/>

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

#### What's improved

* **share**: Add topic sharing functionality, closes [#11448](https://github.com/lobehub/lobe-chat/issues/11448) ([ddca165](https://github.com/lobehub/lobe-chat/commit/ddca165))

</details>

<div align="right">

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

</div>
2026-01-13 14:28:16 +00:00
YuTengjing ddca1652bb feat(share): add topic sharing functionality (#11448) 2026-01-13 22:10:48 +08:00
lobehubbot 97a091d358 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-13 13:33:39 +00:00
semantic-release-bot 8429a32a2b 🔖 chore(release): v2.0.0-next.277 [skip ci]
## [Version&nbsp;2.0.0-next.277](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.276...v2.0.0-next.277)
<sup>Released on **2026-01-13**</sup>

####  Features

- **misc**: Update the community user layout action button, update the cron job visiual way.

<br/>

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

#### What's improved

* **misc**: Update the community user layout action button, closes [#11472](https://github.com/lobehub/lobe-chat/issues/11472) ([2dd6d42](https://github.com/lobehub/lobe-chat/commit/2dd6d42))
* **misc**: Update the cron job visiual way, closes [#11466](https://github.com/lobehub/lobe-chat/issues/11466) ([63d81de](https://github.com/lobehub/lobe-chat/commit/63d81de))

</details>

<div align="right">

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

</div>
2026-01-13 13:31:50 +00:00
Shinji-Li 63d81de3be feat: update the cron job visiual way (#11466)
* feat: change the cron settings page the config form to new

* feat: change the create new Cron to a single cron/new routes to create

* fix: slove the first into cron the content lost error

* feat: add cron dropdown actions delete topic & remove cronjob

* feat: change the delete button way to header bottom

* fix: slove the cronjob the editor will show old values

* feat: change the enableBusinessFeatures into server config

* fix: overrides @lobehub/ui

* feat: update the cronpage ui

* feat: ui fixed

* feat: add the minstep into 30mins
2026-01-13 21:12:26 +08:00
Shinji-Li 2dd6d423b9 feat: update the community user layout action button (#11472)
feat: update the community user layout action button
2026-01-13 21:03:26 +08:00
lobehubbot 3f1948d5cd 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-13 12:23:42 +00:00
semantic-release-bot aee8b7ac4d 🔖 chore(release): v2.0.0-next.276 [skip ci]
## [Version&nbsp;2.0.0-next.276](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.275...v2.0.0-next.276)
<sup>Released on **2026-01-13**</sup>

<br/>

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

</details>

<div align="right">

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

</div>
2026-01-13 12:22:05 +00:00
Innei 479e566833 perf: optimize Portal rendering with React 19 Activity API (#11461)
*  perf: optimize Portal rendering with React 19 Activity API

- Desktop: Use Activity component to pause updates when Portal is hidden
- Mobile: Add destroyOnHidden to Modal to unmount content when closed
- Prevents unnecessary renders and effect updates when Portal is collapsed

* 🔧 chore: update @lobehub/ui to version 4.17.1 and optimize useYamlArguments hook with useMemo for better performance

Signed-off-by: Innei <tukon479@gmail.com>

* fix: header

Signed-off-by: Innei <tukon479@gmail.com>

---------

Signed-off-by: Innei <tukon479@gmail.com>
2026-01-13 20:02:50 +08:00
lobehubbot c22209b72c 📝 docs(bot): Auto sync agents & plugin to readme 2026-01-13 10:45:48 +00:00
semantic-release-bot 4d40d84e2d 🔖 chore(release): v2.0.0-next.275 [skip ci]
## [Version&nbsp;2.0.0-next.275](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.274...v2.0.0-next.275)
<sup>Released on **2026-01-13**</sup>

####  Features

- **misc**: Improve PageEditor header UX with DropdownMenu and i18n support.

<br/>

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

#### What's improved

* **misc**: Improve PageEditor header UX with DropdownMenu and i18n support, closes [#11462](https://github.com/lobehub/lobe-chat/issues/11462) ([ae499c9](https://github.com/lobehub/lobe-chat/commit/ae499c9))

</details>

<div align="right">

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

</div>
2026-01-13 10:44:11 +00:00
Innei ae499c9ab9 feat: improve PageEditor header UX with DropdownMenu and i18n support (#11462)
*  feat: improve PageEditor header UX with DropdownMenu and i18n support

- Migrate Header from Dropdown to DropdownMenu component with checkbox support
- Add i18n for Ask Copilot item using common cmdk.askLobeAI key
- Replace BotIcon with Avatar using DEFAULT_INBOX_AVATAR
- Add hideWhenExpanded prop to ToggleRightPanelButton
- Conditionally show page info section only when lastUpdatedTime exists

* 🔧 chore: update @lobehub/ui dependency to version 4.18.0 in package.json

* feat: unify proxy setting style

Signed-off-by: Innei <tukon479@gmail.com>

* fix: test

Signed-off-by: Innei <tukon479@gmail.com>

* fix: test

Signed-off-by: Innei <tukon479@gmail.com>

---------

Signed-off-by: Innei <tukon479@gmail.com>
2026-01-13 18:25:28 +08:00
1125 changed files with 43846 additions and 10031 deletions
+107
View File
@@ -0,0 +1,107 @@
#!/bin/bash
# Conductor workspace setup script
# This script creates symlinks for .env and all node_modules directories
LOG_FILE="$PWD/.conductor-setup.log"
log() {
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] $1" | tee -a "$LOG_FILE"
}
log "=========================================="
log "Conductor Setup Script Started"
log "=========================================="
log "CONDUCTOR_ROOT_PATH: $CONDUCTOR_ROOT_PATH"
log "Current working directory: $PWD"
log ""
# Check if CONDUCTOR_ROOT_PATH is set
if [ -z "$CONDUCTOR_ROOT_PATH" ]; then
log "ERROR: CONDUCTOR_ROOT_PATH is not set!"
exit 1
fi
# Symlink .env file
log "--- Symlinking .env file ---"
if [ -f "$CONDUCTOR_ROOT_PATH/.env" ]; then
ln -sf "$CONDUCTOR_ROOT_PATH/.env" .env
if [ -L ".env" ]; then
log "SUCCESS: .env symlinked -> $(readlink .env)"
else
log "ERROR: Failed to create .env symlink"
fi
else
log "WARNING: $CONDUCTOR_ROOT_PATH/.env does not exist, skipping"
fi
log ""
log "--- Finding node_modules directories ---"
# Find all node_modules directories (excluding .pnpm internal and .next build cache)
# NODE_MODULES_DIRS=$(find "$CONDUCTOR_ROOT_PATH" -maxdepth 3 -name "node_modules" -type d 2>/dev/null | grep -v ".pnpm" | grep -v ".next")
# log "Found node_modules directories:"
# echo "$NODE_MODULES_DIRS" >> "$LOG_FILE"
# log ""
# log "--- Creating node_modules symlinks ---"
# # Counter for statistics
# total=0
# success=0
# failed=0
# for dir in $NODE_MODULES_DIRS; do
# total=$((total + 1))
# # Get relative path by removing CONDUCTOR_ROOT_PATH prefix
# rel_path="${dir#$CONDUCTOR_ROOT_PATH/}"
# parent_dir=$(dirname "$rel_path")
# log "Processing: $rel_path"
# log " Source: $dir"
# log " Parent dir: $parent_dir"
# # Create parent directory if needed
# if [ "$parent_dir" != "." ]; then
# if [ ! -d "$parent_dir" ]; then
# mkdir -p "$parent_dir"
# log " Created parent directory: $parent_dir"
# fi
# fi
# # Create symlink
# ln -sf "$dir" "$rel_path"
# # Verify symlink was created
# if [ -L "$rel_path" ]; then
# log " SUCCESS: $rel_path -> $(readlink "$rel_path")"
# success=$((success + 1))
# else
# log " ERROR: Failed to create symlink for $rel_path"
# failed=$((failed + 1))
# fi
# log ""
# done
log "=========================================="
log "Setup Complete"
log "=========================================="
log "Total node_modules: $total"
log "Successful symlinks: $success"
log "Failed symlinks: $failed"
log ""
# List created symlinks for verification
log "--- Verification: Listing symlinks in workspace ---"
find . -maxdepth 1 -type l -exec ls -la {} \; 2>/dev/null >> "$LOG_FILE"
find ./packages -maxdepth 2 -type l -name "node_modules" -exec ls -la {} \; 2>/dev/null >> "$LOG_FILE"
find ./apps -maxdepth 2 -type l -name "node_modules" -exec ls -la {} \; 2>/dev/null >> "$LOG_FILE"
find ./e2e -maxdepth 2 -type l -name "node_modules" -exec ls -la {} \; 2>/dev/null >> "$LOG_FILE"
log ""
log "Log file saved to: $LOG_FILE"
log "Setup script finished."
+1 -1
View File
@@ -43,4 +43,4 @@ DROP TABLE "old_table";
CREATE INDEX "users_email_idx" ON "users" ("email");
```
**Important**: After modifying migration SQL (e.g., adding `IF NOT EXISTS` clauses), run `bun run db:generate-client` to update the hash in `packages/database/src/core/migrations.json`.
**Important**: After modifying migration SQL (e.g., adding `IF NOT EXISTS` clauses), run `bun run db:generate:client` to update the hash in `packages/database/src/core/migrations.json`.
+7 -5
View File
@@ -3,9 +3,10 @@ description: 包含添加 console.log 日志请求时
globs:
alwaysApply: false
---
# Debug 包使用指南
本项目使用 [debug](mdc:https:/github.com/debug-js/debug) 包进行调试日志记录。使用此规则来确保团队成员统一调试日志格式。
本项目使用 `debug` 包进行调试日志记录。使用此规则来确保团队成员统一调试日志格式。
## 基本用法
@@ -15,14 +16,14 @@ alwaysApply: false
import debug from 'debug';
```
2. 创建一个命名空间的日志记录器:
1. 创建一个命名空间的日志记录器:
```typescript
// 格式: lobe:[模块]:[子模块]
const log = debug('lobe-[模块名]:[子模块名]');
```
3. 使用日志记录器:
1. 使用日志记录器:
```typescript
log('简单消息');
@@ -46,7 +47,7 @@ log('格式化数字: %d', number);
## 示例
查看 [market/index.ts](mdc:src/server/routers/edge/market/index.ts) 中的使用示例:
查看 `src/server/routers/edge/market/index.ts` 中的使用示例:
```typescript
import debug from 'debug';
@@ -63,8 +64,9 @@ log('getAgent input: %O', input);
### 在浏览器中
在控制台执行:
```javascript
localStorage.debug = 'lobe-*'
localStorage.debug = 'lobe-*';
```
### 在 Node.js 环境中
+2 -1
View File
@@ -3,13 +3,14 @@ description: 桌面端测试
globs:
alwaysApply: false
---
# 桌面端控制器单元测试指南
## 测试框架与目录结构
LobeChat 桌面端使用 Vitest 作为测试框架。控制器的单元测试应放置在对应控制器文件同级的 `__tests__` 目录下,并以原控制器文件名加 `.test.ts` 作为文件名。
```
```plaintext
apps/desktop/src/main/controllers/
├── __tests__/
│ ├── index.test.ts
@@ -3,7 +3,8 @@ description: 当要做 electron 相关工作时
globs:
alwaysApply: false
---
**桌面端新功能实现指南**
# 桌面端新功能实现指南
## 桌面端应用架构概述
@@ -26,6 +27,7 @@ LobeChat 桌面端基于 Electron 框架构建,采用主进程-渲染进程架
### 1. 确定功能需求与设计
首先确定新功能的需求和设计,包括:
- 功能描述和用例
- 是否需要系统级API(如文件系统、网络等)
- UI/UX设计(如必要)
@@ -64,9 +66,10 @@ LobeChat 桌面端基于 Electron 框架构建,采用主进程-渲染进程架
```typescript
// src/services/electron/newFeatureService.ts
import { ensureElectronIpc } from '@/utils/electron/ipc';
import type { NewFeatureParams } from '@lobechat/electron-client-ipc';
import { ensureElectronIpc } from '@/utils/electron/ipc';
const ipc = ensureElectronIpc();
export const newFeatureService = async (params: NewFeatureParams) => {
@@ -84,7 +87,7 @@ LobeChat 桌面端基于 Electron 框架构建,采用主进程-渲染进程架
### 5. 如果是新增内置工具,遵循工具实现流程
参考 [desktop-local-tools-implement.mdc](mdc:desktop-local-tools-implement.mdc) 了解更多关于添加内置工具的详细步骤。
参考 `desktop-local-tools-implement.mdc` 了解更多关于添加内置工具的详细步骤。
### 6. 添加测试
@@ -120,12 +123,13 @@ LobeChat 桌面端基于 Electron 框架构建,采用主进程-渲染进程架
```typescript
// apps/desktop/src/main/controllers/NotificationCtr.ts
import { Notification } from 'electron';
import { ControllerModule, IpcMethod } from '@/controllers';
import type {
DesktopNotificationResult,
ShowDesktopNotificationParams,
} from '@lobechat/electron-client-ipc';
import { Notification } from 'electron';
import { ControllerModule, IpcMethod } from '@/controllers';
export default class NotificationCtr extends ControllerModule {
static override readonly groupName = 'notification';
+67 -66
View File
@@ -3,78 +3,79 @@ description:
globs:
alwaysApply: false
---
**新增桌面端工具流程:**
1. **定义工具接口 (Manifest):**
* **文件:** `src/tools/[tool_category]/index.ts` (例如: `src/tools/local-files/index.ts`)
* **操作:**
* 在 `ApiName` 对象(例如 `LocalFilesApiName`)中添加一个新的、唯一的 API 名称。
* 在 `Manifest` 对象(例如 `LocalFilesManifest`)的 `api` 数组中,新增一个对象来定义新工具的接口。
* **关键字段:**
* `name`: 使用上一步定义的 API 名称。
* `description`: 清晰描述工具的功能,供 Agent 理解和向用户展示。
* `parameters`: 使用 JSON Schema 定义工具所需的输入参数。
* `type`: 通常是 'object'。
* `properties`: 定义每个参数的名称、`description`、`type` (string, number, boolean, array, etc.),使用英文。
* `required`: 一个字符串数组,列出必须提供的参数名称。
1. **定义工具接口 (Manifest):**
- **文件:** `src/tools/[tool_category]/index.ts` (例如: `src/tools/local-files/index.ts`)
- **操作:**
- 在 `ApiName` 对象(例如 `LocalFilesApiName`)中添加一个新的、唯一的 API 名称。
- 在 `Manifest` 对象(例如 `LocalFilesManifest`)的 `api` 数组中,新增一个对象来定义新工具的接口。
- **关键字段:**
- `name`: 使用上一步定义的 API 名称。
- `description`: 清晰描述工具的功能,供 Agent 理解和向用户展示。
- `parameters`: 使用 JSON Schema 定义工具所需的输入参数。
- `type`: 通常是 'object'。
- `properties`: 定义每个参数的名称、`description`、`type` (string, number, boolean, array, etc.),使用英文。
- `required`: 一个字符串数组,列出必须提供的参数名称。
2. **定义相关类型:**
* **文件 1:** `packages/electron-client-ipc/src/types.ts` (或类似的共享 IPC 类型文件)
* **操作:** 定义传递给 IPC 事件的参数类型接口 (例如: `RenameLocalFileParams`, `MoveLocalFileParams`)。确保与 Manifest 中定义的 `parameters` 一致。
* **文件 2:** `src/tools/[tool_category]/type.ts` (例如: `src/tools/local-files/type.ts`)
* **操作:** 定义此工具执行后,存储在前端 Zustand Store 中的状态类型接口 (例如: `LocalRenameFileState`, `LocalMoveFileState`)。这通常包含操作结果(成功/失败)、错误信息以及相关数据(如旧路径、新路径等)。
2. **定义相关类型:**
- **文件 1:** `packages/electron-client-ipc/src/types.ts` (或类似的共享 IPC 类型文件)
- **操作:** 定义传递给 IPC 事件的参数类型接口 (例如: `RenameLocalFileParams`, `MoveLocalFileParams`)。确保与 Manifest 中定义的 `parameters` 一致。
- **文件 2:** `src/tools/[tool_category]/type.ts` (例如: `src/tools/local-files/type.ts`)
- **操作:** 定义此工具执行后,存储在前端 Zustand Store 中的状态类型接口 (例如: `LocalRenameFileState`, `LocalMoveFileState`)。这通常包含操作结果(成功/失败)、错误信息以及相关数据(如旧路径、新路径等)。
3. **实现前端状态管理 (Store Action):**
* **文件:** `src/store/chat/slices/builtinTool/actions/[tool_category].ts` (例如: `src/store/chat/slices/builtinTool/actions/localFile.ts`)
* **操作:**
* 导入在步骤 2 中定义的 IPC 参数类型和状态类型。
* 在 Action 接口 (例如: `LocalFileAction`) 中添加新 Action 的方法签名,使用对应的 IPC 参数类型。
* 在 `createSlice` (例如: `localFileSlice`) 中实现该 Action 方法:
* 接收 `id` (消息 ID) 和 `params` (符合 IPC 参数类型)。
* 设置加载状态 (`toggleLocalFileLoading(id, true)`)。
* 调用对应的 `Service` 层方法 (见步骤 4),传递 `params`。
* 使用 `try...catch` 处理 `Service` 调用可能发生的错误。
* **成功时:**
* 调用 `updatePluginState(id, {...})` 更新插件状态,使用步骤 2 中定义的状态类型。
* 调用 `internal_updateMessageContent(id, JSON.stringify({...}))` 更新消息内容,通常包含成功确认信息。
* **失败时:**
* 记录错误 (`console.error`)。
* 调用 `updatePluginState(id, {...})` 更新插件状态,包含错误信息。
* 调用 `internal_updateMessagePluginError(id, {...})` 设置消息的错误状态。
* 调用 `internal_updateMessageContent(id, JSON.stringify({...}))` 更新消息内容,包含错误信息。
* 在 `finally` 块中取消加载状态 (`toggleLocalFileLoading(id, false)`)。
* 返回操作是否成功 (`boolean`)。
3. **实现前端状态管理 (Store Action):**
- **文件:** `src/store/chat/slices/builtinTool/actions/[tool_category].ts` (例如: `src/store/chat/slices/builtinTool/actions/localFile.ts`)
- **操作:**
- 导入在步骤 2 中定义的 IPC 参数类型和状态类型。
- 在 Action 接口 (例如: `LocalFileAction`) 中添加新 Action 的方法签名,使用对应的 IPC 参数类型。
- 在 `createSlice` (例如: `localFileSlice`) 中实现该 Action 方法:
- 接收 `id` (消息 ID) 和 `params` (符合 IPC 参数类型)。
- 设置加载状态 (`toggleLocalFileLoading(id, true)`)。
- 调用对应的 `Service` 层方法 (见步骤 4),传递 `params`。
- 使用 `try...catch` 处理 `Service` 调用可能发生的错误。
- **成功时:**
- 调用 `updatePluginState(id, {...})` 更新插件状态,使用步骤 2 中定义的状态类型。
- 调用 `internal_updateMessageContent(id, JSON.stringify({...}))` 更新消息内容,通常包含成功确认信息。
- **失败时:**
- 记录错误 (`console.error`)。
- 调用 `updatePluginState(id, {...})` 更新插件状态,包含错误信息。
- 调用 `internal_updateMessagePluginError(id, {...})` 设置消息的错误状态。
- 调用 `internal_updateMessageContent(id, JSON.stringify({...}))` 更新消息内容,包含错误信息。
- 在 `finally` 块中取消加载状态 (`toggleLocalFileLoading(id, false)`)。
- 返回操作是否成功 (`boolean`)。
4. **实现 Service 层 (调用 IPC):**
* **文件:** `src/services/electron/[tool_category]Service.ts` (例如: `src/services/electron/localFileService.ts`)
* **操作:**
* 导入在步骤 2 中定义的 IPC 参数类型。
* 添加一个新的 `async` 方法,方法名通常与 Action 名称对应 (例如: `renameLocalFile`)。
* 方法接收 `params` (符合 IPC 参数类型)。
* 通过 `ensureElectronIpc()` 获取 IPC 代理 (`const ipc = ensureElectronIpc();`),调用与 Manifest 中 `name` 字段匹配的链式方法,并将 `params` 传递过去。
* 定义方法的返回类型,通常是 `Promise<{ success: boolean; error?: string }>`,与后端 Controller 返回的结构一致。
4. **实现 Service 层 (调用 IPC):**
- **文件:** `src/services/electron/[tool_category]Service.ts` (例如: `src/services/electron/localFileService.ts`)
- **操作:**
- 导入在步骤 2 中定义的 IPC 参数类型。
- 添加一个新的 `async` 方法,方法名通常与 Action 名称对应 (例如: `renameLocalFile`)。
- 方法接收 `params` (符合 IPC 参数类型)。
- 通过 `ensureElectronIpc()` 获取 IPC 代理 (`const ipc = ensureElectronIpc();`),调用与 Manifest 中 `name` 字段匹配的链式方法,并将 `params` 传递过去。
- 定义方法的返回类型,通常是 `Promise<{ success: boolean; error?: string }>`,与后端 Controller 返回的结构一致。
5. **实现后端逻辑 (Controller / IPC Handler):**
* **文件:** `apps/desktop/src/main/controllers/[ToolName]Ctr.ts` (例如: `apps/desktop/src/main/controllers/LocalFileCtr.ts`)
* **操作:**
* 导入 Node.js 相关模块 (`fs`, `path` 等) 和 IPC 相关依赖 (`ControllerModule`, `IpcMethod`、参数类型等)。
* 添加一个新的 `async` 方法,方法名通常以 `handle` 开头 (例如: `handleRenameFile`)。
* 使用 `@IpcMethod()` 装饰器将此方法注册为对应 IPC 事件的处理器,确保方法名与 Manifest 中的 `name` 以及 Service 层的链式调用一致。
* 方法的参数应解构自 Service 层传递过来的对象,类型与步骤 2 中定义的 IPC 参数类型匹配。
* 实现核心业务逻辑:
* 进行必要的输入验证。
* 执行文件系统操作或其他后端任务 (例如: `fs.promises.rename`)。
* 使用 `try...catch` 捕获执行过程中的错误。
* 处理特定错误码 (`error.code`) 以提供更友好的错误消息。
* 返回一个包含 `success` (boolean) 和可选 `error` (string) 字段的对象。
5. **实现后端逻辑 (Controller / IPC Handler):**
- **文件:** `apps/desktop/src/main/controllers/[ToolName]Ctr.ts` (例如: `apps/desktop/src/main/controllers/LocalFileCtr.ts`)
- **操作:**
- 导入 Node.js 相关模块 (`fs`, `path` 等) 和 IPC 相关依赖 (`ControllerModule`, `IpcMethod`、参数类型等)。
- 添加一个新的 `async` 方法,方法名通常以 `handle` 开头 (例如: `handleRenameFile`)。
- 使用 `@IpcMethod()` 装饰器将此方法注册为对应 IPC 事件的处理器,确保方法名与 Manifest 中的 `name` 以及 Service 层的链式调用一致。
- 方法的参数应解构自 Service 层传递过来的对象,类型与步骤 2 中定义的 IPC 参数类型匹配。
- 实现核心业务逻辑:
- 进行必要的输入验证。
- 执行文件系统操作或其他后端任务 (例如: `fs.promises.rename`)。
- 使用 `try...catch` 捕获执行过程中的错误。
- 处理特定错误码 (`error.code`) 以提供更友好的错误消息。
- 返回一个包含 `success` (boolean) 和可选 `error` (string) 字段的对象。
6. **更新 Agent 文档 (System Role):**
* **文件:** `src/tools/[tool_category]/systemRole.ts` (例如: `src/tools/local-files/systemRole.ts`)
* **操作:**
* 在 `<core_capabilities>` 部分添加新工具的简要描述。
* 如果需要,更新 `<workflow>`。
* 在 `<tool_usage_guidelines>` 部分为新工具添加详细的使用说明,解释其参数、用途和预期行为。
* 如有必要,更新 `<security_considerations>`。
* 如有必要(例如工具返回了新的数据结构或路径),更新 `<response_format>` 中的示例。
6. **更新 Agent 文档 (System Role):**
- **文件:** `src/tools/[tool_category]/systemRole.ts` (例如: `src/tools/local-files/systemRole.ts`)
- **操作:**
- 在 `<core_capabilities>` 部分添加新工具的简要描述。
- 如果需要,更新 `<workflow>`。
- 在 `<tool_usage_guidelines>` 部分为新工具添加详细的使用说明,解释其参数、用途和预期行为。
- 如有必要,更新 `<security_considerations>`。
- 如有必要(例如工具返回了新的数据结构或路径),更新 `<response_format>` 中的示例。
通过遵循这些步骤,可以系统地将新的桌面端工具集成到 LobeChat 的插件系统中。
+21 -9
View File
@@ -3,7 +3,8 @@ description:
globs:
alwaysApply: false
---
**桌面端菜单配置指南**
# 桌面端菜单配置指南
## 菜单系统概述
@@ -15,7 +16,7 @@ LobeChat 桌面应用有三种主要的菜单类型:
## 菜单相关文件结构
```
```plaintext
apps/desktop/src/main/
├── menus/ # 菜单定义
│ ├── appMenu.ts # 应用菜单配置
@@ -33,8 +34,9 @@ apps/desktop/src/main/
应用菜单在 `apps/desktop/src/main/menus/appMenu.ts` 中定义:
1. **导入依赖**
```typescript
import { app, BrowserWindow, Menu, MenuItem, MenuItemConstructorOptions } from 'electron';
import { BrowserWindow, Menu, MenuItem, MenuItemConstructorOptions, app } from 'electron';
import { is } from 'electron-util';
```
@@ -43,6 +45,7 @@ apps/desktop/src/main/
- 每个菜单项可以包含:label, accelerator (快捷键), role, submenu, click 等属性
3. **创建菜单工厂函数**
```typescript
export const createAppMenu = (win: BrowserWindow) => {
const template = [
@@ -61,6 +64,7 @@ apps/desktop/src/main/
上下文菜单通常在特定元素上右键点击时显示:
1. **在主进程中定义菜单模板**
```typescript
// apps/desktop/src/main/menus/contextMenu.ts
export const createContextMenu = () => {
@@ -73,6 +77,7 @@ apps/desktop/src/main/
```
2. **在适当的事件处理器中显示菜单**
```typescript
const menu = createContextMenu();
menu.popup();
@@ -83,11 +88,13 @@ apps/desktop/src/main/
托盘菜单在 `TrayMenuCtr.ts` 中配置:
1. **创建托盘图标**
```typescript
this.tray = new Tray(trayIconPath);
```
2. **定义托盘菜单**
```typescript
const contextMenu = Menu.buildFromTemplate([
{ label: '显示主窗口', click: this.showMainWindow },
@@ -97,6 +104,7 @@ apps/desktop/src/main/
```
3. **设置托盘菜单**
```typescript
this.tray.setContextMenu(contextMenu);
```
@@ -106,11 +114,13 @@ apps/desktop/src/main/
为菜单添加多语言支持:
1. **导入本地化工具**
```typescript
import { i18n } from '../locales';
```
2. **使用翻译函数**
```typescript
const template = [
{
@@ -118,14 +128,13 @@ apps/desktop/src/main/
submenu: [
{ label: i18n.t('menu.new'), click: createNew },
// ...
]
],
},
// ...
];
```
3. **在语言切换时更新菜单**
在 `MenuCtr.ts` 中监听语言变化事件并重新创建菜单
3. **在语言切换时更新菜单** 在 `MenuCtr.ts` 中监听语言变化事件并重新创建菜单
## 添加新菜单项流程
@@ -134,6 +143,7 @@ apps/desktop/src/main/
- 确定在菜单中的位置(主菜单项或子菜单项)
2. **定义菜单项**
```typescript
const newMenuItem: MenuItemConstructorOptions = {
label: '新功能',
@@ -141,12 +151,11 @@ apps/desktop/src/main/
click: (_, window) => {
// 处理点击事件
if (window) window.webContents.send('trigger-new-feature');
}
},
};
```
3. **添加到菜单模板**
将新菜单项添加到相应的菜单模板中
3. **添加到菜单模板** 将新菜单项添加到相应的菜单模板中
4. **对于与渲染进程交互的功能**
- 使用 `window.webContents.send()` 发送 IPC 消息到渲染进程
@@ -157,6 +166,7 @@ apps/desktop/src/main/
动态控制菜单项状态:
1. **保存对菜单项的引用**
```typescript
this.menuItems = {};
const menu = Menu.buildFromTemplate(template);
@@ -164,6 +174,7 @@ apps/desktop/src/main/
```
2. **根据条件更新状态**
```typescript
updateMenuState(state) {
if (this.menuItems.newFeature) {
@@ -179,6 +190,7 @@ apps/desktop/src/main/
2. **平台特定菜单**
- 使用 `process.platform` 检查为不同平台提供不同菜单
```typescript
if (process.platform === 'darwin') {
template.unshift({ role: 'appMenu' });
+17 -2
View File
@@ -3,7 +3,8 @@ description:
globs:
alwaysApply: false
---
**桌面端窗口管理指南**
# 桌面端窗口管理指南
## 窗口管理概述
@@ -16,7 +17,7 @@ LobeChat 桌面应用使用 Electron 的 `BrowserWindow` 管理应用窗口。
## 相关文件结构
```
```plaintext
apps/desktop/src/main/
├── appBrowsers.ts # 窗口管理的核心文件
├── controllers/
@@ -63,6 +64,7 @@ export const createMainWindow = () => {
实现窗口状态持久化保存和恢复:
1. **保存窗口状态**
```typescript
const saveWindowState = (window: BrowserWindow) => {
if (!window.isMinimized() && !window.isMaximized()) {
@@ -80,6 +82,7 @@ export const createMainWindow = () => {
```
2. **恢复窗口状态**
```typescript
const restoreWindowState = (window: BrowserWindow) => {
const savedState = settings.get('windowState');
@@ -96,6 +99,7 @@ export const createMainWindow = () => {
```
3. **监听窗口事件**
```typescript
window.on('close', () => saveWindowState(window));
window.on('moved', () => saveWindowState(window));
@@ -107,6 +111,7 @@ export const createMainWindow = () => {
对于需要多窗口支持的功能:
1. **跟踪窗口**
```typescript
export class WindowManager {
private windows: Map<string, BrowserWindow> = new Map();
@@ -133,6 +138,7 @@ export const createMainWindow = () => {
```
2. **窗口间通信**
```typescript
// 从一个窗口向另一个窗口发送消息
sendMessageToWindow(targetWindowId, channel, data) {
@@ -148,9 +154,11 @@ export const createMainWindow = () => {
通过 IPC 实现窗口操作:
1. **在主进程中注册 IPC 处理器**
```typescript
// apps/desktop/src/main/controllers/BrowserWindowsCtr.ts
import { BrowserWindow } from 'electron';
import { ControllerModule, IpcMethod } from '@/controllers';
export default class BrowserWindowsCtr extends ControllerModule {
@@ -178,10 +186,12 @@ export const createMainWindow = () => {
}
}
```
- `@IpcMethod()` 根据控制器的 `groupName` 自动将方法映射为 `windows.minimizeWindow` 形式的通道名称。
- 控制器需继承 `ControllerModule`,并在 `controllers/registry.ts` 中通过 `controllerIpcConstructors` 注册,便于类型生成。
2. **在渲染进程中调用**
```typescript
// src/services/electron/windowService.ts
import { ensureElectronIpc } from '@/utils/electron/ipc';
@@ -194,6 +204,7 @@ export const createMainWindow = () => {
close: () => ipc.windows.closeWindow(),
};
```
- `ensureElectronIpc()` 会基于 `DesktopIpcServices` 运行时生成 Proxy,并通过 `window.electronAPI.invoke` 与主进程通信;不再直接使用 `dispatch`。
### 5. 自定义窗口控制 (无边框窗口)
@@ -201,6 +212,7 @@ export const createMainWindow = () => {
对于自定义窗口标题栏:
1. **创建无边框窗口**
```typescript
const window = new BrowserWindow({
frame: false,
@@ -210,6 +222,7 @@ export const createMainWindow = () => {
```
2. **在渲染进程中实现拖拽区域**
```css
/* CSS */
.titlebar {
@@ -229,6 +242,7 @@ export const createMainWindow = () => {
2. **安全性**
- 始终设置适当的 `webPreferences` 确保安全
```typescript
webPreferences: {
preload: path.join(__dirname, '../preload/index.js'),
@@ -255,6 +269,7 @@ export const createMainWindow = () => {
```typescript
// apps/desktop/src/main/controllers/BrowserWindowsCtr.ts
import type { OpenSettingsWindowOptions } from '@lobechat/electron-client-ipc';
import { ControllerModule, IpcMethod } from '@/controllers';
export default class BrowserWindowsCtr extends ControllerModule {
+6 -6
View File
@@ -10,14 +10,14 @@ This document outlines the conventions and best practices for defining PostgreSQ
## Configuration
- Drizzle configuration is managed in [drizzle.config.ts](mdc:drizzle.config.ts)
- Drizzle configuration is managed in `drizzle.config.ts`
- Schema files are located in the src/database/schemas/ directory
- Migration files are output to `src/database/migrations/`
- The project uses `postgresql` dialect with `strict: true`
## Helper Functions
Commonly used column definitions, especially for timestamps, are centralized in [src/database/schemas/\_helpers.ts](mdc:src/database/schemas/_helpers.ts):
Commonly used column definitions, especially for timestamps, are centralized in `src/database/schemas/_helpers.ts`:
- `timestamptz(name: string)`: Creates a timestamp column with timezone
- `createdAt()`, `updatedAt()`, `accessedAt()`: Helper functions for standard timestamp columns
@@ -46,7 +46,7 @@ Commonly used column definitions, especially for timestamps, are centralized in
### Timestamps
- Consistently use the `...timestamps` spread from [\_helpers.ts](mdc:src/database/schemas/_helpers.ts) for `created_at`, `updated_at`, and `accessed_at` columns
- Consistently use the `...timestamps` spread from `_helpers.ts` for `created_at`, `updated_at`, and `accessed_at` columns
### Default Values
@@ -74,12 +74,12 @@ Commonly used column definitions, especially for timestamps, are centralized in
## Relations
- Table relationships are defined centrally in [src/database/schemas/relations.ts](mdc:src/database/schemas/relations.ts) using the `relations()` utility from `drizzle-orm`
- Table relationships are defined centrally in `src/database/schemas/relations.ts` using the `relations()` utility from `drizzle-orm`
## Code Style & Structure
- **File Organization**: Each main database entity typically has its own schema file (e.g., [user.ts](mdc:src/database/schemas/user.ts), [agent.ts](mdc:src/database/schemas/agent.ts))
- All schemas are re-exported from [src/database/schemas/index.ts](mdc:src/database/schemas/index.ts)
- **File Organization**: Each main database entity typically has its own schema file (e.g., `user.ts`, `agent.ts`)
- All schemas are re-exported from `src/database/schemas/index.ts`
- **ESLint**: Files often start with `/* eslint-disable sort-keys-fix/sort-keys-fix */`
- **Comments**: Use JSDoc-style comments to explain the purpose of tables and complex columns, fields that are self-explanatory do not require jsdoc explanations, such as id, user_id, etc.
+1
View File
@@ -1,6 +1,7 @@
---
alwaysApply: false
---
# 如何添加新的快捷键:开发者指南
本指南将带您一步步地向 LobeChat 添加一个新的快捷键功能。我们将通过一个完整示例,演示从定义到实现的整个过程。
+1
View File
@@ -37,6 +37,7 @@ export default {
- `sync.status.ready` - Feature + group + status
**Parameters:** Use `{{variableName}}` syntax
```typescript
'alert.cloud.desc': '我们提供 {{credit}} 额度积分',
```
+53
View File
@@ -0,0 +1,53 @@
---
alwaysApply: true
---
# Linear Issue Management
When working with Linear issues:
1. **Retrieve issue details** before starting work using `mcp__linear-server__get_issue`
2. **Check for sub-issues**: If the issue has sub-issues, retrieve and review ALL sub-issues using `mcp__linear-server__list_issues` with `parentId` filter before starting work
3. **Update issue status** when completing tasks using `mcp__linear-server__update_issue`
4. **MUST add completion comment** using `mcp__linear-server__create_comment`
## Creating Issues
When creating new Linear issues using `mcp__linear-server__create_issue`, **MUST add the `claude code` label** to indicate the issue was created by Claude Code.
## Completion Comment (REQUIRED)
**Every time you complete an issue, you MUST add a comment summarizing the work done.** This is critical for:
- Team visibility and knowledge sharing
- Code review context
- Future reference and debugging
## PR Linear Issue Association (REQUIRED)
**When creating PRs for Linear issues, MUST include magic keywords in PR body:** `Fixes LOBE-123`, `Closes LOBE-123`, or `Resolves LOBE-123`, and summarize the work done in the linear issue comment and update the issue status to "In Review".
## IMPORTANT: Per-Issue Completion Rule
**When working on multiple issues (e.g., parent issue with sub-issues), you MUST update status and add comment for EACH issue IMMEDIATELY after completing it.** Do NOT wait until all issues are done to update them in batch.
**Workflow for EACH individual issue:**
1. Complete the implementation for this specific issue
2. Run type check: `bun run type-check`
3. Run related tests if applicable
4. Create PR if needed
5. **IMMEDIATELY** update issue status to **"In Review"** (NOT "Done"): `mcp__linear-server__update_issue`
6. **IMMEDIATELY** add completion comment: `mcp__linear-server__create_comment`
7. Only then move on to the next issue
**Note:** Issue status should be set to **"In Review"** when PR is created. The status will be updated to **"Done"** only after the PR is merged (usually handled by Linear-GitHub integration or manually).
**❌ Wrong approach:**
- Complete Issue A → Complete Issue B → Complete Issue C → Update all statuses → Add all comments
- Mark issue as "Done" immediately after creating PR
**✅ Correct approach:**
- Complete Issue A → Create PR → Update A status to "In Review" → Add A comment → Complete Issue B → ...
+16 -3
View File
@@ -1,6 +1,5 @@
---
description: Project directory structure overview
alwaysApply: false
alwaysApply: true
---
# LobeChat Project Structure
@@ -27,6 +26,11 @@ lobe-chat/
│ ├── agent-runtime/
│ ├── builtin-agents/
│ ├── builtin-tool-*/ # builtin tool packages
│ ├── business/ # cloud-only business logic packages
│ │ ├── config/
│ │ ├── const/
│ │ └── model-runtime/
│ ├── config/
│ ├── const/
│ ├── context-engine/
│ ├── conversation-flow/
@@ -36,6 +40,8 @@ lobe-chat/
│ │ ├── schemas/
│ │ └── repositories/
│ ├── desktop-bridge/
│ ├── edge-config/
│ ├── editor-runtime/
│ ├── electron-client-ipc/
│ ├── electron-server-ipc/
│ ├── fetch-sse/
@@ -46,7 +52,7 @@ lobe-chat/
│ │ └── src/
│ │ ├── core/
│ │ └── providers/
│ ├── obervability-otel/
│ ├── observability-otel/
│ ├── prompts/
│ ├── python-interpreter/
│ ├── ssrf-safe-fetch/
@@ -72,6 +78,10 @@ lobe-chat/
│ │ │ ├── onboarding/
│ │ │ └── router/
│ │ └── desktop/
│ ├── business/ # cloud-only business logic (client/server)
│ │ ├── client/
│ │ ├── locales/
│ │ └── server/
│ ├── components/
│ ├── config/
│ ├── const/
@@ -130,6 +140,9 @@ lobe-chat/
- Repository (bff-queries): `packages/database/src/repositories`
- Third-party Integrations: `src/libs` — analytics, oidc etc.
- Builtin Tools: `src/tools`, `packages/builtin-tool-*`
- Business (cloud-only): Code specific to LobeHub cloud service, only expose empty interfaces for opens-source version.
- `src/business/*`
- `packages/business/*`
## Data Flow Architecture
+17 -5
View File
@@ -107,7 +107,7 @@ This project uses a **hybrid routing architecture**: Next.js App Router for stat
| Router | oauth, reset-password, etc.) | src/app/[variants]/(auth)/ |
+------------------+--------------------------------+--------------------------------+
| React Router | Main SPA features | BrowserRouter + Routes |
| DOM | (chat, discover, settings) | desktopRouter.config.tsx |
| DOM | (chat, community, settings) | desktopRouter.config.tsx |
| | | mobileRouter.config.tsx |
+------------------+--------------------------------+--------------------------------+
```
@@ -122,16 +122,16 @@ This project uses a **hybrid routing architecture**: Next.js App Router for stat
### Router Utilities
```tsx
import { dynamicElement, redirectElement, ErrorBoundary, RouteConfig } from '@/utils/router';
import { ErrorBoundary, RouteConfig, dynamicElement, redirectElement } from '@/utils/router';
// Lazy load a page component
element: dynamicElement(() => import('./chat'), 'Desktop > Chat')
element: dynamicElement(() => import('./chat'), 'Desktop > Chat');
// Create a redirect
element: redirectElement('/settings/profile')
element: redirectElement('/settings/profile');
// Error boundary for route
errorElement: <ErrorBoundary resetPath="/chat" />
errorElement: <ErrorBoundary resetPath="/chat" />;
```
### Adding New Routes
@@ -142,6 +142,18 @@ errorElement: <ErrorBoundary resetPath="/chat" />
### Navigation
**Important**: For SPA pages (React Router DOM routes), use `Link` from `react-router-dom`, NOT from `next/link`.
```tsx
// ❌ Wrong - next/link in SPA pages
import Link from 'next/link';
<Link href="/">Home</Link>
// ✅ Correct - react-router-dom Link in SPA pages
import { Link } from 'react-router-dom';
<Link to="/">Home</Link>
```
```tsx
// In components - use react-router-dom hooks
import { useNavigate, useParams } from 'react-router-dom';
+2 -1
View File
@@ -42,7 +42,7 @@ const Component = () => {
return (
<div>
{recentTopics.map(topic => (
{recentTopics.map((topic) => (
<div key={topic.id}>{topic.title}</div>
))}
</div>
@@ -81,6 +81,7 @@ const isInit = useSessionStore(recentSelectors.isRecentTopicsInit);
```
**RecentTopic 类型:**
```typescript
interface RecentTopic {
agent: {
+262 -238
View File
@@ -3,173 +3,199 @@ globs: *.test.ts,*.test.tsx
alwaysApply: false
---
# 测试指南 - LobeChat Testing Guide
# LobeChat Testing Guide
## 测试环境概览
## Test Overview
LobeChat 项目使用 Vitest 测试库,配置了两种不同的测试环境:
LobeChat testing consists of **E2E tests** and **Unit tests**. This guide focuses on **Unit tests**.
### 客户端数据库测试环境 (DOM Environment)
Unit tests are organized into three main categories:
- **配置文件**: [vitest.config.ts](mdc:vitest.config.ts)
- **环境**: Happy DOM (浏览器环境模拟)
- **数据库**: PGLite (浏览器环境的 PostgreSQL)
- **用途**: 测试前端组件、客户端逻辑、React 组件等
- **设置文件**: [tests/setup.ts](mdc:tests/setup.ts)
```plaintext
+---------------------+---------------------------+-----------------------------+
| Category | Location | Config File |
+---------------------+---------------------------+-----------------------------+
| Next.js Webapp | src/**/*.test.ts(x) | vitest.config.ts |
| Packages | packages/*/**/*.test.ts | packages/*/vitest.config.ts |
| Desktop App | apps/desktop/**/*.test.ts | apps/desktop/vitest.config.ts |
+---------------------+---------------------------+-----------------------------+
```
### 服务端数据库测试环境 (Node Environment)
### Next.js Webapp Tests
目前只有 `packages/database` 下的测试可以通过配置 `TEST_SERVER_DB=1` 环境变量来使用服务端数据库测试
- **Config File**: `vitest.config.ts`
- **Environment**: Happy DOM (browser environment simulation)
- **Database**: PGLite (PostgreSQL for browser environments)
- **Setup File**: `tests/setup.ts`
- **Purpose**: Testing React components, hooks, stores, utilities, and client-side logic
- **配置文件**: [packages/database/vitest.config.mts](mdc:packages/database/vitest.config.mts) 并且设置环境变量 `TEST_SERVER_DB=1`
- **环境**: Node.js
- **数据库**: 真实的 PostgreSQL 数据库
- **并发限制**: 单线程运行 (`singleFork: true`)
- **用途**: 测试数据库模型、服务端逻辑、API 端点等
- **设置文件**: [packages/database/tests/setup-db.ts](mdc:packages/database/tests/setup-db.ts)
### Packages Tests
## 测试运行命令
Most packages use standard Vitest configuration. However, the `database` package is special:
** 性能警告**: 项目包含 3000+ 测试用例,完整运行需要约 10 分钟。务必使用文件过滤或测试名称过滤。
#### Database Package (Special Case)
### 正确的命令格式
The database package supports **dual-environment testing**:
| Environment | Database | Config | Use Case |
|------------------|-----------------|---------------------------------------|-----------------------------------|
| Client (Default) | PGLite | `packages/database/vitest.config.mts` | Fast local development |
| Server | Real PostgreSQL | Set `TEST_SERVER_DB=1` | CI/CD, compatibility verification |
Server environment details:
- **Concurrency**: Single-threaded (`singleFork: true`)
- **Setup File**: `packages/database/tests/setup-db.ts`
- **Requirement**: `DATABASE_TEST_URL` environment variable must be set
### Desktop App Tests
- **Config File**: `apps/desktop/vitest.config.ts`
- **Environment**: Node.js
- **Purpose**: Testing Electron main process controllers, IPC handlers, and desktop-specific logic
## Test Commands
**Performance Warning**: The project contains 3000+ test cases. A full run takes approximately 10 minutes. Always use file filtering or test name filtering.
### Recommended Command Format
```bash
# 运行所有客户端/服务端测试
bunx vitest run --silent='passed-only' # 客户端测试
cd packages/database && TEST_SERVER_DB=1 bunx vitest run --silent='passed-only' # 服务端测试
# Run all client/server tests
bunx vitest run --silent='passed-only' # Client tests
cd packages/database && TEST_SERVER_DB=1 bunx vitest run --silent='passed-only' # Server tests
# 运行特定测试文件 (支持模糊匹配)
# Run specific test file (supports fuzzy matching)
bunx vitest run --silent='passed-only' user.test.ts
# 运行特定测试用例名称 (使用 -t 参数)
# Run specific test case by name (using -t flag)
bunx vitest run --silent='passed-only' -t "test case name"
# 组合使用文件和测试名称过滤
# Combine file and test name filtering
bunx vitest run --silent='passed-only' filename.test.ts -t "specific test"
# 生成覆盖率报告 (使用 --coverage 参数)
# Generate coverage report (using --coverage flag)
bunx vitest run --silent='passed-only' --coverage
```
### 避免的命令格式
### Commands to Avoid
```bash
# 这些命令会运行所有 3000+ 测试用例,耗时约 10 分钟!
# ❌ These commands run all 3000+ test cases, taking ~10 minutes!
npm test
npm test some-file.test.ts
# 不要使用裸 vitest (会进入 watch 模式)
# ❌ Don't use bare vitest (enters watch mode)
vitest test-file.test.ts
```
## 测试修复原则
## Test Fixing Principles
### 核心原则
### Core Principles
1. **收集足够的上下文**
在修复测试之前,务必做到:
- 完整理解测试的意图和实现
- 强烈建议阅读当前的 git diff PR diff
1. **Gather Sufficient Context**
Before fixing tests, ensure you:
- Fully understand the test's intent and implementation
- Strongly recommended: review the current git diff and PR diff
2. **测试优先修复**
如果是测试本身写错了,应优先修改测试,而不是实现代码。
2. **Prioritize Test Fixes**
If the test itself is incorrect, fix the test first rather than the implementation code.
3. **专注单一问题**
只修复指定的测试,不要顺带添加额外测试。
3. **Focus on a Single Issue**
Only fix the specified test; don't add extra tests along the way.
4. **不自作主张**
发现其他问题时,不要直接修改,需先提出并讨论。
4. **Don't Act Unilaterally**
When discovering other issues, don't modify them directly—raise and discuss first.
### 测试协作最佳实践
### Testing Collaboration Best Practices
基于实际开发经验总结的重要协作原则:
Important collaboration principles based on real development experience:
#### 1. 失败处理策略
#### 1. Failure Handling Strategy
**核心原则**: 避免盲目重试,快速识别问题并寻求帮助。
**Core Principle**: Avoid blind retries; quickly identify problems and seek help.
- **失败阈值**: 当连续尝试修复测试 1-2 次都失败后,应立即停止继续尝试
- **问题总结**: 分析失败原因,整理已尝试的解决方案及其失败原因
- **寻求帮助**: 带着清晰的问题摘要和尝试记录向团队寻求帮助
- **避免陷阱**: 不要陷入"不断尝试相同或类似方法"的循环
- **Failure Threshold**: After 1-2 consecutive failed fix attempts, stop immediately
- **Problem Summary**: Analyze failure reasons and document attempted solutions with their failure causes
- **Seek Help**: Approach the team with a clear problem summary and attempt history
- **Avoid the Trap**: Don't fall into the loop of repeatedly trying the same or similar approaches
```typescript
// 错误做法:连续失败后继续盲目尝试
// 第3次、第4次仍在用相似的方法修复同一个问题
// ❌ Wrong approach: Keep blindly trying after consecutive failures
// 3rd, 4th attempts still using similar methods to fix the same problem
// 正确做法:失败1-2次后总结问题
// ✅ Correct approach: Summarize after 1-2 failures
/*
问题总结:
1. 尝试过的方法:修改 mock 数据结构
2. 失败原因:仍然提示类型不匹配
3. 具体错误:Expected 'UserData' but received 'UserProfile'
4. 需要帮助:不确定最新的 UserData 接口定义
Problem Summary:
1. Attempted method: Modified mock data structure
2. Failure reason: Still getting type mismatch error
3. Specific error: Expected 'UserData' but received 'UserProfile'
4. Help needed: Unsure about the latest UserData interface definition
*/
```
#### 2. 测试用例命名规范
#### 2. Test Case Naming Conventions
**核心原则**: 测试应该关注"行为",而不是"实现细节"。
**Core Principle**: Tests should focus on "behavior," not "implementation details."
- **描述业务场景**: `describe` `it` 的标题应该描述具体的业务场景和预期行为
- **避免实现绑定**: 不要在测试名称中提及具体的代码行号、覆盖率目标或实现细节
- **保持稳定性**: 测试名称应该在代码重构后仍然有意义
- **Describe Business Scenarios**: `describe` and `it` titles should describe specific business scenarios and expected behaviors
- **Avoid Implementation Binding**: Don't mention specific line numbers, coverage goals, or implementation details in test names
- **Maintain Stability**: Test names should remain meaningful after code refactoring
```typescript
// 错误的测试命名
// ❌ Poor test naming
describe('User component coverage', () => {
it('covers line 45-50 in getUserData', () => {
// 为了覆盖第45-50行而写的测试
// Test written just to cover lines 45-50
});
it('tests the else branch', () => {
// 仅为了测试某个分支而存在
// Exists only to test a specific branch
});
});
// 正确的测试命名
// ✅ Good test naming
describe('<UserAvatar />', () => {
it('should render fallback icon when image url is not provided', () => {
// 测试具体的业务场景,自然会覆盖相关代码分支
// Tests a specific business scenario, naturally covering relevant code branches
});
it('should display user initials when avatar image fails to load', () => {
// 描述用户行为和预期结果
// Describes user behavior and expected outcome
});
});
```
**覆盖率提升的正确思路**:
**The Right Approach to Improving Coverage**:
- 通过设计各种业务场景(正常流程、边缘情况、错误处理)来自然提升覆盖率
- 不要为了达到覆盖率数字而写测试,更不要在测试中注释"为了覆盖 xxx 行"
- Naturally improve coverage by designing various business scenarios (happy paths, edge cases, error handling)
- Don't write tests just to hit coverage numbers, and never comment "to cover line xxx" in tests
#### 3. 测试组织结构
#### 3. Test Organization Structure
**核心原则**: 维护清晰的测试层次结构,避免冗余的顶级测试块。
**Core Principle**: Maintain a clear test hierarchy; avoid redundant top-level test blocks.
- **复用现有结构**: 添加新测试时,优先在现有的 `describe` 块中寻找合适的位置
- **逻辑分组**: 相关的测试用例应该组织在同一个 `describe` 块内
- **避免碎片化**: 不要为了单个测试用例就创建新的顶级 `describe` 块
- **Reuse Existing Structure**: When adding new tests, first look for an appropriate place in existing `describe` blocks
- **Logical Grouping**: Related test cases should be organized within the same `describe` block
- **Avoid Fragmentation**: Don't create a new top-level `describe` block for a single test case
```typescript
// 错误的组织方式:创建过多顶级块
// ❌ Poor organization: Too many top-level blocks
describe('<UserProfile />', () => {
it('should render user name', () => {});
});
describe('UserProfile new prop test', () => {
// 不必要的新块
// Unnecessary new block
it('should handle email display', () => {});
});
describe('UserProfile edge cases', () => {
// 不必要的新块
// Unnecessary new block
it('should handle missing avatar', () => {});
});
// 正确的组织方式:合并相关测试
// ✅ Good organization: Merge related tests
describe('<UserProfile />', () => {
it('should render user name', () => {});
@@ -178,78 +204,78 @@ describe('<UserProfile />', () => {
it('should handle missing avatar', () => {});
describe('when user data is incomplete', () => {
// 只有在有多个相关子场景时才创建子组
// Only create sub-groups when there are multiple related sub-scenarios
it('should show placeholder for missing name', () => {});
it('should hide email section when email is undefined', () => {});
});
});
```
**组织决策流程**:
**Organization Decision Flow**:
1. 是否存在逻辑相关的现有 `describe` 块? → 如果有,添加到其中
2. 是否有多个(3个以上)相关的测试用例? → 如果有,可以考虑创建新的子 `describe`
3. 是否是独立的、无关联的功能模块? → 如果是,才考虑创建新的顶级 `describe`
1. Is there a logically related existing `describe` block? → If yes, add to it
2. Are there multiple (3+) related test cases? → If yes, consider creating a new sub-`describe`
3. Is it an independent, unrelated feature module? → Only then consider creating a new top-level `describe`
### 测试修复流程
### Test Fixing Workflow
1. **复现问题**: 定位并运行失败的测试,确认能在本地复现
2. **分析原因**: 阅读测试代码、错误日志和相关文件的 Git 修改历史
3. **建立假设**: 判断问题出在测试逻辑、实现代码还是环境配置
4. **修复验证**: 根据假设进行修复,重新运行测试确认通过
5. **扩大验证**: 运行当前文件内所有测试,确保没有引入新问题
6. **撰写总结**: 说明错误原因和修复方法
1. **Reproduce the Issue**: Locate and run the failing test; confirm it can be reproduced locally
2. **Analyze the Cause**: Read test code, error logs, and Git history of related files
3. **Form a Hypothesis**: Determine if the problem is in test logic, implementation code, or environment configuration
4. **Fix and Verify**: Apply the fix based on your hypothesis; rerun the test to confirm it passes
5. **Expand Verification**: Run all tests in the current file to ensure no new issues were introduced
6. **Write a Summary**: Document the error cause and fix method
### 修复完成后的总结
### Post-Fix Summary
测试修复完成后,应该提供简要说明,包括:
After completing a test fix, provide a brief explanation including:
1. **错误原因分析**: 说明测试失败的根本原因
- 测试逻辑错误
- 实现代码bug
- 环境配置问题
- 依赖变更导致的问题
1. **Root Cause Analysis**: Explain the fundamental reason for the test failure
- Test logic error
- Implementation bug
- Environment configuration issue
- Dependency change
2. **修复方法说明**: 简述采用的修复方式
- 修改了哪些文件
- 采用了什么解决方案
- 为什么选择这种修复方式
2. **Fix Description**: Briefly describe the fix approach
- Which files were modified
- What solution was applied
- Why this fix approach was chosen
**示例格式**:
**Example Format**:
```markdown
## 测试修复总结
## Test Fix Summary
**错误原因**: 测试中的 mock 数据格式与实际 API 返回格式不匹配,导致断言失败。
**Root Cause**: The mock data format in the test didn't match the actual API response format, causing assertion failures.
**修复方法**: 更新了测试文件中的 mock 数据结构,使其与最新的 API 响应格式保持一致。具体修改了 `user.test.ts` 中的 `mockUserData` 对象结构。
**Fix**: Updated the mock data structure in the test file to match the latest API response format. Specifically modified the `mockUserData` object structure in `user.test.ts`.
```
## 测试编写最佳实践
## Test Writing Best Practices
### Mock 数据策略:追求"低成本的真实性"
### Mock Data Strategy: Aim for "Low-Cost Authenticity"
**核心原则**: 测试数据应默认追求真实性,只有在引入"高昂的测试成本"时才进行简化。
**Core Principle**: Test data should default to authenticity; only simplify when it introduces "high testing costs."
#### 什么是"高昂的测试成本"?
#### What Are "High Testing Costs"?
"高成本"指的是测试中引入了外部依赖,使测试变慢、不稳定或复杂:
"High cost" refers to introducing external dependencies in tests that make them slow, unstable, or complex:
- **文件 I/O 操作**:读写硬盘文件
- **网络请求**:HTTP 调用、数据库连接
- **系统调用**:获取系统时间、环境变量等
- **File I/O Operations**: Reading/writing disk files
- **Network Requests**: HTTP calls, database connections
- **System Calls**: Getting system time, environment variables, etc.
#### 推荐做法:Mock 依赖,保留真实数据
#### Recommended Approach: Mock Dependencies, Keep Real Data
```typescript
// 好的做法:Mock I/O 操作,但使用真实的文件内容格式
// ✅ Good approach: Mock I/O operations but use real file content formats
describe('parseContentType', () => {
beforeEach(() => {
// Mock 文件读取操作(避免真实 I/O
// Mock file read operation (avoid real I/O)
vi.spyOn(fs, 'readFileSync').mockImplementation((path) => {
// 但返回真实的文件内容格式
if (path.includes('.pdf')) return '%PDF-1.4\n%âãÏÓ'; // 真实 PDF 文件头
if (path.includes('.png')) return '\x89PNG\r\n\x1a\n'; // 真实 PNG 文件头
// But return real file content formats
if (path.includes('.pdf')) return '%PDF-1.4\n%âãÏÓ'; // Real PDF header
if (path.includes('.png')) return '\x89PNG\r\n\x1a\n'; // Real PNG header
return '';
});
});
@@ -260,40 +286,38 @@ describe('parseContentType', () => {
});
});
// 过度简化:使用不真实的数据
// ❌ Over-simplified: Using unrealistic data
describe('parseContentType', () => {
it('should detect PDF content type correctly', () => {
// 这种简化数据没有测试价值
// This simplified data has no test value
const result = parseContentType('fake-pdf-content');
expect(result).toBe('application/pdf');
});
});
```
#### 真实标识符的价值
#### The Value of Real Identifiers
```typescript
// ✅ 使用真实标识符
// ✅ Use real identifiers
const result = parseModelString('openai', '+gpt-4,+gpt-3.5-turbo');
// ❌ 使用占位符(价值较低)
// ❌ Use placeholders (lower value)
const result = parseModelString('test-provider', '+model1,+model2');
```
### 现代化Mock技巧:环境设置与Mock方法
### Modern Mocking Techniques: Environment Setup and Mock Methods
**环境设置 + Mock方法结合使用**
客户端代码测试时,推荐使用环境注释配合现代化Mock方法:
When testing client-side code, use environment annotations with modern mock methods:
```typescript
/**
* @vitest-environment happy-dom // 提供浏览器API
* @vitest-environment happy-dom // Provides browser APIs
*/
import { beforeEach, vi } from 'vitest';
beforeEach(() => {
// 现代方法1:使用vi.stubGlobal替代global.xxx = ...
// Modern method 1: Use vi.stubGlobal instead of global.xxx = ...
const mockImage = vi.fn().mockImplementation(() => ({
addEventListener: vi.fn(),
naturalHeight: 600,
@@ -301,72 +325,72 @@ beforeEach(() => {
}));
vi.stubGlobal('Image', mockImage);
// 现代方法2:使用vi.spyOn保留原功能,只mock特定方法
// Modern method 2: Use vi.spyOn to preserve original functionality, only mock specific methods
vi.spyOn(URL, 'createObjectURL').mockReturnValue('blob:mock-url');
vi.spyOn(URL, 'revokeObjectURL').mockImplementation(() => {});
});
```
**环境选择优先级**
#### Environment Selection Priority
1. **@vitest-environment happy-dom** (推荐) - 轻量、快速,项目已安装
2. **@vitest-environment jsdom** - 功能完整,但需要额外安装jsdom包
3. **不设置环境** - Node.js环境,需要手动mock所有浏览器API
1. **@vitest-environment happy-dom** (Recommended) - Lightweight, fast, already installed in the project
2. **@vitest-environment jsdom** - Full-featured, but requires additional jsdom package installation
3. **No environment set** - Node.js environment, requires manually mocking all browser APIs
**Mock方法对比**
#### Mock Method Comparison
```typescript
// ❌ 旧方法:直接操作global对象(类型问题)
// ❌ Old method: Directly manipulating global object (type issues)
global.Image = mockImage;
global.URL = { ...global.URL, createObjectURL: mockFn };
// ✅ 现代方法:类型安全的vi API
vi.stubGlobal('Image', mockImage); // 完全替换全局对象
vi.spyOn(URL, 'createObjectURL'); // 部分mock,保留其他功能
// ✅ Modern method: Type-safe vi API
vi.stubGlobal('Image', mockImage); // Completely replace global object
vi.spyOn(URL, 'createObjectURL'); // Partial mock, preserve other functionality
```
### 测试覆盖率原则:代码分支优于用例数量
### Test Coverage Principles: Code Branches Over Test Quantity
**核心原则**: 优先覆盖所有代码分支,而非编写大量重复用例
**Core Principle**: Prioritize covering all code branches rather than writing many repetitive test cases.
```typescript
// ❌ 过度测试:29个测试用例都验证相同分支
// ❌ Over-testing: 29 test cases all validating the same branch
describe('getImageDimensions', () => {
it('should reject .txt files');
it('should reject .pdf files');
// ... 25个类似测试,都走相同的验证分支
// ... 25 similar tests, all hitting the same validation branch
});
// ✅ 精简测试:4个核心用例覆盖所有分支
// ✅ Lean testing: 4 core cases covering all branches
describe('getImageDimensions', () => {
it('should return dimensions for valid File object'); // 成功路径 - File
it('should return dimensions for valid data URI'); // 成功路径 - String
it('should return undefined for invalid inputs'); // 输入验证分支
it('should return undefined when image fails to load'); // 错误处理分支
it('should return dimensions for valid File object'); // Success path - File
it('should return dimensions for valid data URI'); // Success path - String
it('should return undefined for invalid inputs'); // Input validation branch
it('should return undefined when image fails to load'); // Error handling branch
});
```
**分支覆盖策略**
#### Branch Coverage Strategy
1. **成功路径** - 每种输入类型1个测试即可
2. **边界条件** - 合并类似场景到单个测试
3. **错误处理** - 测试代表性错误即可
4. **业务逻辑** - 覆盖所有if/else分支
1. **Success Paths** - One test per input type is sufficient
2. **Boundary Conditions** - Consolidate similar scenarios into a single test
3. **Error Handling** - Test representative errors only
4. **Business Logic** - Cover all if/else branches
**合理测试数量**
#### Reasonable Test Counts
- 简单工具函数:2-5个测试
- 复杂业务逻辑:5-10个测试
- 核心安全功能:适当增加,但避免重复路径
- Simple utility functions: 2-5 tests
- Complex business logic: 5-10 tests
- Core security features: Add more as needed, but avoid duplicate paths
### 错误处理测试:测试"行为"而非"文本"
### Error Handling Tests: Test "Behavior" Not "Text"
**核心原则**: 测试应该验证程序在错误发生时的行为是可预测的,而不是验证易变的错误信息文本。
**Core Principle**: Tests should verify that program behavior is predictable when errors occur, not verify error message text that may change.
#### 推荐的错误测试方式
#### Recommended Error Testing Approach
```typescript
// ✅ 测试错误类型和属性
// ✅ Test error types and properties
expect(() => validateUser({})).toThrow(ValidationError);
expect(() => processPayment({})).toThrow(
expect.objectContaining({
@@ -375,136 +399,136 @@ expect(() => processPayment({})).toThrow(
}),
);
// ❌ 避免测试具体错误文本
expect(() => processUser({})).toThrow('用户数据不能为空,请检查输入参数');
// ❌ Avoid testing specific error text
expect(() => processUser({})).toThrow('User data cannot be empty, please check input parameters');
```
### 疑难解答:警惕模块污染
### Troubleshooting: Beware of Module Pollution
**识别信号**: 当你的测试出现以下"灵异"现象时,优先怀疑模块污染:
**Warning Signs**: When your tests exhibit these "mysterious" behaviors, suspect module pollution first:
- 单独运行某个测试通过,但和其他测试一起运行就失败
- 测试的执行顺序影响结果
- Mock 设置看起来正确,但实际使用的是旧的 Mock 版本
- A test passes when run alone but fails when run with other tests
- Test execution order affects results
- Mock setup appears correct but actually uses an old mock version
#### 典型场景:动态 Mock 同一模块
#### Typical Scenario: Dynamic Mocking of the Same Module
```typescript
// ❌ 问题:动态Mock同一模块
// ❌ Problem: Dynamic mocking of the same module
it('dev mode', async () => {
vi.doMock('./config', () => ({ isDev: true }));
const { getSettings } = await import('./service'); // 可能使用缓存
const { getSettings } = await import('./service'); // May use cache
});
// ✅ 解决:清除模块缓存
// ✅ Solution: Clear module cache
beforeEach(() => {
vi.resetModules(); // 确保每个测试都是干净环境
vi.resetModules(); // Ensure each test has a clean environment
});
```
**记住**: `vi.resetModules()` 是解决测试"灵异"失败的终极武器。
**Remember**: `vi.resetModules()` is the ultimate weapon for resolving "mysterious" test failures.
## 测试文件组织
## Test File Organization
### 文件命名约定
### File Naming Convention
`*.test.ts`, `*.test.tsx` (任意位置)
`*.test.ts`, `*.test.tsx` (any location)
### 测试文件组织风格
### Test File Organization Style
项目采用 **测试文件与源文件同目录** 的组织风格:
The project uses a **co-located test files** organization style:
- 测试文件放在对应源文件的同一目录下
- 命名格式:`原文件名.test.ts` 或 `原文件名.test.tsx`
- Test files are placed in the same directory as the corresponding source files
- Naming format: `originalFileName.test.ts` or `originalFileName.test.tsx`
例如:
Example:
```plaintext
src/components/Button/
├── index.tsx # 源文件
└── index.test.tsx # 测试文件
├── index.tsx # Source file
└── index.test.tsx # Test file
```
- 也有少数情况会统一放到 `__tests__` 文件夹, 例如 `packages/database/src/models/__tests__`
- 测试使用的辅助文件放到 fixtures 文件夹
- In some cases, tests are consolidated in a `__tests__` folder, e.g., `packages/database/src/models/__tests__`
- Test helper files are placed in a fixtures folder
## 测试调试技巧
## Test Debugging Tips
### 测试调试步骤
### Test Debugging Steps
1. **确定测试环境**: 根据文件路径选择正确的配置文件
2. **隔离问题**: 使用 `-t` 参数只运行失败的测试用例
3. **分析错误**: 仔细阅读错误信息、堆栈跟踪和最近的文件修改记录
4. **添加调试**: 在测试中添加 `console.log` 了解执行流程
1. **Determine Test Environment**: Select the correct config file based on file path
2. **Isolate the Problem**: Use the `-t` flag to run only the failing test case
3. **Analyze the Error**: Carefully read error messages, stack traces, and recent file modification history
4. **Add Debugging**: Add `console.log` statements in tests to understand execution flow
### TypeScript 类型处理
### TypeScript Type Handling
在测试中,为了提高编写效率和可读性,可以适当放宽 TypeScript 类型检测:
In tests, you can relax TypeScript type checking to improve writing efficiency and readability:
#### 推荐的类型放宽策略
#### Recommended Type Relaxation Strategies
```typescript
// 使用非空断言访问测试中确定存在的属性
// Use non-null assertion to access properties you're certain exist in tests
const result = await someFunction();
expect(result!.data).toBeDefined();
expect(result!.status).toBe('success');
// 使用 any 类型简化复杂的 Mock 设置
// Use any type to simplify complex mock setups
const mockStream = new ReadableStream() as any;
mockStream.toReadableStream = () => mockStream;
// 访问私有成员
await instance['getFromCache']('key'); // 推荐中括号
await (instance as any).getFromCache('key'); // 避免as any
// Access private members
await instance['getFromCache']('key'); // Bracket notation recommended
await (instance as any).getFromCache('key'); // Avoid as any
```
#### 适用场景
#### Applicable Scenarios
- **Mock 对象**: 对于测试用的 Mock 数据,使用 `as any` 避免复杂的类型定义
- **第三方库**: 处理复杂的第三方库类型时,适当使用 `any` 提高效率
- **测试断言**: 在确定对象存在的测试场景中,使用 `!` 非空断言
- **私有成员访问**: 优先使用中括号 `instance['privateMethod']()` 而不是 `(instance as any).privateMethod()`
- **临时调试**: 快速编写测试时,先用 `any` 保证功能,后续可选择性地优化类型
- **Mock Objects**: Use `as any` for test mock data to avoid complex type definitions
- **Third-Party Libraries**: Use `any` appropriately when handling complex third-party library types
- **Test Assertions**: Use `!` non-null assertion in test scenarios where you're certain the object exists
- **Private Member Access**: Prefer bracket notation `instance['privateMethod']()` over `(instance as any).privateMethod()`
- **Temporary Debugging**: When quickly writing tests, use `any` first to ensure functionality, then optionally optimize types later
#### 注意事项
#### Important Notes
- **适度使用**: 不要过度依赖 `any`,核心业务逻辑的类型仍应保持严格
- **私有成员访问优先级**: 中括号访问 > `as any` 转换,保持更好的类型安全性
- **文档说明**: 对于使用 `any` 的复杂场景,添加注释说明原因
- **测试覆盖**: 确保即使使用了 `any`,测试仍能有效验证功能正确性
- **Use Moderately**: Don't over-rely on `any`; core business logic types should remain strict
- **Private Member Access Priority**: Bracket notation > `as any` casting for better type safety
- **Documentation**: Add comments explaining the reason for complex `any` usage scenarios
- **Test Coverage**: Ensure tests still effectively verify correctness even when using `any`
### 检查最近修改记录
### Checking Recent Modifications
**核心原则**:测试突然失败时,优先检查最近的代码修改。
**Core Principle**: When tests suddenly fail, first check recent code changes.
#### 快速检查方法
#### Quick Check Methods
```bash
git status # 查看当前修改状态
git diff HEAD -- '*.test.*' # 检查测试文件改动
git diff main...HEAD # 对比主分支差异
gh pr diff # 查看PR中的所有改动
git status # View current modification status
git diff HEAD -- '*.test.*' # Check test file changes
git diff main...HEAD # Compare with main branch
gh pr diff # View all changes in the PR
```
#### 常见原因与解决
#### Common Causes and Solutions
- **最新提交引入bug** → 检查并修复实现代码
- **分支代码滞后** → `git rebase main` 同步主分支
- **Latest commit introduced a bug** → Check and fix the implementation code
- **Branch code is outdated** → `git rebase main` to sync with main branch
## 特殊场景的测试
## Special Testing Scenarios
针对一些特殊场景的测试,需要阅读相关 rules
For special testing scenarios, refer to the related rules:
- [Electron IPC 接口测试策略](mdc:./electron-ipc-test.mdc)
- [数据库 Model 测试指南](mdc:./db-model-test.mdc)
- `electron-ipc-test.mdc` - Electron IPC Interface Testing Strategy
- `db-model-test.mdc` - Database Model Testing Guide
## 核心要点
## Key Takeaways
- **命令格式**: 使用 `bunx vitest run --silent='passed-only'` 并指定文件过滤
- **修复原则**: 失败1-2次后寻求帮助,测试命名关注行为而非实现细节
- **调试流程**: 复现 → 分析 → 假设 → 修复 → 验证 → 总结
- **文件组织**: 优先在现有 `describe` 块中添加测试,避免创建冗余顶级块
- **数据策略**: 默认追求真实性,只有高成本(I/O、网络等)时才简化
- **错误测试**: 测试错误类型和行为,避免依赖具体的错误信息文本
- **模块污染**: 测试"灵异"失败时,优先怀疑模块污染,使用 `vi.resetModules()` 解决
- **安全要求**: Model 测试必须包含权限检查,并在双环境下验证通过
- **Command Format**: Use `bunx vitest run --silent='passed-only'` with file filtering
- **Fix Principles**: Seek help after 1-2 failures; focus test naming on behavior, not implementation details
- **Debug Workflow**: Reproduce → Analyze → Hypothesize → Fix → Verify → Summarize
- **File Organization**: Prefer adding tests to existing `describe` blocks; avoid creating redundant top-level blocks
- **Data Strategy**: Default to authenticity; only simplify for high-cost scenarios (I/O, network, etc.)
- **Error Testing**: Test error types and behavior; avoid depending on specific error message text
- **Module Pollution**: When tests fail "mysteriously," suspect module pollution first; use `vi.resetModules()` to resolve
- **Security Requirements**: Model tests must include permission checks and pass in both environments
@@ -1,6 +1,6 @@
---
description: Best practices for testing Zustand store actions
globs: 'src/store/**/*.test.ts'
globs: src/store/**/*.test.ts
alwaysApply: false
---
+1 -1
View File
@@ -16,7 +16,7 @@ Main interfaces exposed for UI component consumption:
- Naming: Verb form (`createTopic`, `sendMessage`, `updateTopicTitle`)
- Responsibilities: Parameter validation, flow orchestration, calling internal actions
- Example: [src/store/chat/slices/topic/action.ts](mdc:src/store/chat/slices/topic/action.ts)
- Example: `src/store/chat/slices/topic/action.ts`
```typescript
// Public Action example
+4 -4
View File
@@ -105,7 +105,7 @@ export const initialTopicState: ChatTopicState = {
};
```
2. `reducer.ts` (复杂状态使用):
1. `reducer.ts` (复杂状态使用):
- 定义纯函数 reducer,处理同步状态转换
- 使用 `immer` 确保不可变更新
@@ -151,7 +151,7 @@ export const topicReducer = (state: ChatTopic[] = [], payload: ChatTopicDispatch
};
```
3. `selectors.ts`:
1. `selectors.ts`:
- 提供状态查询和计算函数
- 供 UI 组件使用的状态订阅接口
- 重要: 使用 `export const xxxSelectors` 模式聚合所有 selectors
@@ -186,7 +186,7 @@ export const topicSelectors = {
当 slice 的 actions 过于复杂时,可以拆分到子目录:
```
```plaintext
src/store/chat/slices/aiChat/
├── actions/
│ ├── generateAIChat.ts # AI 对话生成
@@ -204,7 +204,7 @@ src/store/chat/slices/aiChat/
管理多种内置工具的状态:
```
```plaintext
src/store/chat/slices/builtinTool/
├── actions/
│ ├── dalle.ts # DALL-E 图像生成
+1 -1
View File
@@ -16,7 +16,7 @@
<!-- Link to the issue that is fixed by this PR -->
<!-- Example: Fixes #123, Closes #456, Related to #789 -->
<!-- Example: Fixes #xxx, Closes #xxx, Related to #xxx -->
#### 🔀 Description of Change
@@ -0,0 +1,29 @@
name: Desktop Build Setup
description: Setup Node.js, pnpm and install dependencies for desktop build
inputs:
node-version:
description: Node.js version
required: true
runs:
using: composite
steps:
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
run_install: false
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: ${{ inputs.node-version }}
package-manager-cache: false
- name: Install dependencies
shell: bash
run: pnpm install --node-linker=hoisted
- name: Install deps on Desktop
shell: bash
run: npm run install-isolated --prefix=./apps/desktop
@@ -0,0 +1,46 @@
name: Desktop Upload Artifacts
description: Rename macOS yml for multi-arch and upload build artifacts
inputs:
artifact-name:
description: Name for the uploaded artifact
required: true
retention-days:
description: Number of days to retain artifacts
required: false
default: '5'
runs:
using: composite
steps:
- name: Rename macOS latest-mac.yml for multi-architecture support
if: runner.os == 'macOS'
shell: bash
run: |
cd apps/desktop/release
if [ -f "latest-mac.yml" ]; then
SYSTEM_ARCH=$(uname -m)
if [[ "$SYSTEM_ARCH" == "arm64" ]]; then
ARCH_SUFFIX="arm64"
else
ARCH_SUFFIX="x64"
fi
mv latest-mac.yml "latest-mac-${ARCH_SUFFIX}.yml"
echo "✅ Renamed latest-mac.yml to latest-mac-${ARCH_SUFFIX}.yml"
fi
- name: Upload artifact
uses: actions/upload-artifact@v6
with:
name: ${{ inputs.artifact-name }}
path: |
apps/desktop/release/latest*
apps/desktop/release/*.dmg*
apps/desktop/release/*.zip*
apps/desktop/release/*.exe*
apps/desktop/release/*.AppImage
apps/desktop/release/*.deb*
apps/desktop/release/*.snap*
apps/desktop/release/*.rpm*
apps/desktop/release/*.tar.gz*
retention-days: ${{ inputs.retention-days }}
+11 -1
View File
@@ -73,7 +73,17 @@ jobs:
echo "📦 Using provided version: ${version} (base: ${base_version})"
else
ci_build_number="${{ github.run_number }}"
version="0.0.0-${CHANNEL}.manual.${ci_build_number}"
if [ "$CHANNEL" = "beta" ]; then
channel_suffix="next"
else
channel_suffix="$CHANNEL"
fi
if [[ "$base_version" == *"-${channel_suffix}"* ]]; then
version="${base_version}.manual.${ci_build_number}"
else
version="${base_version}-${channel_suffix}.manual.${ci_build_number}"
fi
echo "📦 Generated version: ${version} (base: ${base_version})"
fi
+76 -115
View File
@@ -1,22 +1,59 @@
name: Release Desktop Beta
# ============================================
# Beta/Nightly 频道发版工作流
# ============================================
# 触发条件: 发布包含 pre-release 标识的 release
# 如: v2.0.0-beta.1, v2.0.0-alpha.1, v2.0.0-rc.1, v2.0.0-nightly.xxx, v2.0.0-next.292
#
# 注意: Stable 版本 (如 v2.0.0) 由 release-desktop-stable.yml 处理
# ============================================
on:
release:
types: [published] # 发布 release 时触发构建
types: [published]
# 确保同一时间只运行一个相同的 workflow,取消正在进行的旧的运行
concurrency:
group: ${{ github.ref }}-${{ github.workflow }}
cancel-in-progress: true
# Add default permissions
permissions: read-all
env:
NODE_VERSION: '24.11.1'
jobs:
# ============================================
# 检查是否为 Beta/Nightly/Next 版本 (排除 Stable)
# ============================================
check-beta:
name: Check if Beta/Nightly/Next Release
runs-on: ubuntu-latest
outputs:
is_beta: ${{ steps.check.outputs.is_beta }}
version: ${{ steps.check.outputs.version }}
steps:
- name: Check release tag
id: check
run: |
version="${{ github.event.release.tag_name }}"
version="${version#v}"
echo "version=${version}" >> $GITHUB_OUTPUT
# Beta/Nightly/Next 版本包含 beta/alpha/rc/nightly/next
if [[ "$version" == *"beta"* ]] || [[ "$version" == *"alpha"* ]] || [[ "$version" == *"rc"* ]] || [[ "$version" == *"nightly"* ]] || [[ "$version" == *"next"* ]]; then
echo "is_beta=true" >> $GITHUB_OUTPUT
echo "✅ Beta/Nightly/Next release detected: $version"
else
echo "is_beta=false" >> $GITHUB_OUTPUT
echo "⏭️ Skipping: $version is a stable release (handled by release-desktop-stable.yml)"
fi
test:
name: Code quality check
# 添加 PR label 触发条件,只有添加了 trigger:build-desktop 标签的 PR 才会触发构建
runs-on: ubuntu-latest # 只在 ubuntu 上运行一次检查
needs: [check-beta]
if: needs.check-beta.outputs.is_beta == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout base
uses: actions/checkout@v6
@@ -26,7 +63,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24.11.1
node-version: ${{ env.NODE_VERSION }}
package-manager-cache: false
- name: Install bun
@@ -40,44 +77,9 @@ jobs:
- name: Lint
run: bun run lint
version:
name: Determine version
runs-on: ubuntu-latest
outputs:
# 输出版本信息,供后续 job 使用
version: ${{ steps.set_version.outputs.version }}
is_pr_build: ${{ steps.set_version.outputs.is_pr_build }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24.11.1
package-manager-cache: false
# 主要逻辑:确定构建版本号
- name: Set version
id: set_version
run: |
# 从 apps/desktop/package.json 读取基础版本号
base_version=$(node -p "require('./apps/desktop/package.json').version")
# Release 事件直接使用 release tag 作为版本号,去掉可能的 v 前缀
version="${{ github.event.release.tag_name }}"
version="${version#v}"
echo "version=${version}" >> $GITHUB_OUTPUT
echo "📦 Release Version: ${version}"
# 输出版本信息总结,方便在 GitHub Actions 界面查看
- name: Version Summary
run: |
echo "🚦 Release Version: ${{ steps.set_version.outputs.version }}"
build:
needs: [version, test]
needs: [check-beta]
if: needs.check-beta.outputs.is_beta == 'true'
name: Build Desktop App
runs-on: ${{ matrix.os }}
strategy:
@@ -88,117 +90,76 @@ jobs:
with:
fetch-depth: 0
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Setup build environment
uses: ./.github/actions/desktop-build-setup
with:
run_install: false
node-version: ${{ env.NODE_VERSION }}
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24.11.1
package-manager-cache: false
# node-linker=hoisted 模式将可以确保 asar 压缩可用
- name: Install dependencies
run: pnpm install --node-linker=hoisted
- name: Install deps on Desktop
run: npm run install-isolated --prefix=./apps/desktop
# 设置 package.json 的版本号
- name: Set package version
run: npm run workflow:set-desktop-version ${{ needs.version.outputs.version }} beta
run: npm run workflow:set-desktop-version ${{ needs.check-beta.outputs.version }} beta
# macOS 构建处理
# macOS 构建
- name: Build artifact on macOS
if: runner.os == 'macOS'
run: npm run desktop:build
env:
UPDATE_CHANNEL: beta
APP_URL: http://localhost:3015
DATABASE_URL: "postgresql://postgres@localhost:5432/postgres"
# 默认添加一个加密 SECRET
KEY_VAULTS_SECRET: "oLXWIiR/AKF+rWaqy9lHkrYgzpATbW3CtJp3UfkVgpE="
# macOS 签名和公证配置
DATABASE_URL: 'postgresql://postgres@localhost:5432/postgres'
KEY_VAULTS_SECRET: 'oLXWIiR/AKF+rWaqy9lHkrYgzpATbW3CtJp3UfkVgpE='
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
# allow provisionally
CSC_FOR_PULL_REQUEST: true
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
NEXT_PUBLIC_DESKTOP_PROJECT_ID: ${{ secrets.UMAMI_BETA_DESKTOP_PROJECT_ID }}
NEXT_PUBLIC_DESKTOP_UMAMI_BASE_URL: ${{ secrets.UMAMI_BETA_DESKTOP_BASE_URL }}
# Windows 平台构建处理
# Windows 构建
- name: Build artifact on Windows
if: runner.os == 'Windows'
run: npm run desktop:build
env:
UPDATE_CHANNEL: beta
APP_URL: http://localhost:3015
DATABASE_URL: "postgresql://postgres@localhost:5432/postgres"
KEY_VAULTS_SECRET: "oLXWIiR/AKF+rWaqy9lHkrYgzpATbW3CtJp3UfkVgpE="
DATABASE_URL: 'postgresql://postgres@localhost:5432/postgres'
KEY_VAULTS_SECRET: 'oLXWIiR/AKF+rWaqy9lHkrYgzpATbW3CtJp3UfkVgpE='
NEXT_PUBLIC_DESKTOP_PROJECT_ID: ${{ secrets.UMAMI_BETA_DESKTOP_PROJECT_ID }}
NEXT_PUBLIC_DESKTOP_UMAMI_BASE_URL: ${{ secrets.UMAMI_BETA_DESKTOP_BASE_URL }}
# 将 TEMP 和 TMP 目录设置到 C 盘
TEMP: C:\temp
TMP: C:\temp
# Linux 平台构建处理
# Linux 构建
- name: Build artifact on Linux
if: runner.os == 'Linux'
run: npm run desktop:build
env:
UPDATE_CHANNEL: beta
APP_URL: http://localhost:3015
DATABASE_URL: "postgresql://postgres@localhost:5432/postgres"
KEY_VAULTS_SECRET: "oLXWIiR/AKF+rWaqy9lHkrYgzpATbW3CtJp3UfkVgpE="
DATABASE_URL: 'postgresql://postgres@localhost:5432/postgres'
KEY_VAULTS_SECRET: 'oLXWIiR/AKF+rWaqy9lHkrYgzpATbW3CtJp3UfkVgpE='
NEXT_PUBLIC_DESKTOP_PROJECT_ID: ${{ secrets.UMAMI_BETA_DESKTOP_PROJECT_ID }}
NEXT_PUBLIC_DESKTOP_UMAMI_BASE_URL: ${{ secrets.UMAMI_BETA_DESKTOP_BASE_URL }}
# 处理 macOS latest-mac.yml 重命名 (避免多架构覆盖)
- name: Rename macOS latest-mac.yml for multi-architecture support
if: runner.os == 'macOS'
run: |
cd apps/desktop/release
if [ -f "latest-mac.yml" ]; then
# 使用系统架构检测,与 electron-builder 输出保持一致
SYSTEM_ARCH=$(uname -m)
if [[ "$SYSTEM_ARCH" == "arm64" ]]; then
ARCH_SUFFIX="arm64"
else
ARCH_SUFFIX="x64"
fi
mv latest-mac.yml "latest-mac-${ARCH_SUFFIX}.yml"
echo "✅ Renamed latest-mac.yml to latest-mac-${ARCH_SUFFIX}.yml (detected: $SYSTEM_ARCH)"
ls -la latest-mac-*.yml
else
echo "⚠️ latest-mac.yml not found, skipping rename"
ls -la latest*.yml || echo "No latest*.yml files found"
fi
# 上传构建产物 (工作流处理重命名,不依赖 electron-builder 钩子)
- name: Upload artifact
uses: actions/upload-artifact@v6
- name: Upload artifacts
uses: ./.github/actions/desktop-upload-artifacts
with:
name: release-${{ matrix.os }}
path: |
apps/desktop/release/latest*
apps/desktop/release/*.dmg*
apps/desktop/release/*.zip*
apps/desktop/release/*.exe*
apps/desktop/release/*.AppImage
apps/desktop/release/*.deb*
apps/desktop/release/*.snap*
apps/desktop/release/*.rpm*
apps/desktop/release/*.tar.gz*
retention-days: 5
artifact-name: release-${{ matrix.os }}
# 汇总门禁: test/build 完成后决定是否继续
gate:
needs: [check-beta, test, build]
if: ${{ needs.check-beta.outputs.is_beta == 'true' && needs.test.result == 'success' && needs.build.result == 'success' }}
name: Gate for publish
runs-on: ubuntu-latest
steps:
- name: Gate passed
run: echo "Gate passed"
# 合并 macOS 多架构 latest-mac.yml 文件
merge-mac-files:
needs: [build, version]
needs: [gate]
name: Merge macOS Release Files
runs-on: ubuntu-latest
permissions:
@@ -210,7 +171,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24.11.1
node-version: ${{ env.NODE_VERSION }}
package-manager-cache: false
- name: Install bun
@@ -0,0 +1,472 @@
name: Release Desktop Stable
# ============================================
# Stable 频道发版工作流
# ============================================
# 触发条件: 发布不含 pre-release 后缀的 release (如 v2.0.0)
#
# 与 Beta 的区别:
# 1. 仅响应 stable 版本 tag (不含任何 '-' 后缀)
# 2. 使用 STABLE 专用的 Umami 配置
# 3. 额外上传到 S3 更新服务器
# 4. 构建时注入 UPDATE_SERVER_URL 让客户端从 S3 检查更新
#
# 需要配置的 Secrets (S3 相关, 统一 UPDATE_ 前缀):
# - UPDATE_AWS_ACCESS_KEY_ID
# - UPDATE_AWS_SECRET_ACCESS_KEY
# - UPDATE_S3_BUCKET (S3 存储桶名称)
# - UPDATE_S3_REGION (可选, 默认 us-east-1)
# - UPDATE_S3_ENDPOINT (可选, 用于 R2/MinIO 等 S3 兼容服务)
# - UPDATE_SERVER_URL (客户端检查更新的 URL)
# ============================================
on:
release:
types: [published]
workflow_dispatch:
inputs:
version:
description: 'Version to build (e.g., 2.0.0)'
required: true
type: string
build_mac:
description: 'Build macOS (ARM64)'
required: false
type: boolean
default: true
build_mac_intel:
description: 'Build macOS (Intel x64)'
required: false
type: boolean
default: true
build_windows:
description: 'Build Windows'
required: false
type: boolean
default: true
build_linux:
description: 'Build Linux'
required: false
type: boolean
default: true
skip_s3_upload:
description: 'Skip S3 upload (for testing)'
required: false
type: boolean
default: true
skip_github_release:
description: 'Skip GitHub release upload (for testing)'
required: false
type: boolean
default: true
concurrency:
group: ${{ github.ref }}-${{ github.workflow }}
cancel-in-progress: true
permissions: read-all
env:
NODE_VERSION: '24.11.1'
jobs:
# ============================================
# 检查版本信息
# ============================================
check-stable:
name: Check Release Version
runs-on: ubuntu-latest
outputs:
is_stable: ${{ steps.check.outputs.is_stable }}
version: ${{ steps.check.outputs.version }}
is_manual: ${{ steps.check.outputs.is_manual }}
release_notes: ${{ steps.check.outputs.release_notes }}
steps:
- name: Check release info
id: check
run: |
# 判断触发方式
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
# 手动触发: 使用输入的版本号
version="${{ inputs.version }}"
version="${version#v}"
echo "is_manual=true" >> $GITHUB_OUTPUT
echo "version=${version}" >> $GITHUB_OUTPUT
echo "release_notes=" >> $GITHUB_OUTPUT
echo "🔧 Manual trigger: version=${version}"
else
# Release 触发: 从 tag 提取版本号
version="${{ github.event.release.tag_name }}"
version="${version#v}"
echo "is_manual=false" >> $GITHUB_OUTPUT
echo "version=${version}" >> $GITHUB_OUTPUT
release_body="${{ github.event.release.body }}"
{
echo "release_notes<<EOF"
printf '%s\n' "$release_body"
echo "EOF"
} >> $GITHUB_OUTPUT
fi
# 检查是否为 stable 版本 (不含任何 '-' 后缀)
if [[ "$version" == *"-"* ]]; then
echo "is_stable=false" >> $GITHUB_OUTPUT
echo "⏭️ Skipping: $version is not a stable release"
else
echo "is_stable=true" >> $GITHUB_OUTPUT
echo "✅ Stable release detected: $version"
fi
# ============================================
# 配置构建矩阵 (检查自托管 Runner)
# ============================================
configure-build:
needs: [check-stable]
if: needs.check-stable.outputs.is_stable == 'true'
name: Configure Build Matrix
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- name: Generate Matrix
id: set-matrix
run: |
# 基础矩阵
static_matrix='[]'
# Windows
if [[ "${{ github.event_name }}" != "workflow_dispatch" ]] || [[ "${{ inputs.build_windows }}" == "true" ]]; then
static_matrix=$(echo "$static_matrix" | jq -c '. + [{"os": "windows-2025", "name": "windows-2025"}]')
fi
# Linux
if [[ "${{ github.event_name }}" != "workflow_dispatch" ]] || [[ "${{ inputs.build_linux }}" == "true" ]]; then
static_matrix=$(echo "$static_matrix" | jq -c '. + [{"os": "ubuntu-latest", "name": "ubuntu-latest"}]')
fi
# macOS (ARM64)
# 使用 GitHub Hosted Runner
if [[ "${{ github.event_name }}" != "workflow_dispatch" ]] || [[ "${{ inputs.build_mac }}" == "true" ]]; then
echo "Using GitHub-Hosted Runner for macOS ARM64"
arm_entry='{"os": "macos-14", "name": "macos-arm64"}'
static_matrix=$(echo "$static_matrix" | jq -c --argjson entry "$arm_entry" '. + [$entry]')
fi
if [[ "${{ github.event_name }}" != "workflow_dispatch" ]] || [[ "${{ inputs.build_mac_intel }}" == "true" ]]; then
echo "Using GitHub-Hosted Runner for macOS Intel x64"
intel_entry='{"os": "macos-15-intel", "name": "macos-intel"}'
static_matrix=$(echo "$static_matrix" | jq -c --argjson entry "$intel_entry" '. + [$entry]')
fi
# 输出
echo "matrix={\"include\":$static_matrix}" >> $GITHUB_OUTPUT
# ============================================
# 多平台构建
# ============================================
build:
needs: [check-stable, configure-build]
if: needs.check-stable.outputs.is_stable == 'true'
name: Build Desktop App
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.configure-build.outputs.matrix) }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup build environment
uses: ./.github/actions/desktop-build-setup
with:
node-version: ${{ env.NODE_VERSION }}
- name: Set package version
run: npm run workflow:set-desktop-version ${{ needs.check-stable.outputs.version }} stable
# macOS 构建
- name: Build artifact on macOS
if: runner.os == 'macOS'
run: npm run desktop:build
env:
UPDATE_CHANNEL: stable
UPDATE_SERVER_URL: ${{ secrets.UPDATE_SERVER_URL }}
RELEASE_NOTES: ${{ needs.check-stable.outputs.release_notes }}
APP_URL: http://localhost:3015
DATABASE_URL: 'postgresql://postgres@localhost:5432/postgres'
KEY_VAULTS_SECRET: 'oLXWIiR/AKF+rWaqy9lHkrYgzpATbW3CtJp3UfkVgpE='
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
CSC_FOR_PULL_REQUEST: true
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
NEXT_PUBLIC_DESKTOP_PROJECT_ID: ${{ secrets.UMAMI_STABLE_DESKTOP_PROJECT_ID }}
NEXT_PUBLIC_DESKTOP_UMAMI_BASE_URL: ${{ secrets.UMAMI_STABLE_DESKTOP_BASE_URL }}
# Windows 构建
- name: Build artifact on Windows
if: runner.os == 'Windows'
run: npm run desktop:build
env:
UPDATE_CHANNEL: stable
UPDATE_SERVER_URL: ${{ secrets.UPDATE_SERVER_URL }}
RELEASE_NOTES: ${{ needs.check-stable.outputs.release_notes }}
APP_URL: http://localhost:3015
DATABASE_URL: 'postgresql://postgres@localhost:5432/postgres'
KEY_VAULTS_SECRET: 'oLXWIiR/AKF+rWaqy9lHkrYgzpATbW3CtJp3UfkVgpE='
NEXT_PUBLIC_DESKTOP_PROJECT_ID: ${{ secrets.UMAMI_STABLE_DESKTOP_PROJECT_ID }}
NEXT_PUBLIC_DESKTOP_UMAMI_BASE_URL: ${{ secrets.UMAMI_STABLE_DESKTOP_BASE_URL }}
TEMP: C:\temp
TMP: C:\temp
# Linux 构建
- name: Build artifact on Linux
if: runner.os == 'Linux'
run: |
npm run desktop:build
tar -czf apps/desktop/release/lobehub-renderer.tar.gz -C out .
env:
UPDATE_CHANNEL: stable
UPDATE_SERVER_URL: ${{ secrets.UPDATE_SERVER_URL }}
RELEASE_NOTES: ${{ needs.check-stable.outputs.release_notes }}
APP_URL: http://localhost:3015
DATABASE_URL: 'postgresql://postgres@localhost:5432/postgres'
KEY_VAULTS_SECRET: 'oLXWIiR/AKF+rWaqy9lHkrYgzpATbW3CtJp3UfkVgpE='
NEXT_PUBLIC_DESKTOP_PROJECT_ID: ${{ secrets.UMAMI_STABLE_DESKTOP_PROJECT_ID }}
NEXT_PUBLIC_DESKTOP_UMAMI_BASE_URL: ${{ secrets.UMAMI_STABLE_DESKTOP_BASE_URL }}
- name: Upload artifacts
uses: ./.github/actions/desktop-upload-artifacts
with:
artifact-name: release-${{ matrix.name }}
# ============================================
# 合并 macOS 多架构文件
# ============================================
merge-mac-files:
needs: [build, check-stable]
name: Merge macOS Release Files
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}
package-manager-cache: false
- name: Install bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Download artifacts
uses: actions/download-artifact@v7
with:
path: release
pattern: release-*
merge-multiple: true
- name: List downloaded artifacts
run: ls -R release
- name: Install yaml only for merge step
run: |
cd scripts/electronWorkflow
if [ ! -f package.json ]; then
echo '{"name":"merge-mac-release","private":true}' > package.json
fi
bun add --no-save yaml@2.8.1
- name: Merge latest-mac.yml files
run: bun run scripts/electronWorkflow/mergeMacReleaseFiles.js
- name: Upload artifacts with merged macOS files
uses: actions/upload-artifact@v6
with:
name: merged-release
path: release/
retention-days: 1
# ============================================
# 发布到 GitHub Releases
# ============================================
publish-github:
needs: [merge-mac-files, check-stable]
name: Publish to GitHub Release
runs-on: ubuntu-latest
# 手动触发时可选择跳过
if: ${{ !(github.event_name == 'workflow_dispatch' && inputs.skip_github_release) }}
permissions:
contents: write
steps:
- name: Download merged artifacts
uses: actions/download-artifact@v7
with:
name: merged-release
path: release
- name: List final artifacts
run: ls -R release
- name: Upload to Release
uses: softprops/action-gh-release@v1
with:
# 手动触发时使用输入的版本号创建 tag
tag_name: ${{ github.event_name == 'workflow_dispatch' && format('v{0}', needs.check-stable.outputs.version) || github.event.release.tag_name }}
# 手动触发时创建为 draft
draft: ${{ github.event_name == 'workflow_dispatch' }}
files: |
release/stable*
release/latest*
release/*.dmg*
release/*.zip*
release/*.exe*
release/*.AppImage
release/*.deb*
release/*.snap*
release/*.rpm*
release/*.tar.gz*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# ============================================
# 发布到 S3 更新服务器
# ============================================
# S3 目录结构:
# s3://bucket/
# stable/
# stable-mac.yml ← electron-updater 检查更新 (stable channel)
# stable.yml ← Windows (stable channel)
# stable-linux.yml ← Linux (stable channel)
# latest-mac.yml ← fallback for GitHub provider
# {version}/ ← 版本目录
# *.dmg, *.zip, *.exe, ...
# ============================================
publish-s3:
needs: [merge-mac-files, check-stable]
name: Publish to S3
runs-on: ubuntu-latest
# 手动触发时可选择跳过
if: ${{ !(github.event_name == 'workflow_dispatch' && inputs.skip_s3_upload) }}
steps:
- name: Download merged artifacts
uses: actions/download-artifact@v7
with:
name: merged-release
path: release
- name: List artifacts to upload
run: |
echo "📦 Artifacts to upload to S3:"
ls -lah release/
echo ""
echo "📋 Version: ${{ needs.check-stable.outputs.version }}"
- name: Upload to S3
env:
AWS_ACCESS_KEY_ID: ${{ secrets.UPDATE_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.UPDATE_AWS_SECRET_ACCESS_KEY }}
AWS_REGION: ${{ secrets.UPDATE_S3_REGION || 'us-east-1' }}
S3_BUCKET: ${{ secrets.UPDATE_S3_BUCKET }}
S3_ENDPOINT: ${{ secrets.UPDATE_S3_ENDPOINT }}
VERSION: ${{ needs.check-stable.outputs.version }}
run: |
if [ -z "$S3_BUCKET" ]; then
echo "⚠️ UPDATE_S3_BUCKET is not configured, skipping S3 upload"
echo ""
echo "To enable S3 upload, configure the following secrets:"
echo " - UPDATE_AWS_ACCESS_KEY_ID"
echo " - UPDATE_AWS_SECRET_ACCESS_KEY"
echo " - UPDATE_S3_BUCKET"
echo " - UPDATE_S3_REGION (optional, defaults to us-east-1)"
echo " - UPDATE_S3_ENDPOINT (optional, for S3-compatible services)"
exit 0
fi
# 构建端点参数
ENDPOINT_ARG=""
if [ -n "$S3_ENDPOINT" ]; then
ENDPOINT_ARG="--endpoint-url $S3_ENDPOINT"
echo "📡 Using custom S3 endpoint: $S3_ENDPOINT"
fi
echo "🚀 Uploading to S3 bucket: $S3_BUCKET"
echo "📁 Target path: s3://$S3_BUCKET/stable/"
echo ""
# 1. 上传安装包到版本目录
echo "📦 Uploading release files to s3://$S3_BUCKET/stable/$VERSION/"
for file in release/*.dmg release/*.zip release/*.exe release/*.AppImage release/*.deb release/*.rpm release/*.snap release/*.tar.gz; do
if [ -f "$file" ]; then
filename=$(basename "$file")
echo " ↗️ $filename"
aws s3 cp "$file" "s3://$S3_BUCKET/stable/$VERSION/$filename" $ENDPOINT_ARG
fi
done
# 2. 创建 stable*.yml (从 latest*.yml 复制,并修改 URL 加上版本目录前缀)
# electron-updater 在 channel=stable 时会找 stable-mac.yml
# S3 目录结构: stable/{version}/xxx.dmg,所以 URL 需要加上 {version}/ 前缀
echo ""
echo "📋 Creating stable*.yml files from latest*.yml..."
for yml in release/latest*.yml; do
if [ -f "$yml" ]; then
stable_name=$(basename "$yml" | sed 's/latest/stable/')
# 复制并修改 URL: 给所有 url 字段加上版本目录前缀
# url: xxx.dmg -> url: {VERSION}/xxx.dmg
sed "s|url: |url: $VERSION/|g" "$yml" > "release/$stable_name"
echo " 📄 Created $stable_name from $(basename $yml) with URL prefix: $VERSION/"
fi
done
# 3. 创建 renderer manifest (用于验证 renderer tar 完整性)
echo ""
echo "📋 Creating renderer manifest..."
RENDERER_TAR="release/lobehub-renderer.tar.gz"
if [ -f "$RENDERER_TAR" ]; then
RENDERER_SHA512=$(shasum -a 512 "$RENDERER_TAR" | awk '{print $1}' | xxd -r -p | base64)
RENDERER_SIZE=$(stat -f%z "$RENDERER_TAR" 2>/dev/null || stat -c%s "$RENDERER_TAR")
RELEASE_DATE=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
echo "version: $VERSION" > "release/stable-renderer.yml"
echo "files:" >> "release/stable-renderer.yml"
echo " - url: $VERSION/lobehub-renderer.tar.gz" >> "release/stable-renderer.yml"
echo " sha512: $RENDERER_SHA512" >> "release/stable-renderer.yml"
echo " size: $RENDERER_SIZE" >> "release/stable-renderer.yml"
echo "path: $VERSION/lobehub-renderer.tar.gz" >> "release/stable-renderer.yml"
echo "sha512: $RENDERER_SHA512" >> "release/stable-renderer.yml"
echo "releaseDate: '$RELEASE_DATE'" >> "release/stable-renderer.yml"
echo " 📄 Created stable-renderer.yml with SHA512 checksum"
else
echo " ⚠️ Renderer tar not found, skipping manifest creation"
fi
# 4. 上传 manifest 到根目录和版本目录
# 根目录: electron-updater 需要,会被每次发版覆盖
# 版本目录: 作为存档保留
echo ""
echo "📋 Uploading manifest files..."
for yml in release/stable*.yml release/latest*.yml; do
if [ -f "$yml" ]; then
filename=$(basename "$yml")
echo " ↗️ $filename -> s3://$S3_BUCKET/stable/$filename"
aws s3 cp "$yml" "s3://$S3_BUCKET/stable/$filename" $ENDPOINT_ARG
echo " ↗️ $filename -> s3://$S3_BUCKET/stable/$VERSION/$filename (archive)"
aws s3 cp "$yml" "s3://$S3_BUCKET/stable/$VERSION/$filename" $ENDPOINT_ARG
fi
done
echo ""
echo "✅ S3 upload completed!"
echo ""
echo "📋 Files in s3://$S3_BUCKET/stable/:"
aws s3 ls "s3://$S3_BUCKET/stable/" $ENDPOINT_ARG || true
echo ""
echo "📋 Files in s3://$S3_BUCKET/stable/$VERSION/:"
aws s3 ls "s3://$S3_BUCKET/stable/$VERSION/" $ENDPOINT_ARG || true
+18 -3
View File
@@ -26,9 +26,24 @@
],
"npm.packageManager": "pnpm",
"search.exclude": {
"**/node_modules": true
// useless to search this big folder
// "locales": true
"**/node_modules": true,
// useless to search this big folder, exclude all locales except en-US and zh-CN
"locales/ar/**": true,
"locales/bg-BG/**": true,
"locales/de-DE/**": true,
"locales/es-ES/**": true,
"locales/fa-IR/**": true,
"locales/fr-FR/**": true,
"locales/it-IT/**": true,
"locales/ja-JP/**": true,
"locales/ko-KR/**": true,
"locales/nl-NL/**": true,
"locales/pl-PL/**": true,
"locales/pt-BR/**": true,
"locales/ru-RU/**": true,
"locales/tr-TR/**": true,
"locales/vi-VN/**": true,
"locales/zh-TW/**": true
},
"stylelint.validate": [
"css",
+982
View File
@@ -2,6 +2,988 @@
# Changelog
## [Version 2.0.0-next.311](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.310...v2.0.0-next.311)
<sup>Released on **2026-01-19**</sup>
#### ♻ Code Refactoring
- **misc**: Refactor market sdk into market servers.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### Code refactoring
- **misc**: Refactor market sdk into market servers, closes [#11604](https://github.com/lobehub/lobe-chat/issues/11604) ([858cc20](https://github.com/lobehub/lobe-chat/commit/858cc20))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.310](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.309...v2.0.0-next.310)
<sup>Released on **2026-01-19**</sup>
#### 💄 Styles
- **misc**: Update i18n.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### Styles
- **misc**: Update i18n, closes [#11596](https://github.com/lobehub/lobe-chat/issues/11596) ([b02d26c](https://github.com/lobehub/lobe-chat/commit/b02d26c))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.309](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.308...v2.0.0-next.309)
<sup>Released on **2026-01-18**</sup>
#### 🐛 Bug Fixes
- **misc**: Fix group sub task execution.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### What's fixed
- **misc**: Fix group sub task execution, closes [#11595](https://github.com/lobehub/lobe-chat/issues/11595) ([32be2b2](https://github.com/lobehub/lobe-chat/commit/32be2b2))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.308](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.307...v2.0.0-next.308)
<sup>Released on **2026-01-18**</sup>
#### 🐛 Bug Fixes
- **misc**: Fix group subagent task issue.
#### 💄 Styles
- **misc**: Update i18n.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### What's fixed
- **misc**: Fix group subagent task issue, closes [#11589](https://github.com/lobehub/lobe-chat/issues/11589) ([9ad468b](https://github.com/lobehub/lobe-chat/commit/9ad468b))
#### Styles
- **misc**: Update i18n, closes [#11482](https://github.com/lobehub/lobe-chat/issues/11482) ([676611e](https://github.com/lobehub/lobe-chat/commit/676611e))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.307](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.306...v2.0.0-next.307)
<sup>Released on **2026-01-18**</sup>
#### 🐛 Bug Fixes
- **upload**: Resolve file upload button unresponsive issue.
- **misc**: Fixed the createGroupWithSupervisor function test, slove when use copy & install group from market, the member system Role is lost.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### What's fixed
- **upload**: Resolve file upload button unresponsive issue, closes [#11588](https://github.com/lobehub/lobe-chat/issues/11588) ([76fd478](https://github.com/lobehub/lobe-chat/commit/76fd478))
- **misc**: Fixed the createGroupWithSupervisor function test, closes [#11590](https://github.com/lobehub/lobe-chat/issues/11590) ([83bb343](https://github.com/lobehub/lobe-chat/commit/83bb343))
- **misc**: Slove when use copy & install group from market, the member system Role is lost, closes [#11585](https://github.com/lobehub/lobe-chat/issues/11585) ([9b73ad7](https://github.com/lobehub/lobe-chat/commit/9b73ad7))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.306](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.305...v2.0.0-next.306)
<sup>Released on **2026-01-18**</sup>
#### 🐛 Bug Fixes
- **misc**: Fix supervisor id issue.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### What's fixed
- **misc**: Fix supervisor id issue, closes [#11584](https://github.com/lobehub/lobe-chat/issues/11584) ([c097584](https://github.com/lobehub/lobe-chat/commit/c097584))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.305](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.304...v2.0.0-next.305)
<sup>Released on **2026-01-18**</sup>
#### 🐛 Bug Fixes
- **desktop**: Add auth required modal and improve error handling.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### What's fixed
- **desktop**: Add auth required modal and improve error handling, closes [#11574](https://github.com/lobehub/lobe-chat/issues/11574) ([4e5a516](https://github.com/lobehub/lobe-chat/commit/4e5a516))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.304](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.303...v2.0.0-next.304)
<sup>Released on **2026-01-18**</sup>
#### 💄 Styles
- **misc**: Improve auto scroll and loading hint.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### Styles
- **misc**: Improve auto scroll and loading hint, closes [#11579](https://github.com/lobehub/lobe-chat/issues/11579) ([277b42d](https://github.com/lobehub/lobe-chat/commit/277b42d))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.303](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.302...v2.0.0-next.303)
<sup>Released on **2026-01-18**</sup>
#### 💄 Styles
- **misc**: Improve operation hint and fix scroll issue.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### Styles
- **misc**: Improve operation hint and fix scroll issue, closes [#11573](https://github.com/lobehub/lobe-chat/issues/11573) ([8505d14](https://github.com/lobehub/lobe-chat/commit/8505d14))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.302](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.301...v2.0.0-next.302)
<sup>Released on **2026-01-17**</sup>
#### 🐛 Bug Fixes
- **misc**: Try to fix group supervisor id not sync successful.
#### 💄 Styles
- **misc**: Fix left panel on group page.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### What's fixed
- **misc**: Try to fix group supervisor id not sync successful, closes [#11570](https://github.com/lobehub/lobe-chat/issues/11570) ([ef51c17](https://github.com/lobehub/lobe-chat/commit/ef51c17))
#### Styles
- **misc**: Fix left panel on group page, closes [#11571](https://github.com/lobehub/lobe-chat/issues/11571) ([de81a42](https://github.com/lobehub/lobe-chat/commit/de81a42))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.301](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.300...v2.0.0-next.301)
<sup>Released on **2026-01-17**</sup>
#### 🐛 Bug Fixes
- **desktop**: Ensure allowPrerelease is set correctly for updater.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### What's fixed
- **desktop**: Ensure allowPrerelease is set correctly for updater, closes [#11566](https://github.com/lobehub/lobe-chat/issues/11566) ([9383c6b](https://github.com/lobehub/lobe-chat/commit/9383c6b))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.300](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.299...v2.0.0-next.300)
<sup>Released on **2026-01-17**</sup>
#### 🐛 Bug Fixes
- **misc**: Fix supervisor group prompt.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### What's fixed
- **misc**: Fix supervisor group prompt, closes [#11543](https://github.com/lobehub/lobe-chat/issues/11543) ([3a6efbc](https://github.com/lobehub/lobe-chat/commit/3a6efbc))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.299](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.298...v2.0.0-next.299)
<sup>Released on **2026-01-17**</sup>
#### ♻ Code Refactoring
- **ui**: Migrate from Dropdown to DropdownMenu/ContextMenuTrigger components.
#### 🐛 Bug Fixes
- **misc**: Fix topic messages display error when switch topic quickly.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### Code refactoring
- **ui**: Migrate from Dropdown to DropdownMenu/ContextMenuTrigger components, closes [#11539](https://github.com/lobehub/lobe-chat/issues/11539) ([9c9d4b1](https://github.com/lobehub/lobe-chat/commit/9c9d4b1))
#### What's fixed
- **misc**: Fix topic messages display error when switch topic quickly, closes [#11542](https://github.com/lobehub/lobe-chat/issues/11542) ([371d91e](https://github.com/lobehub/lobe-chat/commit/371d91e))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.298](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.297...v2.0.0-next.298)
<sup>Released on **2026-01-16**</sup>
#### 🐛 Bug Fixes
- **misc**: Fix switch skill in home.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### What's fixed
- **misc**: Fix switch skill in home, closes [#11537](https://github.com/lobehub/lobe-chat/issues/11537) ([d5561f3](https://github.com/lobehub/lobe-chat/commit/d5561f3))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.297](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.296...v2.0.0-next.297)
<sup>Released on **2026-01-16**</sup>
#### ✨ Features
- **misc**: Add agent group publish into market & use market group agents in lobehub.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### What's improved
- **misc**: Add agent group publish into market & use market group agents in lobehub, closes [#11535](https://github.com/lobehub/lobe-chat/issues/11535) ([02b9e76](https://github.com/lobehub/lobe-chat/commit/02b9e76))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.296](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.295...v2.0.0-next.296)
<sup>Released on **2026-01-16**</sup>
#### 💄 Styles
- **misc**: Improve todo list.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### Styles
- **misc**: Improve todo list, closes [#11533](https://github.com/lobehub/lobe-chat/issues/11533) ([a4b71e9](https://github.com/lobehub/lobe-chat/commit/a4b71e9))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.295](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.294...v2.0.0-next.295)
<sup>Released on **2026-01-15**</sup>
#### ♻ Code Refactoring
- **misc**: Migrate Next.js navigation APIs to React Router for SPA.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### Code refactoring
- **misc**: Migrate Next.js navigation APIs to React Router for SPA, closes [#11394](https://github.com/lobehub/lobe-chat/issues/11394) ([2253d46](https://github.com/lobehub/lobe-chat/commit/2253d46))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.294](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.293...v2.0.0-next.294)
<sup>Released on **2026-01-15**</sup>
#### 🐛 Bug Fixes
- **chat**: Reset activeTopicId when switching agent/group.
- **mcp**: Fix installation check hanging issue in desktop app.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### What's fixed
- **chat**: Reset activeTopicId when switching agent/group, closes [#11523](https://github.com/lobehub/lobe-chat/issues/11523) ([fde54b0](https://github.com/lobehub/lobe-chat/commit/fde54b0))
- **mcp**: Fix installation check hanging issue in desktop app, closes [#11524](https://github.com/lobehub/lobe-chat/issues/11524) ([b9341c3](https://github.com/lobehub/lobe-chat/commit/b9341c3))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.293](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.292...v2.0.0-next.293)
<sup>Released on **2026-01-15**</sup>
#### ✨ Features
- **desktop**: Add desktop release service and API endpoint.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### What's improved
- **desktop**: Add desktop release service and API endpoint, closes [#11520](https://github.com/lobehub/lobe-chat/issues/11520) ([e3dc5be](https://github.com/lobehub/lobe-chat/commit/e3dc5be))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.292](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.291...v2.0.0-next.292)
<sup>Released on **2026-01-15**</sup>
#### ♻ Code Refactoring
- **misc**: Use fallbackData to prevent useActionSWR auto-fetch.
#### ✨ Features
- **desktop**: Add local update testing scripts and stable channel API version check.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### Code refactoring
- **misc**: Use fallbackData to prevent useActionSWR auto-fetch, closes [#11514](https://github.com/lobehub/lobe-chat/issues/11514) ([d446163](https://github.com/lobehub/lobe-chat/commit/d446163))
#### What's improved
- **desktop**: Add local update testing scripts and stable channel API version check, closes [#11474](https://github.com/lobehub/lobe-chat/issues/11474) [#11513](https://github.com/lobehub/lobe-chat/issues/11513) ([959c210](https://github.com/lobehub/lobe-chat/commit/959c210))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.291](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.290...v2.0.0-next.291)
<sup>Released on **2026-01-15**</sup>
#### 🐛 Bug Fixes
- **settings**: Add instant UI feedback for provider config switches.
- **misc**: Click lobe ai topic trigger create new agent.
#### 💄 Styles
- **misc**: Improve agent loading state.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### What's fixed
- **settings**: Add instant UI feedback for provider config switches, closes [#11362](https://github.com/lobehub/lobe-chat/issues/11362) ([a758d01](https://github.com/lobehub/lobe-chat/commit/a758d01))
- **misc**: Click lobe ai topic trigger create new agent, closes [#11508](https://github.com/lobehub/lobe-chat/issues/11508) ([2443189](https://github.com/lobehub/lobe-chat/commit/2443189))
#### Styles
- **misc**: Improve agent loading state, closes [#11511](https://github.com/lobehub/lobe-chat/issues/11511) ([3bb7f33](https://github.com/lobehub/lobe-chat/commit/3bb7f33))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.290](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.289...v2.0.0-next.290)
<sup>Released on **2026-01-15**</sup>
#### 🐛 Bug Fixes
- **misc**: Fix internal editor onTextChange issue and add test case.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### What's fixed
- **misc**: Fix internal editor onTextChange issue and add test case, closes [#11509](https://github.com/lobehub/lobe-chat/issues/11509) ([e5eb03e](https://github.com/lobehub/lobe-chat/commit/e5eb03e))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.289](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.288...v2.0.0-next.289)
<sup>Released on **2026-01-15**</sup>
#### 🐛 Bug Fixes
- **misc**: Fix page content mismatch when switch quickly.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### What's fixed
- **misc**: Fix page content mismatch when switch quickly, closes [#11505](https://github.com/lobehub/lobe-chat/issues/11505) ([0cb1374](https://github.com/lobehub/lobe-chat/commit/0cb1374))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.288](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.287...v2.0.0-next.288)
<sup>Released on **2026-01-15**</sup>
#### ✨ Features
- **misc**: Improve group prompt context engine and fix group supervisor response issue.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### What's improved
- **misc**: Improve group prompt context engine and fix group supervisor response issue, closes [#11490](https://github.com/lobehub/lobe-chat/issues/11490) ([7d066eb](https://github.com/lobehub/lobe-chat/commit/7d066eb))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.287](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.286...v2.0.0-next.287)
<sup>Released on **2026-01-14**</sup>
#### ♻ Code Refactoring
- **desktop**: Unify TITLE_BAR_HEIGHT constant to desktop-bridge.
#### 🐛 Bug Fixes
- **desktop**: Return OFFICIAL_URL in cloud mode for remoteServerUrl selector.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### Code refactoring
- **desktop**: Unify TITLE_BAR_HEIGHT constant to desktop-bridge, closes [#11496](https://github.com/lobehub/lobe-chat/issues/11496) ([e7739e5](https://github.com/lobehub/lobe-chat/commit/e7739e5))
#### What's fixed
- **desktop**: Return OFFICIAL_URL in cloud mode for remoteServerUrl selector, closes [#11502](https://github.com/lobehub/lobe-chat/issues/11502) ([1d11fac](https://github.com/lobehub/lobe-chat/commit/1d11fac))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.286](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.285...v2.0.0-next.286)
<sup>Released on **2026-01-14**</sup>
#### 🐛 Bug Fixes
- **misc**: Prevent auto navigation to profile when clicking topic.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### What's fixed
- **misc**: Prevent auto navigation to profile when clicking topic, closes [#11500](https://github.com/lobehub/lobe-chat/issues/11500) ([1e03005](https://github.com/lobehub/lobe-chat/commit/1e03005))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.285](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.284...v2.0.0-next.285)
<sup>Released on **2026-01-14**</sup>
#### ✨ Features
- **misc**: Update the agent profiles tools check & agentbuilder tools & publish to market button.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### What's improved
- **misc**: Update the agent profiles tools check & agentbuilder tools & publish to market button, closes [#11501](https://github.com/lobehub/lobe-chat/issues/11501) ([85277fa](https://github.com/lobehub/lobe-chat/commit/85277fa))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.284](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.283...v2.0.0-next.284)
<sup>Released on **2026-01-14**</sup>
#### ♻ Code Refactoring
- **misc**: Remove the old lobehub plugins.
#### 🐛 Bug Fixes
- **misc**: Slove the settings/profile change but not refresh the profiles.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### Code refactoring
- **misc**: Remove the old lobehub plugins, closes [#11498](https://github.com/lobehub/lobe-chat/issues/11498) ([e5b47df](https://github.com/lobehub/lobe-chat/commit/e5b47df))
#### What's fixed
- **misc**: Slove the settings/profile change but not refresh the profiles, closes [#11497](https://github.com/lobehub/lobe-chat/issues/11497) ([f1e2111](https://github.com/lobehub/lobe-chat/commit/f1e2111))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.283](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.282...v2.0.0-next.283)
<sup>Released on **2026-01-14**</sup>
#### 💄 Styles
- **misc**: Fix UI issues with tooltip wrapping and dropdown type.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### Styles
- **misc**: Fix UI issues with tooltip wrapping and dropdown type, closes [#11495](https://github.com/lobehub/lobe-chat/issues/11495) ([9d90eba](https://github.com/lobehub/lobe-chat/commit/9d90eba))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.282](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.281...v2.0.0-next.282)
<sup>Released on **2026-01-14**</sup>
#### 💄 Styles
- **misc**: Update readFile content.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### Styles
- **misc**: Update readFile content, closes [#11485](https://github.com/lobehub/lobe-chat/issues/11485) ([050499b](https://github.com/lobehub/lobe-chat/commit/050499b))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.281](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.280...v2.0.0-next.281)
<sup>Released on **2026-01-14**</sup>
#### 🐛 Bug Fixes
- **misc**: Fix group ux and memory retriever.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### What's fixed
- **misc**: Fix group ux and memory retriever, closes [#11481](https://github.com/lobehub/lobe-chat/issues/11481) ([033ca92](https://github.com/lobehub/lobe-chat/commit/033ca92))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.280](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.279...v2.0.0-next.280)
<sup>Released on **2026-01-13**</sup>
#### 💄 Styles
- **misc**: Add MiniMax-M2.1 and GLM-4.7 for Qiniu provider.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### Styles
- **misc**: Add MiniMax-M2.1 and GLM-4.7 for Qiniu provider, closes [#10982](https://github.com/lobehub/lobe-chat/issues/10982) ([695784d](https://github.com/lobehub/lobe-chat/commit/695784d))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.279](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.278...v2.0.0-next.279)
<sup>Released on **2026-01-13**</sup>
#### 🐛 Bug Fixes
- **misc**: Fix new topic flick issue, fix thread portal not open correctly.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### What's fixed
- **misc**: Fix new topic flick issue, closes [#11473](https://github.com/lobehub/lobe-chat/issues/11473) ([c53d372](https://github.com/lobehub/lobe-chat/commit/c53d372))
- **misc**: Fix thread portal not open correctly, closes [#11475](https://github.com/lobehub/lobe-chat/issues/11475) ([e6ff90b](https://github.com/lobehub/lobe-chat/commit/e6ff90b))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.278](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.277...v2.0.0-next.278)
<sup>Released on **2026-01-13**</sup>
#### ✨ Features
- **share**: Add topic sharing functionality.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### What's improved
- **share**: Add topic sharing functionality, closes [#11448](https://github.com/lobehub/lobe-chat/issues/11448) ([ddca165](https://github.com/lobehub/lobe-chat/commit/ddca165))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.277](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.276...v2.0.0-next.277)
<sup>Released on **2026-01-13**</sup>
#### ✨ Features
- **misc**: Update the community user layout action button, update the cron job visiual way.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### What's improved
- **misc**: Update the community user layout action button, closes [#11472](https://github.com/lobehub/lobe-chat/issues/11472) ([2dd6d42](https://github.com/lobehub/lobe-chat/commit/2dd6d42))
- **misc**: Update the cron job visiual way, closes [#11466](https://github.com/lobehub/lobe-chat/issues/11466) ([63d81de](https://github.com/lobehub/lobe-chat/commit/63d81de))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.276](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.275...v2.0.0-next.276)
<sup>Released on **2026-01-13**</sup>
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.275](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.274...v2.0.0-next.275)
<sup>Released on **2026-01-13**</sup>
#### ✨ Features
- **misc**: Improve PageEditor header UX with DropdownMenu and i18n support.
<br/>
<details>
<summary><kbd>Improvements and Fixes</kbd></summary>
#### What's improved
- **misc**: Improve PageEditor header UX with DropdownMenu and i18n support, closes [#11462](https://github.com/lobehub/lobe-chat/issues/11462) ([ae499c9](https://github.com/lobehub/lobe-chat/commit/ae499c9))
</details>
<div align="right">
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
</div>
## [Version 2.0.0-next.274](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.273...v2.0.0-next.274)
<sup>Released on **2026-01-13**</sup>
+3 -49
View File
@@ -34,7 +34,7 @@ see @.cursor/rules/typescript.mdc
### Testing
- **Required Rule**: read `@.cursor/rules/testing-guide/testing-guide.mdc` before writing tests
- **Required Rule**: read `.cursor/rules/testing-guide/testing-guide.mdc` before writing tests
- **Command**:
- web: `bunx vitest run --silent='passed-only' '[file-path-pattern]'`
- packages(eg: database): `cd packages/database && bunx vitest run --silent='passed-only' '[file-path-pattern]'`
@@ -57,55 +57,9 @@ see @.cursor/rules/typescript.mdc
- **Dev**: Translate `locales/zh-CN/namespace.json` and `locales/en-US/namespace.json` locales file only for dev preview
- DON'T run `pnpm i18n`, let CI auto handle it
## Linear Issue Management (ignore if not installed linear mcp)
## Linear Issue Management(ignore if not installed linear mcp)
When working with Linear issues:
1. **Retrieve issue details** before starting work using `mcp__linear-server__get_issue`
2. **Check for sub-issues**: If the issue has sub-issues, retrieve and review ALL sub-issues using `mcp__linear-server__list_issues` with `parentId` filter before starting work
3. **Update issue status** when completing tasks using `mcp__linear-server__update_issue`
4. **MUST add completion comment** using `mcp__linear-server__create_comment`
### Creating Issues
When creating new Linear issues using `mcp__linear-server__create_issue`, **MUST add the `claude code` label** to indicate the issue was created by Claude Code.
### Completion Comment (REQUIRED)
**Every time you complete an issue, you MUST add a comment summarizing the work done.** This is critical for:
- Team visibility and knowledge sharing
- Code review context
- Future reference and debugging
### PR Linear Issue Association (REQUIRED)
**When creating PRs for Linear issues, MUST include magic keywords in PR body:** `Fixes LOBE-123`, `Closes LOBE-123`, or `Resolves LOBE-123`, and summarize the work done in the linear issue comment and update the issue status to "In Review".
### IMPORTANT: Per-Issue Completion Rule
**When working on multiple issues (e.g., parent issue with sub-issues), you MUST update status and add comment for EACH issue IMMEDIATELY after completing it.** Do NOT wait until all issues are done to update them in batch.
**Workflow for EACH individual issue:**
1. Complete the implementation for this specific issue
2. Run type check: `bun run type-check`
3. Run related tests if applicable
4. Create PR if needed
5. **IMMEDIATELY** update issue status to **"In Review"** (NOT "Done"): `mcp__linear-server__update_issue`
6. **IMMEDIATELY** add completion comment: `mcp__linear-server__create_comment`
7. Only then move on to the next issue
**Note:** Issue status should be set to **"In Review"** when PR is created. The status will be updated to **"Done"** only after the PR is merged (usually handled by Linear-GitHub integration or manually).
**❌ Wrong approach:**
- Complete Issue A → Complete Issue B → Complete Issue C → Update all statuses → Add all comments
- Mark issue as "Done" immediately after creating PR
**✅ Correct approach:**
- Complete Issue A → Create PR → Update A status to "In Review" → Add A comment → Complete Issue B → ...
Read @.cursor/rules/linear.mdc when working with Linear issues.
## Rules Index
+1 -1
View File
@@ -34,7 +34,7 @@ see @.cursor/rules/typescript.mdc
### Testing
- **Required Rule**: read `@.cursor/rules/testing-guide/testing-guide.mdc` before writing tests
- **Required Rule**: read `.cursor/rules/testing-guide/testing-guide.mdc` before writing tests
- **Command**:
- web: `bunx vitest run --silent='passed-only' '[file-path-pattern]'`
- packages(eg: database): `cd packages/database && bunx vitest run --silent='passed-only' '[file-path-pattern]'`
+3 -3
View File
@@ -1,14 +1,14 @@
const { defineConfig } = require('@lobehub/i18n-cli');
module.exports = defineConfig({
entry: 'resources/locales/zh-CN',
entryLocale: 'zh-CN',
entry: 'resources/locales/en',
entryLocale: 'en',
output: 'resources/locales',
outputLocales: [
'ar',
'bg-BG',
'zh-TW',
'en',
'zh-CN',
'ru-RU',
'ja-JP',
'ko-KR',
+10
View File
@@ -1,6 +1,16 @@
# 开发环境更新配置
# 可选择 GitHub 或 Generic provider 进行测试
# 方式1: GitHub Provider (默认)
provider: github
owner: lobehub
repo: lobe-chat
updaterCacheDirName: electron-app-updater
allowPrerelease: true
channel: nightly
# 方式2: Generic Provider (测试自定义服务器)
# 取消下面的注释,注释掉上面的 GitHub 配置
# provider: generic
# url: http://localhost:8080
# updaterCacheDirName: electron-app-updater
+40 -10
View File
@@ -10,19 +10,47 @@ dotenv.config();
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const packageJSON = JSON.parse(
await fs.readFile(path.join(__dirname, 'package.json'), 'utf8')
);
const packageJSON = JSON.parse(await fs.readFile(path.join(__dirname, 'package.json'), 'utf8'));
const channel = process.env.UPDATE_CHANNEL;
const arch = os.arch();
const hasAppleCertificate = Boolean(process.env.CSC_LINK);
// 自定义更新服务器 URL (用于 stable 频道)
const updateServerUrl = process.env.UPDATE_SERVER_URL;
console.log(`🚄 Build Version ${packageJSON.version}, Channel: ${channel}`);
console.log(`🏗️ Building for architecture: ${arch}`);
const isNightly = channel === 'nightly';
const isBeta = packageJSON.name.includes('beta');
const isStable = !isNightly && !isBeta;
// 根据 channel 配置不同的 publish provider
// - Stable + UPDATE_SERVER_URL: 使用 generic (自定义 HTTP 服务器)
// - Beta/Nightly: 仅使用 GitHub
const getPublishConfig = () => {
const githubProvider = {
owner: 'lobehub',
provider: 'github',
repo: 'lobe-chat',
};
// Stable channel: 使用自定义服务器 (generic provider)
if (isStable && updateServerUrl) {
console.log(`📦 Stable channel: Using generic provider (${updateServerUrl})`);
const genericProvider = {
provider: 'generic',
url: updateServerUrl,
};
// 同时发布到自定义服务器和 GitHub (GitHub 作为备用/镜像)
return [genericProvider, githubProvider];
}
// Beta/Nightly channel: 仅使用 GitHub
console.log(`📦 ${channel || 'default'} channel: Using GitHub provider`);
return [githubProvider];
};
// Keep only these Electron Framework localization folders (*.lproj)
// (aligned with previous Electron Forge build config)
@@ -221,13 +249,15 @@ const config = {
schemes: [protocolScheme],
},
],
publish: [
{
owner: 'lobehub',
provider: 'github',
repo: 'lobe-chat',
},
],
publish: getPublishConfig(),
// Release notes 配置
// 可以通过环境变量 RELEASE_NOTES 传入,或从文件读取
// 这会被写入 latest-mac.yml / latest.yml 中,供 generic provider 使用
releaseInfo: {
releaseNotes: process.env.RELEASE_NOTES || undefined,
},
win: {
executableName: 'LobeHub',
},
+1 -3
View File
@@ -21,11 +21,9 @@ export default defineConfig({
},
sourcemap: isDev ? 'inline' : false,
},
// 这里是关键:在构建时进行文本替换
define: {
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
'process.env.OFFICIAL_CLOUD_SERVER': JSON.stringify(process.env.OFFICIAL_CLOUD_SERVER),
'process.env.UPDATE_CHANNEL': JSON.stringify(process.env.UPDATE_CHANNEL),
'process.env.UPDATE_SERVER_URL': JSON.stringify(process.env.UPDATE_SERVER_URL),
},
resolve: {
+2 -1
View File
@@ -36,7 +36,8 @@
"stylelint": "stylelint \"src/**/*.{js,jsx,ts,tsx}\" --fix",
"test": "vitest --run",
"type-check": "tsgo --noEmit -p tsconfig.json",
"typecheck": "tsgo --noEmit -p tsconfig.json"
"typecheck": "tsgo --noEmit -p tsconfig.json",
"update-server": "sh scripts/update-test/run-test.sh"
},
"dependencies": {
"electron-updater": "^6.6.2",
@@ -11,6 +11,10 @@
"error.detail": "حدث خطأ أثناء العملية، يرجى المحاولة لاحقًا",
"error.message": "حدث خطأ",
"error.title": "خطأ",
"fullDiskAccess.message": "يحتاج LobeHub إلى الوصول الكامل إلى القرص لقراءة الملفات وتمكين ميزات قاعدة المعرفة. يرجى منح الوصول في إعدادات النظام.",
"fullDiskAccess.openSettings": "افتح الإعدادات",
"fullDiskAccess.skip": "لاحقًا",
"fullDiskAccess.title": "مطلوب الوصول الكامل إلى القرص",
"update.downloadAndInstall": "تنزيل وتثبيت",
"update.downloadComplete": "اكتمل التنزيل",
"update.downloadCompleteMessage": "تم تنزيل حزمة التحديث، هل ترغب في التثبيت الآن؟",
@@ -20,4 +24,4 @@
"update.newVersion": "تم اكتشاف إصدار جديد",
"update.newVersionAvailable": "تم اكتشاف إصدار جديد: {{version}}",
"update.skipThisVersion": "تخطي هذا الإصدار"
}
}
@@ -3,10 +3,24 @@
"dev.devPanel": "لوحة المطور",
"dev.devTools": "أدوات المطور",
"dev.forceReload": "إعادة تحميل قسري",
"dev.openSettingsFile": "فتح ملف الإعدادات",
"dev.openStore": "فتح ملف التخزين",
"dev.openUpdaterCacheDir": "فتح ذاكرة التخزين المؤقت للمحدث",
"dev.openUserDataDir": "فتح بيانات المستخدم",
"dev.permissions.accessibility.request": "طلب إذن الوصول",
"dev.permissions.fullDisk.open": "فتح إعدادات الوصول الكامل إلى القرص",
"dev.permissions.fullDisk.request": "طلب إذن الوصول الكامل إلى القرص",
"dev.permissions.microphone.request": "طلب إذن الميكروفون",
"dev.permissions.notification.request": "طلب إذن الإشعارات",
"dev.permissions.screen.request": "طلب إذن تسجيل الشاشة",
"dev.permissions.title": "الأذونات",
"dev.refreshMenu": "تحديث القائمة",
"dev.reload": "إعادة تحميل",
"dev.simulateAutoDownload": "محاكاة التنزيل التلقائي (3 ثوانٍ)",
"dev.simulateDownloadComplete": "محاكاة اكتمال التنزيل",
"dev.simulateDownloadProgress": "محاكاة تقدم التنزيل",
"dev.title": "تطوير",
"dev.updaterSimulation": "محاكاة المحدث",
"edit.copy": "نسخ",
"edit.cut": "قص",
"edit.delete": "حذف",
@@ -23,6 +37,8 @@
"file.title": "ملف",
"help.about": "حول",
"help.githubRepo": "مستودع GitHub",
"help.openConfigDir": "فتح دليل الإعدادات",
"help.openLogsDir": "فتح دليل السجلات",
"help.reportIssue": "الإبلاغ عن مشكلة",
"help.title": "مساعدة",
"help.visitWebsite": "زيارة الموقع الرسمي",
@@ -11,6 +11,10 @@
"error.detail": "Възникна грешка по време на операцията, моля опитайте отново по-късно",
"error.message": "Възникна грешка",
"error.title": "Грешка",
"fullDiskAccess.message": "LobeHub се нуждае от пълен достъп до диска, за да чете файлове и да активира функциите на базата знания. Моля, предоставете достъп в Системните настройки.",
"fullDiskAccess.openSettings": "Отвори настройки",
"fullDiskAccess.skip": "По-късно",
"fullDiskAccess.title": "Изисква се пълен достъп до диска",
"update.downloadAndInstall": "Изтегли и инсталирай",
"update.downloadComplete": "Изтеглянето е завършено",
"update.downloadCompleteMessage": "Актуализационният пакет е изтеглен, желаете ли да го инсталирате веднага?",
@@ -20,4 +24,4 @@
"update.newVersion": "Открита нова версия",
"update.newVersionAvailable": "Открита нова версия: {{version}}",
"update.skipThisVersion": "Пропусни тази версия"
}
}
@@ -3,10 +3,24 @@
"dev.devPanel": "Панел на разработчика",
"dev.devTools": "Инструменти за разработчици",
"dev.forceReload": "Принудително презареждане",
"dev.openSettingsFile": "Отвори файл с настройки",
"dev.openStore": "Отворете файла за съхранение",
"dev.openUpdaterCacheDir": "Отвори кеш на актуализации",
"dev.openUserDataDir": "Отвори потребителски данни",
"dev.permissions.accessibility.request": "Заяви разрешение за достъпност",
"dev.permissions.fullDisk.open": "Отвори настройки за пълен достъп до диска",
"dev.permissions.fullDisk.request": "Заяви разрешение за пълен достъп до диска",
"dev.permissions.microphone.request": "Заяви разрешение за микрофон",
"dev.permissions.notification.request": "Заяви разрешение за известия",
"dev.permissions.screen.request": "Заяви разрешение за запис на екрана",
"dev.permissions.title": "Разрешения",
"dev.refreshMenu": "Освежаване на менюто",
"dev.reload": "Презареждане",
"dev.simulateAutoDownload": "Симулирай автоматично изтегляне (3с)",
"dev.simulateDownloadComplete": "Симулирай завършване на изтегляне",
"dev.simulateDownloadProgress": "Симулирай напредък на изтегляне",
"dev.title": "Разработка",
"dev.updaterSimulation": "Симулация на актуализатор",
"edit.copy": "Копиране",
"edit.cut": "Изрязване",
"edit.delete": "Изтрий",
@@ -23,6 +37,8 @@
"file.title": "Файл",
"help.about": "За",
"help.githubRepo": "GitHub хранилище",
"help.openConfigDir": "Отвори директория с конфигурации",
"help.openLogsDir": "Отвори директория с логове",
"help.reportIssue": "Докладвай проблем",
"help.title": "Помощ",
"help.visitWebsite": "Посети уебсайта",
@@ -11,6 +11,10 @@
"error.detail": "Während der Operation ist ein Fehler aufgetreten, bitte versuchen Sie es später erneut",
"error.message": "Ein Fehler ist aufgetreten",
"error.title": "Fehler",
"fullDiskAccess.message": "LobeHub benötigt vollen Festplattenzugriff, um Dateien zu lesen und Funktionen der Wissensdatenbank zu aktivieren. Bitte gewähren Sie den Zugriff in den Systemeinstellungen.",
"fullDiskAccess.openSettings": "Einstellungen öffnen",
"fullDiskAccess.skip": "Später",
"fullDiskAccess.title": "Voller Festplattenzugriff erforderlich",
"update.downloadAndInstall": "Herunterladen und installieren",
"update.downloadComplete": "Download abgeschlossen",
"update.downloadCompleteMessage": "Das Update-Paket wurde heruntergeladen, möchten Sie es jetzt installieren?",
@@ -20,4 +24,4 @@
"update.newVersion": "Neue Version gefunden",
"update.newVersionAvailable": "Neue Version verfügbar: {{version}}",
"update.skipThisVersion": "Diese Version überspringen"
}
}
@@ -3,10 +3,24 @@
"dev.devPanel": "Entwicklerpanel",
"dev.devTools": "Entwicklerwerkzeuge",
"dev.forceReload": "Erzwinge Neuladen",
"dev.openSettingsFile": "Einstellungsdatei öffnen",
"dev.openStore": "Speicherdatei öffnen",
"dev.openUpdaterCacheDir": "Updater-Cache öffnen",
"dev.openUserDataDir": "Benutzerdaten öffnen",
"dev.permissions.accessibility.request": "Zugriffsberechtigung anfordern",
"dev.permissions.fullDisk.open": "Einstellungen für vollen Festplattenzugriff öffnen",
"dev.permissions.fullDisk.request": "Berechtigung für vollen Festplattenzugriff anfordern",
"dev.permissions.microphone.request": "Mikrofonberechtigung anfordern",
"dev.permissions.notification.request": "Benachrichtigungsberechtigung anfordern",
"dev.permissions.screen.request": "Bildschirmaufnahmeberechtigung anfordern",
"dev.permissions.title": "Berechtigungen",
"dev.refreshMenu": "Menü aktualisieren",
"dev.reload": "Neuladen",
"dev.simulateAutoDownload": "Automatischen Download simulieren (3s)",
"dev.simulateDownloadComplete": "Download abgeschlossen simulieren",
"dev.simulateDownloadProgress": "Downloadfortschritt simulieren",
"dev.title": "Entwicklung",
"dev.updaterSimulation": "Updater-Simulation",
"edit.copy": "Kopieren",
"edit.cut": "Ausschneiden",
"edit.delete": "Löschen",
@@ -23,6 +37,8 @@
"file.title": "Datei",
"help.about": "Über",
"help.githubRepo": "GitHub-Repository",
"help.openConfigDir": "Konfigurationsverzeichnis öffnen",
"help.openLogsDir": "Protokollverzeichnis öffnen",
"help.reportIssue": "Problem melden",
"help.title": "Hilfe",
"help.visitWebsite": "Besuche die Website",
@@ -0,0 +1,26 @@
{
"actions.add": "Add",
"actions.back": "Back",
"actions.cancel": "Cancel",
"actions.close": "Close",
"actions.confirm": "Confirm",
"actions.delete": "Delete",
"actions.edit": "Edit",
"actions.more": "More",
"actions.next": "Next",
"actions.ok": "OK",
"actions.previous": "Previous",
"actions.refresh": "Refresh",
"actions.remove": "Remove",
"actions.retry": "Retry",
"actions.save": "Save",
"actions.search": "Search",
"actions.submit": "Submit",
"app.description": "Where Agents Collaborate",
"app.name": "LobeHub",
"status.error": "Error",
"status.info": "Information",
"status.loading": "Loading",
"status.success": "Success",
"status.warning": "Warning"
}
@@ -0,0 +1,27 @@
{
"about.button": "OK",
"about.detail": "An LLM-powered chat app",
"about.message": "{{appName}} {{appVersion}}",
"about.title": "About",
"confirm.cancel": "Cancel",
"confirm.no": "Cancel",
"confirm.title": "Please confirm",
"confirm.yes": "Continue",
"error.button": "OK",
"error.detail": "Couldn't complete the action. Retry or try again later.",
"error.message": "An error occurred",
"error.title": "Error",
"fullDiskAccess.message": "LobeHub needs Full Disk Access to read files and enable knowledge base features. Please grant access in System Settings.",
"fullDiskAccess.openSettings": "Open Settings",
"fullDiskAccess.skip": "Later",
"fullDiskAccess.title": "Full Disk Access Required",
"update.downloadAndInstall": "Download and Install",
"update.downloadComplete": "Download Complete",
"update.downloadCompleteMessage": "Update downloaded. Install now?",
"update.installLater": "Install Later",
"update.installNow": "Install Now",
"update.later": "Remind Me Later",
"update.newVersion": "New Version Found",
"update.newVersionAvailable": "New version: {{version}}",
"update.skipThisVersion": "Skip This Version"
}
@@ -0,0 +1,73 @@
{
"common.checkUpdates": "Check for updates...",
"dev.devPanel": "Developer Panel",
"dev.devTools": "Developer Tools",
"dev.forceReload": "Force Reload",
"dev.openSettingsFile": "Open Settings File",
"dev.openStore": "Open Data Folder",
"dev.openUpdaterCacheDir": "Open Updater Cache",
"dev.openUserDataDir": "Open User Data",
"dev.permissions.accessibility.request": "Request Accessibility Permission",
"dev.permissions.fullDisk.open": "Open Full Disk Access Settings",
"dev.permissions.fullDisk.request": "Request Full Disk Access Permission",
"dev.permissions.microphone.request": "Request Microphone Permission",
"dev.permissions.notification.request": "Request Notification Permission",
"dev.permissions.screen.request": "Request Screen Recording Permission",
"dev.permissions.title": "Permissions",
"dev.refreshMenu": "Refresh Menu",
"dev.reload": "Reload",
"dev.simulateAutoDownload": "Simulate Auto Download (3s)",
"dev.simulateDownloadComplete": "Simulate Download Complete",
"dev.simulateDownloadProgress": "Simulate Download Progress",
"dev.title": "Development",
"dev.updaterSimulation": "Updater Simulation",
"edit.copy": "Copy",
"edit.cut": "Cut",
"edit.delete": "Delete",
"edit.paste": "Paste",
"edit.redo": "Redo",
"edit.selectAll": "Select All",
"edit.speech": "Speech",
"edit.startSpeaking": "Start Speaking",
"edit.stopSpeaking": "Stop Speaking",
"edit.title": "Edit",
"edit.undo": "Undo",
"file.preferences": "Preferences",
"file.quit": "Quit",
"file.title": "File",
"help.about": "About",
"help.githubRepo": "GitHub Repository",
"help.openConfigDir": "Open Config Directory",
"help.openLogsDir": "Open Logs Directory",
"help.reportIssue": "Send Feedback",
"help.title": "Help",
"help.visitWebsite": "Open Website",
"history.back": "Back",
"history.forward": "Forward",
"history.home": "Home",
"history.title": "Go",
"macOS.about": "About {{appName}}",
"macOS.devTools": "LobeHub Developer Tools",
"macOS.hide": "Hide {{appName}}",
"macOS.hideOthers": "Hide Others",
"macOS.preferences": "Preferences...",
"macOS.services": "Services",
"macOS.unhide": "Show All",
"tray.open": "Open {{appName}}",
"tray.quit": "Quit",
"tray.show": "Show {{appName}}",
"view.forceReload": "Force Reload",
"view.reload": "Reload",
"view.resetZoom": "Reset Zoom",
"view.title": "View",
"view.toggleFullscreen": "Toggle Fullscreen",
"view.zoomIn": "Zoom In",
"view.zoomOut": "Zoom Out",
"window.bringAllToFront": "Bring All Windows to Front",
"window.close": "Close",
"window.front": "Bring All Windows to Front",
"window.minimize": "Minimize",
"window.title": "Window",
"window.toggleFullscreen": "Toggle Fullscreen",
"window.zoom": "Zoom"
}
@@ -11,6 +11,10 @@
"error.detail": "Se produjo un error durante la operación, por favor intente de nuevo más tarde",
"error.message": "Se produjo un error",
"error.title": "Error",
"fullDiskAccess.message": "LobeHub necesita acceso completo al disco para leer archivos y habilitar las funciones de la base de conocimientos. Por favor, concede acceso en la Configuración del Sistema.",
"fullDiskAccess.openSettings": "Abrir Configuración",
"fullDiskAccess.skip": "Más tarde",
"fullDiskAccess.title": "Se requiere acceso completo al disco",
"update.downloadAndInstall": "Descargar e instalar",
"update.downloadComplete": "Descarga completada",
"update.downloadCompleteMessage": "El paquete de actualización se ha descargado, ¿desea instalarlo ahora?",
@@ -20,4 +24,4 @@
"update.newVersion": "Nueva versión disponible",
"update.newVersionAvailable": "Nueva versión encontrada: {{version}}",
"update.skipThisVersion": "Saltar esta versión"
}
}
@@ -3,10 +3,24 @@
"dev.devPanel": "Panel de desarrollador",
"dev.devTools": "Herramientas de desarrollador",
"dev.forceReload": "Recargar forzosamente",
"dev.openSettingsFile": "Abrir archivo de configuración",
"dev.openStore": "Abrir archivo de almacenamiento",
"dev.openUpdaterCacheDir": "Abrir caché del actualizador",
"dev.openUserDataDir": "Abrir datos de usuario",
"dev.permissions.accessibility.request": "Solicitar permiso de accesibilidad",
"dev.permissions.fullDisk.open": "Abrir configuración de acceso completo al disco",
"dev.permissions.fullDisk.request": "Solicitar permiso de acceso completo al disco",
"dev.permissions.microphone.request": "Solicitar permiso de micrófono",
"dev.permissions.notification.request": "Solicitar permiso de notificaciones",
"dev.permissions.screen.request": "Solicitar permiso para grabar pantalla",
"dev.permissions.title": "Permisos",
"dev.refreshMenu": "Actualizar menú",
"dev.reload": "Recargar",
"dev.simulateAutoDownload": "Simular descarga automática (3s)",
"dev.simulateDownloadComplete": "Simular descarga completada",
"dev.simulateDownloadProgress": "Simular progreso de descarga",
"dev.title": "Desarrollo",
"dev.updaterSimulation": "Simulación del actualizador",
"edit.copy": "Copiar",
"edit.cut": "Cortar",
"edit.delete": "Eliminar",
@@ -23,6 +37,8 @@
"file.title": "Archivo",
"help.about": "Acerca de",
"help.githubRepo": "Repositorio de GitHub",
"help.openConfigDir": "Abrir directorio de configuración",
"help.openLogsDir": "Abrir directorio de registros",
"help.reportIssue": "Reportar un problema",
"help.title": "Ayuda",
"help.visitWebsite": "Visitar el sitio web",
@@ -11,6 +11,10 @@
"error.detail": "در حین انجام عملیات خطایی رخ داده است، لطفاً بعداً دوباره تلاش کنید",
"error.message": "خطا رخ داده است",
"error.title": "خطا",
"fullDiskAccess.message": "LobeHub برای خواندن فایل‌ها و فعال‌سازی ویژگی‌های پایگاه دانش به دسترسی کامل به دیسک نیاز دارد. لطفاً در تنظیمات سیستم دسترسی را اعطا کنید.",
"fullDiskAccess.openSettings": "باز کردن تنظیمات",
"fullDiskAccess.skip": "بعداً",
"fullDiskAccess.title": "دسترسی کامل به دیسک مورد نیاز است",
"update.downloadAndInstall": "دانلود و نصب",
"update.downloadComplete": "دانلود کامل شد",
"update.downloadCompleteMessage": "بسته به‌روزرسانی دانلود شده است، آیا می‌خواهید بلافاصله نصب کنید؟",
@@ -20,4 +24,4 @@
"update.newVersion": "نسخه جدیدی پیدا شد",
"update.newVersionAvailable": "نسخه جدید پیدا شد: {{version}}",
"update.skipThisVersion": "این نسخه را نادیده بگیرید"
}
}
@@ -3,10 +3,24 @@
"dev.devPanel": "پنل توسعه‌دهنده",
"dev.devTools": "ابزارهای توسعه‌دهنده",
"dev.forceReload": "بارگذاری اجباری",
"dev.openSettingsFile": "باز کردن فایل تنظیمات",
"dev.openStore": "باز کردن فایل‌های ذخیره شده",
"dev.openUpdaterCacheDir": "باز کردن کش به‌روزرسان",
"dev.openUserDataDir": "باز کردن داده‌های کاربر",
"dev.permissions.accessibility.request": "درخواست دسترسی به قابلیت‌های دسترسی",
"dev.permissions.fullDisk.open": "باز کردن تنظیمات دسترسی کامل به دیسک",
"dev.permissions.fullDisk.request": "درخواست دسترسی کامل به دیسک",
"dev.permissions.microphone.request": "درخواست دسترسی به میکروفون",
"dev.permissions.notification.request": "درخواست دسترسی به اعلان‌ها",
"dev.permissions.screen.request": "درخواست دسترسی به ضبط صفحه",
"dev.permissions.title": "مجوزها",
"dev.refreshMenu": "به‌روزرسانی منو",
"dev.reload": "بارگذاری مجدد",
"dev.simulateAutoDownload": "شبیه‌سازی دانلود خودکار (۳ ثانیه)",
"dev.simulateDownloadComplete": "شبیه‌سازی اتمام دانلود",
"dev.simulateDownloadProgress": "شبیه‌سازی پیشرفت دانلود",
"dev.title": "توسعه",
"dev.updaterSimulation": "شبیه‌سازی به‌روزرسان",
"edit.copy": "کپی",
"edit.cut": "برش",
"edit.delete": "حذف",
@@ -23,6 +37,8 @@
"file.title": "فایل",
"help.about": "درباره",
"help.githubRepo": "مخزن GitHub",
"help.openConfigDir": "باز کردن پوشه تنظیمات",
"help.openLogsDir": "باز کردن پوشه گزارش‌ها",
"help.reportIssue": "گزارش مشکل",
"help.title": "کمک",
"help.visitWebsite": "بازدید از وب‌سایت",
@@ -11,6 +11,10 @@
"error.detail": "Une erreur s'est produite lors de l'opération, veuillez réessayer plus tard",
"error.message": "Une erreur s'est produite",
"error.title": "Erreur",
"fullDiskAccess.message": "LobeHub nécessite un accès complet au disque pour lire les fichiers et activer les fonctionnalités de la base de connaissances. Veuillez accorder l'accès dans les paramètres système.",
"fullDiskAccess.openSettings": "Ouvrir les paramètres",
"fullDiskAccess.skip": "Plus tard",
"fullDiskAccess.title": "Accès complet au disque requis",
"update.downloadAndInstall": "Télécharger et installer",
"update.downloadComplete": "Téléchargement terminé",
"update.downloadCompleteMessage": "Le paquet de mise à jour a été téléchargé, souhaitez-vous l'installer maintenant ?",
@@ -20,4 +24,4 @@
"update.newVersion": "Nouvelle version détectée",
"update.newVersionAvailable": "Nouvelle version disponible : {{version}}",
"update.skipThisVersion": "Ignorer cette version"
}
}
@@ -3,10 +3,24 @@
"dev.devPanel": "Panneau de développement",
"dev.devTools": "Outils de développement",
"dev.forceReload": "Recharger de force",
"dev.openSettingsFile": "Ouvrir le fichier de configuration",
"dev.openStore": "Ouvrir le fichier de stockage",
"dev.openUpdaterCacheDir": "Ouvrir le cache de mise à jour",
"dev.openUserDataDir": "Ouvrir les données utilisateur",
"dev.permissions.accessibility.request": "Demander l'autorisation d'accessibilité",
"dev.permissions.fullDisk.open": "Ouvrir les paramètres d'accès complet au disque",
"dev.permissions.fullDisk.request": "Demander l'autorisation d'accès complet au disque",
"dev.permissions.microphone.request": "Demander l'autorisation du microphone",
"dev.permissions.notification.request": "Demander l'autorisation de notification",
"dev.permissions.screen.request": "Demander l'autorisation d'enregistrement d'écran",
"dev.permissions.title": "Autorisations",
"dev.refreshMenu": "Rafraîchir le menu",
"dev.reload": "Recharger",
"dev.simulateAutoDownload": "Simuler le téléchargement automatique (3s)",
"dev.simulateDownloadComplete": "Simuler le téléchargement terminé",
"dev.simulateDownloadProgress": "Simuler la progression du téléchargement",
"dev.title": "Développement",
"dev.updaterSimulation": "Simulation de mise à jour",
"edit.copy": "Copier",
"edit.cut": "Couper",
"edit.delete": "Supprimer",
@@ -23,6 +37,8 @@
"file.title": "Fichier",
"help.about": "À propos",
"help.githubRepo": "Dépôt GitHub",
"help.openConfigDir": "Ouvrir le répertoire de configuration",
"help.openLogsDir": "Ouvrir le répertoire des journaux",
"help.reportIssue": "Signaler un problème",
"help.title": "Aide",
"help.visitWebsite": "Visiter le site officiel",
@@ -11,6 +11,10 @@
"error.detail": "Si è verificato un errore durante l'operazione, riprovare più tardi",
"error.message": "Si è verificato un errore",
"error.title": "Errore",
"fullDiskAccess.message": "LobeHub necessita dell'accesso completo al disco per leggere i file e abilitare le funzionalità della base di conoscenza. Si prega di concedere l'accesso nelle Impostazioni di Sistema.",
"fullDiskAccess.openSettings": "Apri Impostazioni",
"fullDiskAccess.skip": "Più tardi",
"fullDiskAccess.title": "Accesso Completo al Disco Richiesto",
"update.downloadAndInstall": "Scarica e installa",
"update.downloadComplete": "Download completato",
"update.downloadCompleteMessage": "Il pacchetto di aggiornamento è stato scaricato, vuoi installarlo subito?",
@@ -20,4 +24,4 @@
"update.newVersion": "Nuova versione disponibile",
"update.newVersionAvailable": "Nuova versione trovata: {{version}}",
"update.skipThisVersion": "Salta questa versione"
}
}
@@ -3,10 +3,24 @@
"dev.devPanel": "Pannello sviluppatore",
"dev.devTools": "Strumenti per sviluppatori",
"dev.forceReload": "Ricarica forzata",
"dev.openSettingsFile": "Apri file impostazioni",
"dev.openStore": "Apri il file di archiviazione",
"dev.openUpdaterCacheDir": "Apri cache aggiornamenti",
"dev.openUserDataDir": "Apri dati utente",
"dev.permissions.accessibility.request": "Richiedi permesso di accessibilità",
"dev.permissions.fullDisk.open": "Apri impostazioni accesso completo al disco",
"dev.permissions.fullDisk.request": "Richiedi permesso di accesso completo al disco",
"dev.permissions.microphone.request": "Richiedi permesso microfono",
"dev.permissions.notification.request": "Richiedi permesso notifiche",
"dev.permissions.screen.request": "Richiedi permesso registrazione schermo",
"dev.permissions.title": "Permessi",
"dev.refreshMenu": "Aggiorna menu",
"dev.reload": "Ricarica",
"dev.simulateAutoDownload": "Simula download automatico (3s)",
"dev.simulateDownloadComplete": "Simula completamento download",
"dev.simulateDownloadProgress": "Simula progresso download",
"dev.title": "Sviluppo",
"dev.updaterSimulation": "Simulazione aggiornamento",
"edit.copy": "Copia",
"edit.cut": "Taglia",
"edit.delete": "Elimina",
@@ -23,6 +37,8 @@
"file.title": "File",
"help.about": "Informazioni",
"help.githubRepo": "Repository GitHub",
"help.openConfigDir": "Apri directory configurazione",
"help.openLogsDir": "Apri directory log",
"help.reportIssue": "Segnala un problema",
"help.title": "Aiuto",
"help.visitWebsite": "Visita il sito ufficiale",
@@ -11,6 +11,10 @@
"error.detail": "操作中にエラーが発生しました。後で再試行してください。",
"error.message": "エラーが発生しました",
"error.title": "エラー",
"fullDiskAccess.message": "LobeHubはファイルを読み取り、ナレッジベース機能を有効にするためにフルディスクアクセスが必要です。システム設定でアクセスを許可してください。",
"fullDiskAccess.openSettings": "設定を開く",
"fullDiskAccess.skip": "後で",
"fullDiskAccess.title": "フルディスクアクセスが必要です",
"update.downloadAndInstall": "ダウンロードしてインストール",
"update.downloadComplete": "ダウンロード完了",
"update.downloadCompleteMessage": "更新パッケージのダウンロードが完了しました。今すぐインストールしますか?",
@@ -20,4 +24,4 @@
"update.newVersion": "新しいバージョンが見つかりました",
"update.newVersionAvailable": "新しいバージョンが見つかりました: {{version}}",
"update.skipThisVersion": "このバージョンをスキップ"
}
}
@@ -3,10 +3,24 @@
"dev.devPanel": "開発者パネル",
"dev.devTools": "開発者ツール",
"dev.forceReload": "強制再読み込み",
"dev.openSettingsFile": "設定ファイルを開く",
"dev.openStore": "ストレージファイルを開く",
"dev.openUpdaterCacheDir": "アップデーターキャッシュを開く",
"dev.openUserDataDir": "ユーザーデータを開く",
"dev.permissions.accessibility.request": "アクセシビリティ権限をリクエスト",
"dev.permissions.fullDisk.open": "フルディスクアクセス設定を開く",
"dev.permissions.fullDisk.request": "フルディスクアクセス権限をリクエスト",
"dev.permissions.microphone.request": "マイク権限をリクエスト",
"dev.permissions.notification.request": "通知権限をリクエスト",
"dev.permissions.screen.request": "画面録画権限をリクエスト",
"dev.permissions.title": "権限",
"dev.refreshMenu": "メニューを更新",
"dev.reload": "再読み込み",
"dev.simulateAutoDownload": "自動ダウンロードをシミュレート(3秒)",
"dev.simulateDownloadComplete": "ダウンロード完了をシミュレート",
"dev.simulateDownloadProgress": "ダウンロード進行をシミュレート",
"dev.title": "開発",
"dev.updaterSimulation": "アップデーターシミュレーション",
"edit.copy": "コピー",
"edit.cut": "切り取り",
"edit.delete": "削除",
@@ -23,6 +37,8 @@
"file.title": "ファイル",
"help.about": "について",
"help.githubRepo": "GitHub リポジトリ",
"help.openConfigDir": "設定ディレクトリを開く",
"help.openLogsDir": "ログディレクトリを開く",
"help.reportIssue": "問題を報告",
"help.title": "ヘルプ",
"help.visitWebsite": "公式ウェブサイトを訪問",
@@ -11,6 +11,10 @@
"error.detail": "작업 중 오류가 발생했습니다. 나중에 다시 시도해 주세요.",
"error.message": "오류 발생",
"error.title": "오류",
"fullDiskAccess.message": "LobeHub는 파일을 읽고 지식 기반 기능을 활성화하기 위해 전체 디스크 접근 권한이 필요합니다. 시스템 설정에서 접근 권한을 부여해 주세요.",
"fullDiskAccess.openSettings": "설정 열기",
"fullDiskAccess.skip": "나중에",
"fullDiskAccess.title": "전체 디스크 접근 권한 필요",
"update.downloadAndInstall": "다운로드 및 설치",
"update.downloadComplete": "다운로드 완료",
"update.downloadCompleteMessage": "업데이트 패키지가 다운로드 완료되었습니다. 지금 설치하시겠습니까?",
@@ -20,4 +24,4 @@
"update.newVersion": "새 버전 발견",
"update.newVersionAvailable": "새 버전 발견: {{version}}",
"update.skipThisVersion": "이 버전 건너뛰기"
}
}
@@ -3,10 +3,24 @@
"dev.devPanel": "개발자 패널",
"dev.devTools": "개발자 도구",
"dev.forceReload": "강제 새로 고침",
"dev.openSettingsFile": "설정 파일 열기",
"dev.openStore": "저장 파일 열기",
"dev.openUpdaterCacheDir": "업데이터 캐시 열기",
"dev.openUserDataDir": "사용자 데이터 열기",
"dev.permissions.accessibility.request": "접근성 권한 요청",
"dev.permissions.fullDisk.open": "전체 디스크 접근 설정 열기",
"dev.permissions.fullDisk.request": "전체 디스크 접근 권한 요청",
"dev.permissions.microphone.request": "마이크 권한 요청",
"dev.permissions.notification.request": "알림 권한 요청",
"dev.permissions.screen.request": "화면 녹화 권한 요청",
"dev.permissions.title": "권한",
"dev.refreshMenu": "메뉴 새로 고침",
"dev.reload": "새로 고침",
"dev.simulateAutoDownload": "자동 다운로드 시뮬레이션 (3초)",
"dev.simulateDownloadComplete": "다운로드 완료 시뮬레이션",
"dev.simulateDownloadProgress": "다운로드 진행 시뮬레이션",
"dev.title": "개발",
"dev.updaterSimulation": "업데이터 시뮬레이션",
"edit.copy": "복사",
"edit.cut": "잘라내기",
"edit.delete": "삭제",
@@ -23,6 +37,8 @@
"file.title": "파일",
"help.about": "정보",
"help.githubRepo": "GitHub 저장소",
"help.openConfigDir": "설정 디렉터리 열기",
"help.openLogsDir": "로그 디렉터리 열기",
"help.reportIssue": "문제 보고",
"help.title": "도움말",
"help.visitWebsite": "웹사이트 방문",
@@ -11,6 +11,10 @@
"error.detail": "Er is een fout opgetreden tijdens de operatie, probeer het later opnieuw",
"error.message": "Er is een fout opgetreden",
"error.title": "Fout",
"fullDiskAccess.message": "LobeHub heeft volledige schijf toegang nodig om bestanden te lezen en functies van de kennisbank mogelijk te maken. Geef toegang via de Systeeminstellingen.",
"fullDiskAccess.openSettings": "Instellingen openen",
"fullDiskAccess.skip": "Later",
"fullDiskAccess.title": "Volledige schijf toegang vereist",
"update.downloadAndInstall": "Downloaden en installeren",
"update.downloadComplete": "Download voltooid",
"update.downloadCompleteMessage": "Het updatepakket is gedownload, wilt u het nu installeren?",
@@ -20,4 +24,4 @@
"update.newVersion": "Nieuwe versie gevonden",
"update.newVersionAvailable": "Nieuwe versie beschikbaar: {{version}}",
"update.skipThisVersion": "Deze versie overslaan"
}
}
@@ -3,10 +3,24 @@
"dev.devPanel": "Ontwikkelaarspaneel",
"dev.devTools": "Ontwikkelaarstools",
"dev.forceReload": "Forceer herladen",
"dev.openSettingsFile": "Instellingenbestand openen",
"dev.openStore": "Open opslagbestand",
"dev.openUpdaterCacheDir": "Updater-cache openen",
"dev.openUserDataDir": "Gebruikersgegevens openen",
"dev.permissions.accessibility.request": "Toegankelijkheidsmachtiging aanvragen",
"dev.permissions.fullDisk.open": "Instellingen voor volledige schijf toegang openen",
"dev.permissions.fullDisk.request": "Machtiging voor volledige schijf toegang aanvragen",
"dev.permissions.microphone.request": "Microfoonmachtiging aanvragen",
"dev.permissions.notification.request": "Machtiging voor meldingen aanvragen",
"dev.permissions.screen.request": "Machtiging voor schermopname aanvragen",
"dev.permissions.title": "Machtigingen",
"dev.refreshMenu": "Menu verversen",
"dev.reload": "Herladen",
"dev.simulateAutoDownload": "Automatisch downloaden simuleren (3s)",
"dev.simulateDownloadComplete": "Download voltooid simuleren",
"dev.simulateDownloadProgress": "Downloadvoortgang simuleren",
"dev.title": "Ontwikkeling",
"dev.updaterSimulation": "Updater-simulatie",
"edit.copy": "Kopiëren",
"edit.cut": "Knippen",
"edit.delete": "Verwijderen",
@@ -23,6 +37,8 @@
"file.title": "Bestand",
"help.about": "Over",
"help.githubRepo": "GitHub-repo",
"help.openConfigDir": "Configuratiemap openen",
"help.openLogsDir": "Logmap openen",
"help.reportIssue": "Probleem melden",
"help.title": "Hulp",
"help.visitWebsite": "Bezoek de website",
@@ -11,6 +11,10 @@
"error.detail": "Wystąpił błąd podczas operacji, spróbuj ponownie później",
"error.message": "Wystąpił błąd",
"error.title": "Błąd",
"fullDiskAccess.message": "LobeHub wymaga pełnego dostępu do dysku, aby odczytywać pliki i umożliwić funkcje bazy wiedzy. Proszę przyznać dostęp w ustawieniach systemowych.",
"fullDiskAccess.openSettings": "Otwórz ustawienia",
"fullDiskAccess.skip": "Później",
"fullDiskAccess.title": "Wymagany pełny dostęp do dysku",
"update.downloadAndInstall": "Pobierz i zainstaluj",
"update.downloadComplete": "Pobieranie zakończone",
"update.downloadCompleteMessage": "Pakiet aktualizacji został pobrany, czy chcesz go teraz zainstalować?",
@@ -20,4 +24,4 @@
"update.newVersion": "Nowa wersja dostępna",
"update.newVersionAvailable": "Znaleziono nową wersję: {{version}}",
"update.skipThisVersion": "Pomiń tę wersję"
}
}
@@ -3,10 +3,24 @@
"dev.devPanel": "Panel dewelopera",
"dev.devTools": "Narzędzia dewelopera",
"dev.forceReload": "Wymuś ponowne załadowanie",
"dev.openSettingsFile": "Otwórz plik ustawień",
"dev.openStore": "Otwórz plik magazynu",
"dev.openUpdaterCacheDir": "Otwórz pamięć podręczną aktualizatora",
"dev.openUserDataDir": "Otwórz dane użytkownika",
"dev.permissions.accessibility.request": "Poproś o uprawnienia dostępu",
"dev.permissions.fullDisk.open": "Otwórz ustawienia pełnego dostępu do dysku",
"dev.permissions.fullDisk.request": "Poproś o pełny dostęp do dysku",
"dev.permissions.microphone.request": "Poproś o dostęp do mikrofonu",
"dev.permissions.notification.request": "Poproś o pozwolenie na powiadomienia",
"dev.permissions.screen.request": "Poproś o pozwolenie na nagrywanie ekranu",
"dev.permissions.title": "Uprawnienia",
"dev.refreshMenu": "Odśwież menu",
"dev.reload": "Przeładuj",
"dev.simulateAutoDownload": "Symuluj automatyczne pobieranie (3s)",
"dev.simulateDownloadComplete": "Symuluj zakończenie pobierania",
"dev.simulateDownloadProgress": "Symuluj postęp pobierania",
"dev.title": "Rozwój",
"dev.updaterSimulation": "Symulacja aktualizatora",
"edit.copy": "Kopiuj",
"edit.cut": "Wytnij",
"edit.delete": "Usuń",
@@ -23,6 +37,8 @@
"file.title": "Plik",
"help.about": "O",
"help.githubRepo": "Repozytorium GitHub",
"help.openConfigDir": "Otwórz katalog konfiguracji",
"help.openLogsDir": "Otwórz katalog logów",
"help.reportIssue": "Zgłoś problem",
"help.title": "Pomoc",
"help.visitWebsite": "Odwiedź stronę internetową",
@@ -11,6 +11,10 @@
"error.detail": "Ocorreu um erro durante a operação, por favor tente novamente mais tarde",
"error.message": "Ocorreu um erro",
"error.title": "Erro",
"fullDiskAccess.message": "O LobeHub precisa de Acesso Completo ao Disco para ler arquivos e ativar os recursos da base de conhecimento. Por favor, conceda acesso nas Configurações do Sistema.",
"fullDiskAccess.openSettings": "Abrir Configurações",
"fullDiskAccess.skip": "Depois",
"fullDiskAccess.title": "Acesso Completo ao Disco Necessário",
"update.downloadAndInstall": "Baixar e instalar",
"update.downloadComplete": "Download completo",
"update.downloadCompleteMessage": "O pacote de atualização foi baixado com sucesso, deseja instalá-lo agora?",
@@ -20,4 +24,4 @@
"update.newVersion": "Nova versão disponível",
"update.newVersionAvailable": "Nova versão encontrada: {{version}}",
"update.skipThisVersion": "Ignorar esta versão"
}
}
@@ -3,10 +3,24 @@
"dev.devPanel": "Painel do Desenvolvedor",
"dev.devTools": "Ferramentas do Desenvolvedor",
"dev.forceReload": "Recarregar Forçadamente",
"dev.openSettingsFile": "Abrir arquivo de configurações",
"dev.openStore": "Abrir arquivo de armazenamento",
"dev.openUpdaterCacheDir": "Abrir cache do atualizador",
"dev.openUserDataDir": "Abrir dados do usuário",
"dev.permissions.accessibility.request": "Solicitar permissão de acessibilidade",
"dev.permissions.fullDisk.open": "Abrir configurações de acesso total ao disco",
"dev.permissions.fullDisk.request": "Solicitar permissão de acesso total ao disco",
"dev.permissions.microphone.request": "Solicitar permissão para microfone",
"dev.permissions.notification.request": "Solicitar permissão para notificações",
"dev.permissions.screen.request": "Solicitar permissão para gravação de tela",
"dev.permissions.title": "Permissões",
"dev.refreshMenu": "Atualizar menu",
"dev.reload": "Recarregar",
"dev.simulateAutoDownload": "Simular download automático (3s)",
"dev.simulateDownloadComplete": "Simular download concluído",
"dev.simulateDownloadProgress": "Simular progresso do download",
"dev.title": "Desenvolvimento",
"dev.updaterSimulation": "Simulação do atualizador",
"edit.copy": "Copiar",
"edit.cut": "Cortar",
"edit.delete": "Excluir",
@@ -23,6 +37,8 @@
"file.title": "Arquivo",
"help.about": "Sobre",
"help.githubRepo": "Repositório do GitHub",
"help.openConfigDir": "Abrir diretório de configuração",
"help.openLogsDir": "Abrir diretório de logs",
"help.reportIssue": "Reportar Problema",
"help.title": "Ajuda",
"help.visitWebsite": "Visitar o Site",
@@ -11,6 +11,10 @@
"error.detail": "Произошла ошибка во время операции, пожалуйста, попробуйте позже",
"error.message": "Произошла ошибка",
"error.title": "Ошибка",
"fullDiskAccess.message": "LobeHub требует полный доступ к диску для чтения файлов и активации функций базы знаний. Пожалуйста, предоставьте доступ в системных настройках.",
"fullDiskAccess.openSettings": "Открыть настройки",
"fullDiskAccess.skip": "Позже",
"fullDiskAccess.title": "Требуется полный доступ к диску",
"update.downloadAndInstall": "Скачать и установить",
"update.downloadComplete": "Скачивание завершено",
"update.downloadCompleteMessage": "Обновление загружено, хотите установить сейчас?",
@@ -20,4 +24,4 @@
"update.newVersion": "Обнаружена новая версия",
"update.newVersionAvailable": "Обнаружена новая версия: {{version}}",
"update.skipThisVersion": "Пропустить эту версию"
}
}
@@ -3,10 +3,24 @@
"dev.devPanel": "Панель разработчика",
"dev.devTools": "Инструменты разработчика",
"dev.forceReload": "Принудительная перезагрузка",
"dev.openSettingsFile": "Открыть файл настроек",
"dev.openStore": "Открыть файл хранилища",
"dev.openUpdaterCacheDir": "Открыть кэш обновлений",
"dev.openUserDataDir": "Открыть данные пользователя",
"dev.permissions.accessibility.request": "Запросить разрешение на доступность",
"dev.permissions.fullDisk.open": "Открыть настройки полного доступа к диску",
"dev.permissions.fullDisk.request": "Запросить разрешение на полный доступ к диску",
"dev.permissions.microphone.request": "Запросить разрешение на использование микрофона",
"dev.permissions.notification.request": "Запросить разрешение на уведомления",
"dev.permissions.screen.request": "Запросить разрешение на запись экрана",
"dev.permissions.title": "Разрешения",
"dev.refreshMenu": "Обновить меню",
"dev.reload": "Перезагрузить",
"dev.simulateAutoDownload": "Симуляция автозагрузки (3 с)",
"dev.simulateDownloadComplete": "Симуляция завершения загрузки",
"dev.simulateDownloadProgress": "Симуляция прогресса загрузки",
"dev.title": "Разработка",
"dev.updaterSimulation": "Симуляция обновления",
"edit.copy": "Копировать",
"edit.cut": "Вырезать",
"edit.delete": "Удалить",
@@ -23,6 +37,8 @@
"file.title": "Файл",
"help.about": "О программе",
"help.githubRepo": "Репозиторий GitHub",
"help.openConfigDir": "Открыть каталог конфигурации",
"help.openLogsDir": "Открыть каталог журналов",
"help.reportIssue": "Сообщить о проблеме",
"help.title": "Помощь",
"help.visitWebsite": "Посетить сайт",
@@ -11,6 +11,10 @@
"error.detail": "İşlem sırasında bir hata oluştu, lütfen daha sonra tekrar deneyin",
"error.message": "Hata oluştu",
"error.title": "Hata",
"fullDiskAccess.message": "LobeHub, dosyaları okuyabilmek ve bilgi tabanı özelliklerini etkinleştirebilmek için Tam Disk Erişimi gerektirir. Lütfen Sistem Ayarları'ndan erişim izni verin.",
"fullDiskAccess.openSettings": "Ayarları Aç",
"fullDiskAccess.skip": "Daha Sonra",
"fullDiskAccess.title": "Tam Disk Erişimi Gerekiyor",
"update.downloadAndInstall": "İndir ve Yükle",
"update.downloadComplete": "İndirme tamamlandı",
"update.downloadCompleteMessage": "Güncelleme paketi indirildi, hemen yüklemek ister misiniz?",
@@ -20,4 +24,4 @@
"update.newVersion": "Yeni sürüm bulundu",
"update.newVersionAvailable": "Yeni sürüm bulundu: {{version}}",
"update.skipThisVersion": "Bu sürümü atla"
}
}
@@ -3,10 +3,24 @@
"dev.devPanel": "Geliştirici Paneli",
"dev.devTools": "Geliştirici Araçları",
"dev.forceReload": "Zorla Yenile",
"dev.openSettingsFile": "Ayarlar Dosyasını Aç",
"dev.openStore": "Depolama dosyasını aç",
"dev.openUpdaterCacheDir": "Güncelleyici Önbelleğini Aç",
"dev.openUserDataDir": "Kullanıcı Verilerini Aç",
"dev.permissions.accessibility.request": "Erişilebilirlik İzni İste",
"dev.permissions.fullDisk.open": "Tam Disk Erişimi Ayarlarını Aç",
"dev.permissions.fullDisk.request": "Tam Disk Erişimi İzni İste",
"dev.permissions.microphone.request": "Mikrofon İzni İste",
"dev.permissions.notification.request": "Bildirim İzni İste",
"dev.permissions.screen.request": "Ekran Kaydı İzni İste",
"dev.permissions.title": "İzinler",
"dev.refreshMenu": "Menüyü yenile",
"dev.reload": "Yenile",
"dev.simulateAutoDownload": "Otomatik İndirmeyi Simüle Et (3s)",
"dev.simulateDownloadComplete": "İndirme Tamamlanmasını Simüle Et",
"dev.simulateDownloadProgress": "İndirme İlerlemesini Simüle Et",
"dev.title": "Geliştir",
"dev.updaterSimulation": "Güncelleyici Simülasyonu",
"edit.copy": "Kopyala",
"edit.cut": "Kes",
"edit.delete": "Sil",
@@ -23,6 +37,8 @@
"file.title": "Dosya",
"help.about": "Hakkında",
"help.githubRepo": "GitHub Deposu",
"help.openConfigDir": "Yapılandırma Dizini Aç",
"help.openLogsDir": "Kayıtlar Dizini Aç",
"help.reportIssue": "Sorun Bildir",
"help.title": "Yardım",
"help.visitWebsite": "Resmi Web Sitesini Ziyaret Et",
@@ -11,6 +11,10 @@
"error.detail": "Đã xảy ra lỗi trong quá trình thực hiện, vui lòng thử lại sau",
"error.message": "Đã xảy ra lỗi",
"error.title": "Lỗi",
"fullDiskAccess.message": "LobeHub cần quyền truy cập toàn bộ đĩa để đọc tệp và kích hoạt các tính năng cơ sở tri thức. Vui lòng cấp quyền truy cập trong Cài đặt Hệ thống.",
"fullDiskAccess.openSettings": "Mở Cài đặt",
"fullDiskAccess.skip": "Để sau",
"fullDiskAccess.title": "Yêu cầu quyền truy cập toàn bộ đĩa",
"update.downloadAndInstall": "Tải xuống và cài đặt",
"update.downloadComplete": "Tải xuống hoàn tất",
"update.downloadCompleteMessage": "Gói cập nhật đã tải xuống hoàn tất, có muốn cài đặt ngay không?",
@@ -20,4 +24,4 @@
"update.newVersion": "Phát hiện phiên bản mới",
"update.newVersionAvailable": "Phát hiện phiên bản mới: {{version}}",
"update.skipThisVersion": "Bỏ qua phiên bản này"
}
}
@@ -3,10 +3,24 @@
"dev.devPanel": "Bảng điều khiển nhà phát triển",
"dev.devTools": "Công cụ phát triển",
"dev.forceReload": "Tải lại cưỡng bức",
"dev.openSettingsFile": "Mở Tệp Cài Đặt",
"dev.openStore": "Mở tệp lưu trữ",
"dev.openUpdaterCacheDir": "Mở Bộ Nhớ Đệm Cập Nhật",
"dev.openUserDataDir": "Mở Thư Mục Dữ Liệu Người Dùng",
"dev.permissions.accessibility.request": "Yêu Cầu Quyền Truy Cập Trợ Năng",
"dev.permissions.fullDisk.open": "Mở Cài Đặt Truy Cập Ổ Đĩa Toàn Bộ",
"dev.permissions.fullDisk.request": "Yêu Cầu Quyền Truy Cập Ổ Đĩa Toàn Bộ",
"dev.permissions.microphone.request": "Yêu Cầu Quyền Sử Dụng Micro",
"dev.permissions.notification.request": "Yêu Cầu Quyền Thông Báo",
"dev.permissions.screen.request": "Yêu Cầu Quyền Ghi Màn Hình",
"dev.permissions.title": "Quyền Truy Cập",
"dev.refreshMenu": "Làm mới menu",
"dev.reload": "Tải lại",
"dev.simulateAutoDownload": "Mô Phỏng Tải Tự Động (3 giây)",
"dev.simulateDownloadComplete": "Mô Phỏng Hoàn Thành Tải",
"dev.simulateDownloadProgress": "Mô Phỏng Tiến Trình Tải",
"dev.title": "Phát triển",
"dev.updaterSimulation": "Mô Phỏng Trình Cập Nhật",
"edit.copy": "Sao chép",
"edit.cut": "Cắt",
"edit.delete": "Xóa",
@@ -23,6 +37,8 @@
"file.title": "Tập tin",
"help.about": "Về",
"help.githubRepo": "Kho lưu trữ GitHub",
"help.openConfigDir": "Mở Thư Mục Cấu Hình",
"help.openLogsDir": "Mở Thư Mục Nhật Ký",
"help.reportIssue": "Báo cáo sự cố",
"help.title": "Trợ giúp",
"help.visitWebsite": "Truy cập trang web",
@@ -11,6 +11,10 @@
"error.detail": "操作過程中發生錯誤,請稍後重試",
"error.message": "發生錯誤",
"error.title": "錯誤",
"fullDiskAccess.message": "LobeHub 需要完整磁碟存取權限以讀取檔案並啟用知識庫功能。請在系統設定中授予存取權限。",
"fullDiskAccess.openSettings": "開啟設定",
"fullDiskAccess.skip": "稍後",
"fullDiskAccess.title": "需要完整磁碟存取權限",
"update.downloadAndInstall": "下載並安裝",
"update.downloadComplete": "下載完成",
"update.downloadCompleteMessage": "更新包已下載完成,是否立即安裝?",
@@ -20,4 +24,4 @@
"update.newVersion": "發現新版本",
"update.newVersionAvailable": "發現新版本: {{version}}",
"update.skipThisVersion": "跳過此版本"
}
}
@@ -3,10 +3,24 @@
"dev.devPanel": "開發者面板",
"dev.devTools": "開發者工具",
"dev.forceReload": "強制重新載入",
"dev.openSettingsFile": "開啟設定檔案",
"dev.openStore": "打開儲存檔案",
"dev.openUpdaterCacheDir": "開啟更新快取資料夾",
"dev.openUserDataDir": "開啟使用者資料夾",
"dev.permissions.accessibility.request": "請求輔助功能權限",
"dev.permissions.fullDisk.open": "開啟完整磁碟存取設定",
"dev.permissions.fullDisk.request": "請求完整磁碟存取權限",
"dev.permissions.microphone.request": "請求麥克風權限",
"dev.permissions.notification.request": "請求通知權限",
"dev.permissions.screen.request": "請求螢幕錄製權限",
"dev.permissions.title": "權限",
"dev.refreshMenu": "刷新選單",
"dev.reload": "重新載入",
"dev.simulateAutoDownload": "模擬自動下載(3秒)",
"dev.simulateDownloadComplete": "模擬下載完成",
"dev.simulateDownloadProgress": "模擬下載進度",
"dev.title": "開發",
"dev.updaterSimulation": "更新模擬",
"edit.copy": "複製",
"edit.cut": "剪下",
"edit.delete": "刪除",
@@ -23,6 +37,8 @@
"file.title": "檔案",
"help.about": "關於",
"help.githubRepo": "GitHub 倉庫",
"help.openConfigDir": "開啟設定目錄",
"help.openLogsDir": "開啟日誌目錄",
"help.reportIssue": "報告問題",
"help.title": "幫助",
"help.visitWebsite": "訪問網站",
+237
View File
@@ -0,0 +1,237 @@
# 本地更新测试指南
本目录包含用于在本地测试 Desktop 应用更新功能的工具和脚本。
## 目录结构
```
scripts/update-test/
├── README.md # 本文档
├── setup.sh # 一键设置脚本
├── start-server.sh # 启动本地更新服务器
├── stop-server.sh # 停止本地更新服务器
├── generate-manifest.sh # 生成 manifest 和目录结构
├── dev-app-update.local.yml # 本地测试用的更新配置模板
└── server/ # 本地服务器文件目录 (自动生成)
├── stable/ # stable 渠道
│ ├── latest-mac.yml
│ └── {version}/
│ ├── xxx.dmg
│ └── xxx.zip
├── beta/ # beta 渠道
│ └── ...
└── nightly/ # nightly 渠道
└── ...
```
## 快速开始
### 1. 首次设置
```bash
cd apps/desktop/scripts/update-test
chmod +x *.sh
./setup.sh
```
### 2. 构建测试包
```bash
# 回到 desktop 目录
cd ../..
# 构建未签名的本地测试包
bun run build
bun run build-local
```
如果需要模拟 CI 的渠道构建(Nightly / Beta / Stable),可以使用根目录脚本:
```bash
# 回到仓库根目录
cd ../../..
# 指定渠道与版本号
npm run desktop:build-channel -- nightly 2.1.0-nightly.1
npm run desktop:build-channel -- beta 2.1.0-beta.1
npm run desktop:build-channel -- stable 2.1.0
# 保留 package.json 与 icon 变更
npm run desktop:build-channel -- stable 2.1.0 --keep-changes
```
### 3. 生成更新文件
```bash
cd scripts/update-test
# 从 release 目录自动检测并生成 (默认 stable 渠道)
./generate-manifest.sh --from-release
# 指定版本号 (用于模拟更新)
./generate-manifest.sh --from-release -v 0.0.1
# 指定渠道
./generate-manifest.sh --from-release -c beta -v 2.1.0-beta.1
```
### 4. 启动本地服务器
```bash
./start-server.sh
# 服务器默认在 http://localhost:8787 启动
```
### 5. 配置应用使用本地服务器
```bash
# 复制本地测试配置到 desktop 根目录
cp dev-app-update.local.yml ../../dev-app-update.yml
# 或者直接编辑 dev-app-update.yml,确保 URL 指向正确的渠道:
# url: http://localhost:8787/stable
```
### 6. 运行应用测试
```bash
cd ../..
bun run dev
```
### 7. 测试完成后
```bash
cd scripts/update-test
./stop-server.sh
# 恢复默认的 dev-app-update.yml(可选)
cd ../..
git checkout dev-app-update.yml
```
---
## generate-manifest.sh 用法
```bash
用法: ./generate-manifest.sh [选项]
选项:
-v, --version VERSION 指定版本号 (例如: 2.0.1)
-c, --channel CHANNEL 指定渠道 (stable|beta|nightly, 默认: stable)
-d, --dmg FILE 指定 DMG 文件名
-z, --zip FILE 指定 ZIP 文件名
-n, --notes TEXT 指定 release notes
-f, --from-release 从 release 目录自动复制文件
-h, --help 显示帮助信息
示例:
./generate-manifest.sh --from-release
./generate-manifest.sh -v 2.0.1 -c stable --from-release
./generate-manifest.sh -v 2.1.0-beta.1 -c beta --from-release
```
---
## 详细说明
### 关于 macOS 签名验证
本地测试的包未经签名和公证,macOS 会阻止运行。解决方法:
#### 方法 1:临时禁用 Gatekeeper(推荐)
```bash
# 禁用
sudo spctl --master-disable
# 测试完成后务必重新启用!
sudo spctl --master-enable
```
#### 方法 2:手动移除隔离属性
```bash
# 对下载的 DMG 或解压后的 .app 执行
xattr -cr /path/to/YourApp.app
```
#### 方法 3:系统偏好设置
1. 打开「系统偏好设置」→「安全性与隐私」→「通用」
2. 点击「仍要打开」允许未签名的应用
### 自定义 Release Notes
编辑 `server/{channel}/latest-mac.yml` 中的 `releaseNotes` 字段:
```yaml
releaseNotes: |
## 🎉 v2.0.1 测试版本
### ✨ 新功能
- 功能 A
- 功能 B
### 🐛 修复
- 修复问题 X
```
### 测试不同场景
| 场景 | 操作 |
| ------------ | ----------------------------------------------------- |
| 有新版本可用 | 设置 manifest 中的 `version` 大于当前应用版本 (0.0.0) |
| 无新版本 | 设置 `version` 小于或等于当前版本 |
| 下载失败 | 删除 server/{channel}/{version}/ 中的 DMG 文件 |
| 网络错误 | 停止本地服务器 |
| 测试不同渠道 | 修改 dev-app-update.yml 中的 URL 指向不同渠道 |
### 环境变量
也可以通过环境变量指定更新服务器:
```bash
UPDATE_SERVER_URL=http://localhost:8787/stable bun run dev
```
---
## 故障排除
### 1. 服务器启动失败
```bash
# 检查端口是否被占用
lsof -i :8787
# 使用其他端口
PORT=9000 ./start-server.sh
```
### 2. 更新检测不到
- 确认 `dev-app-update.yml` 中的 URL 包含渠道路径 (如 `/stable`)
- 确认 manifest 中的版本号大于当前版本 (0.0.0)
- 查看日志:`tail -f ~/Library/Logs/lobehub-desktop-dev/main.log`
### 3. 请求了错误的 yml 文件
- 如果请求的是 `stable-mac.yml` 而不是 `latest-mac.yml`,说明代码中设置了 channel
- 确保在 dev 模式下运行,代码不会设置 `autoUpdater.channel`
### 4. 下载后无法安装
- 确认已禁用 Gatekeeper 或移除隔离属性
- 确认 DMG 文件完整
---
## 注意事项
⚠️ **安全提醒**
1. 测试完成后务必重新启用 Gatekeeper
2. 这些脚本仅用于本地开发测试
3. 不要将未签名的包分发给其他用户
@@ -0,0 +1,18 @@
# 本地更新测试配置
# 将此文件复制到 apps/desktop/dev-app-update.yml 以使用本地服务器测试
#
# 使用方法:
# cp scripts/update-test/dev-app-update.local.yml dev-app-update.yml
#
# 恢复默认配置:
# git checkout dev-app-update.yml
provider: generic
# URL 格式: http://localhost:8787/{channel}
# 可选渠道: stable, beta, nightly
url: http://localhost:8787/stable
updaterCacheDirName: lobehub-desktop-local-test
# 设置 channel 为 stable 以匹配生产环境行为
# stable channel 会找 stable-mac.yml
channel: stable
+277
View File
@@ -0,0 +1,277 @@
#!/bin/bash
# ============================================
# 生成更新 manifest 文件 ({channel}-mac.yml)
#
# 目录结构:
# server/
# {channel}/
# {channel}-mac.yml (e.g., stable-mac.yml)
# {version}/
# xxx.dmg
# xxx.zip
# ============================================
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SERVER_DIR="$SCRIPT_DIR/server"
DESKTOP_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")"
RELEASE_DIR="$DESKTOP_DIR/release"
# 默认值
VERSION=""
CHANNEL="stable"
DMG_FILE=""
ZIP_FILE=""
RELEASE_NOTES=""
FROM_RELEASE=false
# 帮助信息
show_help() {
echo "用法: $0 [选项]"
echo ""
echo "选项:"
echo " -v, --version VERSION 指定版本号 (例如: 2.0.1)"
echo " -c, --channel CHANNEL 指定渠道 (stable|beta|nightly, 默认: stable)"
echo " -d, --dmg FILE 指定 DMG 文件名"
echo " -z, --zip FILE 指定 ZIP 文件名"
echo " -n, --notes TEXT 指定 release notes"
echo " -f, --from-release 从 release 目录自动复制文件"
echo " -h, --help 显示帮助信息"
echo ""
echo "示例:"
echo " $0 --from-release"
echo " $0 -v 2.0.1 -c stable -d LobeHub-2.0.1-arm64.dmg"
echo " $0 -v 2.1.0-beta.1 -c beta --from-release"
echo ""
echo "生成的目录结构:"
echo " server/"
echo " {channel}/"
echo " {channel}-mac.yml (e.g., stable-mac.yml)"
echo " {version}/"
echo " xxx.dmg"
echo " xxx.zip"
echo ""
}
# 计算 SHA512
calc_sha512() {
local file="$1"
if [ -f "$file" ]; then
shasum -a 512 "$file" | awk '{print $1}' | xxd -r -p | base64
else
echo "placeholder-sha512-file-not-found"
fi
}
# 获取文件大小
get_file_size() {
local file="$1"
if [ -f "$file" ]; then
stat -f%z "$file" 2>/dev/null || stat --printf="%s" "$file" 2>/dev/null || echo "0"
else
echo "0"
fi
}
# 解析参数
FROM_RELEASE=false
while [[ $# -gt 0 ]]; do
case $1 in
-v|--version)
VERSION="$2"
shift 2
;;
-c|--channel)
CHANNEL="$2"
shift 2
;;
-d|--dmg)
DMG_FILE="$2"
shift 2
;;
-z|--zip)
ZIP_FILE="$2"
shift 2
;;
-n|--notes)
RELEASE_NOTES="$2"
shift 2
;;
-f|--from-release)
FROM_RELEASE=true
shift
;;
-h|--help)
show_help
exit 0
;;
*)
echo "未知参数: $1"
show_help
exit 1
;;
esac
done
echo "🔧 生成更新 manifest 文件..."
echo " 渠道: $CHANNEL"
echo ""
# 渠道目录
CHANNEL_DIR="$SERVER_DIR/$CHANNEL"
# 自动从 release 目录检测和复制
if [ "$FROM_RELEASE" = true ]; then
echo "📂 从 release 目录检测文件..."
if [ ! -d "$RELEASE_DIR" ]; then
echo "❌ release 目录不存在: $RELEASE_DIR"
echo " 请先运行构建命令"
exit 1
fi
# 查找 DMG 文件
DMG_PATH=$(find "$RELEASE_DIR" -maxdepth 1 -name "*.dmg" -type f | head -1)
if [ -n "$DMG_PATH" ]; then
DMG_FILE=$(basename "$DMG_PATH")
echo " 找到 DMG: $DMG_FILE"
fi
# 查找 ZIP 文件
ZIP_PATH=$(find "$RELEASE_DIR" -maxdepth 1 -name "*-mac.zip" -type f | head -1)
if [ -n "$ZIP_PATH" ]; then
ZIP_FILE=$(basename "$ZIP_PATH")
echo " 找到 ZIP: $ZIP_FILE"
fi
# 从文件名提取版本号
# 文件名格式: lobehub-desktop-dev-0.0.0-arm64.dmg
# 版本号格式: x.y.z 或 x.y.z-beta.1 等
if [ -z "$VERSION" ] && [ -n "$DMG_FILE" ]; then
# 先尝试匹配带预发布标签的版本 (如 2.0.0-beta.1)
VERSION=$(echo "$DMG_FILE" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+-(alpha|beta|rc|nightly)\.[0-9]+' | head -1)
# 如果没有预发布标签,只匹配基本版本号 (如 2.0.0)
if [ -z "$VERSION" ]; then
VERSION=$(echo "$DMG_FILE" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
fi
fi
fi
# 设置默认版本号
if [ -z "$VERSION" ]; then
VERSION="0.0.1"
echo "⚠️ 未指定版本号,使用默认值: $VERSION"
fi
# 版本目录
VERSION_DIR="$CHANNEL_DIR/$VERSION"
# 创建目录结构
echo ""
echo "📁 创建目录结构..."
mkdir -p "$VERSION_DIR"
echo " $CHANNEL_DIR/"
echo " $VERSION/"
# 复制文件到版本目录
if [ "$FROM_RELEASE" = true ]; then
if [ -n "$DMG_PATH" ] && [ -f "$DMG_PATH" ]; then
echo " 复制 $DMG_FILE -> $VERSION/"
cp "$DMG_PATH" "$VERSION_DIR/"
fi
if [ -n "$ZIP_PATH" ] && [ -f "$ZIP_PATH" ]; then
echo " 复制 $ZIP_FILE -> $VERSION/"
cp "$ZIP_PATH" "$VERSION_DIR/"
fi
fi
# 设置默认 release notes
if [ -z "$RELEASE_NOTES" ]; then
RELEASE_NOTES="## 🎉 v$VERSION 本地测试版本
这是一个用于本地测试更新功能的模拟版本。
### ✨ 新功能
- 测试自动更新功能
- 验证更新流程
### 🐛 修复
- 本地测试环境配置"
fi
# 生成 {channel}-mac.yml (e.g., stable-mac.yml)
MANIFEST_FILE="$CHANNEL-mac.yml"
echo ""
echo "📝 生成 $CHANNEL/$MANIFEST_FILE..."
DMG_SHA512=""
DMG_SIZE="0"
ZIP_SHA512=""
ZIP_SIZE="0"
if [ -n "$DMG_FILE" ] && [ -f "$VERSION_DIR/$DMG_FILE" ]; then
echo " 计算 DMG SHA512..."
DMG_SHA512=$(calc_sha512 "$VERSION_DIR/$DMG_FILE")
DMG_SIZE=$(get_file_size "$VERSION_DIR/$DMG_FILE")
fi
if [ -n "$ZIP_FILE" ] && [ -f "$VERSION_DIR/$ZIP_FILE" ]; then
echo " 计算 ZIP SHA512..."
ZIP_SHA512=$(calc_sha512 "$VERSION_DIR/$ZIP_FILE")
ZIP_SIZE=$(get_file_size "$VERSION_DIR/$ZIP_FILE")
fi
# 写入 manifest 文件 (放在渠道目录下)
cat > "$CHANNEL_DIR/$MANIFEST_FILE" << EOF
version: $VERSION
files:
EOF
if [ -n "$DMG_FILE" ]; then
cat >> "$CHANNEL_DIR/$MANIFEST_FILE" << EOF
- url: $VERSION/$DMG_FILE
sha512: ${DMG_SHA512:-placeholder}
size: $DMG_SIZE
EOF
fi
if [ -n "$ZIP_FILE" ]; then
cat >> "$CHANNEL_DIR/$MANIFEST_FILE" << EOF
- url: $VERSION/$ZIP_FILE
sha512: ${ZIP_SHA512:-placeholder}
size: $ZIP_SIZE
EOF
fi
cat >> "$CHANNEL_DIR/$MANIFEST_FILE" << EOF
path: $VERSION/${DMG_FILE:-LobeHub-$VERSION-arm64.dmg}
sha512: ${DMG_SHA512:-placeholder}
releaseDate: '$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")'
releaseNotes: |
$(echo "$RELEASE_NOTES" | sed 's/^/ /')
EOF
echo "✅ 已生成 $CHANNEL_DIR/$MANIFEST_FILE"
# 显示生成的文件内容
echo ""
echo "📋 文件内容:"
echo "----------------------------------------"
cat "$CHANNEL_DIR/$MANIFEST_FILE"
echo "----------------------------------------"
# 显示目录结构
echo ""
echo "📁 目录结构:"
find "$CHANNEL_DIR" -type f | sed "s|$SERVER_DIR/||" | sort
echo ""
echo "✅ 完成!"
echo ""
echo "下一步:"
echo " 1. 启动服务器: ./start-server.sh"
echo " 2. 确认 dev-app-update.yml 的 URL 为: http://localhost:8787/$CHANNEL"
echo " 3. 运行应用: cd ../.. && bun run dev"
+105
View File
@@ -0,0 +1,105 @@
#!/bin/bash
# ============================================
# 一键启动本地更新测试
# ============================================
#
# 此脚本会:
# 1. 设置测试环境
# 2. 从 release 目录复制文件
# 3. 生成 manifest
# 4. 启动本地服务器
# 5. 配置应用使用本地服务器
# 6. 启动应用
#
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DESKTOP_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")"
echo "============================================"
echo "🧪 本地更新测试 - 一键启动"
echo "============================================"
echo ""
# 检查 macOS Gatekeeper 状态
check_gatekeeper() {
if command -v spctl &> /dev/null; then
STATUS=$(spctl --status 2>&1 || true)
if [[ "$STATUS" == *"enabled"* ]]; then
echo "⚠️ 警告: macOS Gatekeeper 已启用"
echo ""
echo " 未签名的应用可能无法安装。你可以:"
echo " 1. 临时禁用: sudo spctl --master-disable"
echo " 2. 或者在安装后手动允许应用"
echo ""
read -p "是否继续?[y/N] " -n 1 -r
echo ""
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
else
echo "✅ Gatekeeper 已禁用,可以安装未签名应用"
fi
fi
}
# 步骤 1: 设置
echo "📦 步骤 1/5: 设置测试环境..."
cd "$SCRIPT_DIR"
chmod +x *.sh
mkdir -p server
# 步骤 2: 检查 release 目录
echo ""
echo "📂 步骤 2/5: 检查构建产物..."
if [ ! -d "$DESKTOP_DIR/release" ] || [ -z "$(ls -A "$DESKTOP_DIR/release"/*.dmg 2>/dev/null)" ]; then
echo "❌ 未找到构建产物"
echo ""
echo "请先构建应用:"
echo " cd $DESKTOP_DIR"
echo " npm run build-local"
echo ""
exit 1
fi
# 步骤 3: 生成 manifest
echo ""
echo "📝 步骤 3/5: 生成 manifest 文件..."
./generate-manifest.sh --from-release
# 步骤 4: 启动服务器
echo ""
echo "🚀 步骤 4/5: 启动本地服务器..."
./start-server.sh
# 步骤 5: 配置并启动应用
echo ""
echo "⚙️ 步骤 5/5: 配置应用..."
cp "$SCRIPT_DIR/dev-app-update.local.yml" "$DESKTOP_DIR/dev-app-update.yml"
echo "✅ 已更新 dev-app-update.yml"
# 检查 Gatekeeper
echo ""
check_gatekeeper
echo ""
echo "============================================"
echo "✅ 准备完成!"
echo "============================================"
echo ""
echo "现在可以运行应用进行测试:"
echo ""
echo " cd $DESKTOP_DIR"
echo " npm run dev"
echo ""
echo "或者直接运行:"
read -p "是否现在启动应用?[Y/n] " -n 1 -r
echo ""
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
echo ""
echo "🚀 启动应用..."
cd "$DESKTOP_DIR"
npm run dev
fi
+111
View File
@@ -0,0 +1,111 @@
#!/bin/bash
# ============================================
# 本地更新测试 - 一键设置脚本
# ============================================
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SERVER_DIR="$SCRIPT_DIR/server"
echo "🚀 设置本地更新测试环境..."
# 创建服务器目录
mkdir -p "$SERVER_DIR"
echo "✅ 创建服务器目录: $SERVER_DIR"
# 设置脚本执行权限
chmod +x "$SCRIPT_DIR"/*.sh
echo "✅ 设置脚本执行权限"
# 检查是否安装了 serve
if ! command -v npx &> /dev/null; then
echo "❌ 需要安装 Node.js 和 npm"
exit 1
fi
# 创建示例 latest-mac.yml
cat > "$SERVER_DIR/latest-mac.yml" << 'EOF'
version: 99.0.0
files:
- url: LobeHub-99.0.0-arm64.dmg
sha512: placeholder-sha512-will-be-replaced
size: 100000000
- url: LobeHub-99.0.0-arm64-mac.zip
sha512: placeholder-sha512-will-be-replaced
size: 100000000
path: LobeHub-99.0.0-arm64.dmg
sha512: placeholder-sha512-will-be-replaced
releaseDate: '2026-01-15T10:00:00.000Z'
releaseNotes: |
## 🎉 v99.0.0 本地测试版本
这是一个用于本地测试更新功能的模拟版本。
### ✨ 新功能
- 测试功能 A
- 测试功能 B
### 🐛 修复
- 修复测试问题 X
EOF
echo "✅ 创建示例 latest-mac.yml"
# 创建 Windows 版本的 manifest (可选)
cat > "$SERVER_DIR/latest.yml" << 'EOF'
version: 99.0.0
files:
- url: LobeHub-99.0.0-setup.exe
sha512: placeholder-sha512-will-be-replaced
size: 100000000
path: LobeHub-99.0.0-setup.exe
sha512: placeholder-sha512-will-be-replaced
releaseDate: '2026-01-15T10:00:00.000Z'
releaseNotes: |
## 🎉 v99.0.0 本地测试版本
这是一个用于本地测试更新功能的模拟版本。
EOF
echo "✅ 创建示例 latest.yml (Windows)"
# 创建本地测试用的 dev-app-update.yml
cat > "$SCRIPT_DIR/dev-app-update.local.yml" << 'EOF'
# 本地更新测试配置
# 将此文件复制到 apps/desktop/dev-app-update.yml 以使用本地服务器测试
provider: generic
url: http://localhost:8787
updaterCacheDirName: lobehub-desktop-local-test
EOF
echo "✅ 创建本地测试配置文件"
echo ""
echo "============================================"
echo "✅ 设置完成!"
echo "============================================"
echo ""
echo "下一步操作:"
echo ""
echo "1. 构建测试包:"
echo " cd $(dirname "$SCRIPT_DIR")"
echo " npm run build-local"
echo ""
echo "2. 复制构建产物到服务器目录:"
echo " cp release/*.dmg scripts/update-test/server/"
echo " cp release/*.zip scripts/update-test/server/"
echo ""
echo "3. 更新 manifest 文件(可选):"
echo " cd scripts/update-test"
echo " ./generate-manifest.sh"
echo ""
echo "4. 启动本地服务器:"
echo " ./start-server.sh"
echo ""
echo "5. 配置应用使用本地服务器:"
echo " cp dev-app-update.local.yml ../../dev-app-update.yml"
echo ""
echo "6. 运行应用:"
echo " cd ../.."
echo " npm run dev"
echo ""
+70
View File
@@ -0,0 +1,70 @@
#!/bin/bash
# ============================================
# 启动本地更新服务器
# ============================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SERVER_DIR="$SCRIPT_DIR/server"
PID_FILE="$SCRIPT_DIR/.server.pid"
LOG_FILE="$SCRIPT_DIR/.server.log"
PORT="${PORT:-8787}"
# 检查服务器目录
if [ ! -d "$SERVER_DIR" ]; then
echo "❌ 服务器目录不存在,请先运行 ./setup.sh"
exit 1
fi
# 检查是否已经在运行
if [ -f "$PID_FILE" ]; then
OLD_PID=$(cat "$PID_FILE")
if ps -p "$OLD_PID" > /dev/null 2>&1; then
echo "⚠️ 服务器已经在运行 (PID: $OLD_PID)"
echo " 地址: http://localhost:$PORT"
echo ""
echo " 如需重启,请先运行 ./stop-server.sh"
exit 0
else
rm -f "$PID_FILE"
fi
fi
echo "🚀 启动本地更新服务器..."
echo " 目录: $SERVER_DIR"
echo " 端口: $PORT"
echo ""
# 列出服务器目录中的文件
echo "📦 可用文件:"
ls -la "$SERVER_DIR" | grep -v "^d" | grep -v "^total" | awk '{print " " $NF}'
echo ""
# 启动服务器 (后台运行)
cd "$SERVER_DIR"
nohup npx serve -p "$PORT" --cors -n > "$LOG_FILE" 2>&1 &
SERVER_PID=$!
echo "$SERVER_PID" > "$PID_FILE"
# 等待服务器启动
sleep 2
# 检查是否启动成功
if ps -p "$SERVER_PID" > /dev/null 2>&1; then
echo "✅ 服务器已启动!"
echo ""
echo " 地址: http://localhost:$PORT"
echo " PID: $SERVER_PID"
echo " 日志: $LOG_FILE"
echo ""
echo "📋 测试 URL:"
echo " latest-mac.yml: http://localhost:$PORT/latest-mac.yml"
echo " latest.yml: http://localhost:$PORT/latest.yml"
echo ""
echo "🛑 停止服务器: ./stop-server.sh"
else
echo "❌ 服务器启动失败"
echo " 查看日志: cat $LOG_FILE"
rm -f "$PID_FILE"
exit 1
fi
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash
# ============================================
# 停止本地更新服务器
# ============================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PID_FILE="$SCRIPT_DIR/.server.pid"
LOG_FILE="$SCRIPT_DIR/.server.log"
if [ ! -f "$PID_FILE" ]; then
echo "ℹ️ 服务器未运行 (找不到 PID 文件)"
exit 0
fi
PID=$(cat "$PID_FILE")
if ps -p "$PID" > /dev/null 2>&1; then
echo "🛑 停止服务器 (PID: $PID)..."
kill "$PID"
sleep 1
# 强制终止(如果还在运行)
if ps -p "$PID" > /dev/null 2>&1; then
kill -9 "$PID" 2>/dev/null
fi
echo "✅ 服务器已停止"
else
echo "ℹ️ 服务器进程已不存在"
fi
rm -f "$PID_FILE"
+8 -8
View File
@@ -1,36 +1,36 @@
/**
* 路由拦截类型,描述拦截路由和目标窗口的映射关系
* Route interception type, describing the mapping between intercepted routes and target windows
*/
export interface RouteInterceptConfig {
/**
* 是否始终在新窗口中打开,即使目标窗口已经存在
* Whether to always open in a new window, even if target window already exists
*/
alwaysOpenNew?: boolean;
/**
* 描述
* Description
*/
description: string;
/**
* 是否启用拦截
* Whether interception is enabled
*/
enabled: boolean;
/**
* 路由模式前缀,例如 '/settings'
* Route pattern prefix, e.g., '/settings'
*/
pathPrefix: string;
/**
* 目标窗口标识符
* Target window identifier
*/
targetWindow: string;
}
/**
* 拦截路由配置列表
* 定义了所有需要特殊处理的路由
* Intercepted route configuration list
* Defines all routes that require special handling
*/
export const interceptRoutes: RouteInterceptConfig[] = [
{
@@ -2,7 +2,6 @@
* Mock for node-mac-permissions native module
* Used in tests since the native module only works on macOS
*/
import { vi } from 'vitest';
export const askForAccessibilityAccess = vi.fn(() => undefined);
-1
View File
@@ -1,7 +1,6 @@
/**
* Vitest setup file for mocking native modules
*/
import { vi } from 'vitest';
// Mock node-mac-permissions before any imports
+2 -2
View File
@@ -25,9 +25,9 @@ export const appStorageDir = join(userDataDir, 'lobehub-storage');
// ------ Application storage directory ---- //
// 本地存储文件(模拟 S3
// Local storage files (simulating S3)
export const FILE_STORAGE_DIR = 'file-storage';
// Plugin 安装目录
// Plugin installation directory
export const INSTALL_PLUGINS_DIR = 'plugins';
// Desktop file service
+4 -4
View File
@@ -13,18 +13,18 @@ export const isLinux = linux();
function getIsWindows11() {
if (!isWindows) return false;
// 获取操作系统版本(如 "10.0.22621"
// Get OS version (e.g., "10.0.22621")
const release = os.release();
const parts = release.split('.');
// 主版本和次版本
// Major and minor version
const majorVersion = parseInt(parts[0], 10);
const minorVersion = parseInt(parts[1], 10);
// 构建号是第三部分
// Build number is the third part
const buildNumber = parseInt(parts[2], 10);
// Windows 11 的构建号从 22000 开始
// Windows 11 build numbers start from 22000
return majorVersion === 10 && minorVersion === 0 && buildNumber >= 22_000;
}
+3 -3
View File
@@ -1,5 +1,5 @@
/**
* 应用设置存储相关常量
* Application settings storage related constants
*/
import { NetworkProxySettings } from '@lobechat/electron-client-ipc';
@@ -8,7 +8,7 @@ import { DEFAULT_SHORTCUTS_CONFIG } from '@/shortcuts';
import { ElectronMainStore } from '@/types/store';
/**
* 存储名称
* Storage name
*/
export const STORE_NAME = 'lobehub-settings';
@@ -22,7 +22,7 @@ export const defaultProxySettings: NetworkProxySettings = {
};
/**
* 存储默认值
* Storage default values
*/
export const STORE_DEFAULTS: ElectronMainStore = {
dataSyncConfig: { storageMode: 'cloud' },
-3
View File
@@ -4,8 +4,5 @@ export const BACKGROUND_LIGHT = '#f8f8f8';
export const SYMBOL_COLOR_DARK = '#ffffff80';
export const SYMBOL_COLOR_LIGHT = '#00000080';
// Window dimensions and constraints
export const TITLE_BAR_HEIGHT = 29;
// Default window configuration
export const THEME_CHANGE_DELAY = 100;
+1 -1
View File
@@ -143,7 +143,7 @@ export default class AuthCtr extends ControllerModule {
}
/**
* 启动轮询机制获取凭证
* Start polling mechanism to get credentials
*/
private startPolling() {
if (!this.authRequestState) {
+50 -18
View File
@@ -7,7 +7,7 @@ import superjson from 'superjson';
import FileService from '@/services/fileSrv';
import { createLogger } from '@/utils/logger';
import { MCPClient } from '../libs/mcp/client';
import { MCPClient, MCPConnectionError } from '../libs/mcp/client';
import type { MCPClientParams, ToolCallContent, ToolCallResult } from '../libs/mcp/types';
import { ControllerModule, IpcMethod } from './index';
@@ -228,8 +228,9 @@ export default class McpCtr extends ControllerModule {
type: 'stdio',
};
const client = await this.createClient(params);
let client: MCPClient | undefined;
try {
client = await this.createClient(params);
const manifest = await client.listManifests();
const identifier = input.name;
@@ -257,8 +258,25 @@ export default class McpCtr extends ControllerModule {
mcpParams: params,
type: 'mcp' as any,
});
} catch (error) {
// If it's an MCPConnectionError with stderr logs, enhance the error message
if (error instanceof MCPConnectionError && error.stderrLogs.length > 0) {
const stderrOutput = error.stderrLogs.join('\n');
const enhancedError = new Error(
`${error.message}\n\n--- STDIO Process Output ---\n${stderrOutput}`,
);
enhancedError.name = error.name;
logger.error('getStdioMcpServerManifest failed with STDIO logs:', {
message: error.message,
stderrLogs: error.stderrLogs,
});
throw enhancedError;
}
throw error;
} finally {
await client.disconnect();
if (client) {
await client.disconnect();
}
}
}
@@ -313,8 +331,9 @@ export default class McpCtr extends ControllerModule {
type: 'stdio',
};
const client = await this.createClient(params);
let client: MCPClient | undefined;
try {
client = await this.createClient(params);
const args = safeParseToRecord(input.args);
const raw = (await client.callTool(input.toolName, args)) as ToolCallResult;
@@ -328,10 +347,25 @@ export default class McpCtr extends ControllerModule {
success: true,
});
} catch (error) {
// If it's an MCPConnectionError with stderr logs, enhance the error message
if (error instanceof MCPConnectionError && error.stderrLogs.length > 0) {
const stderrOutput = error.stderrLogs.join('\n');
const enhancedError = new Error(
`${error.message}\n\n--- STDIO Process Output ---\n${stderrOutput}`,
);
enhancedError.name = error.name;
logger.error('callTool failed with STDIO logs:', {
message: error.message,
stderrLogs: error.stderrLogs,
});
throw enhancedError;
}
logger.error('callTool failed:', error);
throw error;
} finally {
await client.disconnect();
if (client) {
await client.disconnect();
}
}
}
@@ -361,8 +395,9 @@ export default class McpCtr extends ControllerModule {
}
private async checkSystemDependency(dependency: any) {
const checkCommand = dependency.checkCommand || `${dependency.name} --version`;
try {
const checkCommand = dependency.checkCommand || `${dependency.name} --version`;
const { stdout, stderr } = await execPromise(checkCommand);
if (stderr && !stdout) {
@@ -444,22 +479,19 @@ export default class McpCtr extends ControllerModule {
const packageName = details?.packageName;
if (!packageName) return { installed: false };
// Only check global npm list - do NOT use npx as it may download packages
try {
const { stdout } = await execPromise(`npm list -g ${packageName} --depth=0`);
if (!stdout.includes('(empty)') && stdout.includes(packageName)) return { installed: true };
if (!stdout.includes('(empty)') && stdout.includes(packageName)) {
return { installed: true };
}
} catch {
// ignore
// ignore - package not found in global list
}
try {
await execPromise(`npx -y ${packageName} --version`);
return { installed: true };
} catch (error) {
return {
error: error instanceof Error ? error.message : 'Unknown error',
installed: false,
};
}
// For npm packages, we don't require pre-installation
// npx will handle downloading and running on-demand during actual MCP connection
return { installed: false };
}
if (installationMethod === 'python') {
@@ -553,7 +585,7 @@ export default class McpCtr extends ControllerModule {
const bestResult = recommendedResult || firstInstallableResult || results[0];
const checkResult: CheckMcpInstallResult = {
...(bestResult || {}),
...bestResult,
allOptions: results as any,
platform: process.platform,
success: true,
@@ -8,23 +8,23 @@ const logger = createLogger('controllers:McpInstallCtr');
const protocolHandler = createProtocolHandler('plugin');
/**
* 验证 MCP Schema 对象结构
* Validate MCP Schema object structure
*/
function validateMcpSchema(schema: any): schema is McpSchema {
if (!schema || typeof schema !== 'object') return false;
// 必填字段验证
// Required field validation
if (typeof schema.identifier !== 'string' || !schema.identifier) return false;
if (typeof schema.name !== 'string' || !schema.name) return false;
if (typeof schema.author !== 'string' || !schema.author) return false;
if (typeof schema.description !== 'string' || !schema.description) return false;
if (typeof schema.version !== 'string' || !schema.version) return false;
// 可选字段验证
// Optional field validation
if (schema.homepage !== undefined && typeof schema.homepage !== 'string') return false;
if (schema.icon !== undefined && typeof schema.icon !== 'string') return false;
// config 字段验证
// config field validation
if (!schema.config || typeof schema.config !== 'object') return false;
const config = schema.config;
@@ -35,13 +35,13 @@ function validateMcpSchema(schema: any): schema is McpSchema {
} else if (config.type === 'http') {
if (typeof config.url !== 'string' || !config.url) return false;
try {
new URL(config.url); // 验证URL格式
new URL(config.url); // Validate URL format
} catch {
return false;
}
if (config.headers !== undefined && typeof config.headers !== 'object') return false;
} else {
return false; // 未知的 config type
return false; // Unknown config type
}
return true;
@@ -54,8 +54,8 @@ interface McpInstallParams {
}
/**
* MCP 插件安装控制器
* 负责处理 MCP 插件安装流程
* MCP plugin installation controller
* Responsible for handling MCP plugin installation process
*/
export default class McpInstallController extends ControllerModule {
/**
@@ -16,13 +16,13 @@ import { ControllerModule, IpcMethod } from './index';
const logger = createLogger('controllers:NetworkProxyCtr');
/**
* 网络代理控制器
* 处理桌面应用的网络代理相关功能
* Network proxy controller
* Handle desktop application network proxy related functions
*/
export default class NetworkProxyCtr extends ControllerModule {
static override readonly groupName = 'networkProxy';
/**
* 获取代理设置
* Get proxy settings
*/
@IpcMethod()
async getDesktopSettings(): Promise<NetworkProxySettings> {
@@ -43,18 +43,18 @@ export default class NetworkProxyCtr extends ControllerModule {
}
/**
* 设置代理配置
* Set proxy configuration
*/
@IpcMethod()
async setProxySettings(config: Partial<NetworkProxySettings>): Promise<void> {
try {
// 获取当前配置
// Get current configuration
const currentConfig = this.app.storeManager.get(
'networkProxy',
defaultProxySettings,
) as NetworkProxySettings;
// 合并配置并验证
// Merge configuration and validate
const newConfig = merge({}, currentConfig, config);
const validation = ProxyConfigValidator.validate(newConfig);
@@ -69,10 +69,10 @@ export default class NetworkProxyCtr extends ControllerModule {
return;
}
// 应用代理设置
// Apply proxy settings
await ProxyDispatcherManager.applyProxySettings(newConfig);
// 保存到存储
// Save to storage
this.app.storeManager.set('networkProxy', newConfig);
logger.info('Proxy settings updated successfully', {
@@ -149,7 +149,7 @@ export default class NetworkProxyCtr extends ControllerModule {
return;
}
// 应用代理设置
// Apply proxy settings
await ProxyDispatcherManager.applyProxySettings(networkProxy);
logger.info('Initial proxy settings applied successfully', {
@@ -47,7 +47,7 @@ export default class RemoteServerSyncCtr extends ControllerModule {
}
/**
* 处理流式请求的 IPC 调用
* Handle IPC calls for streaming requests
*/
private handleStreamRequest = async (event: IpcMainEvent, args: ProxyTRPCStreamRequestParams) => {
const { requestId } = args;
@@ -79,7 +79,7 @@ export default class RemoteServerSyncCtr extends ControllerModule {
return;
}
// 调用新的流式转发方法
// Call new streaming forwarding method
await this.forwardStreamRequest(event.sender, {
...args,
accessToken: token,
@@ -95,7 +95,7 @@ export default class RemoteServerSyncCtr extends ControllerModule {
};
/**
* 执行实际的流式请求转发
* Execute actual streaming request forwarding
*/
private async forwardStreamRequest(
sender: WebContents,
@@ -123,14 +123,14 @@ export default class RemoteServerSyncCtr extends ControllerModule {
const clientReq = requester.request(requestOptions, (clientRes: IncomingMessage) => {
logger.debug(`${logPrefix} Received response with status ${clientRes.statusCode}`);
// 添加调试信息
// Add debug information
logger.debug(`${logPrefix} Response details:`, {
headers: clientRes.headers,
statusCode: clientRes.statusCode,
statusMessage: clientRes.statusMessage,
});
// 1. 立刻发送响应头和状态码
// 1. Immediately send response headers and status code
const responseData = {
headers: clientRes.headers || {},
status: clientRes.statusCode || 500,
@@ -140,21 +140,21 @@ export default class RemoteServerSyncCtr extends ControllerModule {
logger.debug(`${logPrefix} Sending response data:`, responseData);
sender.send(`stream:response:${requestId}`, responseData);
// 2. 监听数据块并转发
// 2. Listen for data chunks and forward
clientRes.on('data', (chunk: Buffer) => {
if (sender.isDestroyed()) return;
logger.debug(`${logPrefix} Received data chunk, size: ${chunk.length}. Forwarding...`);
sender.send(`stream:data:${requestId}`, chunk);
});
// 3. 监听结束信号并转发
// 3. Listen for end signal and forward
clientRes.on('end', () => {
logger.debug(`${logPrefix} Stream ended. Forwarding end signal...`);
if (sender.isDestroyed()) return;
sender.send(`stream:end:${requestId}`);
});
// 4. 监听响应流错误并转发
// 4. Listen for response stream errors and forward
clientRes.on('error', (error) => {
logger.error(`${logPrefix} Error reading response stream:`, error);
if (sender.isDestroyed()) return;
@@ -4,10 +4,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { App } from '@/core/App';
import type { IpcContext } from '@/utils/ipc';
import { IpcHandler } from '@/utils/ipc/base';
import {
__resetMacPermissionsModuleCache,
__setMacPermissionsModule,
} from '@/utils/permissions';
import { __resetMacPermissionsModuleCache, __setMacPermissionsModule } from '@/utils/permissions';
import SystemController from '../SystemCtr';
+9 -9
View File
@@ -127,7 +127,7 @@ export class App {
// initialize protocol handlers
this.protocolManager.initialize();
// 统一处理 before-quit 事件
// Unified handling of before-quit event
app.on('before-quit', this.handleBeforeQuit);
// Initialize theme mode from store
@@ -224,10 +224,10 @@ export class App {
/**
* Handle protocol request by dispatching to registered handlers
* @param urlType 协议URL类型 (如: 'plugin')
* @param action 操作类型 (如: 'install')
* @param data 解析后的协议数据
* @returns 是否成功处理
* @param urlType Protocol URL type (e.g., 'plugin')
* @param action Action type (e.g., 'install')
* @param data Parsed protocol data
* @returns Whether successfully handled
*/
async handleProtocolRequest(urlType: string, action: string, data: any): Promise<boolean> {
const key = `${urlType}:${action}`;
@@ -241,7 +241,7 @@ export class App {
try {
logger.debug(`Dispatching protocol request ${key} to controller`);
const result = await handler.controller[handler.methodName](data);
return result !== false; // 假设控制器返回 false 表示处理失败
return result !== false; // Assume controller returning false indicates handling failure
} catch (error) {
logger.error(`Error handling protocol request ${key}:`, error);
return false;
@@ -385,17 +385,17 @@ export class App {
this.ipcServer = new ElectronIPCServer(name, ipcServerEvents);
}
// 新增 before-quit 处理函数
// Add before-quit handler function
private handleBeforeQuit = () => {
logger.info('Application is preparing to quit');
this.isQuiting = true;
// 销毁托盘
// Destroy tray
if (process.platform === 'win32') {
this.trayManager.destroyAll();
}
// 执行清理操作
// Execute cleanup operations
this.staticFileServerManager.destroy();
};
}
@@ -1,3 +1,4 @@
import { TITLE_BAR_HEIGHT } from '@lobechat/desktop-bridge';
import { MainBroadcastEventKey, MainBroadcastParams } from '@lobechat/electron-client-ipc';
import {
BrowserWindow,
@@ -12,7 +13,6 @@ import { join } from 'node:path';
import { preloadDir, resourcesDir } from '@/const/dir';
import { isMac } from '@/const/env';
import { ELECTRON_BE_PROTOCOL_SCHEME } from '@/const/protocol';
import { TITLE_BAR_HEIGHT } from '@/const/theme';
import RemoteServerConfigCtr from '@/controllers/RemoteServerConfigCtr';
import { backendProxyProtocolManager } from '@/core/infrastructure/BackendProxyProtocolManager';
import { setResponseHeader } from '@/utils/http-headers';
@@ -1,3 +1,4 @@
import { TITLE_BAR_HEIGHT } from '@lobechat/desktop-bridge';
import { BrowserWindow, nativeTheme } from 'electron';
import { join } from 'node:path';
@@ -9,7 +10,6 @@ import {
SYMBOL_COLOR_DARK,
SYMBOL_COLOR_LIGHT,
THEME_CHANGE_DELAY,
TITLE_BAR_HEIGHT,
} from '@/const/theme';
import { createLogger } from '@/utils/logger';
@@ -91,7 +91,8 @@ export class WindowThemeManager {
icon: isDev ? join(buildDir, 'icon-dev.ico') : undefined,
titleBarOverlay: {
color: isDarkMode ? BACKGROUND_DARK : BACKGROUND_LIGHT,
height: TITLE_BAR_HEIGHT,
// Reduce 2px to prevent blocking the container border edge
height: TITLE_BAR_HEIGHT - 2,
symbolColor: isDarkMode ? SYMBOL_COLOR_DARK : SYMBOL_COLOR_LIGHT,
},
titleBarStyle: 'hidden',
@@ -108,7 +108,6 @@ vi.mock('@/const/theme', () => ({
SYMBOL_COLOR_DARK: '#ffffff',
SYMBOL_COLOR_LIGHT: '#000000',
THEME_CHANGE_DELAY: 0,
TITLE_BAR_HEIGHT: 32,
}));
describe('Browser', () => {
@@ -38,13 +38,16 @@ vi.mock('@/const/env', () => ({
isWindows: true,
}));
vi.mock('@lobechat/desktop-bridge', () => ({
TITLE_BAR_HEIGHT: 38,
}));
vi.mock('@/const/theme', () => ({
BACKGROUND_DARK: '#1a1a1a',
BACKGROUND_LIGHT: '#ffffff',
SYMBOL_COLOR_DARK: '#ffffff',
SYMBOL_COLOR_LIGHT: '#000000',
THEME_CHANGE_DELAY: 0,
TITLE_BAR_HEIGHT: 32,
}));
describe('WindowThemeManager', () => {
@@ -89,7 +92,7 @@ describe('WindowThemeManager', () => {
icon: undefined,
titleBarOverlay: {
color: '#1a1a1a',
height: 32,
height: 36,
symbolColor: '#ffffff',
},
titleBarStyle: 'hidden',
@@ -106,7 +109,7 @@ describe('WindowThemeManager', () => {
icon: undefined,
titleBarOverlay: {
color: '#ffffff',
height: 32,
height: 36,
symbolColor: '#000000',
},
titleBarStyle: 'hidden',
@@ -183,7 +186,7 @@ describe('WindowThemeManager', () => {
expect(mockBrowserWindow.setBackgroundColor).toHaveBeenCalledWith('#1a1a1a');
expect(mockBrowserWindow.setTitleBarOverlay).toHaveBeenCalledWith({
color: '#1a1a1a',
height: 32,
height: 36,
symbolColor: '#ffffff',
});
});
@@ -195,7 +198,7 @@ describe('WindowThemeManager', () => {
expect(mockBrowserWindow.setBackgroundColor).toHaveBeenCalledWith('#ffffff');
expect(mockBrowserWindow.setTitleBarOverlay).toHaveBeenCalledWith({
color: '#ffffff',
height: 32,
height: 36,
symbolColor: '#000000',
});
});
@@ -31,7 +31,24 @@ export class BackendProxyProtocolManager {
private readonly handledSessions = new WeakSet<Session>();
private readonly logger = createLogger('core:BackendProxyProtocolManager');
/**
* Debounce timer for authorization required notifications.
* Prevents multiple rapid 401 responses from triggering duplicate notifications.
*/
// eslint-disable-next-line no-undef
private authRequiredDebounceTimer: NodeJS.Timeout | null = null;
private static readonly AUTH_REQUIRED_DEBOUNCE_MS = 1000;
private notifyAuthorizationRequired() {
// Debounce: skip if a notification is already scheduled
if (this.authRequiredDebounceTimer) {
return;
}
this.authRequiredDebounceTimer = setTimeout(() => {
this.authRequiredDebounceTimer = null;
}, BackendProxyProtocolManager.AUTH_REQUIRED_DEBOUNCE_MS);
const allWindows = BrowserWindow.getAllWindows();
for (const win of allWindows) {
if (!win.isDestroyed()) {
@@ -146,8 +163,14 @@ export class BackendProxyProtocolManager {
responseHeaders.set('Access-Control-Allow-Headers', '*');
responseHeaders.set('X-Src-Url', rewrittenUrl);
if (!token && upstreamResponse.status === 401) {
this.notifyAuthorizationRequired();
// Handle 401 Unauthorized: only notify authorization required for real auth failures
// The server sets X-Auth-Required header for real authentication failures (e.g., token expired)
// Other 401 errors (e.g., invalid API keys) should not trigger re-authentication
if (upstreamResponse.status === 401) {
const authRequired = upstreamResponse.headers.get('X-Auth-Required') === 'true';
if (authRequired) {
this.notifyAuthorizationRequired();
}
}
return new Response(upstreamResponse.body, {
@@ -160,7 +160,7 @@ export class StaticFileServerManager {
logger.debug(`Request method: ${req.method}`);
logger.debug(`Request headers: ${JSON.stringify(req.headers)}`);
// 提取文件路径:从 /desktop-file/path/to/file.png 中提取相对路径
// 提取File path:从 /desktop-file/path/to/file.png 中提取相对路径
let filePath = decodeURIComponent(url.pathname.slice(1)); // 移除开头的 /
logger.debug(`Initial file path after decode: ${filePath}`);
@@ -243,7 +243,7 @@ export class StaticFileServerManager {
}
/**
* 获取文件服务器域名
* Get file server domain
*/
getFileServerDomain(): string {
if (!this.isInitialized || !this.serverPort) {
@@ -2,11 +2,21 @@ import log from 'electron-log';
import { autoUpdater } from 'electron-updater';
import { isDev, isWindows } from '@/const/env';
import { UPDATE_CHANNEL as channel, updaterConfig } from '@/modules/updater/configs';
import { getDesktopEnv } from '@/env';
import {
UPDATE_SERVER_URL,
UPDATE_CHANNEL as channel,
githubConfig,
isStableChannel,
updaterConfig,
} from '@/modules/updater/configs';
import { createLogger } from '@/utils/logger';
import type { App as AppCore } from '../App';
// Allow forcing dev update config via env (for testing updates in packaged app)
const FORCE_DEV_UPDATE_CONFIG = getDesktopEnv().FORCE_DEV_UPDATE_CONFIG;
// Create logger
const logger = createLogger('core:UpdaterManager');
@@ -16,6 +26,7 @@ export class UpdaterManager {
private downloading: boolean = false;
private updateAvailable: boolean = false;
private isManualCheck: boolean = false;
private usingFallbackProvider: boolean = false;
constructor(app: AppCore) {
this.app = app;
@@ -42,16 +53,26 @@ export class UpdaterManager {
// Configure autoUpdater
autoUpdater.autoDownload = false; // Set to false, we'll control downloads manually
autoUpdater.autoInstallOnAppQuit = false;
autoUpdater.channel = channel;
autoUpdater.allowPrerelease = channel !== 'stable';
autoUpdater.allowDowngrade = false;
// Enable test mode in development environment
if (isDev) {
logger.info(`Running in dev mode, forcing update check, channel: ${autoUpdater.channel}`);
// Allow testing updates in development environment
// Enable test mode in development environment or when forced via env
// IMPORTANT: This must be set BEFORE channel configuration so that
// dev-app-update.yml takes precedence over programmatic configuration
const useDevConfig = isDev || FORCE_DEV_UPDATE_CONFIG;
if (useDevConfig) {
// In dev mode, use dev-app-update.yml for all configuration including channel
// Don't set channel here - let dev-app-update.yml control it (defaults to "latest")
autoUpdater.forceDevUpdateConfig = true;
logger.info(
`Using dev update config (isDev=${isDev}, FORCE_DEV_UPDATE_CONFIG=${FORCE_DEV_UPDATE_CONFIG})`,
);
logger.info('Dev mode: Using dev-app-update.yml for update configuration');
} else {
// Only configure channel and update provider programmatically in production
// Note: channel is configured in configureUpdateProvider based on provider type
autoUpdater.allowPrerelease = channel !== 'stable';
logger.info(`Production mode: channel=${channel}, allowPrerelease=${channel !== 'stable'}`);
this.configureUpdateProvider();
}
// Register events
@@ -83,8 +104,35 @@ export class UpdaterManager {
this.checking = true;
this.isManualCheck = manual;
// Ensure allowPrerelease is correctly set before each check
// This guards against any internal state reset by electron-updater
if (!isStableChannel) {
autoUpdater.allowPrerelease = true;
}
logger.info(`${manual ? 'Manually checking' : 'Auto checking'} for updates...`);
// Log detailed updater configuration for debugging
const inferredChannel =
autoUpdater.channel ||
(autoUpdater.currentVersion?.prerelease?.[0]
? String(autoUpdater.currentVersion.prerelease[0])
: null);
logger.info('[Updater Config] Channel:', autoUpdater.channel);
logger.info('[Updater Config] inferredChannel:', inferredChannel);
logger.info('[Updater Config] allowPrerelease:', autoUpdater.allowPrerelease);
logger.info('[Updater Config] currentVersion:', autoUpdater.currentVersion?.version);
logger.info('[Updater Config] allowDowngrade:', autoUpdater.allowDowngrade);
logger.info('[Updater Config] autoDownload:', autoUpdater.autoDownload);
logger.info('[Updater Config] forceDevUpdateConfig:', autoUpdater.forceDevUpdateConfig);
logger.info('[Updater Config] Build channel from config:', channel);
logger.info('[Updater Config] isStableChannel:', isStableChannel);
logger.info('[Updater Config] UPDATE_SERVER_URL:', UPDATE_SERVER_URL || '(not set)');
logger.info('[Updater Config] usingFallbackProvider:', this.usingFallbackProvider);
logger.info('[Updater Config] GitHub config:', JSON.stringify(githubConfig));
// If manual check, notify renderer process about check start
if (manual) {
this.mainWindow.broadcast('manualUpdateCheckStart');
@@ -291,17 +339,107 @@ export class UpdaterManager {
}, 300);
};
/**
* Configure update provider based on channel
* - Stable channel + UPDATE_SERVER_URL: Use generic HTTP provider (S3) as primary, channel=stable
* - Other channels (beta/nightly) or no S3: Use GitHub provider, channel unset (defaults to latest)
*
* Important: S3 has stable-mac.yml, GitHub has latest-mac.yml
*/
private configureUpdateProvider() {
if (isStableChannel && UPDATE_SERVER_URL && !this.usingFallbackProvider) {
// Stable channel uses custom update server (generic HTTP) as primary
// S3 has stable-mac.yml, so we set channel to 'stable'
autoUpdater.channel = 'stable';
logger.info(`Configuring generic provider for stable channel (primary)`);
logger.info(`Update server URL: ${UPDATE_SERVER_URL}`);
logger.info(`Channel set to: stable (will look for stable-mac.yml)`);
autoUpdater.setFeedURL({
provider: 'generic',
url: UPDATE_SERVER_URL,
});
} else {
// GitHub provider:
// - stable: use default latest-mac.yml (GitHub uploads latest* only)
// - beta/nightly: leave channel unset so prerelease matching uses tag (e.g. next)
const reason = this.usingFallbackProvider ? '(fallback from S3)' : '';
logger.info(`Configuring GitHub provider for ${channel} channel ${reason}`);
if (autoUpdater.channel !== null) {
autoUpdater.channel = null;
}
logger.info('Channel left unset (defaults to latest-mac.yml for GitHub)');
// For beta/nightly channels, we need prerelease versions
const needPrerelease = channel !== 'stable';
autoUpdater.setFeedURL({
owner: githubConfig.owner,
provider: 'github',
repo: githubConfig.repo,
});
// Ensure allowPrerelease is set correctly after setFeedURL
// setFeedURL may reset some internal states
autoUpdater.allowPrerelease = needPrerelease;
logger.info(
`GitHub update URL configured: ${githubConfig.owner}/${githubConfig.repo}, allowPrerelease=${needPrerelease}`,
);
}
}
/**
* Switch to fallback provider (GitHub) and retry update check
* Called when primary provider (S3) fails
*/
private switchToFallbackAndRetry = async () => {
// Only fallback if we're on stable channel with S3 configured and haven't already fallen back
if (!isStableChannel || !UPDATE_SERVER_URL || this.usingFallbackProvider) {
return false;
}
logger.info('Primary update server (S3) failed, switching to GitHub fallback...');
this.usingFallbackProvider = true;
this.configureUpdateProvider();
// Retry update check with fallback provider
try {
await autoUpdater.checkForUpdates();
return true;
} catch (error) {
logger.error('Fallback provider (GitHub) also failed:', error);
return false;
}
};
/**
* Reset to primary provider for next update check
*/
private resetToPrimaryProvider = () => {
if (this.usingFallbackProvider) {
logger.info('Resetting to primary update provider (S3)');
this.usingFallbackProvider = false;
this.configureUpdateProvider();
}
};
private registerEvents() {
logger.debug('Registering updater events');
autoUpdater.on('checking-for-update', () => {
logger.info('[Updater] Checking for update...');
logger.info('[Updater] Current channel:', autoUpdater.channel);
logger.info('[Updater] Current allowPrerelease:', autoUpdater.allowPrerelease);
});
autoUpdater.on('update-available', (info) => {
logger.info(`Update available: ${info.version}`);
this.updateAvailable = true;
// Reset to primary provider for next check cycle
this.resetToPrimaryProvider();
if (this.isManualCheck) {
this.mainWindow.broadcast('manualUpdateAvailable', info);
} else {
@@ -313,13 +451,35 @@ export class UpdaterManager {
autoUpdater.on('update-not-available', (info) => {
logger.info(`Update not available. Current: ${info.version}`);
// Reset to primary provider for next check cycle
this.resetToPrimaryProvider();
if (this.isManualCheck) {
this.mainWindow.broadcast('manualUpdateNotAvailable', info);
}
});
autoUpdater.on('error', (err) => {
autoUpdater.on('error', async (err) => {
logger.error('Error in auto-updater:', err);
// Log configuration state when error occurs for debugging
logger.error('[Updater Error Context] Channel:', autoUpdater.channel);
logger.error('[Updater Error Context] allowPrerelease:', autoUpdater.allowPrerelease);
logger.error('[Updater Error Context] Build channel from config:', channel);
logger.error('[Updater Error Context] isStableChannel:', isStableChannel);
logger.error('[Updater Error Context] UPDATE_SERVER_URL:', UPDATE_SERVER_URL || '(not set)');
logger.error('[Updater Error Context] usingFallbackProvider:', this.usingFallbackProvider);
logger.error('[Updater Error Context] GitHub config:', JSON.stringify(githubConfig));
// Try fallback to GitHub if S3 failed
if (!this.usingFallbackProvider && isStableChannel && UPDATE_SERVER_URL) {
logger.info('Attempting fallback to GitHub provider...');
const fallbackSucceeded = await this.switchToFallbackAndRetry();
if (fallbackSucceeded) {
return; // Fallback initiated, don't report error yet
}
}
if (this.isManualCheck) {
this.mainWindow.broadcast('updateError', err.message);
}
@@ -36,6 +36,7 @@ vi.mock('electron-updater', () => ({
logger: null as any,
on: vi.fn(),
quitAndInstall: vi.fn(),
setFeedURL: vi.fn(),
},
}));
@@ -62,6 +63,12 @@ vi.mock('@/utils/logger', () => ({
// Mock updater configs
vi.mock('@/modules/updater/configs', () => ({
UPDATE_CHANNEL: 'stable',
UPDATE_SERVER_URL: 'https://mock.update.server',
githubConfig: {
owner: 'lobehub',
repo: 'lobe-chat',
},
isStableChannel: true,
updaterConfig: {
app: {
autoCheckUpdate: false,
@@ -73,6 +80,13 @@ vi.mock('@/modules/updater/configs', () => ({
},
}));
// Mock env
vi.mock('@/env', () => ({
getDesktopEnv: () => ({
FORCE_DEV_UPDATE_CONFIG: false,
}),
}));
// Mock isDev
vi.mock('@/const/env', () => ({
isDev: false,
@@ -454,9 +468,11 @@ describe('UpdaterManager', () => {
vi.mocked(autoUpdater.checkForUpdates).mockResolvedValue({} as any);
await updaterManager.checkForUpdates({ manual: true });
vi.mocked(autoUpdater.checkForUpdates).mockRejectedValueOnce(new Error('Fallback failed'));
const error = new Error('Update error');
const handler = registeredEvents.get('error');
handler?.(error);
await handler?.(error);
expect(mockBroadcast).toHaveBeenCalledWith('updateError', 'Update error');
});
@@ -77,25 +77,25 @@ export class ShortcutManager {
try {
logger.debug(`Updating shortcut ${id} to ${accelerator}`);
// 1. 检查 ID 是否有效
// 1. Check if ID is valid
if (!DEFAULT_SHORTCUTS_CONFIG[id]) {
logger.error(`Invalid shortcut ID: ${id}`);
return { errorType: 'INVALID_ID', success: false };
}
// 2. 基本格式校验
// 2. Basic format validation
if (!accelerator || typeof accelerator !== 'string' || accelerator.trim() === '') {
logger.error(`Invalid accelerator format: ${accelerator}`);
return { errorType: 'INVALID_FORMAT', success: false };
}
// 转换前端格式到 Electron 格式
// Convert frontend format to Electron format
const convertedAccelerator = this.convertAcceleratorFormat(accelerator.trim());
const cleanAccelerator = convertedAccelerator.toLowerCase();
logger.debug(`Converted accelerator from ${accelerator} to ${convertedAccelerator}`);
// 3. 检查是否包含 + 号(修饰键格式)
// 3. Check if contains + sign (modifier key format)
if (!cleanAccelerator.includes('+')) {
logger.error(
`Invalid accelerator format: ${cleanAccelerator}. Must contain modifier keys like 'CommandOrControl+E'`,
@@ -103,7 +103,7 @@ export class ShortcutManager {
return { errorType: 'INVALID_FORMAT', success: false };
}
// 4. 检查是否有基本的修饰键
// 4. Check if has basic modifier keys
const hasModifier = ['CommandOrControl', 'Command', 'Ctrl', 'Alt', 'Shift'].some((modifier) =>
cleanAccelerator.includes(modifier.toLowerCase()),
);
@@ -113,7 +113,7 @@ export class ShortcutManager {
return { errorType: 'NO_MODIFIER', success: false };
}
// 5. 检查冲突
// 5. Check for conflicts
for (const [existingId, existingAccelerator] of Object.entries(this.shortcutsConfig)) {
if (
existingId !== id &&
@@ -125,7 +125,7 @@ export class ShortcutManager {
}
}
// 6. 尝试注册测试(检查是否被系统占用)
// 6. Try test registration (check if occupied by system)
const testSuccess = globalShortcut.register(convertedAccelerator, () => {});
if (!testSuccess) {
logger.error(
@@ -133,11 +133,11 @@ export class ShortcutManager {
);
return { errorType: 'SYSTEM_OCCUPIED', success: false };
} else {
// 测试成功,立即取消注册
// Test successful, immediately unregister
globalShortcut.unregister(convertedAccelerator);
}
// 7. 更新配置
// 7. Update configuration
this.shortcutsConfig[id] = convertedAccelerator;
this.saveShortcutsConfig();
@@ -285,7 +285,7 @@ export class ShortcutManager {
Object.entries(this.shortcutsConfig).forEach(([id, accelerator]) => {
logger.debug(`Registering shortcut '${id}' with ${accelerator}`);
// 只注册在 DEFAULT_SHORTCUTS_CONFIG 中存在的快捷键
// Only register shortcuts that exist in DEFAULT_SHORTCUTS_CONFIG
if (!DEFAULT_SHORTCUTS_CONFIG[id]) {
logger.debug(`Skipping shortcut '${id}' - not found in DEFAULT_SHORTCUTS_CONFIG`);
return;
+12 -12
View File
@@ -8,11 +8,11 @@ import { createLogger } from '@/utils/logger';
import type { App } from '../App';
import { Tray, TrayOptions } from './Tray';
// 创建日志记录器
// Create logger
const logger = createLogger('core:TrayManager');
/**
* 托盘标识符类型
* Tray identifier type
*/
export type TrayIdentifiers = 'main';
@@ -20,41 +20,41 @@ export class TrayManager {
app: App;
/**
* 存储所有托盘实例
* Store all tray instances
*/
trays: Map<TrayIdentifiers, Tray> = new Map();
/**
* 构造方法
* @param app 应用实例
* Constructor
* @param app Application instance
*/
constructor(app: App) {
logger.debug('初始化 TrayManager');
logger.debug('Initialize TrayManager');
this.app = app;
}
/**
* 初始化所有托盘
* Initialize all trays
*/
initializeTrays() {
logger.debug('初始化应用托盘');
logger.debug('Initialize application tray');
// 初始化主托盘
// Initialize main tray
this.initializeMainTray();
}
/**
* 获取主托盘
* Get main tray
*/
getMainTray() {
return this.retrieveByIdentifier('main');
}
/**
* 初始化主托盘
* Initialize main tray
*/
initializeMainTray() {
logger.debug('初始化主托盘');
logger.debug('Initialize main tray');
return this.retrieveOrInitialize({
iconPath: isMac
? nativeTheme.shouldUseDarkColorsForSystemIntegratedUI

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