import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import { providersApi, settingsApi, type AppId } from "@/lib/api"; import type { SwitchResult } from "@/lib/api/providers"; import type { Provider, Settings } from "@/types"; import { extractErrorMessage } from "@/utils/errorUtils"; import { generateUUID } from "@/utils/uuid"; import { openclawKeys } from "@/hooks/useOpenClaw"; export const useAddProviderMutation = (appId: AppId) => { const queryClient = useQueryClient(); const { t } = useTranslation(); return useMutation({ mutationFn: async ( providerInput: Omit & { providerKey?: string }, ) => { let id: string; if (appId === "opencode" || appId === "openclaw") { if (providerInput.category === "omo") { id = `omo-${generateUUID()}`; } else { if (!providerInput.providerKey) { throw new Error(`Provider key is required for ${appId}`); } id = providerInput.providerKey; } } else { id = generateUUID(); } const { providerKey: _providerKey, ...rest } = providerInput; const newProvider: Provider = { ...rest, id, createdAt: Date.now(), }; delete (newProvider as any).providerKey; await providersApi.add(newProvider, appId); return newProvider; }, onSuccess: async () => { await queryClient.invalidateQueries({ queryKey: ["providers", appId] }); if (appId === "opencode") { await queryClient.invalidateQueries({ queryKey: ["omo", "current-provider-id"], }); await queryClient.invalidateQueries({ queryKey: ["omo", "provider-count"], }); } try { await providersApi.updateTrayMenu(); } catch (trayError) { console.error( "Failed to update tray menu after adding provider", trayError, ); } toast.success( t("notifications.providerAdded", { defaultValue: "供应商已添加", }), { closeButton: true, }, ); }, onError: (error: Error) => { const detail = extractErrorMessage(error) || t("common.unknown"); toast.error( t("notifications.addFailed", { defaultValue: "添加供应商失败: {{error}}", error: detail, }), ); }, }); }; export const useUpdateProviderMutation = (appId: AppId) => { const queryClient = useQueryClient(); const { t } = useTranslation(); return useMutation({ mutationFn: async (provider: Provider) => { await providersApi.update(provider, appId); return provider; }, onSuccess: async () => { await queryClient.invalidateQueries({ queryKey: ["providers", appId] }); toast.success( t("notifications.updateSuccess", { defaultValue: "供应商更新成功", }), { closeButton: true, }, ); }, onError: (error: Error) => { const detail = extractErrorMessage(error) || t("common.unknown"); toast.error( t("notifications.updateFailed", { defaultValue: "更新供应商失败: {{error}}", error: detail, }), ); }, }); }; export const useDeleteProviderMutation = (appId: AppId) => { const queryClient = useQueryClient(); const { t } = useTranslation(); return useMutation({ mutationFn: async (providerId: string) => { await providersApi.delete(providerId, appId); }, onSuccess: async () => { await queryClient.invalidateQueries({ queryKey: ["providers", appId] }); if (appId === "opencode") { await queryClient.invalidateQueries({ queryKey: ["omo", "current-provider-id"], }); await queryClient.invalidateQueries({ queryKey: ["omo", "provider-count"], }); } try { await providersApi.updateTrayMenu(); } catch (trayError) { console.error( "Failed to update tray menu after deleting provider", trayError, ); } toast.success( t("notifications.deleteSuccess", { defaultValue: "供应商已删除", }), { closeButton: true, }, ); }, onError: (error: Error) => { const detail = extractErrorMessage(error) || t("common.unknown"); toast.error( t("notifications.deleteFailed", { defaultValue: "删除供应商失败: {{error}}", error: detail, }), ); }, }); }; export const useSwitchProviderMutation = (appId: AppId) => { const queryClient = useQueryClient(); const { t } = useTranslation(); return useMutation({ mutationFn: async (providerId: string): Promise => { return await providersApi.switch(providerId, appId); }, onSuccess: async () => { await queryClient.invalidateQueries({ queryKey: ["providers", appId] }); // OpenCode/OpenClaw: also invalidate live provider IDs cache to update button state if (appId === "opencode") { await queryClient.invalidateQueries({ queryKey: ["opencodeLiveProviderIds"], }); await queryClient.invalidateQueries({ queryKey: ["omo", "current-provider-id"], }); } if (appId === "openclaw") { await queryClient.invalidateQueries({ queryKey: openclawKeys.liveProviderIds, }); await queryClient.invalidateQueries({ queryKey: openclawKeys.defaultModel, }); } try { await providersApi.updateTrayMenu(); } catch (trayError) { console.error( "Failed to update tray menu after switching provider", trayError, ); } }, onError: (error: Error) => { const detail = extractErrorMessage(error) || t("common.unknown"); toast.error( t("notifications.switchFailedTitle", { defaultValue: "切换失败" }), { description: t("notifications.switchFailed", { defaultValue: "切换失败:{{error}}", error: detail, }), duration: 6000, action: { label: t("common.copy", { defaultValue: "复制" }), onClick: () => { navigator.clipboard?.writeText(detail).catch(() => undefined); }, }, }, ); }, }); }; export const useSaveSettingsMutation = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (settings: Settings) => { await settingsApi.save(settings); }, onSuccess: async () => { await queryClient.invalidateQueries({ queryKey: ["settings"] }); }, }); };