mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 12:44:18 +08:00
65a5464fcc
Claude Desktop's only dimension managed by cc-switch is its provider (MCP/Skills are hardcoded unsupported and prompts have no live file), so snapshots capture just the current desktop provider while the empty MCP/Skills sets and None prompt make apply a natural no-op for the other dimensions - no per-dimension special casing needed. - Add claude-desktop slot to PROFILE_APPS and PerApp (serde key uses the hyphenated app id); old payloads without the key deserialize to None and leave Claude Desktop untouched on apply - Gate the tray Projects submenu by iterating PROFILE_APPS instead of hardcoding Claude/Codex visibility - Show the profile switcher on the Claude Desktop tab, mirror the PerApp type, invalidate the claude-desktop providers cache on apply, and extend the switcher tooltip in all four locales - Extend the roundtrip integration test with the desktop provider dimension; the desktop switch is cfg-gated to macOS/Windows because desktop live writes error on Linux where CI runs cargo test
149 lines
4.5 KiB
TypeScript
149 lines
4.5 KiB
TypeScript
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
import { useTranslation } from "react-i18next";
|
|
import { toast } from "sonner";
|
|
import { profilesApi, providersApi } from "@/lib/api";
|
|
import { extractErrorMessage } from "@/utils/errorUtils";
|
|
|
|
const updateTrayMenuSafely = async () => {
|
|
try {
|
|
await providersApi.updateTrayMenu();
|
|
} catch (trayError) {
|
|
console.error("Failed to update tray menu after profile change", trayError);
|
|
}
|
|
};
|
|
|
|
export const useProfilesQuery = () => {
|
|
return useQuery({
|
|
queryKey: ["profiles"],
|
|
queryFn: () => profilesApi.list(),
|
|
});
|
|
};
|
|
|
|
export const useCreateProfileMutation = () => {
|
|
const queryClient = useQueryClient();
|
|
const { t } = useTranslation();
|
|
|
|
return useMutation({
|
|
mutationFn: (name: string) => profilesApi.create(name),
|
|
onSuccess: async () => {
|
|
await queryClient.invalidateQueries({ queryKey: ["profiles"] });
|
|
await updateTrayMenuSafely();
|
|
toast.success(t("profiles.createSuccess"), { closeButton: true });
|
|
},
|
|
onError: (error: Error) => {
|
|
const detail = extractErrorMessage(error) || t("common.unknown");
|
|
toast.error(t("profiles.createFailed", { detail }), {
|
|
closeButton: true,
|
|
});
|
|
},
|
|
});
|
|
};
|
|
|
|
export const useUpdateProfileMutation = () => {
|
|
const queryClient = useQueryClient();
|
|
const { t } = useTranslation();
|
|
|
|
return useMutation({
|
|
mutationFn: ({
|
|
id,
|
|
name,
|
|
resnapshot,
|
|
}: {
|
|
id: string;
|
|
name?: string;
|
|
resnapshot?: boolean;
|
|
}) => profilesApi.update(id, { name, resnapshot }),
|
|
onSuccess: async () => {
|
|
await queryClient.invalidateQueries({ queryKey: ["profiles"] });
|
|
await updateTrayMenuSafely();
|
|
toast.success(t("profiles.updateSuccess"), { closeButton: true });
|
|
},
|
|
onError: (error: Error) => {
|
|
const detail = extractErrorMessage(error) || t("common.unknown");
|
|
toast.error(t("profiles.updateFailed", { detail }), {
|
|
closeButton: true,
|
|
});
|
|
},
|
|
});
|
|
};
|
|
|
|
export const useDeleteProfileMutation = () => {
|
|
const queryClient = useQueryClient();
|
|
const { t } = useTranslation();
|
|
|
|
return useMutation({
|
|
mutationFn: (id: string) => profilesApi.delete(id),
|
|
onSuccess: async () => {
|
|
await queryClient.invalidateQueries({ queryKey: ["profiles"] });
|
|
await updateTrayMenuSafely();
|
|
toast.success(t("profiles.deleteSuccess"), { closeButton: true });
|
|
},
|
|
onError: (error: Error) => {
|
|
const detail = extractErrorMessage(error) || t("common.unknown");
|
|
toast.error(t("profiles.deleteFailed", { detail }), {
|
|
closeButton: true,
|
|
});
|
|
},
|
|
});
|
|
};
|
|
|
|
export const useClearProfileMutation = () => {
|
|
const queryClient = useQueryClient();
|
|
const { t } = useTranslation();
|
|
|
|
return useMutation({
|
|
mutationFn: () => profilesApi.clearCurrent(),
|
|
onSuccess: async () => {
|
|
await queryClient.invalidateQueries({ queryKey: ["profiles"] });
|
|
await updateTrayMenuSafely();
|
|
toast.success(t("profiles.clearSuccess"), { closeButton: true });
|
|
},
|
|
onError: (error: Error) => {
|
|
const detail = extractErrorMessage(error) || t("common.unknown");
|
|
toast.error(t("profiles.applyFailed", { detail }), {
|
|
closeButton: true,
|
|
});
|
|
},
|
|
});
|
|
};
|
|
|
|
export const useApplyProfileMutation = () => {
|
|
const queryClient = useQueryClient();
|
|
const { t } = useTranslation();
|
|
|
|
return useMutation({
|
|
mutationFn: (id: string) => profilesApi.apply(id),
|
|
onSuccess: async (warnings) => {
|
|
await queryClient.invalidateQueries({ queryKey: ["profiles"] });
|
|
await queryClient.invalidateQueries({
|
|
queryKey: ["providers", "claude"],
|
|
});
|
|
await queryClient.invalidateQueries({
|
|
queryKey: ["providers", "claude-desktop"],
|
|
});
|
|
await queryClient.invalidateQueries({ queryKey: ["providers", "codex"] });
|
|
await queryClient.invalidateQueries({ queryKey: ["mcp", "all"] });
|
|
await queryClient.invalidateQueries({ queryKey: ["skills"] });
|
|
await updateTrayMenuSafely();
|
|
|
|
if (warnings.length > 0) {
|
|
toast.warning(
|
|
t("profiles.applyWarnings", {
|
|
warningCount: warnings.length,
|
|
details: warnings.join("\n"),
|
|
}),
|
|
{ closeButton: true, duration: 10000 },
|
|
);
|
|
} else {
|
|
toast.success(t("profiles.applySuccess"), { closeButton: true });
|
|
}
|
|
},
|
|
onError: (error: Error) => {
|
|
const detail = extractErrorMessage(error) || t("common.unknown");
|
|
toast.error(t("profiles.applyFailed", { detail }), {
|
|
closeButton: true,
|
|
});
|
|
},
|
|
});
|
|
};
|