mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-03-23 23:59:24 +08:00
Skills import previously inferred app enablement from filesystem presence, causing incorrect multi-app activation when the same skill directory existed under multiple app paths. Now the frontend submits explicit app selections via ImportSkillSelection, and schema migration preserves a snapshot of legacy app mappings to avoid lossy reconstruction. Also adds reconciliation to sync_to_app (removes disabled/orphaned symlinks) and MCP sync_all_enabled (removes disabled servers from live config).
174 lines
4.2 KiB
TypeScript
174 lines
4.2 KiB
TypeScript
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||
import {
|
||
skillsApi,
|
||
type DiscoverableSkill,
|
||
type ImportSkillSelection,
|
||
type InstalledSkill,
|
||
} from "@/lib/api/skills";
|
||
import type { AppId } from "@/lib/api/types";
|
||
|
||
/**
|
||
* 查询所有已安装的 Skills
|
||
*/
|
||
export function useInstalledSkills() {
|
||
return useQuery({
|
||
queryKey: ["skills", "installed"],
|
||
queryFn: () => skillsApi.getInstalled(),
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 发现可安装的 Skills(从仓库获取)
|
||
*/
|
||
export function useDiscoverableSkills() {
|
||
return useQuery({
|
||
queryKey: ["skills", "discoverable"],
|
||
queryFn: () => skillsApi.discoverAvailable(),
|
||
staleTime: Infinity, // 无限缓存,直到仓库变化时 invalidate
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 安装 Skill
|
||
*/
|
||
export function useInstallSkill() {
|
||
const queryClient = useQueryClient();
|
||
return useMutation({
|
||
mutationFn: ({
|
||
skill,
|
||
currentApp,
|
||
}: {
|
||
skill: DiscoverableSkill;
|
||
currentApp: AppId;
|
||
}) => skillsApi.installUnified(skill, currentApp),
|
||
onSuccess: () => {
|
||
queryClient.invalidateQueries({ queryKey: ["skills", "installed"] });
|
||
queryClient.invalidateQueries({ queryKey: ["skills", "discoverable"] });
|
||
},
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 卸载 Skill
|
||
*/
|
||
export function useUninstallSkill() {
|
||
const queryClient = useQueryClient();
|
||
return useMutation({
|
||
mutationFn: (id: string) => skillsApi.uninstallUnified(id),
|
||
onSuccess: () => {
|
||
queryClient.invalidateQueries({ queryKey: ["skills", "installed"] });
|
||
queryClient.invalidateQueries({ queryKey: ["skills", "discoverable"] });
|
||
},
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 切换 Skill 在特定应用的启用状态
|
||
*/
|
||
export function useToggleSkillApp() {
|
||
const queryClient = useQueryClient();
|
||
return useMutation({
|
||
mutationFn: ({
|
||
id,
|
||
app,
|
||
enabled,
|
||
}: {
|
||
id: string;
|
||
app: AppId;
|
||
enabled: boolean;
|
||
}) => skillsApi.toggleApp(id, app, enabled),
|
||
onSuccess: () => {
|
||
queryClient.invalidateQueries({ queryKey: ["skills", "installed"] });
|
||
},
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 扫描未管理的 Skills
|
||
*/
|
||
export function useScanUnmanagedSkills() {
|
||
return useQuery({
|
||
queryKey: ["skills", "unmanaged"],
|
||
queryFn: () => skillsApi.scanUnmanaged(),
|
||
enabled: false, // 手动触发
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 从应用目录导入 Skills
|
||
*/
|
||
export function useImportSkillsFromApps() {
|
||
const queryClient = useQueryClient();
|
||
return useMutation({
|
||
mutationFn: (imports: ImportSkillSelection[]) =>
|
||
skillsApi.importFromApps(imports),
|
||
onSuccess: () => {
|
||
queryClient.invalidateQueries({ queryKey: ["skills", "installed"] });
|
||
queryClient.invalidateQueries({ queryKey: ["skills", "unmanaged"] });
|
||
},
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 获取仓库列表
|
||
*/
|
||
export function useSkillRepos() {
|
||
return useQuery({
|
||
queryKey: ["skills", "repos"],
|
||
queryFn: () => skillsApi.getRepos(),
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 添加仓库
|
||
*/
|
||
export function useAddSkillRepo() {
|
||
const queryClient = useQueryClient();
|
||
return useMutation({
|
||
mutationFn: skillsApi.addRepo,
|
||
onSuccess: () => {
|
||
queryClient.invalidateQueries({ queryKey: ["skills", "repos"] });
|
||
queryClient.invalidateQueries({ queryKey: ["skills", "discoverable"] });
|
||
},
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 删除仓库
|
||
*/
|
||
export function useRemoveSkillRepo() {
|
||
const queryClient = useQueryClient();
|
||
return useMutation({
|
||
mutationFn: ({ owner, name }: { owner: string; name: string }) =>
|
||
skillsApi.removeRepo(owner, name),
|
||
onSuccess: () => {
|
||
queryClient.invalidateQueries({ queryKey: ["skills", "repos"] });
|
||
queryClient.invalidateQueries({ queryKey: ["skills", "discoverable"] });
|
||
},
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 从 ZIP 文件安装 Skills
|
||
*/
|
||
export function useInstallSkillsFromZip() {
|
||
const queryClient = useQueryClient();
|
||
return useMutation({
|
||
mutationFn: ({
|
||
filePath,
|
||
currentApp,
|
||
}: {
|
||
filePath: string;
|
||
currentApp: AppId;
|
||
}) => skillsApi.installFromZip(filePath, currentApp),
|
||
onSuccess: () => {
|
||
queryClient.invalidateQueries({ queryKey: ["skills", "installed"] });
|
||
queryClient.invalidateQueries({ queryKey: ["skills", "unmanaged"] });
|
||
},
|
||
});
|
||
}
|
||
|
||
// ========== 辅助类型 ==========
|
||
|
||
export type { InstalledSkill, DiscoverableSkill, ImportSkillSelection, AppId };
|