mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-07-10 23:12:05 +08:00
chore: release v0.9.7
This commit is contained in:
2
src-tauri/Cargo.lock
generated
2
src-tauri/Cargo.lock
generated
@@ -340,7 +340,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clawpanel"
|
||||
version = "0.9.6"
|
||||
version = "0.9.7"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "clawpanel"
|
||||
version = "0.9.6"
|
||||
version = "0.9.7"
|
||||
edition = "2021"
|
||||
description = "ClawPanel - OpenClaw 可视化管理面板"
|
||||
authors = ["qingchencloud"]
|
||||
|
||||
@@ -102,7 +102,7 @@ pub async fn list_agents() -> Result<Value, String> {
|
||||
Ok(Value::Array(enriched))
|
||||
}
|
||||
|
||||
/// 创建新 agent(走 CLI,自动创建 workspace/sessions 等文件)
|
||||
/// 创建新 agent(优先走 CLI,失败则直接写 openclaw.json 兜底)
|
||||
#[tauri::command]
|
||||
pub async fn add_agent(
|
||||
name: String,
|
||||
@@ -128,29 +128,105 @@ pub async fn add_agent(
|
||||
|
||||
if !model.is_empty() {
|
||||
args.push("--model".to_string());
|
||||
args.push(model);
|
||||
args.push(model.clone());
|
||||
}
|
||||
|
||||
let output = openclaw_command_async()
|
||||
.args(&args)
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
if e.kind() == std::io::ErrorKind::NotFound {
|
||||
"OpenClaw CLI 未找到,请确认已安装并重启 ClawPanel。".to_string()
|
||||
} else {
|
||||
format!("执行失败: {e}")
|
||||
}
|
||||
})?;
|
||||
// 尝试 CLI(15s 超时),失败则直接写配置兜底
|
||||
let cli_ok = match tokio::time::timeout(
|
||||
std::time::Duration::from_secs(15),
|
||||
openclaw_command_async().args(&args).output(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Ok(o)) if o.status.success() => true,
|
||||
Ok(Ok(o)) => {
|
||||
let stderr = String::from_utf8_lossy(&o.stderr);
|
||||
eprintln!(
|
||||
"[agent] CLI 创建失败: {}",
|
||||
stderr.chars().take(200).collect::<String>()
|
||||
);
|
||||
false
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
eprintln!("[agent] CLI 执行错误: {e}");
|
||||
false
|
||||
}
|
||||
Err(_) => {
|
||||
eprintln!("[agent] CLI 超时 (15s)");
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
return Err(format!("创建 Agent 失败: {stderr}"));
|
||||
if !cli_ok {
|
||||
// 兜底:直接写 openclaw.json
|
||||
add_agent_to_config(&name, &model, &ws)?;
|
||||
}
|
||||
|
||||
// 确保 workspace 目录存在
|
||||
if !ws.exists() {
|
||||
let _ = fs::create_dir_all(&ws);
|
||||
}
|
||||
|
||||
list_agents().await
|
||||
}
|
||||
|
||||
/// 直接写 openclaw.json 创建 agent(CLI 不可用时的兜底方案)
|
||||
fn add_agent_to_config(id: &str, model: &str, workspace: &std::path::Path) -> Result<(), String> {
|
||||
let config_path = super::openclaw_dir().join("openclaw.json");
|
||||
if !config_path.exists() {
|
||||
return Err("openclaw.json 不存在,请先安装 OpenClaw".to_string());
|
||||
}
|
||||
let content = fs::read_to_string(&config_path).map_err(|e| format!("读取配置失败: {e}"))?;
|
||||
let mut config: Value =
|
||||
serde_json::from_str(&content).map_err(|e| format!("解析 JSON 失败: {e}"))?;
|
||||
|
||||
// 确保 agents.list 存在
|
||||
if config.get("agents").is_none() {
|
||||
config
|
||||
.as_object_mut()
|
||||
.ok_or("配置格式错误")?
|
||||
.insert("agents".to_string(), serde_json::json!({}));
|
||||
}
|
||||
if config["agents"].get("list").is_none() {
|
||||
config["agents"]
|
||||
.as_object_mut()
|
||||
.ok_or("agents 格式错误")?
|
||||
.insert("list".to_string(), serde_json::json!([]));
|
||||
}
|
||||
|
||||
let list = config["agents"]["list"]
|
||||
.as_array_mut()
|
||||
.ok_or("agents.list 格式错误")?;
|
||||
|
||||
// 检查是否已存在同名 agent
|
||||
let exists = list
|
||||
.iter()
|
||||
.any(|a| a.get("id").and_then(|v| v.as_str()) == Some(id));
|
||||
if exists {
|
||||
return Err(format!("Agent「{id}」已存在"));
|
||||
}
|
||||
|
||||
let mut agent = serde_json::json!({
|
||||
"id": id,
|
||||
"workspace": workspace.to_string_lossy(),
|
||||
});
|
||||
if !model.is_empty() {
|
||||
agent
|
||||
.as_object_mut()
|
||||
.unwrap()
|
||||
.insert("model".to_string(), serde_json::json!({ "primary": model }));
|
||||
}
|
||||
list.push(agent);
|
||||
|
||||
// 备份 + 写回
|
||||
let bak = super::openclaw_dir().join("openclaw.json.bak");
|
||||
let _ = fs::copy(&config_path, &bak);
|
||||
let json = serde_json::to_string_pretty(&config).map_err(|e| format!("序列化失败: {e}"))?;
|
||||
fs::write(&config_path, json).map_err(|e| format!("写入配置失败: {e}"))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 删除 agent(直接操作 openclaw.json + 删除 agent 目录,不走 CLI)
|
||||
#[tauri::command]
|
||||
pub async fn delete_agent(id: String) -> Result<String, String> {
|
||||
|
||||
@@ -2587,6 +2587,66 @@ pub async fn restart_gateway() -> Result<String, String> {
|
||||
reload_gateway().await
|
||||
}
|
||||
|
||||
/// 运行 openclaw doctor --fix 自动修复配置问题
|
||||
#[tauri::command]
|
||||
pub async fn doctor_fix() -> Result<Value, String> {
|
||||
use crate::utils::openclaw_command_async;
|
||||
|
||||
let result = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(30),
|
||||
openclaw_command_async().args(["doctor", "--fix"]).output(),
|
||||
)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(Ok(o)) => {
|
||||
let stdout = String::from_utf8_lossy(&o.stdout).to_string();
|
||||
let stderr = String::from_utf8_lossy(&o.stderr).to_string();
|
||||
let success = o.status.success();
|
||||
Ok(json!({
|
||||
"success": success,
|
||||
"output": stdout.trim(),
|
||||
"errors": stderr.trim(),
|
||||
"exitCode": o.status.code(),
|
||||
}))
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
if e.kind() == std::io::ErrorKind::NotFound {
|
||||
Err("OpenClaw CLI 未找到,请先安装".to_string())
|
||||
} else {
|
||||
Err(format!("执行 doctor 失败: {e}"))
|
||||
}
|
||||
}
|
||||
Err(_) => Err("doctor --fix 执行超时 (30s)".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// 运行 openclaw doctor(仅诊断,不修复)
|
||||
#[tauri::command]
|
||||
pub async fn doctor_check() -> Result<Value, String> {
|
||||
use crate::utils::openclaw_command_async;
|
||||
|
||||
let result = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(20),
|
||||
openclaw_command_async().args(["doctor"]).output(),
|
||||
)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(Ok(o)) => {
|
||||
let stdout = String::from_utf8_lossy(&o.stdout).to_string();
|
||||
let stderr = String::from_utf8_lossy(&o.stderr).to_string();
|
||||
Ok(json!({
|
||||
"success": o.status.success(),
|
||||
"output": stdout.trim(),
|
||||
"errors": stderr.trim(),
|
||||
}))
|
||||
}
|
||||
Ok(Err(e)) => Err(format!("执行 doctor 失败: {e}")),
|
||||
Err(_) => Err("doctor 执行超时 (20s)".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// 清理 base URL:去掉尾部斜杠和已知端点路径,防止用户粘贴完整端点 URL 导致路径重复
|
||||
fn normalize_base_url(raw: &str) -> String {
|
||||
let mut base = raw.trim_end_matches('/').to_string();
|
||||
@@ -3080,9 +3140,25 @@ pub async fn check_panel_update() -> Result<Value, String> {
|
||||
|
||||
// === 面板配置 (clawpanel.json) ===
|
||||
|
||||
/// 获取当前生效的 OpenClaw 配置目录路径
|
||||
#[tauri::command]
|
||||
pub fn get_openclaw_dir() -> Result<Value, String> {
|
||||
let resolved = super::openclaw_dir();
|
||||
let is_custom = super::read_panel_config_value()
|
||||
.and_then(|v| v.get("openclawDir")?.as_str().map(String::from))
|
||||
.map(|s| !s.is_empty())
|
||||
.unwrap_or(false);
|
||||
let config_exists = resolved.join("openclaw.json").exists();
|
||||
Ok(json!({
|
||||
"path": resolved.to_string_lossy(),
|
||||
"isCustom": is_custom,
|
||||
"configExists": config_exists,
|
||||
}))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn read_panel_config() -> Result<Value, String> {
|
||||
let path = super::openclaw_dir().join("clawpanel.json");
|
||||
let path = super::panel_config_path();
|
||||
if !path.exists() {
|
||||
return Ok(serde_json::json!({}));
|
||||
}
|
||||
@@ -3092,15 +3168,29 @@ pub fn read_panel_config() -> Result<Value, String> {
|
||||
|
||||
#[tauri::command]
|
||||
pub fn write_panel_config(config: Value) -> Result<(), String> {
|
||||
let dir = super::openclaw_dir();
|
||||
if !dir.exists() {
|
||||
fs::create_dir_all(&dir).map_err(|e| format!("创建目录失败: {e}"))?;
|
||||
let path = super::panel_config_path();
|
||||
if let Some(dir) = path.parent() {
|
||||
if !dir.exists() {
|
||||
fs::create_dir_all(dir).map_err(|e| format!("创建目录失败: {e}"))?;
|
||||
}
|
||||
}
|
||||
let path = dir.join("clawpanel.json");
|
||||
let json = serde_json::to_string_pretty(&config).map_err(|e| format!("序列化失败: {e}"))?;
|
||||
fs::write(&path, json).map_err(|e| format!("写入失败: {e}"))
|
||||
}
|
||||
|
||||
/// 重启应用(用于设置变更后自动重启)
|
||||
#[tauri::command]
|
||||
pub async fn relaunch_app(app: tauri::AppHandle) -> Result<(), String> {
|
||||
let exe = std::env::current_exe().map_err(|e| format!("获取可执行文件路径失败: {e}"))?;
|
||||
std::process::Command::new(&exe)
|
||||
.spawn()
|
||||
.map_err(|e| format!("重启失败: {e}"))?;
|
||||
// 短暂延迟后退出当前进程
|
||||
tokio::time::sleep(std::time::Duration::from_millis(300)).await;
|
||||
app.exit(0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 测试代理连通性:通过配置的代理访问指定 URL,返回状态码和耗时
|
||||
#[tauri::command]
|
||||
pub async fn test_proxy(url: Option<String>) -> Result<Value, String> {
|
||||
|
||||
@@ -64,6 +64,8 @@ async fn agent_workspace(agent_id: &str) -> Result<PathBuf, String> {
|
||||
.and_then(|w| w.as_str())
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| super::openclaw_dir().join("workspace"));
|
||||
// 解析符号链接
|
||||
let default_workspace = fs::canonicalize(&default_workspace).unwrap_or(default_workspace);
|
||||
|
||||
let mut new_map = HashMap::new();
|
||||
// main agent 使用默认 workspace
|
||||
@@ -93,6 +95,8 @@ async fn agent_workspace(agent_id: &str) -> Result<PathBuf, String> {
|
||||
.join("workspace")
|
||||
}
|
||||
});
|
||||
// 解析符号链接,确保软连接的 workspace 也能正确访问
|
||||
let ws = fs::canonicalize(&ws).unwrap_or(ws);
|
||||
new_map.insert(id.to_string(), ws);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,13 +16,32 @@ pub mod service;
|
||||
pub mod skills;
|
||||
pub mod update;
|
||||
|
||||
/// 获取 OpenClaw 配置目录 (~/.openclaw/)
|
||||
pub fn openclaw_dir() -> PathBuf {
|
||||
/// 默认 OpenClaw 配置目录(ClawPanel 自身配置始终在此)
|
||||
fn default_openclaw_dir() -> PathBuf {
|
||||
dirs::home_dir().unwrap_or_default().join(".openclaw")
|
||||
}
|
||||
|
||||
/// 获取 OpenClaw 配置目录
|
||||
/// 优先使用 clawpanel.json 中的 openclawDir 自定义路径,不存在则回退默认 ~/.openclaw
|
||||
pub fn openclaw_dir() -> PathBuf {
|
||||
// 直接读 clawpanel.json(始终在默认目录下),避免循环依赖
|
||||
let config_path = default_openclaw_dir().join("clawpanel.json");
|
||||
if let Ok(content) = std::fs::read_to_string(&config_path) {
|
||||
if let Ok(v) = serde_json::from_str::<serde_json::Value>(&content) {
|
||||
if let Some(custom) = v.get("openclawDir").and_then(|d| d.as_str()) {
|
||||
let p = PathBuf::from(custom);
|
||||
if !custom.is_empty() && p.exists() {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
default_openclaw_dir()
|
||||
}
|
||||
|
||||
fn panel_config_path() -> PathBuf {
|
||||
openclaw_dir().join("clawpanel.json")
|
||||
// ClawPanel 自身配置始终在默认目录,不随 openclawDir 变化
|
||||
default_openclaw_dir().join("clawpanel.json")
|
||||
}
|
||||
|
||||
fn read_panel_config_value() -> Option<serde_json::Value> {
|
||||
@@ -184,14 +203,15 @@ fn build_enhanced_path() -> String {
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
// 版本管理器路径优先于系统路径,确保 nvm/volta/fnm 管理的 Node.js 版本被优先检测到
|
||||
let mut extra: Vec<String> = vec![
|
||||
"/usr/local/bin".into(),
|
||||
"/opt/homebrew/bin".into(),
|
||||
format!("{}/.nvm/current/bin", home.display()),
|
||||
format!("{}/.volta/bin", home.display()),
|
||||
format!("{}/.nodenv/shims", home.display()),
|
||||
format!("{}/n/bin", home.display()),
|
||||
format!("{}/.npm-global/bin", home.display()),
|
||||
"/usr/local/bin".into(),
|
||||
"/opt/homebrew/bin".into(),
|
||||
];
|
||||
// NPM_CONFIG_PREFIX: 用户通过 npm config set prefix 自定义的全局安装路径
|
||||
if let Ok(prefix) = std::env::var("NPM_CONFIG_PREFIX") {
|
||||
@@ -238,16 +258,17 @@ fn build_enhanced_path() -> String {
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
// 版本管理器路径优先于系统路径,确保 nvm/volta/fnm 管理的 Node.js 版本被优先检测到
|
||||
let mut extra: Vec<String> = vec![
|
||||
"/usr/local/bin".into(),
|
||||
"/usr/bin".into(),
|
||||
"/snap/bin".into(),
|
||||
format!("{}/.local/bin", home.display()),
|
||||
format!("{}/.nvm/current/bin", home.display()),
|
||||
format!("{}/.volta/bin", home.display()),
|
||||
format!("{}/.nodenv/shims", home.display()),
|
||||
format!("{}/n/bin", home.display()),
|
||||
format!("{}/.npm-global/bin", home.display()),
|
||||
format!("{}/.local/bin", home.display()),
|
||||
"/usr/local/bin".into(),
|
||||
"/usr/bin".into(),
|
||||
"/snap/bin".into(),
|
||||
];
|
||||
// NPM_CONFIG_PREFIX: 用户通过 npm config set prefix 自定义的全局安装路径
|
||||
if let Ok(prefix) = std::env::var("NPM_CONFIG_PREFIX") {
|
||||
@@ -316,35 +337,17 @@ fn build_enhanced_path() -> String {
|
||||
let localappdata = std::env::var("LOCALAPPDATA").unwrap_or_default();
|
||||
let appdata = std::env::var("APPDATA").unwrap_or_default();
|
||||
|
||||
let mut extra: Vec<String> = vec![format!(r"{}\nodejs", pf), format!(r"{}\nodejs", pf86)];
|
||||
if !localappdata.is_empty() {
|
||||
extra.push(format!(r"{}\Programs\nodejs", localappdata));
|
||||
extra.push(format!(r"{}\fnm_multishells", localappdata));
|
||||
}
|
||||
if !appdata.is_empty() {
|
||||
extra.push(format!(r"{}\npm", appdata));
|
||||
extra.push(format!(r"{}\nvm", appdata));
|
||||
// 扫描 nvm-windows 实际安装的版本目录
|
||||
let nvm_dir = std::path::Path::new(&appdata).join("nvm");
|
||||
if nvm_dir.is_dir() {
|
||||
if let Ok(entries) = std::fs::read_dir(&nvm_dir) {
|
||||
for entry in entries.flatten() {
|
||||
let p = entry.path();
|
||||
if p.is_dir() && p.join("node.exe").exists() {
|
||||
extra.push(p.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// NVM_SYMLINK 环境变量(nvm-windows 的活跃版本符号链接,如 D:\nodejs)
|
||||
// 版本管理器路径优先,确保 nvm/volta/fnm 管理的 Node.js 被优先检测到
|
||||
let mut extra: Vec<String> = vec![];
|
||||
|
||||
// 1. NVM_SYMLINK(nvm-windows 活跃版本符号链接,如 D:\nodejs)—— 最高优先级
|
||||
if let Ok(nvm_symlink) = std::env::var("NVM_SYMLINK") {
|
||||
let symlink_path = std::path::Path::new(&nvm_symlink);
|
||||
if symlink_path.is_dir() {
|
||||
extra.push(nvm_symlink.clone());
|
||||
}
|
||||
}
|
||||
// NVM_HOME 环境变量(用户可能自定义了 nvm 安装目录)
|
||||
// 2. NVM_HOME(用户自定义 nvm 安装目录)
|
||||
if let Ok(nvm_home) = std::env::var("NVM_HOME") {
|
||||
let nvm_path = std::path::Path::new(&nvm_home);
|
||||
if nvm_path.is_dir() {
|
||||
@@ -358,8 +361,27 @@ fn build_enhanced_path() -> String {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 3. %APPDATA%\nvm(nvm-windows 默认安装目录)
|
||||
if !appdata.is_empty() {
|
||||
extra.push(format!(r"{}\nvm", appdata));
|
||||
let nvm_dir = std::path::Path::new(&appdata).join("nvm");
|
||||
if nvm_dir.is_dir() {
|
||||
if let Ok(entries) = std::fs::read_dir(&nvm_dir) {
|
||||
for entry in entries.flatten() {
|
||||
let p = entry.path();
|
||||
if p.is_dir() && p.join("node.exe").exists() {
|
||||
extra.push(p.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 4. volta
|
||||
extra.push(format!(r"{}\.volta\bin", home.display()));
|
||||
// fnm: 扫描 %FNM_DIR% 或默认 %APPDATA%\fnm 下的版本目录
|
||||
// 5. fnm
|
||||
if !localappdata.is_empty() {
|
||||
extra.push(format!(r"{}\fnm_multishells", localappdata));
|
||||
}
|
||||
let fnm_base = std::env::var("FNM_DIR")
|
||||
.ok()
|
||||
.map(std::path::PathBuf::from)
|
||||
@@ -375,8 +397,17 @@ fn build_enhanced_path() -> String {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 扫描常见盘符下的 Node 安装(用户可能装在 D:\、F:\ 等)
|
||||
// 6. npm 全局(openclaw.cmd 通常在这里)
|
||||
if !appdata.is_empty() {
|
||||
extra.push(format!(r"{}\npm", appdata));
|
||||
}
|
||||
// 7. 系统默认 Node.js 安装路径(优先级最低)
|
||||
extra.push(format!(r"{}\nodejs", pf));
|
||||
extra.push(format!(r"{}\nodejs", pf86));
|
||||
if !localappdata.is_empty() {
|
||||
extra.push(format!(r"{}\Programs\nodejs", localappdata));
|
||||
}
|
||||
// 8. 扫描常见盘符下的 Node 安装(用户可能装在 D:\、F:\ 等)
|
||||
for drive in &["C", "D", "E", "F"] {
|
||||
extra.push(format!(r"{}:\nodejs", drive));
|
||||
extra.push(format!(r"{}:\Node", drive));
|
||||
|
||||
@@ -8,20 +8,35 @@ use std::os::windows::process::CommandExt;
|
||||
/// 列出所有 Skills 及其状态(openclaw skills list --json)
|
||||
#[tauri::command]
|
||||
pub async fn skills_list() -> Result<Value, String> {
|
||||
let output = openclaw_command_async()
|
||||
.args(["skills", "list", "--json"])
|
||||
.output()
|
||||
.await;
|
||||
let output = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(15),
|
||||
openclaw_command_async()
|
||||
.args(["skills", "list", "--json"])
|
||||
.output(),
|
||||
)
|
||||
.await;
|
||||
|
||||
match output {
|
||||
Ok(o) if o.status.success() => {
|
||||
Ok(Ok(o)) => {
|
||||
let stdout = String::from_utf8_lossy(&o.stdout);
|
||||
// CLI output may contain non-JSON lines (Node warnings, update prompts).
|
||||
// Extract the first valid JSON object or array from stdout.
|
||||
extract_json(&stdout).ok_or_else(|| "解析失败: 输出中未找到有效 JSON".to_string())
|
||||
// CLI 可能在有 skill 缺依赖时返回非零退出码,但 JSON 输出仍然有效
|
||||
// 优先尝试解析 JSON,无论退出码
|
||||
match extract_json(&stdout) {
|
||||
Some(v) => Ok(v),
|
||||
None => {
|
||||
let stderr = String::from_utf8_lossy(&o.stderr);
|
||||
eprintln!(
|
||||
"[skills] CLI JSON 解析失败 (exit={}),兜底扫描。stdout={} stderr={}",
|
||||
o.status.code().unwrap_or(-1),
|
||||
stdout.chars().take(200).collect::<String>(),
|
||||
stderr.chars().take(200).collect::<String>()
|
||||
);
|
||||
scan_local_skills()
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// CLI 不可用时,兜底扫描本地 skills 目录
|
||||
// CLI 不可用或超时,兜底扫描本地 skills 目录
|
||||
scan_local_skills()
|
||||
}
|
||||
}
|
||||
@@ -144,14 +159,14 @@ pub async fn skills_skillhub_check() -> Result<Value, String> {
|
||||
#[cfg(target_os = "windows")]
|
||||
let mut cmd = {
|
||||
let mut c = tokio::process::Command::new("cmd");
|
||||
c.args(["/c", "skillhub", "--cli-version"]);
|
||||
c.args(["/c", "skillhub", "--version"]);
|
||||
c.creation_flags(0x08000000);
|
||||
c
|
||||
};
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let mut cmd = {
|
||||
let mut c = tokio::process::Command::new("skillhub");
|
||||
c.arg("--cli-version");
|
||||
c.arg("--version");
|
||||
c
|
||||
};
|
||||
cmd.env("PATH", &path_env);
|
||||
|
||||
@@ -89,6 +89,7 @@ pub fn run() {
|
||||
config::uninstall_gateway,
|
||||
config::patch_model_vision,
|
||||
config::check_panel_update,
|
||||
config::get_openclaw_dir,
|
||||
config::read_panel_config,
|
||||
config::write_panel_config,
|
||||
config::test_proxy,
|
||||
@@ -99,6 +100,9 @@ pub fn run() {
|
||||
config::configure_git_https,
|
||||
config::invalidate_path_cache,
|
||||
config::get_status_summary,
|
||||
config::doctor_fix,
|
||||
config::doctor_check,
|
||||
config::relaunch_app,
|
||||
// 设备密钥 + Gateway 握手
|
||||
device::create_connect_frame,
|
||||
// 设备配对
|
||||
@@ -178,6 +182,13 @@ pub fn run() {
|
||||
update::rollback_frontend_update,
|
||||
update::get_update_status,
|
||||
])
|
||||
.on_window_event(|window, event| {
|
||||
// 关闭窗口时最小化到托盘,不退出应用
|
||||
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
|
||||
api.prevent_close();
|
||||
let _ = window.hide();
|
||||
}
|
||||
})
|
||||
.build(tauri::generate_context!())
|
||||
.expect("启动 ClawPanel 失败")
|
||||
.run(|_app, event| {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/tauri-apps/tauri/dev/crates/tauri-config-schema/schema.json",
|
||||
"productName": "ClawPanel",
|
||||
"version": "0.9.6",
|
||||
"version": "0.9.7",
|
||||
"identifier": "ai.openclaw.clawpanel",
|
||||
"build": {
|
||||
"frontendDist": "../dist",
|
||||
|
||||
Reference in New Issue
Block a user