Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 115 additions & 19 deletions apps/codex-plus-manager/src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -918,6 +918,7 @@ pub struct SkillsPayload {
pub repository_skills: Vec<codex_plus_core::skills::SkillEntry>,
pub repo_counts: std::collections::BTreeMap<String, usize>,
pub backups: Vec<codex_plus_core::skills::SkillBackup>,
pub backups_ok: bool,
/// 单个仓库拉取失败不该让整块面板空掉,把错误单独带回前端提示。
pub repo_errors: Vec<String>,
pub skills_dir: String,
Expand Down Expand Up @@ -7606,6 +7607,75 @@ pub async fn discover_skill_source_evidence(id: String, local_path: Option<Strin
}).await.map_err(|error| format!("检查来源线索失败:{error}"))
}

#[tauri::command]
pub async fn preview_skill_in_place_update(
id: String,
repo_key: String,
local_path: Option<String>,
) -> CommandResult<codex_plus_core::skills::SkillInPlaceUpdatePreview> {
let manager = default_skills_manager();
let result = async {
let inventory = manager.try_local_inventory()?;
let repo_path = if let Some(path) = local_path.as_deref() {
let path = std::path::Path::new(path);
inventory.iter().filter(|skill| skill.id == id).find_map(|skill| {
skill.locations.iter()
.find(|location| location.original_path.as_deref() == Some(path) && location.repo_key == repo_key)
.map(|location| location.repo_path.clone())
.or_else(|| (skill.original_dir.as_deref() == Some(path) && skill.repo_key == repo_key)
.then(|| skill.repo_path.clone()))
})
} else {
inventory.iter().find(|skill| skill.id == id && skill.repo_key == repo_key)
.map(|skill| skill.repo_path.clone())
}.ok_or_else(|| anyhow::anyhow!("未找到指定位置已关联的 Skill"))?;
let remote = resolve_skill_repo_for_operation(&manager, &repo_key).await?
.into_iter().find(|skill| skill.repo_path == repo_path)
.ok_or_else(|| anyhow::anyhow!("仓库中已找不到该 Skill 路径"))?;
let repo = manager.list_repos().into_iter().find(|repo| repo.key() == repo_key)
.ok_or_else(|| anyhow::anyhow!("仓库已不存在"))?;
let zip = codex_plus_core::skills::download_repo_zip(&repo).await?;
match local_path.as_deref() {
Some(path) => manager.preview_skill_in_place_update_for_instance(
&id, std::path::Path::new(path), remote, zip,
),
None => manager.preview_skill_in_place_update(&id, remote, zip),
}
}.await;
match result {
Ok(preview) => ok("请检查原目录及更新版本。", preview),
Err(error) => failed(
&format!("预览原位更新失败:{error:#}"),
codex_plus_core::skills::SkillInPlaceUpdatePreview::default(),
),
}
}

#[tauri::command]
pub fn discard_skill_in_place_update_preview(preview_id: String) {
codex_plus_core::skills::SkillsManager::discard_in_place_update_preview(&preview_id);
}

#[tauri::command]
pub async fn execute_skill_in_place_update(preview_id: String) -> CommandResult<SkillsPayload> {
let manager = default_skills_manager();
let result = async {
let expected = manager.in_place_update_preview_remote(&preview_id)?;
let observed = resolve_skill_repo_for_operation(&manager, &expected.repo_key).await?
.into_iter().find(|skill| skill.repo_path == expected.repo_path)
.ok_or_else(|| anyhow::anyhow!("预览后仓库中已找不到该 Skill,请重新预览"))?;
let worker = manager.clone();
tauri::async_runtime::spawn_blocking(move ||
worker.execute_skill_in_place_update(&preview_id, &observed)
).await.map_err(|error| anyhow::anyhow!("原位更新任务失败:{error}"))??;
Ok::<_, anyhow::Error>(())
}.await;
match result {
Ok(()) => ok("Skill 已原位更新,原版本已备份。", current_skills_payload(&manager)),
Err(error) => failed(&format!("原位更新失败:{error:#}"), current_skills_payload(&manager)),
}
}

/// 导入 cc-switch 已安装的 Skills。cc-switch 的 Skill 文件位于
/// `%USERPROFILE%/.cc-switch/skills/<id>/SKILL.md`,不在其 SQLite 数据库中。
#[tauri::command]
Expand Down Expand Up @@ -7828,27 +7898,43 @@ pub fn uninstall_skill(id: String) -> CommandResult<SkillsPayload> {
}

#[tauri::command]
pub fn restore_skill_backup(backup_id: String) -> CommandResult<SkillsPayload> {
let manager = default_skills_manager();
match manager.restore_backup(backup_id.trim()) {
Ok(_) => ok("Skill 已从备份恢复。", current_skills_payload(&manager)),
Err(error) => failed(
&format!("从备份恢复失败:{error}"),
current_skills_payload(&manager),
),
}
pub async fn restore_skill_backup(backup_id: String) -> Result<CommandResult<SkillsPayload>, String> {
tauri::async_runtime::spawn_blocking(move || {
let manager = default_skills_manager();
match manager.restore_backup(backup_id.trim()) {
Ok(_) => ok("Skill 已从备份恢复。", current_skills_payload(&manager)),
Err(error) => failed(
&format!("从备份恢复失败:{error}"),
current_skills_payload(&manager),
),
}
}).await.map_err(|error| format!("从备份恢复失败:{error}"))
}

#[tauri::command]
pub fn delete_skill_backup(backup_id: String) -> CommandResult<SkillsPayload> {
let manager = default_skills_manager();
match manager.delete_backup(backup_id.trim()) {
Ok(_) => ok("备份已删除。", current_skills_payload(&manager)),
Err(error) => failed(
&format!("删除备份失败:{error}"),
current_skills_payload(&manager),
),
}
pub async fn delete_skill_backup(
backup_id: String,
missing_only: Option<bool>,
) -> Result<CommandResult<SkillsPayload>, String> {
tauri::async_runtime::spawn_blocking(move || {
let manager = default_skills_manager();
let missing_only = missing_only.unwrap_or(false);
let result = if missing_only {
manager.delete_missing_backup(backup_id.trim())
} else {
manager.delete_backup(backup_id.trim())
};
match result {
Ok(_) => ok(
if missing_only { "已清除丢失的备份记录。" } else { "备份已删除。" },
current_skills_payload(&manager),
),
Err(error) => failed(
&format!("删除备份失败:{error}"),
current_skills_payload(&manager),
),
}
}).await.map_err(|error| format!("删除备份失败:{error}"))
}

#[tauri::command]
Expand Down Expand Up @@ -8080,13 +8166,23 @@ fn skills_payload(
Vec::new()
}
};
let backups = manager.try_list_backups();
let backups_ok = backups.is_ok();
let backups = match backups {
Ok(backups) => backups,
Err(error) => {
repo_errors.push(format!("读取 Skill 备份失败:{error:#}"));
Vec::new()
}
};
SkillsPayload {
skills,
inventory_ok,
repos,
repository_skills,
repo_counts,
backups: manager.list_backups(),
backups,
backups_ok,
repo_errors,
skills_dir: manager.source_dir().to_string_lossy().to_string(),
codex_skills_dir: manager.linked_dir().to_string_lossy().to_string(),
Expand Down
5 changes: 5 additions & 0 deletions apps/codex-plus-manager/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub mod install;
mod lightweight;
mod provider_lookup_commands;
mod runtime_health;
mod skill_backup_usage;
mod tray_panel;
mod shutdown_watchdog;

Expand Down Expand Up @@ -591,6 +592,9 @@ pub fn run() {
commands::find_skill_repository_matches,
commands::apply_skill_repository_matches,
commands::discover_skill_source_evidence,
commands::preview_skill_in_place_update,
commands::discard_skill_in_place_update_preview,
commands::execute_skill_in_place_update,
commands::import_ccs_skills,
commands::import_local_skills,
commands::install_skill,
Expand All @@ -600,6 +604,7 @@ pub fn run() {
commands::uninstall_skill,
commands::restore_skill_backup,
commands::delete_skill_backup,
skill_backup_usage::skill_backup_usage,
commands::upsert_skill_repo,
commands::inspect_skill_repo,
commands::delete_skill_repo,
Expand Down
9 changes: 9 additions & 0 deletions apps/codex-plus-manager/src-tauri/src/skill_backup_usage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#[tauri::command]
pub async fn skill_backup_usage() -> Result<codex_plus_core::skills::SkillBackupUsage, String> {
tauri::async_runtime::spawn_blocking(|| {
crate::commands::default_skills_manager().backup_usage()
.map_err(|error| format!("统计 Skill 备份失败:{error:#}"))
})
.await
.map_err(|error| format!("统计 Skill 备份失败:{error}"))?
}
Loading