mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-09-24 23:10:56 +08:00
fix: resolve Windows compilation errors and warnings
- Add Manager trait import for windows_startup_cmd.rs - Add explicit type annotations for PathBuf in windows_startup_cmd.rs - Fix unused import warning in screenshot_capture_service.rs - Prefix unused variables with underscore in runner.rs and aster_agent_cmd.rs - Update package.json with Node.js engine requirement
This commit is contained in:
@@ -97,10 +97,14 @@ pub fn run() {
|
||||
let logs_clone = logs.clone();
|
||||
let db_clone = db.clone();
|
||||
let pool_service_clone = provider_pool_service_state.0.clone();
|
||||
let api_key_provider_service_clone = api_key_provider_service_state.0.clone();
|
||||
let connect_state_clone = connect_state.0.clone();
|
||||
let model_registry_clone = model_registry_state.clone();
|
||||
let skill_service_clone = skill_service_state.0.clone();
|
||||
#[cfg(debug_assertions)]
|
||||
let _api_key_provider_service_clone = api_key_provider_service_state.0.clone();
|
||||
#[cfg(debug_assertions)]
|
||||
let _connect_state_clone = connect_state.0.clone();
|
||||
#[cfg(debug_assertions)]
|
||||
let _model_registry_clone = model_registry_state.clone();
|
||||
#[cfg(debug_assertions)]
|
||||
let _skill_service_clone = skill_service_state.0.clone();
|
||||
let token_cache_clone = token_cache_service_state.0.clone();
|
||||
let shared_stats_clone = shared_stats.clone();
|
||||
let shared_tokens_clone = shared_tokens.clone();
|
||||
@@ -983,6 +987,8 @@ pub fn run() {
|
||||
commands::openclaw_cmd::openclaw_check_git_available,
|
||||
commands::openclaw_cmd::openclaw_get_node_download_url,
|
||||
commands::openclaw_cmd::openclaw_get_git_download_url,
|
||||
commands::openclaw_cmd::openclaw_get_command_preview,
|
||||
commands::openclaw_cmd::openclaw_get_progress_logs,
|
||||
commands::openclaw_cmd::openclaw_install,
|
||||
commands::openclaw_cmd::openclaw_uninstall,
|
||||
commands::openclaw_cmd::openclaw_start_gateway,
|
||||
|
||||
@@ -736,7 +736,7 @@ impl WorkspaceSandboxedBashTool {
|
||||
format!("'{}'", value.replace('\'', "'\"'\"'"))
|
||||
}
|
||||
|
||||
fn build_shell_command(&self, command: &str, context: &ToolContext) -> (String, Vec<String>) {
|
||||
fn build_shell_command(&self, command: &str, _context: &ToolContext) -> (String, Vec<String>) {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
return (
|
||||
|
||||
@@ -2,8 +2,8 @@ use crate::commands::api_key_provider_cmd::ApiKeyProviderServiceState;
|
||||
use crate::database::DbConnection;
|
||||
use crate::services::openclaw_service::{
|
||||
openclaw_install_event_name, ActionResult, BinaryAvailabilityStatus, BinaryInstallStatus,
|
||||
ChannelInfo, GatewayStatusInfo, HealthInfo, NodeCheckResult, OpenClawServiceState,
|
||||
SyncModelEntry,
|
||||
ChannelInfo, CommandPreview, GatewayStatusInfo, HealthInfo, InstallProgressEvent,
|
||||
NodeCheckResult, OpenClawServiceState, SyncModelEntry,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::{AppHandle, State};
|
||||
@@ -57,12 +57,24 @@ pub async fn openclaw_get_git_download_url(
|
||||
Ok(service.get_git_download_url())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn openclaw_get_command_preview(
|
||||
app: AppHandle,
|
||||
service: State<'_, OpenClawServiceState>,
|
||||
operation: String,
|
||||
port: Option<u16>,
|
||||
) -> Result<CommandPreview, String> {
|
||||
let mut service = service.0.lock().await;
|
||||
service.get_command_preview(&app, &operation, port).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn openclaw_install(
|
||||
app: AppHandle,
|
||||
service: State<'_, OpenClawServiceState>,
|
||||
) -> Result<ActionResult, String> {
|
||||
let service = service.0.lock().await;
|
||||
let mut service = service.0.lock().await;
|
||||
service.clear_progress_logs();
|
||||
service.install(&app).await
|
||||
}
|
||||
|
||||
@@ -72,6 +84,7 @@ pub async fn openclaw_uninstall(
|
||||
service: State<'_, OpenClawServiceState>,
|
||||
) -> Result<ActionResult, String> {
|
||||
let mut service = service.0.lock().await;
|
||||
service.clear_progress_logs();
|
||||
service.uninstall(&app).await
|
||||
}
|
||||
|
||||
@@ -82,6 +95,7 @@ pub async fn openclaw_start_gateway(
|
||||
service: State<'_, OpenClawServiceState>,
|
||||
) -> Result<ActionResult, String> {
|
||||
let mut service = service.0.lock().await;
|
||||
service.clear_progress_logs();
|
||||
service.start_gateway(Some(&app), port).await
|
||||
}
|
||||
|
||||
@@ -91,6 +105,7 @@ pub async fn openclaw_stop_gateway(
|
||||
service: State<'_, OpenClawServiceState>,
|
||||
) -> Result<ActionResult, String> {
|
||||
let mut service = service.0.lock().await;
|
||||
service.clear_progress_logs();
|
||||
service.stop_gateway(Some(&app)).await
|
||||
}
|
||||
|
||||
@@ -100,9 +115,18 @@ pub async fn openclaw_restart_gateway(
|
||||
service: State<'_, OpenClawServiceState>,
|
||||
) -> Result<ActionResult, String> {
|
||||
let mut service = service.0.lock().await;
|
||||
service.clear_progress_logs();
|
||||
service.restart_gateway(&app).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn openclaw_get_progress_logs(
|
||||
service: State<'_, OpenClawServiceState>,
|
||||
) -> Result<Vec<InstallProgressEvent>, String> {
|
||||
let service = service.0.lock().await;
|
||||
Ok(service.get_progress_logs())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn openclaw_get_status(
|
||||
service: State<'_, OpenClawServiceState>,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use serde::Serialize;
|
||||
use tauri::AppHandle;
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
use std::path::PathBuf;
|
||||
@@ -126,14 +126,14 @@ pub fn collect_windows_startup_diagnostics(app: &AppHandle) -> WindowsStartupDia
|
||||
let mut errors = Vec::new();
|
||||
let mut warnings = Vec::new();
|
||||
|
||||
let app_data_dir = app.path().app_data_dir().ok();
|
||||
let app_data_dir: Option<PathBuf> = app.path().app_data_dir().ok();
|
||||
let home_dir = dirs::home_dir();
|
||||
let legacy_proxycast_dir = home_dir.clone().map(|home| home.join(".proxycast"));
|
||||
let db_path = crate::database::get_db_path().ok();
|
||||
let webview2_version = detect_webview2_runtime_version();
|
||||
let current_exe = std::env::current_exe().ok();
|
||||
let current_dir = std::env::current_dir().ok();
|
||||
let resource_dir = app.path().resource_dir().ok();
|
||||
let resource_dir: Option<PathBuf> = app.path().resource_dir().ok();
|
||||
let shell_env = get_env_path_value("SHELL");
|
||||
let comspec_env = get_env_path_value("COMSPEC");
|
||||
let resolved_terminal_shell =
|
||||
|
||||
@@ -25,6 +25,7 @@ use rusqlite::{params_from_iter, types::Value};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::path::PathBuf;
|
||||
use tauri::Manager;
|
||||
|
||||
fn load_model_registry_provider_ids_from_db(
|
||||
state: &DevBridgeState,
|
||||
@@ -1237,6 +1238,214 @@ pub async fn handle_command(
|
||||
}))
|
||||
}
|
||||
|
||||
// ========== OpenClaw ==========
|
||||
"openclaw_check_installed" => {
|
||||
let app_handle = state
|
||||
.app_handle
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Dev Bridge 未持有 AppHandle".to_string())?;
|
||||
let service = app_handle.state::<crate::services::openclaw_service::OpenClawServiceState>();
|
||||
let service = service.0.lock().await;
|
||||
Ok(serde_json::to_value(service.check_installed().await?)?)
|
||||
}
|
||||
|
||||
"openclaw_check_node_version" => {
|
||||
let app_handle = state
|
||||
.app_handle
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Dev Bridge 未持有 AppHandle".to_string())?;
|
||||
let service = app_handle.state::<crate::services::openclaw_service::OpenClawServiceState>();
|
||||
let service = service.0.lock().await;
|
||||
Ok(serde_json::to_value(service.check_node_version().await?)?)
|
||||
}
|
||||
|
||||
"openclaw_check_git_available" => {
|
||||
let app_handle = state
|
||||
.app_handle
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Dev Bridge 未持有 AppHandle".to_string())?;
|
||||
let service = app_handle.state::<crate::services::openclaw_service::OpenClawServiceState>();
|
||||
let service = service.0.lock().await;
|
||||
Ok(serde_json::to_value(service.check_git_available().await?)?)
|
||||
}
|
||||
|
||||
"openclaw_get_node_download_url" => {
|
||||
let app_handle = state
|
||||
.app_handle
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Dev Bridge 未持有 AppHandle".to_string())?;
|
||||
let service = app_handle.state::<crate::services::openclaw_service::OpenClawServiceState>();
|
||||
let service = service.0.lock().await;
|
||||
Ok(serde_json::json!(service.get_node_download_url()))
|
||||
}
|
||||
|
||||
"openclaw_get_git_download_url" => {
|
||||
let app_handle = state
|
||||
.app_handle
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Dev Bridge 未持有 AppHandle".to_string())?;
|
||||
let service = app_handle.state::<crate::services::openclaw_service::OpenClawServiceState>();
|
||||
let service = service.0.lock().await;
|
||||
Ok(serde_json::json!(service.get_git_download_url()))
|
||||
}
|
||||
|
||||
"openclaw_get_command_preview" => {
|
||||
let app_handle = state
|
||||
.app_handle
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Dev Bridge 未持有 AppHandle".to_string())?;
|
||||
let args = args.unwrap_or_default();
|
||||
let operation = get_string_arg(&args, "operation", "operation")?;
|
||||
let port = args.get("port").and_then(|value| value.as_u64()).map(|value| value as u16);
|
||||
let service = app_handle.state::<crate::services::openclaw_service::OpenClawServiceState>();
|
||||
let mut service = service.0.lock().await;
|
||||
Ok(serde_json::to_value(service.get_command_preview(app_handle, &operation, port).await?)?)
|
||||
}
|
||||
|
||||
"openclaw_install" => {
|
||||
let app_handle = state
|
||||
.app_handle
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Dev Bridge 未持有 AppHandle".to_string())?;
|
||||
let service = app_handle.state::<crate::services::openclaw_service::OpenClawServiceState>();
|
||||
let mut service = service.0.lock().await;
|
||||
service.clear_progress_logs();
|
||||
Ok(serde_json::to_value(service.install(app_handle).await?)?)
|
||||
}
|
||||
|
||||
"openclaw_uninstall" => {
|
||||
let app_handle = state
|
||||
.app_handle
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Dev Bridge 未持有 AppHandle".to_string())?;
|
||||
let service = app_handle.state::<crate::services::openclaw_service::OpenClawServiceState>();
|
||||
let mut service = service.0.lock().await;
|
||||
service.clear_progress_logs();
|
||||
Ok(serde_json::to_value(service.uninstall(app_handle).await?)?)
|
||||
}
|
||||
|
||||
"openclaw_start_gateway" => {
|
||||
let app_handle = state
|
||||
.app_handle
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Dev Bridge 未持有 AppHandle".to_string())?;
|
||||
let port = args
|
||||
.as_ref()
|
||||
.and_then(|value| value.get("port"))
|
||||
.and_then(|value| value.as_u64())
|
||||
.map(|value| value as u16);
|
||||
let service = app_handle.state::<crate::services::openclaw_service::OpenClawServiceState>();
|
||||
let mut service = service.0.lock().await;
|
||||
service.clear_progress_logs();
|
||||
Ok(serde_json::to_value(service.start_gateway(Some(app_handle), port).await?)?)
|
||||
}
|
||||
|
||||
"openclaw_stop_gateway" => {
|
||||
let app_handle = state
|
||||
.app_handle
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Dev Bridge 未持有 AppHandle".to_string())?;
|
||||
let service = app_handle.state::<crate::services::openclaw_service::OpenClawServiceState>();
|
||||
let mut service = service.0.lock().await;
|
||||
service.clear_progress_logs();
|
||||
Ok(serde_json::to_value(service.stop_gateway(Some(app_handle)).await?)?)
|
||||
}
|
||||
|
||||
"openclaw_restart_gateway" => {
|
||||
let app_handle = state
|
||||
.app_handle
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Dev Bridge 未持有 AppHandle".to_string())?;
|
||||
let service = app_handle.state::<crate::services::openclaw_service::OpenClawServiceState>();
|
||||
let mut service = service.0.lock().await;
|
||||
service.clear_progress_logs();
|
||||
Ok(serde_json::to_value(service.restart_gateway(app_handle).await?)?)
|
||||
}
|
||||
|
||||
"openclaw_get_status" => {
|
||||
let app_handle = state
|
||||
.app_handle
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Dev Bridge 未持有 AppHandle".to_string())?;
|
||||
let service = app_handle.state::<crate::services::openclaw_service::OpenClawServiceState>();
|
||||
let mut service = service.0.lock().await;
|
||||
Ok(serde_json::to_value(service.get_status().await?)?)
|
||||
}
|
||||
|
||||
"openclaw_check_health" => {
|
||||
let app_handle = state
|
||||
.app_handle
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Dev Bridge 未持有 AppHandle".to_string())?;
|
||||
let service = app_handle.state::<crate::services::openclaw_service::OpenClawServiceState>();
|
||||
let mut service = service.0.lock().await;
|
||||
Ok(serde_json::to_value(service.check_health().await?)?)
|
||||
}
|
||||
|
||||
"openclaw_get_dashboard_url" => {
|
||||
let app_handle = state
|
||||
.app_handle
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Dev Bridge 未持有 AppHandle".to_string())?;
|
||||
let service = app_handle.state::<crate::services::openclaw_service::OpenClawServiceState>();
|
||||
let mut service = service.0.lock().await;
|
||||
Ok(serde_json::json!(service.get_dashboard_url()))
|
||||
}
|
||||
|
||||
"openclaw_get_channels" => {
|
||||
let app_handle = state
|
||||
.app_handle
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Dev Bridge 未持有 AppHandle".to_string())?;
|
||||
let service = app_handle.state::<crate::services::openclaw_service::OpenClawServiceState>();
|
||||
let mut service = service.0.lock().await;
|
||||
Ok(serde_json::to_value(service.get_channels().await?)?)
|
||||
}
|
||||
|
||||
"openclaw_get_progress_logs" => {
|
||||
let app_handle = state
|
||||
.app_handle
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Dev Bridge 未持有 AppHandle".to_string())?;
|
||||
let service = app_handle.state::<crate::services::openclaw_service::OpenClawServiceState>();
|
||||
let service = service.0.lock().await;
|
||||
Ok(serde_json::to_value(service.get_progress_logs())?)
|
||||
}
|
||||
|
||||
"openclaw_sync_provider_config" => {
|
||||
let app_handle = state
|
||||
.app_handle
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Dev Bridge 未持有 AppHandle".to_string())?;
|
||||
let request: crate::commands::openclaw_cmd::OpenClawSyncConfigRequest =
|
||||
parse_nested_arg(&args.unwrap_or_default(), "request")?;
|
||||
let db = get_db(state)?;
|
||||
let provider = state
|
||||
.api_key_provider_service
|
||||
.get_provider(db, &request.provider_id)?
|
||||
.ok_or_else(|| "未找到指定 Provider。".to_string())?;
|
||||
|
||||
if !provider.provider.enabled {
|
||||
return Ok(serde_json::json!({
|
||||
"success": false,
|
||||
"message": "该 Provider 已被禁用。"
|
||||
}));
|
||||
}
|
||||
|
||||
let api_key = state
|
||||
.api_key_provider_service
|
||||
.get_next_api_key(db, &request.provider_id)?
|
||||
.unwrap_or_default();
|
||||
let service = app_handle.state::<crate::services::openclaw_service::OpenClawServiceState>();
|
||||
let mut service = service.0.lock().await;
|
||||
Ok(serde_json::to_value(service.sync_provider_config(
|
||||
&provider.provider,
|
||||
&api_key,
|
||||
&request.primary_model_id,
|
||||
&request.models,
|
||||
)?)?)
|
||||
}
|
||||
|
||||
// ========== Agent 会话管理 ==========
|
||||
"agent_create_session" => {
|
||||
let args = args.unwrap_or_default();
|
||||
|
||||
@@ -5,13 +5,13 @@ use rand::{distributions::Alphanumeric, Rng};
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Map, Value};
|
||||
use std::collections::HashSet;
|
||||
use std::collections::{HashSet, VecDeque};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Stdio;
|
||||
use std::sync::OnceLock;
|
||||
use std::time::SystemTime;
|
||||
use tauri::{AppHandle, Emitter, Manager};
|
||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||
use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncReadExt, BufReader};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::process::{Child, Command};
|
||||
use tokio::sync::Mutex;
|
||||
@@ -24,6 +24,7 @@ const OPENCLAW_CN_PACKAGE: &str = "@qingchencloud/openclaw-zh@latest";
|
||||
const OPENCLAW_DEFAULT_PACKAGE: &str = "openclaw@latest";
|
||||
const NPM_MIRROR_CN: &str = "https://registry.npmmirror.com";
|
||||
const NODE_MIN_VERSION: (u64, u64, u64) = (22, 0, 0);
|
||||
const OPENCLAW_PROGRESS_LOG_LIMIT: usize = 400;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -54,6 +55,13 @@ pub struct ActionResult {
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CommandPreview {
|
||||
pub title: String,
|
||||
pub command: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GatewayStatusInfo {
|
||||
@@ -110,6 +118,7 @@ pub struct OpenClawService {
|
||||
gateway_port: u16,
|
||||
gateway_auth_token: String,
|
||||
gateway_started_at: Option<SystemTime>,
|
||||
progress_logs: VecDeque<InstallProgressEvent>,
|
||||
}
|
||||
|
||||
impl Default for OpenClawService {
|
||||
@@ -120,6 +129,7 @@ impl Default for OpenClawService {
|
||||
gateway_port: DEFAULT_GATEWAY_PORT,
|
||||
gateway_auth_token: String::new(),
|
||||
gateway_started_at: None,
|
||||
progress_logs: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,6 +143,38 @@ impl Default for OpenClawServiceState {
|
||||
}
|
||||
|
||||
impl OpenClawService {
|
||||
pub fn clear_progress_logs(&mut self) {
|
||||
self.progress_logs.clear();
|
||||
}
|
||||
|
||||
pub fn get_progress_logs(&self) -> Vec<InstallProgressEvent> {
|
||||
self.progress_logs.iter().cloned().collect()
|
||||
}
|
||||
|
||||
fn push_progress_log(&mut self, message: String, level: String) {
|
||||
if self.progress_logs.len() >= OPENCLAW_PROGRESS_LOG_LIMIT {
|
||||
self.progress_logs.pop_front();
|
||||
}
|
||||
self.progress_logs
|
||||
.push_back(InstallProgressEvent { message, level });
|
||||
}
|
||||
|
||||
pub async fn get_command_preview(
|
||||
&mut self,
|
||||
app: &AppHandle,
|
||||
operation: &str,
|
||||
port: Option<u16>,
|
||||
) -> Result<CommandPreview, String> {
|
||||
match operation {
|
||||
"install" => self.build_install_command_preview(app).await,
|
||||
"uninstall" => self.build_uninstall_command_preview().await,
|
||||
"restart" => self.build_restart_command_preview(port).await,
|
||||
"start" => self.build_start_command_preview(port).await,
|
||||
"stop" => self.build_stop_command_preview(port).await,
|
||||
_ => Err(format!("不支持的 OpenClaw 操作预览: {operation}")),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn check_installed(&self) -> Result<BinaryInstallStatus, String> {
|
||||
let path = find_command_in_shell("openclaw").await?;
|
||||
Ok(BinaryInstallStatus {
|
||||
@@ -215,29 +257,8 @@ impl OpenClawService {
|
||||
}
|
||||
|
||||
pub async fn install(&self, app: &AppHandle) -> Result<ActionResult, String> {
|
||||
let npm_path = find_command_in_shell("npm")
|
||||
.await?
|
||||
.ok_or_else(|| "未检测到 npm,可先安装或修复 Node.js 环境。".to_string())?;
|
||||
let npm_prefix = detect_npm_global_prefix(&npm_path).await;
|
||||
let package = if should_use_china_package(app) {
|
||||
OPENCLAW_CN_PACKAGE
|
||||
} else {
|
||||
OPENCLAW_DEFAULT_PACKAGE
|
||||
};
|
||||
|
||||
let prefix_env = npm_prefix
|
||||
.as_deref()
|
||||
.map(shell_env_assignment)
|
||||
.unwrap_or_default();
|
||||
let npm_cmd = shell_escape(&npm_path);
|
||||
let cleanup_command = format!(
|
||||
"{prefix_env}{npm_cmd} uninstall -g openclaw @qingchencloud/openclaw-zh || true"
|
||||
);
|
||||
let install_command = if should_use_china_package(app) {
|
||||
format!("{prefix_env}{npm_cmd} install -g {package} --registry={NPM_MIRROR_CN}")
|
||||
} else {
|
||||
format!("{prefix_env}{npm_cmd} install -g {package}")
|
||||
};
|
||||
let (_, npm_path, npm_prefix, cleanup_command, install_command) =
|
||||
self.resolve_install_commands(app).await?;
|
||||
let command = format!("{cleanup_command}\n{install_command}");
|
||||
|
||||
emit_install_progress(app, &format!("使用 npm: {npm_path}"), "info");
|
||||
@@ -255,19 +276,7 @@ impl OpenClawService {
|
||||
let _ = self.stop_gateway(None).await;
|
||||
}
|
||||
|
||||
let npm_path = find_command_in_shell("npm")
|
||||
.await?
|
||||
.ok_or_else(|| "未检测到 npm,可先安装或修复 Node.js 环境。".to_string())?;
|
||||
let npm_prefix = detect_npm_global_prefix(&npm_path).await;
|
||||
let prefix_env = npm_prefix
|
||||
.as_deref()
|
||||
.map(shell_env_assignment)
|
||||
.unwrap_or_default();
|
||||
let command = format!(
|
||||
"{}{} uninstall -g openclaw @qingchencloud/openclaw-zh",
|
||||
prefix_env,
|
||||
shell_escape(&npm_path)
|
||||
);
|
||||
let (npm_path, npm_prefix, command) = self.resolve_uninstall_command().await?;
|
||||
|
||||
emit_install_progress(app, &format!("使用 npm: {npm_path}"), "info");
|
||||
if let Some(prefix) = npm_prefix {
|
||||
@@ -902,6 +911,148 @@ impl OpenClawService {
|
||||
std::fs::write(proxycast_config_path, content).map_err(|e| format!("写入配置失败: {e}"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn resolve_install_commands(
|
||||
&self,
|
||||
app: &AppHandle,
|
||||
) -> Result<(String, String, Option<String>, String, String), String> {
|
||||
let npm_path = find_command_in_shell("npm")
|
||||
.await?
|
||||
.ok_or_else(|| "未检测到 npm,可先安装或修复 Node.js 环境。".to_string())?;
|
||||
let npm_prefix = detect_npm_global_prefix(&npm_path).await;
|
||||
let package = if should_use_china_package(app).await {
|
||||
OPENCLAW_CN_PACKAGE
|
||||
} else {
|
||||
OPENCLAW_DEFAULT_PACKAGE
|
||||
};
|
||||
let prefix_env = npm_prefix
|
||||
.as_deref()
|
||||
.map(shell_env_assignment)
|
||||
.unwrap_or_default();
|
||||
let npm_cmd = shell_escape(&npm_path);
|
||||
let cleanup_command = format!(
|
||||
"{prefix_env}{npm_cmd} uninstall -g openclaw @qingchencloud/openclaw-zh || true"
|
||||
);
|
||||
let install_command = if should_use_china_package(app).await {
|
||||
format!("{prefix_env}{npm_cmd} install -g {package} --registry={NPM_MIRROR_CN}")
|
||||
} else {
|
||||
format!("{prefix_env}{npm_cmd} install -g {package}")
|
||||
};
|
||||
Ok((
|
||||
package.to_string(),
|
||||
npm_path,
|
||||
npm_prefix,
|
||||
cleanup_command,
|
||||
install_command,
|
||||
))
|
||||
}
|
||||
|
||||
async fn resolve_uninstall_command(&self) -> Result<(String, Option<String>, String), String> {
|
||||
let npm_path = find_command_in_shell("npm")
|
||||
.await?
|
||||
.ok_or_else(|| "未检测到 npm,可先安装或修复 Node.js 环境。".to_string())?;
|
||||
let npm_prefix = detect_npm_global_prefix(&npm_path).await;
|
||||
let prefix_env = npm_prefix
|
||||
.as_deref()
|
||||
.map(shell_env_assignment)
|
||||
.unwrap_or_default();
|
||||
let command = format!(
|
||||
"{}{} uninstall -g openclaw @qingchencloud/openclaw-zh",
|
||||
prefix_env,
|
||||
shell_escape(&npm_path)
|
||||
);
|
||||
Ok((npm_path, npm_prefix, command))
|
||||
}
|
||||
|
||||
async fn build_install_command_preview(
|
||||
&self,
|
||||
app: &AppHandle,
|
||||
) -> Result<CommandPreview, String> {
|
||||
let (package, npm_path, npm_prefix, cleanup_command, install_command) =
|
||||
self.resolve_install_commands(app).await?;
|
||||
let prefix_note = npm_prefix
|
||||
.map(|prefix| format!("npm: {npm_path}\nprefix: {prefix}\n"))
|
||||
.unwrap_or_else(|| format!("npm: {npm_path}\n"));
|
||||
Ok(CommandPreview {
|
||||
title: format!("安装 {package}"),
|
||||
command: format!("{prefix_note}{cleanup_command}\n{install_command}"),
|
||||
})
|
||||
}
|
||||
|
||||
async fn build_uninstall_command_preview(&self) -> Result<CommandPreview, String> {
|
||||
let (npm_path, npm_prefix, command) = self.resolve_uninstall_command().await?;
|
||||
let prefix_note = npm_prefix
|
||||
.map(|prefix| format!("npm: {npm_path}\nprefix: {prefix}\n"))
|
||||
.unwrap_or_else(|| format!("npm: {npm_path}\n"));
|
||||
Ok(CommandPreview {
|
||||
title: "卸载 OpenClaw".to_string(),
|
||||
command: format!("{prefix_note}{command}"),
|
||||
})
|
||||
}
|
||||
|
||||
async fn build_start_command_preview(
|
||||
&mut self,
|
||||
port: Option<u16>,
|
||||
) -> Result<CommandPreview, String> {
|
||||
if let Some(next_port) = port {
|
||||
self.gateway_port = next_port.max(1);
|
||||
}
|
||||
self.restore_auth_token_from_config();
|
||||
let binary = find_command_in_shell("openclaw")
|
||||
.await?
|
||||
.ok_or_else(|| "未检测到 OpenClaw 可执行文件,请先安装。".to_string())?;
|
||||
let config_path = openclaw_proxycast_config_path();
|
||||
Ok(CommandPreview {
|
||||
title: "启动 Gateway".to_string(),
|
||||
command: format!(
|
||||
"{}OPENCLAW_CONFIG_PATH={} {} gateway --port {}",
|
||||
if cfg!(target_os = "windows") {
|
||||
"set "
|
||||
} else {
|
||||
""
|
||||
},
|
||||
shell_escape(config_path.to_string_lossy().as_ref()),
|
||||
shell_escape(&binary),
|
||||
self.gateway_port
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
async fn build_stop_command_preview(
|
||||
&mut self,
|
||||
port: Option<u16>,
|
||||
) -> Result<CommandPreview, String> {
|
||||
if let Some(next_port) = port {
|
||||
self.gateway_port = next_port.max(1);
|
||||
}
|
||||
self.restore_auth_token_from_config();
|
||||
let binary = find_command_in_shell("openclaw")
|
||||
.await?
|
||||
.ok_or_else(|| "未检测到 OpenClaw 可执行文件,请先安装。".to_string())?;
|
||||
let config_path = openclaw_proxycast_config_path();
|
||||
Ok(CommandPreview {
|
||||
title: "停止 Gateway".to_string(),
|
||||
command: format!(
|
||||
"OPENCLAW_CONFIG_PATH={} {} gateway stop --url {} --token {}",
|
||||
shell_escape(config_path.to_string_lossy().as_ref()),
|
||||
shell_escape(&binary),
|
||||
self.gateway_ws_url(),
|
||||
shell_escape(&self.gateway_auth_token)
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
async fn build_restart_command_preview(
|
||||
&mut self,
|
||||
port: Option<u16>,
|
||||
) -> Result<CommandPreview, String> {
|
||||
let stop = self.build_stop_command_preview(port).await?;
|
||||
let start = self.build_start_command_preview(port).await?;
|
||||
Ok(CommandPreview {
|
||||
title: "重启 Gateway".to_string(),
|
||||
command: format!("{}\n{}", stop.command, start.command),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn openclaw_install_event_name() -> &'static str {
|
||||
@@ -1091,12 +1242,12 @@ fn generate_auth_token() -> String {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn should_use_china_package(app: &AppHandle) -> bool {
|
||||
async fn should_use_china_package(app: &AppHandle) -> bool {
|
||||
if let Some(app_state) = app.try_state::<AppState>() {
|
||||
let language = tauri::async_runtime::block_on(async {
|
||||
let language = {
|
||||
let state = app_state.read().await;
|
||||
state.config.language.clone()
|
||||
});
|
||||
};
|
||||
|
||||
if language.starts_with("zh") {
|
||||
return true;
|
||||
@@ -1273,31 +1424,14 @@ async fn run_shell_command_with_progress(
|
||||
let stdout_task = child.stdout.take().map(|stdout| {
|
||||
let app = app.clone();
|
||||
tokio::spawn(async move {
|
||||
let mut lines = BufReader::new(stdout).lines();
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
let trimmed = line.trim();
|
||||
if !trimmed.is_empty() {
|
||||
emit_install_progress(&app, trimmed, "info");
|
||||
}
|
||||
}
|
||||
stream_reader_to_progress(app, stdout, "info").await;
|
||||
})
|
||||
});
|
||||
|
||||
let stderr_task = child.stderr.take().map(|stderr| {
|
||||
let app = app.clone();
|
||||
tokio::spawn(async move {
|
||||
let mut lines = BufReader::new(stderr).lines();
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
let trimmed = line.trim();
|
||||
if !trimmed.is_empty() {
|
||||
let level = if trimmed.to_ascii_lowercase().contains("warn") {
|
||||
"warn"
|
||||
} else {
|
||||
"error"
|
||||
};
|
||||
emit_install_progress(&app, trimmed, level);
|
||||
}
|
||||
}
|
||||
stream_reader_to_progress(app, stderr, "error").await;
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1337,6 +1471,15 @@ fn spawn_shell_command(command_line: &str) -> Result<Child, String> {
|
||||
let mut cmd = Command::new("cmd");
|
||||
cmd.arg("/C").arg(command_line);
|
||||
cmd
|
||||
} else if cfg!(target_os = "macos") {
|
||||
let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/zsh".to_string());
|
||||
let mut cmd = Command::new("script");
|
||||
cmd.arg("-q")
|
||||
.arg("/dev/null")
|
||||
.arg(shell)
|
||||
.arg("-lc")
|
||||
.arg(command_line);
|
||||
cmd
|
||||
} else {
|
||||
let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string());
|
||||
let mut cmd = Command::new(shell);
|
||||
@@ -1344,11 +1487,109 @@ fn spawn_shell_command(command_line: &str) -> Result<Child, String> {
|
||||
cmd
|
||||
};
|
||||
|
||||
command.stdout(Stdio::piped()).stderr(Stdio::piped());
|
||||
command
|
||||
.env("NO_COLOR", "1")
|
||||
.env("CLICOLOR", "0")
|
||||
.env("FORCE_COLOR", "0")
|
||||
.env("npm_config_color", "false")
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped());
|
||||
command.spawn().map_err(|e| format!("启动命令失败: {e}"))
|
||||
}
|
||||
|
||||
async fn stream_reader_to_progress<R>(app: AppHandle, mut reader: R, default_level: &'static str)
|
||||
where
|
||||
R: AsyncRead + Unpin,
|
||||
{
|
||||
let mut buffer = [0_u8; 2048];
|
||||
let mut pending = String::new();
|
||||
|
||||
loop {
|
||||
match reader.read(&mut buffer).await {
|
||||
Ok(0) => break,
|
||||
Ok(size) => {
|
||||
pending.push_str(&String::from_utf8_lossy(&buffer[..size]));
|
||||
flush_progress_chunks(&app, &mut pending, default_level);
|
||||
}
|
||||
Err(error) => {
|
||||
emit_install_progress(&app, &format!("读取命令输出失败: {error}"), "warn");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let tail = pending.trim();
|
||||
if !tail.is_empty() {
|
||||
emit_install_progress(&app, tail, classify_progress_level(tail, default_level));
|
||||
}
|
||||
}
|
||||
|
||||
fn flush_progress_chunks(app: &AppHandle, pending: &mut String, default_level: &'static str) {
|
||||
loop {
|
||||
let next_break = pending.find(['\n', '\r']);
|
||||
let Some(index) = next_break else {
|
||||
break;
|
||||
};
|
||||
|
||||
let mut line = pending[..index].trim().to_string();
|
||||
let mut consume_len = index + 1;
|
||||
while pending
|
||||
.get(consume_len..consume_len + 1)
|
||||
.is_some_and(|ch| ch == "\n" || ch == "\r")
|
||||
{
|
||||
consume_len += 1;
|
||||
}
|
||||
|
||||
pending.drain(..consume_len);
|
||||
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
line = sanitize_progress_line(&line);
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
emit_install_progress(app, &line, classify_progress_level(&line, default_level));
|
||||
}
|
||||
|
||||
if pending.len() > 4096 {
|
||||
let line = sanitize_progress_line(pending.trim());
|
||||
if !line.is_empty() {
|
||||
emit_install_progress(app, &line, classify_progress_level(&line, default_level));
|
||||
}
|
||||
pending.clear();
|
||||
}
|
||||
}
|
||||
|
||||
fn sanitize_progress_line(value: &str) -> String {
|
||||
value
|
||||
.replace('\u{1b}', "")
|
||||
.replace("[?25h", "")
|
||||
.replace("[?25l", "")
|
||||
.trim()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn classify_progress_level(message: &str, default_level: &'static str) -> &'static str {
|
||||
let lower = message.to_ascii_lowercase();
|
||||
if lower.contains("error") || lower.contains("fatal") {
|
||||
"error"
|
||||
} else if lower.contains("warn") || lower.contains("warning") {
|
||||
"warn"
|
||||
} else {
|
||||
default_level
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_install_progress(app: &AppHandle, message: &str, level: &str) {
|
||||
if let Some(service_state) = app.try_state::<OpenClawServiceState>() {
|
||||
if let Ok(mut service) = service_state.0.try_lock() {
|
||||
service.push_progress_log(message.to_string(), level.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
let payload = InstallProgressEvent {
|
||||
message: message.to_string(),
|
||||
level: level.to_string(),
|
||||
|
||||
Reference in New Issue
Block a user