Files
mfakbar127-Claude-Samurai/src/lib/query.ts
T
2025-10-29 00:05:40 +08:00

579 lines
15 KiB
TypeScript

import {
useMutation,
useQuery,
useQueryClient,
useSuspenseQuery,
} from "@tanstack/react-query";
import { invoke } from "@tauri-apps/api/core";
import { nanoid } from "nanoid";
import { toast } from "sonner";
import i18n from "../i18n";
export type ConfigType =
| "user"
| "enterprise_macos"
| "enterprise_linux"
| "enterprise_windows"
| "mcp_macos"
| "mcp_linux"
| "mcp_windows";
export interface ConfigFile {
path: string;
content: unknown;
exists: boolean;
}
export interface ClaudeSettings {
model?: string;
permissions?: Record<string, any>;
env?: Record<string, string>;
[key: string]: any;
}
export interface ConfigStore {
id: string; // nanoid(6)
title: string;
createdAt: number;
settings: ClaudeSettings;
using: boolean;
}
export interface McpServer {
config: Record<string, any>;
}
export interface NotificationSettings {
enable: boolean;
enabled_hooks: string[];
}
export interface CommandFile {
name: string;
content: string;
exists: boolean;
}
export const useConfigFiles = () => {
return useQuery({
queryKey: ["config-files"],
queryFn: () => invoke<ConfigType[]>("list_config_files"),
});
};
export const useConfigFile = (configType: ConfigType) => {
return useQuery({
queryKey: ["config-file", configType],
queryFn: () => invoke<ConfigFile>("read_config_file", { configType }),
enabled: !!configType,
});
};
export const useWriteConfigFile = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
configType,
content,
}: {
configType: ConfigType;
content: unknown;
}) => invoke<void>("write_config_file", { configType, content }),
onSuccess: (_, variables) => {
toast.success(
i18n.t("toast.configSaved", { configType: variables.configType }),
);
queryClient.invalidateQueries({
queryKey: ["config-file", variables.configType],
});
queryClient.invalidateQueries({ queryKey: ["config-files"] });
},
onError: (error) => {
const errorMessage =
error instanceof Error ? error.message : String(error);
toast.error(i18n.t("toast.configSaveFailed", { error: errorMessage }));
},
});
};
export const useBackupClaudeConfigs = () => {
return useMutation({
mutationFn: () => invoke<void>("backup_claude_configs"),
onSuccess: () => {
toast.success(i18n.t("toast.backupSuccess"));
},
onError: (error) => {
const errorMessage =
error instanceof Error ? error.message : String(error);
toast.error(i18n.t("toast.backupFailed", { error: errorMessage }));
},
});
};
// Store management hooks
export const useStores = (options?: { storeId?: string }) => {
return useSuspenseQuery({
queryKey: ["stores", options?.storeId],
queryFn: () => invoke<ConfigStore[]>("get_stores"),
});
};
export const useStore = (storeId: string) => {
return useSuspenseQuery({
queryKey: ["store", storeId],
queryFn: () => invoke<ConfigStore>("get_store", { storeId }),
});
};
export const useCurrentStore = () => {
return useSuspenseQuery({
queryKey: ["current-store"],
queryFn: () => invoke<ConfigStore | null>("get_current_store"),
});
};
export const useCreateConfig = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({
title,
settings,
}: {
title: string;
settings: unknown;
}) => {
const id = nanoid(6);
return invoke<ConfigStore>("create_config", { id, title, settings });
},
onSuccess: async () => {
toast.success(i18n.t("toast.storeCreated"));
queryClient.invalidateQueries({ queryKey: ["stores"] });
queryClient.invalidateQueries({ queryKey: ["current-store"] });
await rebuildTrayMenu();
},
onError: (error) => {
const errorMessage =
error instanceof Error ? error.message : String(error);
toast.error(i18n.t("toast.storeCreateFailed", { error: errorMessage }));
},
});
};
export const useDeleteConfig = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (body: { storeId: string }) =>
invoke<void>("delete_config", {
storeId: body.storeId,
}),
onSuccess: async () => {
toast.success(i18n.t("toast.storeDeleted"));
queryClient.invalidateQueries({ queryKey: ["stores"] });
queryClient.invalidateQueries({ queryKey: ["current-store"] });
await rebuildTrayMenu();
},
onError: (error) => {
const errorMessage =
error instanceof Error ? error.message : String(error);
toast.error(i18n.t("toast.storeDeleteFailed", { error: errorMessage }));
},
});
};
export const useSetUsingConfig = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (storeId: string) =>
invoke<void>("set_using_config", { storeId }),
onSuccess: () => {
toast.success(i18n.t("toast.storeActivated"));
queryClient.invalidateQueries({ queryKey: ["stores"] });
queryClient.invalidateQueries({ queryKey: ["current-store"] });
queryClient.invalidateQueries({ queryKey: ["config-file", "user"] });
},
onError: (error) => {
const errorMessage =
error instanceof Error ? error.message : String(error);
toast.error(i18n.t("toast.storeActivateFailed", { error: errorMessage }));
},
});
};
export const useSetCurrentConfig = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (storeId: string) =>
invoke<void>("set_using_config", { storeId }),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["stores"] });
queryClient.invalidateQueries({ queryKey: ["current-store"] });
queryClient.invalidateQueries({ queryKey: ["config-file", "user"] });
},
});
};
export const useResetToOriginalConfig = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: () => invoke<void>("reset_to_original_config"),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["stores"] });
queryClient.invalidateQueries({ queryKey: ["current-store"] });
queryClient.invalidateQueries({ queryKey: ["config-file", "user"] });
},
});
};
export const useUpdateConfig = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
storeId,
title,
settings,
}: {
storeId: string;
title: string;
settings: unknown;
}) => invoke<ConfigStore>("update_config", { storeId, title, settings }),
onSuccess: async (data) => {
if (data.using) {
toast.success(
i18n.t("toast.storeSavedAndActive", { title: data.title }),
);
} else {
toast.success(i18n.t("toast.storeSaved", { title: data.title }));
}
queryClient.invalidateQueries({ queryKey: ["stores"] });
queryClient.invalidateQueries({ queryKey: ["store", data.id] });
queryClient.invalidateQueries({ queryKey: ["current-store"] });
if (data.using) {
queryClient.invalidateQueries({ queryKey: ["config-file", "user"] });
}
await rebuildTrayMenu();
},
onError: (error) => {
const errorMessage =
error instanceof Error ? error.message : String(error);
toast.error(i18n.t("toast.storeSaveFailed", { error: errorMessage }));
},
});
};
export interface UpdateInfo {
available: boolean;
version?: string;
body?: string;
date?: string;
}
export const useCheckForUpdates = () => {
return useQuery({
queryKey: ["check-updates"],
queryFn: () => invoke<UpdateInfo>("check_for_updates"),
refetchInterval: 1000 * 60 * 30, // Check every 30 minutes
retry: 1,
refetchOnWindowFocus: false,
});
};
export const useInstallAndRestart = () => {
return useMutation({
mutationFn: () => {
console.log("🚀 Frontend: Starting update installation");
return invoke<void>("install_and_restart");
},
onSuccess: () => {
console.log(
"✅ Frontend: Update installation completed, preparing to restart",
);
toast.success(i18n.t("toast.updateInstalled"));
},
onError: (error) => {
console.log("❌ Frontend: Update installation failed", error);
const errorMessage =
error instanceof Error ? error.message : String(error);
toast.error(i18n.t("toast.updateInstallFailed", { error: errorMessage }));
},
});
};
// MCP Server management hooks
export const useGlobalMcpServers = () => {
return useSuspenseQuery({
queryKey: ["global-mcp-servers"],
queryFn: () => invoke<Record<string, McpServer>>("get_global_mcp_servers"),
});
};
export const useUpdateGlobalMcpServer = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
serverName,
serverConfig,
}: {
serverName: string;
serverConfig: Record<string, any>;
}) =>
invoke<void>("update_global_mcp_server", { serverName, serverConfig }),
onSuccess: () => {
toast.success("MCP server configuration updated successfully");
queryClient.invalidateQueries({ queryKey: ["global-mcp-servers"] });
},
onError: (error) => {
const errorMessage =
error instanceof Error ? error.message : String(error);
toast.error(`Failed to update MCP server: ${errorMessage}`);
},
});
};
export const useAddGlobalMcpServer = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
serverName,
serverConfig,
}: {
serverName: string;
serverConfig: Record<string, any>;
}) =>
invoke<void>("update_global_mcp_server", { serverName, serverConfig }),
onSuccess: () => {
toast.success("MCP server added successfully");
queryClient.invalidateQueries({ queryKey: ["global-mcp-servers"] });
},
onError: (error) => {
const errorMessage =
error instanceof Error ? error.message : String(error);
toast.error(`Failed to add MCP server: ${errorMessage}`);
},
});
};
export const useCheckMcpServerExists = (
serverName: string,
options?: { enabled?: boolean },
) => {
return useQuery({
queryKey: ["check-mcp-server-exists", serverName],
queryFn: () => invoke<boolean>("check_mcp_server_exists", { serverName }),
enabled: options?.enabled !== false && !!serverName, // Only run when serverName is provided
});
};
export const useDeleteGlobalMcpServer = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (serverName: string) =>
invoke<void>("delete_global_mcp_server", { serverName }),
onSuccess: () => {
toast.success("MCP server deleted successfully");
queryClient.invalidateQueries({ queryKey: ["global-mcp-servers"] });
},
onError: (error) => {
const errorMessage =
error instanceof Error ? error.message : String(error);
toast.error(`Failed to delete MCP server: ${errorMessage}`);
},
});
};
export interface UsageData {
input_tokens?: number;
cache_read_input_tokens?: number;
output_tokens?: number;
}
export interface ProjectUsageRecord {
uuid: string;
timestamp: string;
model?: string;
usage?: UsageData;
}
export const useProjectUsageFiles = () => {
return useQuery({
queryKey: ["project-usage-files"],
queryFn: () => invoke<ProjectUsageRecord[]>("read_project_usage_files"),
});
};
// Memory management hooks
export interface MemoryFile {
path: string;
content: string;
exists: boolean;
}
export const useClaudeMemory = () => {
return useSuspenseQuery({
queryKey: ["claude-memory"],
queryFn: () => invoke<MemoryFile>("read_claude_memory"),
});
};
export const useWriteClaudeMemory = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (content: string) =>
invoke<void>("write_claude_memory", { content }),
onSuccess: () => {
toast.success("Memory saved successfully");
queryClient.invalidateQueries({ queryKey: ["claude-memory"] });
},
onError: (error) => {
const errorMessage =
error instanceof Error ? error.message : String(error);
toast.error(`Failed to save memory: ${errorMessage}`);
},
});
};
// Projects management hooks
export interface ProjectConfig {
path: string;
config: Record<string, any>;
}
export const useClaudeProjects = () => {
return useQuery({
queryKey: ["claude-projects"],
queryFn: () => invoke<ProjectConfig[]>("read_claude_projects"),
});
};
export interface ClaudeConfigFile {
path: string;
content: unknown;
exists: boolean;
}
export const useClaudeConfigFile = () => {
return useQuery({
queryKey: ["claude-config-file"],
queryFn: () => invoke<ClaudeConfigFile>("read_claude_config_file"),
});
};
export const useWriteClaudeConfigFile = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (content: unknown) =>
invoke<void>("write_claude_config_file", { content }),
onSuccess: () => {
toast.success("Claude configuration saved successfully");
queryClient.invalidateQueries({ queryKey: ["claude-config-file"] });
queryClient.invalidateQueries({ queryKey: ["claude-projects"] });
},
onError: (error) => {
const errorMessage =
error instanceof Error ? error.message : String(error);
toast.error(`Failed to save Claude configuration: ${errorMessage}`);
},
});
};
// Notification settings hooks
export const useNotificationSettings = () => {
return useQuery({
queryKey: ["notification-settings"],
queryFn: () =>
invoke<NotificationSettings | null>("get_notification_settings"),
});
};
export const useUpdateNotificationSettings = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (settings: NotificationSettings) => {
return invoke<void>("update_notification_settings", { settings });
},
onSuccess: () => {
toast.success("Notification settings updated successfully");
queryClient.invalidateQueries({ queryKey: ["notification-settings"] });
},
onError: (error) => {
const errorMessage =
error instanceof Error ? error.message : String(error);
toast.error(`Failed to update notification settings: ${errorMessage}`);
},
});
};
// Command management hooks
export const useClaudeCommands = () =>
useQuery({
queryKey: ["claude-commands"],
queryFn: () => invoke<CommandFile[]>("read_claude_commands"),
});
export const useWriteClaudeCommand = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
commandName,
content,
}: {
commandName: string;
content: string;
}) => invoke<void>("write_claude_command", { commandName, content }),
onSuccess: () => {
toast.success(i18n.t("toast.commandSaved"));
queryClient.invalidateQueries({ queryKey: ["claude-commands"] });
},
onError: (error) => {
const errorMessage =
error instanceof Error ? error.message : String(error);
toast.error(i18n.t("toast.commandSaveFailed", { error: errorMessage }));
},
});
};
export const useDeleteClaudeCommand = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (commandName: string) =>
invoke<void>("delete_claude_command", { commandName }),
onSuccess: () => {
toast.success(i18n.t("toast.commandDeleted"));
queryClient.invalidateQueries({ queryKey: ["claude-commands"] });
},
onError: (error) => {
const errorMessage =
error instanceof Error ? error.message : String(error);
toast.error(i18n.t("toast.commandDeleteFailed", { error: errorMessage }));
},
});
};
// Helper function to rebuild tray menu
const rebuildTrayMenu = async () => {
try {
await invoke<void>("rebuild_tray_menu_command");
} catch (error) {
console.error("Failed to rebuild tray menu:", error);
}
};