@lobehub/lobehub 2.0.0-next.291 → 2.0.0-next.293

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/.conductor/setup.sh +107 -0
  2. package/.cursor/rules/linear.mdc +53 -0
  3. package/.github/actions/desktop-build-setup/action.yml +29 -0
  4. package/.github/actions/desktop-upload-artifacts/action.yml +46 -0
  5. package/.github/workflows/release-desktop-beta.yml +76 -115
  6. package/.github/workflows/release-desktop-stable.yml +472 -0
  7. package/CHANGELOG.md +58 -0
  8. package/CLAUDE.md +2 -48
  9. package/apps/desktop/dev-app-update.yml +10 -0
  10. package/apps/desktop/electron-builder.mjs +40 -10
  11. package/apps/desktop/electron.vite.config.ts +3 -2
  12. package/apps/desktop/package.json +2 -1
  13. package/apps/desktop/scripts/update-test/README.md +222 -0
  14. package/apps/desktop/scripts/update-test/dev-app-update.local.yml +18 -0
  15. package/apps/desktop/scripts/update-test/generate-manifest.sh +277 -0
  16. package/apps/desktop/scripts/update-test/run-test.sh +105 -0
  17. package/apps/desktop/scripts/update-test/setup.sh +111 -0
  18. package/apps/desktop/scripts/update-test/start-server.sh +70 -0
  19. package/apps/desktop/scripts/update-test/stop-server.sh +33 -0
  20. package/apps/desktop/src/main/core/infrastructure/UpdaterManager.ts +120 -9
  21. package/apps/desktop/src/main/core/infrastructure/__tests__/UpdaterManager.test.ts +17 -1
  22. package/apps/desktop/src/main/env.ts +19 -11
  23. package/apps/desktop/src/main/modules/updater/configs.ts +14 -1
  24. package/changelog/v1.json +14 -0
  25. package/conductor.json +5 -0
  26. package/locales/en-US/subscription.json +2 -2
  27. package/locales/zh-CN/subscription.json +2 -2
  28. package/package.json +1 -1
  29. package/packages/builtin-tool-notebook/src/client/Render/CreateDocument/DocumentCard.tsx +16 -14
  30. package/packages/const/src/cacheControl.ts +1 -0
  31. package/packages/electron-client-ipc/src/useWatchBroadcast.ts +10 -4
  32. package/packages/model-bank/src/aiModels/qiniu.ts +6 -6
  33. package/packages/observability-otel/src/node.ts +39 -37
  34. package/scripts/electronWorkflow/mergeMacReleaseFiles.js +22 -8
  35. package/src/app/(backend)/api/desktop/latest/route.ts +115 -0
  36. package/src/app/(backend)/api/version/route.ts +13 -0
  37. package/src/app/(backend)/middleware/validate/createValidator.test.ts +61 -0
  38. package/src/app/(backend)/middleware/validate/createValidator.ts +79 -0
  39. package/src/app/(backend)/middleware/validate/index.ts +3 -0
  40. package/src/app/[variants]/(desktop)/desktop-onboarding/_layout/index.tsx +2 -1
  41. package/src/app/[variants]/(main)/_layout/index.tsx +2 -1
  42. package/src/app/[variants]/(main)/agent/cron/[cronId]/features/CronJobScheduleConfig.tsx +0 -1
  43. package/src/app/[variants]/(main)/agent/cron/[cronId]/index.tsx +5 -5
  44. package/src/app/[variants]/(main)/agent/features/Conversation/ThreadHydration.tsx +3 -1
  45. package/src/app/[variants]/(main)/group/features/Conversation/ThreadHydration.tsx +3 -1
  46. package/src/app/[variants]/(main)/settings/provider/features/ProviderConfig/Checker.tsx +3 -3
  47. package/src/app/[variants]/(mobile)/router/mobileRouter.config.tsx +1 -4
  48. package/src/app/[variants]/router/desktopRouter.config.tsx +1 -4
  49. package/src/components/HtmlPreview/PreviewDrawer.tsx +1 -1
  50. package/src/features/Conversation/Messages/AssistantGroup/components/GroupItem.tsx +12 -2
  51. package/src/features/Conversation/Messages/Tool/Tool/index.tsx +10 -1
  52. package/src/features/{ElectronTitlebar/hooks → Electron/navigation}/useNavigationHistory.ts +1 -1
  53. package/src/features/{ElectronTitlebar/NavigationBar/index.tsx → Electron/titlebar/NavigationBar.tsx} +1 -1
  54. package/src/features/{ElectronTitlebar/NavigationBar → Electron/titlebar}/RecentlyViewed.tsx +1 -1
  55. package/src/features/{ElectronTitlebar/index.tsx → Electron/titlebar/TitleBar.tsx} +19 -9
  56. package/src/features/Electron/titlebar/WinControl.tsx +5 -0
  57. package/src/features/Electron/updater/UpdateModal.tsx +299 -0
  58. package/src/features/LibraryModal/AddFilesToKnowledgeBase/index.test.tsx +24 -0
  59. package/src/features/LibraryModal/AddFilesToKnowledgeBase/index.tsx +21 -24
  60. package/src/features/LibraryModal/CreateNew/index.tsx +18 -22
  61. package/src/features/PluginDevModal/index.tsx +1 -1
  62. package/src/layout/GlobalProvider/AppTheme.tsx +1 -1
  63. package/src/libs/swr/index.ts +26 -30
  64. package/src/server/services/desktopRelease/index.test.ts +65 -0
  65. package/src/server/services/desktopRelease/index.ts +208 -0
  66. package/src/store/aiInfra/slices/aiProvider/action.ts +16 -17
  67. package/src/store/chat/slices/portal/action.test.ts +0 -2
  68. package/src/store/chat/slices/portal/action.ts +17 -44
  69. package/src/store/chat/slices/thread/action.test.ts +4 -1
  70. package/src/store/chat/slices/thread/action.ts +6 -1
  71. package/src/components/FunctionModal/createModalHooks.ts +0 -48
  72. package/src/components/FunctionModal/index.ts +0 -1
  73. package/src/components/FunctionModal/style.tsx +0 -44
  74. package/src/features/ElectronTitlebar/UpdateModal.tsx +0 -274
  75. package/src/features/ElectronTitlebar/WinControl/index.tsx +0 -90
  76. /package/src/features/{ElectronTitlebar/Connection/index.tsx → Electron/connection/Connection.tsx} +0 -0
  77. /package/src/features/{ElectronTitlebar/Connection → Electron/connection}/ConnectionMode.tsx +0 -0
  78. /package/src/features/{ElectronTitlebar/Connection → Electron/connection}/Option.tsx +0 -0
  79. /package/src/features/{ElectronTitlebar/Connection → Electron/connection}/RemoteStatus.tsx +0 -0
  80. /package/src/features/{ElectronTitlebar/Connection → Electron/connection}/Waiting.tsx +0 -0
  81. /package/src/features/{ElectronTitlebar/Connection → Electron/connection}/WaitingAnim.tsx +0 -0
  82. /package/src/features/{ElectronTitlebar/helpers → Electron/navigation}/routeMetadata.ts +0 -0
  83. /package/src/features/{ElectronTitlebar/hooks → Electron/system}/useWatchThemeUpdate.ts +0 -0
  84. /package/src/features/{ElectronTitlebar → Electron/titlebar}/SimpleTitleBar.tsx +0 -0
  85. /package/src/features/{ElectronTitlebar → Electron/updater}/UpdateNotification.tsx +0 -0
@@ -0,0 +1,111 @@
1
+ #!/bin/bash
2
+
3
+ # ============================================
4
+ # 本地更新测试 - 一键设置脚本
5
+ # ============================================
6
+
7
+ set -e
8
+
9
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
+ SERVER_DIR="$SCRIPT_DIR/server"
11
+
12
+ echo "🚀 设置本地更新测试环境..."
13
+
14
+ # 创建服务器目录
15
+ mkdir -p "$SERVER_DIR"
16
+ echo "✅ 创建服务器目录: $SERVER_DIR"
17
+
18
+ # 设置脚本执行权限
19
+ chmod +x "$SCRIPT_DIR"/*.sh
20
+ echo "✅ 设置脚本执行权限"
21
+
22
+ # 检查是否安装了 serve
23
+ if ! command -v npx &> /dev/null; then
24
+ echo "❌ 需要安装 Node.js 和 npm"
25
+ exit 1
26
+ fi
27
+
28
+ # 创建示例 latest-mac.yml
29
+ cat > "$SERVER_DIR/latest-mac.yml" << 'EOF'
30
+ version: 99.0.0
31
+ files:
32
+ - url: LobeHub-99.0.0-arm64.dmg
33
+ sha512: placeholder-sha512-will-be-replaced
34
+ size: 100000000
35
+ - url: LobeHub-99.0.0-arm64-mac.zip
36
+ sha512: placeholder-sha512-will-be-replaced
37
+ size: 100000000
38
+ path: LobeHub-99.0.0-arm64.dmg
39
+ sha512: placeholder-sha512-will-be-replaced
40
+ releaseDate: '2026-01-15T10:00:00.000Z'
41
+ releaseNotes: |
42
+ ## 🎉 v99.0.0 本地测试版本
43
+
44
+ 这是一个用于本地测试更新功能的模拟版本。
45
+
46
+ ### ✨ 新功能
47
+ - 测试功能 A
48
+ - 测试功能 B
49
+
50
+ ### 🐛 修复
51
+ - 修复测试问题 X
52
+ EOF
53
+ echo "✅ 创建示例 latest-mac.yml"
54
+
55
+ # 创建 Windows 版本的 manifest (可选)
56
+ cat > "$SERVER_DIR/latest.yml" << 'EOF'
57
+ version: 99.0.0
58
+ files:
59
+ - url: LobeHub-99.0.0-setup.exe
60
+ sha512: placeholder-sha512-will-be-replaced
61
+ size: 100000000
62
+ path: LobeHub-99.0.0-setup.exe
63
+ sha512: placeholder-sha512-will-be-replaced
64
+ releaseDate: '2026-01-15T10:00:00.000Z'
65
+ releaseNotes: |
66
+ ## 🎉 v99.0.0 本地测试版本
67
+
68
+ 这是一个用于本地测试更新功能的模拟版本。
69
+ EOF
70
+ echo "✅ 创建示例 latest.yml (Windows)"
71
+
72
+ # 创建本地测试用的 dev-app-update.yml
73
+ cat > "$SCRIPT_DIR/dev-app-update.local.yml" << 'EOF'
74
+ # 本地更新测试配置
75
+ # 将此文件复制到 apps/desktop/dev-app-update.yml 以使用本地服务器测试
76
+
77
+ provider: generic
78
+ url: http://localhost:8787
79
+ updaterCacheDirName: lobehub-desktop-local-test
80
+ EOF
81
+ echo "✅ 创建本地测试配置文件"
82
+
83
+ echo ""
84
+ echo "============================================"
85
+ echo "✅ 设置完成!"
86
+ echo "============================================"
87
+ echo ""
88
+ echo "下一步操作:"
89
+ echo ""
90
+ echo "1. 构建测试包:"
91
+ echo " cd $(dirname "$SCRIPT_DIR")"
92
+ echo " npm run build-local"
93
+ echo ""
94
+ echo "2. 复制构建产物到服务器目录:"
95
+ echo " cp release/*.dmg scripts/update-test/server/"
96
+ echo " cp release/*.zip scripts/update-test/server/"
97
+ echo ""
98
+ echo "3. 更新 manifest 文件(可选):"
99
+ echo " cd scripts/update-test"
100
+ echo " ./generate-manifest.sh"
101
+ echo ""
102
+ echo "4. 启动本地服务器:"
103
+ echo " ./start-server.sh"
104
+ echo ""
105
+ echo "5. 配置应用使用本地服务器:"
106
+ echo " cp dev-app-update.local.yml ../../dev-app-update.yml"
107
+ echo ""
108
+ echo "6. 运行应用:"
109
+ echo " cd ../.."
110
+ echo " npm run dev"
111
+ echo ""
@@ -0,0 +1,70 @@
1
+ #!/bin/bash
2
+
3
+ # ============================================
4
+ # 启动本地更新服务器
5
+ # ============================================
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ SERVER_DIR="$SCRIPT_DIR/server"
9
+ PID_FILE="$SCRIPT_DIR/.server.pid"
10
+ LOG_FILE="$SCRIPT_DIR/.server.log"
11
+ PORT="${PORT:-8787}"
12
+
13
+ # 检查服务器目录
14
+ if [ ! -d "$SERVER_DIR" ]; then
15
+ echo "❌ 服务器目录不存在,请先运行 ./setup.sh"
16
+ exit 1
17
+ fi
18
+
19
+ # 检查是否已经在运行
20
+ if [ -f "$PID_FILE" ]; then
21
+ OLD_PID=$(cat "$PID_FILE")
22
+ if ps -p "$OLD_PID" > /dev/null 2>&1; then
23
+ echo "⚠️ 服务器已经在运行 (PID: $OLD_PID)"
24
+ echo " 地址: http://localhost:$PORT"
25
+ echo ""
26
+ echo " 如需重启,请先运行 ./stop-server.sh"
27
+ exit 0
28
+ else
29
+ rm -f "$PID_FILE"
30
+ fi
31
+ fi
32
+
33
+ echo "🚀 启动本地更新服务器..."
34
+ echo " 目录: $SERVER_DIR"
35
+ echo " 端口: $PORT"
36
+ echo ""
37
+
38
+ # 列出服务器目录中的文件
39
+ echo "📦 可用文件:"
40
+ ls -la "$SERVER_DIR" | grep -v "^d" | grep -v "^total" | awk '{print " " $NF}'
41
+ echo ""
42
+
43
+ # 启动服务器 (后台运行)
44
+ cd "$SERVER_DIR"
45
+ nohup npx serve -p "$PORT" --cors -n > "$LOG_FILE" 2>&1 &
46
+ SERVER_PID=$!
47
+ echo "$SERVER_PID" > "$PID_FILE"
48
+
49
+ # 等待服务器启动
50
+ sleep 2
51
+
52
+ # 检查是否启动成功
53
+ if ps -p "$SERVER_PID" > /dev/null 2>&1; then
54
+ echo "✅ 服务器已启动!"
55
+ echo ""
56
+ echo " 地址: http://localhost:$PORT"
57
+ echo " PID: $SERVER_PID"
58
+ echo " 日志: $LOG_FILE"
59
+ echo ""
60
+ echo "📋 测试 URL:"
61
+ echo " latest-mac.yml: http://localhost:$PORT/latest-mac.yml"
62
+ echo " latest.yml: http://localhost:$PORT/latest.yml"
63
+ echo ""
64
+ echo "🛑 停止服务器: ./stop-server.sh"
65
+ else
66
+ echo "❌ 服务器启动失败"
67
+ echo " 查看日志: cat $LOG_FILE"
68
+ rm -f "$PID_FILE"
69
+ exit 1
70
+ fi
@@ -0,0 +1,33 @@
1
+ #!/bin/bash
2
+
3
+ # ============================================
4
+ # 停止本地更新服务器
5
+ # ============================================
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ PID_FILE="$SCRIPT_DIR/.server.pid"
9
+ LOG_FILE="$SCRIPT_DIR/.server.log"
10
+
11
+ if [ ! -f "$PID_FILE" ]; then
12
+ echo "ℹ️ 服务器未运行 (找不到 PID 文件)"
13
+ exit 0
14
+ fi
15
+
16
+ PID=$(cat "$PID_FILE")
17
+
18
+ if ps -p "$PID" > /dev/null 2>&1; then
19
+ echo "🛑 停止服务器 (PID: $PID)..."
20
+ kill "$PID"
21
+ sleep 1
22
+
23
+ # 强制终止(如果还在运行)
24
+ if ps -p "$PID" > /dev/null 2>&1; then
25
+ kill -9 "$PID" 2>/dev/null
26
+ fi
27
+
28
+ echo "✅ 服务器已停止"
29
+ else
30
+ echo "ℹ️ 服务器进程已不存在"
31
+ fi
32
+
33
+ rm -f "$PID_FILE"
@@ -2,11 +2,21 @@ import log from 'electron-log';
2
2
  import { autoUpdater } from 'electron-updater';
3
3
 
4
4
  import { isDev, isWindows } from '@/const/env';
5
- import { UPDATE_CHANNEL as channel, updaterConfig } from '@/modules/updater/configs';
5
+ import { getDesktopEnv } from '@/env';
6
+ import {
7
+ UPDATE_SERVER_URL,
8
+ UPDATE_CHANNEL as channel,
9
+ githubConfig,
10
+ isStableChannel,
11
+ updaterConfig,
12
+ } from '@/modules/updater/configs';
6
13
  import { createLogger } from '@/utils/logger';
7
14
 
8
15
  import type { App as AppCore } from '../App';
9
16
 
17
+ // Allow forcing dev update config via env (for testing updates in packaged app)
18
+ const FORCE_DEV_UPDATE_CONFIG = getDesktopEnv().FORCE_DEV_UPDATE_CONFIG;
19
+
10
20
  // Create logger
11
21
  const logger = createLogger('core:UpdaterManager');
12
22
 
@@ -16,6 +26,7 @@ export class UpdaterManager {
16
26
  private downloading: boolean = false;
17
27
  private updateAvailable: boolean = false;
18
28
  private isManualCheck: boolean = false;
29
+ private usingFallbackProvider: boolean = false;
19
30
 
20
31
  constructor(app: AppCore) {
21
32
  this.app = app;
@@ -42,16 +53,26 @@ export class UpdaterManager {
42
53
  // Configure autoUpdater
43
54
  autoUpdater.autoDownload = false; // Set to false, we'll control downloads manually
44
55
  autoUpdater.autoInstallOnAppQuit = false;
45
-
46
- autoUpdater.channel = channel;
47
- autoUpdater.allowPrerelease = channel !== 'stable';
48
56
  autoUpdater.allowDowngrade = false;
49
57
 
50
- // Enable test mode in development environment
51
- if (isDev) {
52
- logger.info(`Running in dev mode, forcing update check, channel: ${autoUpdater.channel}`);
53
- // Allow testing updates in development environment
58
+ // Enable test mode in development environment or when forced via env
59
+ // IMPORTANT: This must be set BEFORE channel configuration so that
60
+ // dev-app-update.yml takes precedence over programmatic configuration
61
+ const useDevConfig = isDev || FORCE_DEV_UPDATE_CONFIG;
62
+ if (useDevConfig) {
63
+ // In dev mode, use dev-app-update.yml for all configuration including channel
64
+ // Don't set channel here - let dev-app-update.yml control it (defaults to "latest")
54
65
  autoUpdater.forceDevUpdateConfig = true;
66
+ logger.info(
67
+ `Using dev update config (isDev=${isDev}, FORCE_DEV_UPDATE_CONFIG=${FORCE_DEV_UPDATE_CONFIG})`,
68
+ );
69
+ logger.info('Dev mode: Using dev-app-update.yml for update configuration');
70
+ } else {
71
+ // Only configure channel and update provider programmatically in production
72
+ // Note: channel is configured in configureUpdateProvider based on provider type
73
+ autoUpdater.allowPrerelease = channel !== 'stable';
74
+ logger.info(`Production mode: channel=${channel}, allowPrerelease=${channel !== 'stable'}`);
75
+ this.configureUpdateProvider();
55
76
  }
56
77
 
57
78
  // Register events
@@ -291,6 +312,79 @@ export class UpdaterManager {
291
312
  }, 300);
292
313
  };
293
314
 
315
+ /**
316
+ * Configure update provider based on channel
317
+ * - Stable channel + UPDATE_SERVER_URL: Use generic HTTP provider (S3) as primary, channel=stable
318
+ * - Other channels (beta/nightly) or no S3: Use GitHub provider, channel=latest
319
+ *
320
+ * Important: S3 has stable-mac.yml, GitHub has latest-mac.yml
321
+ */
322
+ private configureUpdateProvider() {
323
+ if (isStableChannel && UPDATE_SERVER_URL && !this.usingFallbackProvider) {
324
+ // Stable channel uses custom update server (generic HTTP) as primary
325
+ // S3 has stable-mac.yml, so we set channel to 'stable'
326
+ autoUpdater.channel = 'stable';
327
+ logger.info(`Configuring generic provider for stable channel (primary)`);
328
+ logger.info(`Update server URL: ${UPDATE_SERVER_URL}`);
329
+ logger.info(`Channel set to: stable (will look for stable-mac.yml)`);
330
+
331
+ autoUpdater.setFeedURL({
332
+ provider: 'generic',
333
+ url: UPDATE_SERVER_URL,
334
+ });
335
+ } else {
336
+ // Beta/nightly channels use GitHub, or fallback to GitHub if UPDATE_SERVER_URL not configured
337
+ // GitHub releases have latest-mac.yml, so we use default channel (latest)
338
+ autoUpdater.channel = 'latest';
339
+ const reason = this.usingFallbackProvider ? '(fallback from S3)' : '';
340
+ logger.info(`Configuring GitHub provider for ${channel} channel ${reason}`);
341
+ logger.info(`Channel set to: latest (will look for latest-mac.yml)`);
342
+
343
+ autoUpdater.setFeedURL({
344
+ owner: githubConfig.owner,
345
+ provider: 'github',
346
+ repo: githubConfig.repo,
347
+ });
348
+
349
+ logger.info(`GitHub update URL configured: ${githubConfig.owner}/${githubConfig.repo}`);
350
+ }
351
+ }
352
+
353
+ /**
354
+ * Switch to fallback provider (GitHub) and retry update check
355
+ * Called when primary provider (S3) fails
356
+ */
357
+ private switchToFallbackAndRetry = async () => {
358
+ // Only fallback if we're on stable channel with S3 configured and haven't already fallen back
359
+ if (!isStableChannel || !UPDATE_SERVER_URL || this.usingFallbackProvider) {
360
+ return false;
361
+ }
362
+
363
+ logger.info('Primary update server (S3) failed, switching to GitHub fallback...');
364
+ this.usingFallbackProvider = true;
365
+ this.configureUpdateProvider();
366
+
367
+ // Retry update check with fallback provider
368
+ try {
369
+ await autoUpdater.checkForUpdates();
370
+ return true;
371
+ } catch (error) {
372
+ logger.error('Fallback provider (GitHub) also failed:', error);
373
+ return false;
374
+ }
375
+ };
376
+
377
+ /**
378
+ * Reset to primary provider for next update check
379
+ */
380
+ private resetToPrimaryProvider = () => {
381
+ if (this.usingFallbackProvider) {
382
+ logger.info('Resetting to primary update provider (S3)');
383
+ this.usingFallbackProvider = false;
384
+ this.configureUpdateProvider();
385
+ }
386
+ };
387
+
294
388
  private registerEvents() {
295
389
  logger.debug('Registering updater events');
296
390
 
@@ -302,6 +396,9 @@ export class UpdaterManager {
302
396
  logger.info(`Update available: ${info.version}`);
303
397
  this.updateAvailable = true;
304
398
 
399
+ // Reset to primary provider for next check cycle
400
+ this.resetToPrimaryProvider();
401
+
305
402
  if (this.isManualCheck) {
306
403
  this.mainWindow.broadcast('manualUpdateAvailable', info);
307
404
  } else {
@@ -313,13 +410,27 @@ export class UpdaterManager {
313
410
 
314
411
  autoUpdater.on('update-not-available', (info) => {
315
412
  logger.info(`Update not available. Current: ${info.version}`);
413
+
414
+ // Reset to primary provider for next check cycle
415
+ this.resetToPrimaryProvider();
416
+
316
417
  if (this.isManualCheck) {
317
418
  this.mainWindow.broadcast('manualUpdateNotAvailable', info);
318
419
  }
319
420
  });
320
421
 
321
- autoUpdater.on('error', (err) => {
422
+ autoUpdater.on('error', async (err) => {
322
423
  logger.error('Error in auto-updater:', err);
424
+
425
+ // Try fallback to GitHub if S3 failed
426
+ if (!this.usingFallbackProvider && isStableChannel && UPDATE_SERVER_URL) {
427
+ logger.info('Attempting fallback to GitHub provider...');
428
+ const fallbackSucceeded = await this.switchToFallbackAndRetry();
429
+ if (fallbackSucceeded) {
430
+ return; // Fallback initiated, don't report error yet
431
+ }
432
+ }
433
+
323
434
  if (this.isManualCheck) {
324
435
  this.mainWindow.broadcast('updateError', err.message);
325
436
  }
@@ -36,6 +36,7 @@ vi.mock('electron-updater', () => ({
36
36
  logger: null as any,
37
37
  on: vi.fn(),
38
38
  quitAndInstall: vi.fn(),
39
+ setFeedURL: vi.fn(),
39
40
  },
40
41
  }));
41
42
 
@@ -62,6 +63,12 @@ vi.mock('@/utils/logger', () => ({
62
63
  // Mock updater configs
63
64
  vi.mock('@/modules/updater/configs', () => ({
64
65
  UPDATE_CHANNEL: 'stable',
66
+ UPDATE_SERVER_URL: 'https://mock.update.server',
67
+ githubConfig: {
68
+ owner: 'lobehub',
69
+ repo: 'lobe-chat',
70
+ },
71
+ isStableChannel: true,
65
72
  updaterConfig: {
66
73
  app: {
67
74
  autoCheckUpdate: false,
@@ -73,6 +80,13 @@ vi.mock('@/modules/updater/configs', () => ({
73
80
  },
74
81
  }));
75
82
 
83
+ // Mock env
84
+ vi.mock('@/env', () => ({
85
+ getDesktopEnv: () => ({
86
+ FORCE_DEV_UPDATE_CONFIG: false,
87
+ }),
88
+ }));
89
+
76
90
  // Mock isDev
77
91
  vi.mock('@/const/env', () => ({
78
92
  isDev: false,
@@ -454,9 +468,11 @@ describe('UpdaterManager', () => {
454
468
  vi.mocked(autoUpdater.checkForUpdates).mockResolvedValue({} as any);
455
469
  await updaterManager.checkForUpdates({ manual: true });
456
470
 
471
+ vi.mocked(autoUpdater.checkForUpdates).mockRejectedValueOnce(new Error('Fallback failed'));
472
+
457
473
  const error = new Error('Update error');
458
474
  const handler = registeredEvents.get('error');
459
- handler?.(error);
475
+ await handler?.(error);
460
476
 
461
477
  expect(mockBroadcast).toHaveBeenCalledWith('updateError', 'Update error');
462
478
  });
@@ -59,29 +59,37 @@ const envNumber = (defaultValue: number) =>
59
59
  */
60
60
  export const getDesktopEnv = memoize(() =>
61
61
  createEnv({
62
+ client: {},
63
+ clientPrefix: 'PUBLIC_',
64
+ emptyStringAsUndefined: true,
65
+ isServer: true,
66
+ runtimeEnv: process.env,
62
67
  server: {
63
68
  DEBUG_VERBOSE: envBoolean(false),
64
69
 
65
- // keep optional to preserve existing behavior:
66
- // - unset NODE_ENV should behave like "not production" in logger runtime paths
67
- NODE_ENV: z.enum(['development', 'production', 'test']).optional(),
68
-
69
70
  // escape hatch: allow testing static renderer in dev via env
70
71
  DESKTOP_RENDERER_STATIC: envBoolean(false),
71
72
 
72
- // updater
73
- UPDATE_CHANNEL: z.string().optional(),
73
+ // Force use dev-app-update.yml even in packaged app (for testing updates)
74
+ FORCE_DEV_UPDATE_CONFIG: envBoolean(false),
74
75
 
75
76
  // mcp client
76
77
  MCP_TOOL_TIMEOUT: envNumber(60_000),
77
78
 
79
+ // keep optional to preserve existing behavior:
80
+ // - unset NODE_ENV should behave like "not production" in logger runtime paths
81
+ NODE_ENV: z.enum(['development', 'production', 'test']).optional(),
82
+
78
83
  // cloud server url (can be overridden for selfhost/dev)
79
84
  OFFICIAL_CLOUD_SERVER: z.string().optional().default('https://app.lobehub.com'),
85
+
86
+ // updater
87
+ // process.env.xxx will replace in build stage
88
+ UPDATE_CHANNEL: z.string().optional().default(process.env.UPDATE_CHANNEL),
89
+
90
+ // Custom update server URL (for stable channel)
91
+ // e.g., https://releases.lobehub.com/stable or https://your-bucket.s3.amazonaws.com/releases
92
+ UPDATE_SERVER_URL: z.string().optional().default(process.env.UPDATE_SERVER_URL),
80
93
  },
81
- clientPrefix: 'PUBLIC_',
82
- client: {},
83
- runtimeEnv: process.env,
84
- emptyStringAsUndefined: true,
85
- isServer: true,
86
94
  }),
87
95
  );
@@ -2,7 +2,20 @@ import { isDev } from '@/const/env';
2
2
  import { getDesktopEnv } from '@/env';
3
3
 
4
4
  // 更新频道(stable, beta, alpha 等)
5
- export const UPDATE_CHANNEL = getDesktopEnv().UPDATE_CHANNEL;
5
+ export const UPDATE_CHANNEL = getDesktopEnv().UPDATE_CHANNEL || 'stable';
6
+
7
+ // 判断是否为 stable 频道
8
+ export const isStableChannel = UPDATE_CHANNEL === 'stable' || !UPDATE_CHANNEL;
9
+
10
+ // 自定义更新服务器 URL (用于 stable 频道)
11
+ // e.g., https://releases.lobehub.com/stable
12
+ export const UPDATE_SERVER_URL = getDesktopEnv().UPDATE_SERVER_URL;
13
+
14
+ // GitHub 配置 (用于 beta/nightly 频道,或作为 fallback)
15
+ export const githubConfig = {
16
+ owner: 'lobehub',
17
+ repo: 'lobe-chat',
18
+ };
6
19
 
7
20
  export const updaterConfig = {
8
21
  // 应用更新配置
package/changelog/v1.json CHANGED
@@ -1,4 +1,18 @@
1
1
  [
2
+ {
3
+ "children": {},
4
+ "date": "2026-01-15",
5
+ "version": "2.0.0-next.293"
6
+ },
7
+ {
8
+ "children": {
9
+ "improvements": [
10
+ "Use fallbackData to prevent useActionSWR auto-fetch."
11
+ ]
12
+ },
13
+ "date": "2026-01-15",
14
+ "version": "2.0.0-next.292"
15
+ },
2
16
  {
3
17
  "children": {
4
18
  "fixes": [
package/conductor.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "scripts": {
3
+ "setup": "bash \"$CONDUCTOR_ROOT_PATH/.conductor/setup.sh\""
4
+ }
5
+ }
@@ -54,11 +54,11 @@
54
54
  "funds.packages.expiresIn": "Expires in {{days}} days",
55
55
  "funds.packages.expiresToday": "Expires today",
56
56
  "funds.packages.expiringSoon": "Expiring soon",
57
+ "funds.packages.gift": "Gift",
58
+ "funds.packages.giftedOn": "Gifted on {{date}}",
57
59
  "funds.packages.noPackages": "No credit packages",
58
60
  "funds.packages.purchaseFirst": "Purchase your first credit package",
59
61
  "funds.packages.purchasedOn": "Purchased on {{date}}",
60
- "funds.packages.gift": "Gift",
61
- "funds.packages.giftedOn": "Gifted on {{date}}",
62
62
  "funds.packages.sort.amountAsc": "Amount: Low to High",
63
63
  "funds.packages.sort.amountDesc": "Amount: High to Low",
64
64
  "funds.packages.sort.balanceAsc": "Balance: Low to High",
@@ -54,11 +54,11 @@
54
54
  "funds.packages.expiresIn": "{{days}} 天后过期",
55
55
  "funds.packages.expiresToday": "今日过期",
56
56
  "funds.packages.expiringSoon": "即将过期",
57
+ "funds.packages.gift": "赠送",
58
+ "funds.packages.giftedOn": "赠送于 {{date}}",
57
59
  "funds.packages.noPackages": "暂无积分包",
58
60
  "funds.packages.purchaseFirst": "购买您的第一个积分包",
59
61
  "funds.packages.purchasedOn": "购买于 {{date}}",
60
- "funds.packages.gift": "赠送",
61
- "funds.packages.giftedOn": "赠送于 {{date}}",
62
62
  "funds.packages.sort.amountAsc": "金额:从低到高",
63
63
  "funds.packages.sort.amountDesc": "金额:从高到低",
64
64
  "funds.packages.sort.balanceAsc": "余额:从低到高",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.291",
3
+ "version": "2.0.0-next.293",
4
4
  "description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { ActionIcon, CopyButton, Flexbox, Markdown, ScrollShadow } from '@lobehub/ui';
3
+ import { ActionIcon, CopyButton, Flexbox, Markdown, ScrollShadow, TooltipGroup } from '@lobehub/ui';
4
4
  import { Button } from 'antd';
5
5
  import { createStaticStyles } from 'antd-style';
6
6
  import { Maximize2, Minimize2, NotebookText, PencilLine } from 'lucide-react';
@@ -85,19 +85,21 @@ const DocumentCard = memo<DocumentCardProps>(({ document }) => {
85
85
  <Flexbox flex={1}>
86
86
  <div className={styles.title}>{document.title}</div>
87
87
  </Flexbox>
88
- <Flexbox gap={4} horizontal>
89
- <CopyButton
90
- content={document.content}
91
- size={'small'}
92
- title={t('builtins.lobe-notebook.actions.copy')}
93
- />
94
- <ActionIcon
95
- icon={PencilLine}
96
- onClick={handleToggle}
97
- size={'small'}
98
- title={t('builtins.lobe-notebook.actions.edit')}
99
- />
100
- </Flexbox>
88
+ <TooltipGroup>
89
+ <Flexbox gap={4} horizontal>
90
+ <CopyButton
91
+ content={document.content}
92
+ size={'small'}
93
+ title={t('builtins.lobe-notebook.actions.copy')}
94
+ />
95
+ <ActionIcon
96
+ icon={PencilLine}
97
+ onClick={handleToggle}
98
+ size={'small'}
99
+ title={t('builtins.lobe-notebook.actions.edit')}
100
+ />
101
+ </Flexbox>
102
+ </TooltipGroup>
101
103
  </Flexbox>
102
104
  {/* Content */}
103
105
  <ScrollShadow className={styles.content} offset={12} size={12} style={{ maxHeight: 400 }}>
@@ -1,3 +1,4 @@
1
1
  export enum FetchCacheTag {
2
2
  Changelog = 'changelog',
3
+ DesktopRelease = 'desktop-release',
3
4
  }
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { useEffect } from 'react';
3
+ import { useEffect, useLayoutEffect, useRef } from 'react';
4
4
 
5
5
  import { MainBroadcastEventKey, MainBroadcastParams } from './events';
6
6
 
@@ -21,11 +21,17 @@ export const useWatchBroadcast = <T extends MainBroadcastEventKey>(
21
21
  event: T,
22
22
  handler: (data: MainBroadcastParams<T>) => void,
23
23
  ) => {
24
+ const handlerRef = useRef<typeof handler>(handler);
25
+
26
+ useLayoutEffect(() => {
27
+ handlerRef.current = handler;
28
+ }, [handler]);
29
+
24
30
  useEffect(() => {
25
31
  if (!window.electron) return;
26
32
 
27
- const listener = (e: any, data: MainBroadcastParams<T>) => {
28
- handler(data);
33
+ const listener = (_e: any, data: MainBroadcastParams<T>) => {
34
+ handlerRef.current(data);
29
35
  };
30
36
 
31
37
  window.electron.ipcRenderer.on(event, listener);
@@ -33,5 +39,5 @@ export const useWatchBroadcast = <T extends MainBroadcastEventKey>(
33
39
  return () => {
34
40
  window.electron.ipcRenderer.removeListener(event, listener);
35
41
  };
36
- }, []);
42
+ }, [event]);
37
43
  };