@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.
- package/.conductor/setup.sh +107 -0
- package/.cursor/rules/linear.mdc +53 -0
- package/.github/actions/desktop-build-setup/action.yml +29 -0
- package/.github/actions/desktop-upload-artifacts/action.yml +46 -0
- package/.github/workflows/release-desktop-beta.yml +76 -115
- package/.github/workflows/release-desktop-stable.yml +472 -0
- package/CHANGELOG.md +58 -0
- package/CLAUDE.md +2 -48
- package/apps/desktop/dev-app-update.yml +10 -0
- package/apps/desktop/electron-builder.mjs +40 -10
- package/apps/desktop/electron.vite.config.ts +3 -2
- package/apps/desktop/package.json +2 -1
- package/apps/desktop/scripts/update-test/README.md +222 -0
- package/apps/desktop/scripts/update-test/dev-app-update.local.yml +18 -0
- package/apps/desktop/scripts/update-test/generate-manifest.sh +277 -0
- package/apps/desktop/scripts/update-test/run-test.sh +105 -0
- package/apps/desktop/scripts/update-test/setup.sh +111 -0
- package/apps/desktop/scripts/update-test/start-server.sh +70 -0
- package/apps/desktop/scripts/update-test/stop-server.sh +33 -0
- package/apps/desktop/src/main/core/infrastructure/UpdaterManager.ts +120 -9
- package/apps/desktop/src/main/core/infrastructure/__tests__/UpdaterManager.test.ts +17 -1
- package/apps/desktop/src/main/env.ts +19 -11
- package/apps/desktop/src/main/modules/updater/configs.ts +14 -1
- package/changelog/v1.json +14 -0
- package/conductor.json +5 -0
- package/locales/en-US/subscription.json +2 -2
- package/locales/zh-CN/subscription.json +2 -2
- package/package.json +1 -1
- package/packages/builtin-tool-notebook/src/client/Render/CreateDocument/DocumentCard.tsx +16 -14
- package/packages/const/src/cacheControl.ts +1 -0
- package/packages/electron-client-ipc/src/useWatchBroadcast.ts +10 -4
- package/packages/model-bank/src/aiModels/qiniu.ts +6 -6
- package/packages/observability-otel/src/node.ts +39 -37
- package/scripts/electronWorkflow/mergeMacReleaseFiles.js +22 -8
- package/src/app/(backend)/api/desktop/latest/route.ts +115 -0
- package/src/app/(backend)/api/version/route.ts +13 -0
- package/src/app/(backend)/middleware/validate/createValidator.test.ts +61 -0
- package/src/app/(backend)/middleware/validate/createValidator.ts +79 -0
- package/src/app/(backend)/middleware/validate/index.ts +3 -0
- package/src/app/[variants]/(desktop)/desktop-onboarding/_layout/index.tsx +2 -1
- package/src/app/[variants]/(main)/_layout/index.tsx +2 -1
- package/src/app/[variants]/(main)/agent/cron/[cronId]/features/CronJobScheduleConfig.tsx +0 -1
- package/src/app/[variants]/(main)/agent/cron/[cronId]/index.tsx +5 -5
- package/src/app/[variants]/(main)/agent/features/Conversation/ThreadHydration.tsx +3 -1
- package/src/app/[variants]/(main)/group/features/Conversation/ThreadHydration.tsx +3 -1
- package/src/app/[variants]/(main)/settings/provider/features/ProviderConfig/Checker.tsx +3 -3
- package/src/app/[variants]/(mobile)/router/mobileRouter.config.tsx +1 -4
- package/src/app/[variants]/router/desktopRouter.config.tsx +1 -4
- package/src/components/HtmlPreview/PreviewDrawer.tsx +1 -1
- package/src/features/Conversation/Messages/AssistantGroup/components/GroupItem.tsx +12 -2
- package/src/features/Conversation/Messages/Tool/Tool/index.tsx +10 -1
- package/src/features/{ElectronTitlebar/hooks → Electron/navigation}/useNavigationHistory.ts +1 -1
- package/src/features/{ElectronTitlebar/NavigationBar/index.tsx → Electron/titlebar/NavigationBar.tsx} +1 -1
- package/src/features/{ElectronTitlebar/NavigationBar → Electron/titlebar}/RecentlyViewed.tsx +1 -1
- package/src/features/{ElectronTitlebar/index.tsx → Electron/titlebar/TitleBar.tsx} +19 -9
- package/src/features/Electron/titlebar/WinControl.tsx +5 -0
- package/src/features/Electron/updater/UpdateModal.tsx +299 -0
- package/src/features/LibraryModal/AddFilesToKnowledgeBase/index.test.tsx +24 -0
- package/src/features/LibraryModal/AddFilesToKnowledgeBase/index.tsx +21 -24
- package/src/features/LibraryModal/CreateNew/index.tsx +18 -22
- package/src/features/PluginDevModal/index.tsx +1 -1
- package/src/layout/GlobalProvider/AppTheme.tsx +1 -1
- package/src/libs/swr/index.ts +26 -30
- package/src/server/services/desktopRelease/index.test.ts +65 -0
- package/src/server/services/desktopRelease/index.ts +208 -0
- package/src/store/aiInfra/slices/aiProvider/action.ts +16 -17
- package/src/store/chat/slices/portal/action.test.ts +0 -2
- package/src/store/chat/slices/portal/action.ts +17 -44
- package/src/store/chat/slices/thread/action.test.ts +4 -1
- package/src/store/chat/slices/thread/action.ts +6 -1
- package/src/components/FunctionModal/createModalHooks.ts +0 -48
- package/src/components/FunctionModal/index.ts +0 -1
- package/src/components/FunctionModal/style.tsx +0 -44
- package/src/features/ElectronTitlebar/UpdateModal.tsx +0 -274
- package/src/features/ElectronTitlebar/WinControl/index.tsx +0 -90
- /package/src/features/{ElectronTitlebar/Connection/index.tsx → Electron/connection/Connection.tsx} +0 -0
- /package/src/features/{ElectronTitlebar/Connection → Electron/connection}/ConnectionMode.tsx +0 -0
- /package/src/features/{ElectronTitlebar/Connection → Electron/connection}/Option.tsx +0 -0
- /package/src/features/{ElectronTitlebar/Connection → Electron/connection}/RemoteStatus.tsx +0 -0
- /package/src/features/{ElectronTitlebar/Connection → Electron/connection}/Waiting.tsx +0 -0
- /package/src/features/{ElectronTitlebar/Connection → Electron/connection}/WaitingAnim.tsx +0 -0
- /package/src/features/{ElectronTitlebar/helpers → Electron/navigation}/routeMetadata.ts +0 -0
- /package/src/features/{ElectronTitlebar/hooks → Electron/system}/useWatchThemeUpdate.ts +0 -0
- /package/src/features/{ElectronTitlebar → Electron/titlebar}/SimpleTitleBar.tsx +0 -0
- /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 {
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
//
|
|
73
|
-
|
|
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
|
@@ -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.
|
|
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
|
-
<
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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,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 = (
|
|
28
|
-
|
|
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
|
};
|