diff --git a/src-tauri/src/commands/config.rs b/src-tauri/src/commands/config.rs index a9e8d69..33ea39c 100644 --- a/src-tauri/src/commands/config.rs +++ b/src-tauri/src/commands/config.rs @@ -6,6 +6,26 @@ use std::process::Command; use crate::models::types::VersionInfo; +/// 预设 npm 源列表 +const DEFAULT_REGISTRY: &str = "https://registry.npmmirror.com"; + +/// 读取用户配置的 npm registry,fallback 到淘宝镜像 +fn get_configured_registry() -> String { + let path = super::openclaw_dir().join("npm-registry.txt"); + fs::read_to_string(&path) + .ok() + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()) + .unwrap_or_else(|| DEFAULT_REGISTRY.to_string()) +} + +/// 创建使用配置源的 npm Command +fn npm_command() -> Command { + let mut cmd = Command::new("npm"); + cmd.args(["--registry", &get_configured_registry()]); + cmd +} + fn backups_dir() -> PathBuf { super::openclaw_dir().join("backups") } @@ -76,7 +96,8 @@ async fn get_latest_version_for(source: &str) -> Option { .build() .ok()?; let pkg = npm_package_name(source).replace('/', "%2F").replace('@', "%40"); - let url = format!("https://registry.npmjs.org/{pkg}/latest"); + let registry = get_configured_registry(); + let url = format!("{registry}/{pkg}/latest"); let resp = client.get(&url).send().await.ok()?; let json: Value = resp.json().await.ok()?; json.get("version") @@ -95,7 +116,7 @@ fn detect_installed_source() -> String { return "official".into(); } // 方法2:fallback 到 npm list - if let Ok(o) = Command::new("npm") + if let Ok(o) = npm_command() .args(["list", "-g", "@qingchencloud/openclaw-zh", "--depth=0"]) .output() { @@ -151,7 +172,7 @@ pub async fn upgrade_openclaw(app: tauri::AppHandle, source: String) -> Result Result Result { Ok("Gateway 服务已卸载".to_string()) } + +#[tauri::command] +pub fn get_npm_registry() -> Result { + Ok(get_configured_registry()) +} + +#[tauri::command] +pub fn set_npm_registry(registry: String) -> Result<(), String> { + let path = super::openclaw_dir().join("npm-registry.txt"); + fs::write(&path, registry.trim()) + .map_err(|e| format!("保存失败: {e}")) +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index c7716a0..3022685 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -25,6 +25,8 @@ pub fn run() { config::upgrade_openclaw, config::install_gateway, config::uninstall_gateway, + config::get_npm_registry, + config::set_npm_registry, // 服务 service::get_services_status, service::start_service, diff --git a/src/lib/tauri-api.js b/src/lib/tauri-api.js index ada0765..1f12670 100644 --- a/src/lib/tauri-api.js +++ b/src/lib/tauri-api.js @@ -99,6 +99,8 @@ function mockInvoke(cmd, args) { upgrade_openclaw: () => '升级成功,当前版本: 2026.2.26-zh.3 (mock)', install_gateway: () => 'Gateway 服务已安装 (mock)', uninstall_gateway: () => 'Gateway 服务已卸载 (mock)', + get_npm_registry: () => 'https://registry.npmmirror.com', + set_npm_registry: () => true, test_model: ({ modelId }) => `模型 ${modelId} 连通正常 (mock)`, list_remote_models: () => ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'gpt-3.5-turbo', 'o3-mini', 'dall-e-3', 'text-embedding-3-small'], write_env_file: () => true, @@ -144,6 +146,8 @@ export const api = { upgradeOpenclaw: (source = 'chinese') => invoke('upgrade_openclaw', { source }), installGateway: () => invoke('install_gateway'), uninstallGateway: () => invoke('uninstall_gateway'), + getNpmRegistry: () => invoke('get_npm_registry'), + setNpmRegistry: (registry) => invoke('set_npm_registry', { registry }), testModel: (baseUrl, apiKey, modelId) => invoke('test_model', { baseUrl, apiKey, modelId }), listRemoteModels: (baseUrl, apiKey) => invoke('list_remote_models', { baseUrl, apiKey }), diff --git a/src/pages/services.js b/src/pages/services.js index 44c4002..50a6d8b 100644 --- a/src/pages/services.js +++ b/src/pages/services.js @@ -27,6 +27,10 @@ export async function render() {
加载中...
+
+
npm 源设置
+
加载中...
+
配置备份
@@ -45,6 +49,7 @@ async function loadAll(page) { await Promise.all([ loadVersion(page), loadServices(page), + loadRegistry(page), loadBackups(page), ]) } @@ -85,6 +90,41 @@ async function loadVersion(page) { } } +// ===== npm 源设置 ===== + +const REGISTRIES = [ + { label: '淘宝镜像 (推荐)', value: 'https://registry.npmmirror.com' }, + { label: 'npm 官方源', value: 'https://registry.npmjs.org' }, + { label: '华为云镜像', value: 'https://repo.huaweicloud.com/repository/npm/' }, +] + +async function loadRegistry(page) { + const bar = page.querySelector('#registry-bar') + try { + const current = await api.getNpmRegistry() + const isPreset = REGISTRIES.some(r => r.value === current) + bar.innerHTML = ` +
+ + + +
+
升级和版本检测使用此源下载 npm 包,国内用户推荐淘宝镜像
+ ` + // 切换预设/自定义 + const select = bar.querySelector('[data-name="registry"]') + const customInput = bar.querySelector('[data-name="custom-registry"]') + select.onchange = () => { + customInput.style.display = select.value === 'custom' ? '' : 'none' + } + } catch (e) { + bar.innerHTML = `
加载失败: ${escapeHtml(String(e))}
` + } +} + // ===== 服务列表 ===== async function loadServices(page) { @@ -214,6 +254,9 @@ function bindEvents(page) { case 'uninstall-gateway': await handleUninstallGateway(btn, page) break + case 'save-registry': + await handleSaveRegistry(btn, page) + break } } catch (e) { toast(e.toString(), 'error') @@ -310,3 +353,13 @@ async function handleUninstallGateway(btn, page) { toast('Gateway 服务已卸载', 'success') await loadServices(page) } + +async function handleSaveRegistry(btn, page) { + const section = page.querySelector('#registry-section') + const select = section.querySelector('[data-name="registry"]') + const customInput = section.querySelector('[data-name="custom-registry"]') + const registry = select.value === 'custom' ? customInput.value.trim() : select.value + if (!registry) { toast('请输入源地址', 'error'); return } + await api.setNpmRegistry(registry) + toast('npm 源已保存', 'success') +}