import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { authApi, chatApi, knowledgeApi, marketApi, imBotApi, profileApi, tokenApi, type TokenOverview, } from '@/api/client'; // ─── Auth ─────────────────────────────────────────────────────────────────────── export function useLogin() { const qc = useQueryClient(); return useMutation({ mutationFn: (data: { username: string; password: string }) => authApi.login(data), onSuccess: (res) => { localStorage.setItem('auth_token', res.access_token); qc.invalidateQueries({ queryKey: ['auth', 'me'] }); }, }); } export function useCurrentUser() { return useQuery({ queryKey: ['auth', 'me'], queryFn: authApi.me, retry: false, enabled: !!localStorage.getItem('auth_token'), }); } // ─── Chat ──────────────────────────────────────────────────────────────────────── export function useConversations() { return useQuery({ queryKey: ['chat', 'conversations'], queryFn: chatApi.listConversations, }); } export function useMessages(convId: string | null) { return useQuery({ queryKey: ['chat', 'messages', convId], queryFn: () => chatApi.getMessages(convId!), enabled: !!convId, }); } export function useCreateConversation() { const qc = useQueryClient(); return useMutation({ mutationFn: (data: { title?: string; model?: string }) => chatApi.createConversation(data), onSuccess: () => qc.invalidateQueries({ queryKey: ['chat', 'conversations'] }), }); } export function useSendMessage(convId: string) { const qc = useQueryClient(); return useMutation({ mutationFn: (data: { content: string; model?: string }) => chatApi.sendMessage(convId, data), onSuccess: () => qc.invalidateQueries({ queryKey: ['chat', 'messages', convId] }), }); } export function useDeleteConversation() { const qc = useQueryClient(); return useMutation({ mutationFn: (id: string) => chatApi.deleteConversation(id), onSuccess: () => qc.invalidateQueries({ queryKey: ['chat', 'conversations'] }), }); } export function useModels() { return useQuery({ queryKey: ['chat', 'models'], queryFn: chatApi.getModels, }); } // ─── Knowledge ─────────────────────────────────────────────────────────────────── export function useKnowledgeBases() { return useQuery({ queryKey: ['knowledge', 'bases'], queryFn: knowledgeApi.listBases, }); } export function useCreateKnowledgeBase() { const qc = useQueryClient(); return useMutation({ mutationFn: (data: { name: string; description?: string }) => knowledgeApi.createBase(data), onSuccess: () => qc.invalidateQueries({ queryKey: ['knowledge', 'bases'] }), }); } export function useDeleteKnowledgeBase() { const qc = useQueryClient(); return useMutation({ mutationFn: (id: string) => knowledgeApi.deleteBase(id), onSuccess: () => qc.invalidateQueries({ queryKey: ['knowledge', 'bases'] }), }); } export function useKnowledgeSearch(baseId: string | null, query: string) { return useQuery({ queryKey: ['knowledge', 'search', baseId, query], queryFn: () => knowledgeApi.search(baseId!, query), enabled: !!baseId && query.length > 0, }); } // ─── Market / Apps ────────────────────────────────────────────────────────────── // Backend list endpoints may return a bare array, `{data:[...]}`, or an error // object. Coerce to a real array so consumers can always call .slice/.map/.filter. function asArray(v: unknown): T[] { if (Array.isArray(v)) return v as T[]; if (v && typeof v === 'object' && Array.isArray((v as any).data)) return (v as any).data; if (v && typeof v === 'object' && Array.isArray((v as any).items)) return (v as any).items; return []; } export function useMarketApps() { return useQuery({ queryKey: ['market', 'apps'], queryFn: marketApi.listApps, select: (d: unknown) => asArray(d), }); } export function useMyApps() { return useQuery({ queryKey: ['market', 'my-apps'], queryFn: marketApi.getMyApps, select: (d: unknown) => asArray(d), }); } export function useInstallApp() { const qc = useQueryClient(); return useMutation({ mutationFn: (appId: string) => marketApi.installApp(appId), onSuccess: () => { qc.invalidateQueries({ queryKey: ['market', 'my-apps'] }); qc.invalidateQueries({ queryKey: ['market', 'apps'] }); }, }); } // ─── IM Bot ────────────────────────────────────────────────────────────────────── export function useImBotStatus() { return useQuery({ queryKey: ['im-bot', 'status'], queryFn: imBotApi.getStatus, refetchInterval: 30000, // poll every 30s }); } export function useImBotQRCode() { return useQuery({ queryKey: ['im-bot', 'qrcode'], queryFn: imBotApi.getQRCode, enabled: false, // only on demand }); } export function useDisconnectBot() { const qc = useQueryClient(); return useMutation({ mutationFn: () => imBotApi.disconnect(), onSuccess: () => qc.invalidateQueries({ queryKey: ['im-bot', 'status'] }), }); } // ─── Profile ───────────────────────────────────────────────────────────────────── export function useProfile() { return useQuery({ queryKey: ['profile'], queryFn: profileApi.get, }); } export function useUpdateProfile() { const qc = useQueryClient(); return useMutation({ mutationFn: (data: Parameters[0]) => profileApi.update(data), onSuccess: () => qc.invalidateQueries({ queryKey: ['profile'] }), }); } export function useProfileBalance() { return useQuery({ queryKey: ['profile', 'balance'], queryFn: profileApi.getBalance, }); } export function useProfileMembership() { return useQuery({ queryKey: ['profile', 'membership'], queryFn: profileApi.getMembership, }); } export function useProfileLoginLogs(page = 1, page_size = 10) { return useQuery({ queryKey: ['profile', 'login-logs', page, page_size], queryFn: () => profileApi.getLoginLogs(page, page_size), }); } export function useProfileApiKeys() { return useQuery({ queryKey: ['profile', 'api-keys'], queryFn: profileApi.getApiKeys, }); } export function useProfilePurchasedApps() { return useQuery({ queryKey: ['profile', 'purchased-apps'], queryFn: profileApi.getPurchasedApps, }); } // ─── Profile 写操作 ───────────────────────────────────────────────────────────── export function useUpdateProfilePassword() { return useMutation({ mutationFn: (data: { old_password: string; new_password: string }) => profileApi.updatePassword(data), }); } export function useCreateApiKey() { const qc = useQueryClient(); return useMutation({ mutationFn: (name: string) => profileApi.createApiKey(name), onSuccess: () => qc.invalidateQueries({ queryKey: ['profile', 'api-keys'] }), }); } export function useUpdateApiKey() { const qc = useQueryClient(); return useMutation({ mutationFn: ({ keyId, status }: { keyId: string; status: 'enabled' | 'disabled' | 'revoked' }) => profileApi.updateApiKey(keyId, status), onSuccess: () => qc.invalidateQueries({ queryKey: ['profile', 'api-keys'] }), }); } // ─── Token 驾驶舱 ───────────────────────────────────────────────────────────────── // Landing hero still shows a few headline numbers; reuse the real token overview // so the homepage never ships fake/mock values. export function useDashboardStats() { return useQuery({ queryKey: ['token', 'overview'], queryFn: tokenApi.getOverview, select: (d: TokenOverview) => ({ active_users: d.today_apps, today_conversations: d.today_apps, skill_count: d.total_used, revenue: d.balance, user_change: '', conv_change: '', }), }); } export function useTokenOverview() { return useQuery({ queryKey: ['token', 'overview'], queryFn: tokenApi.getOverview, refetchInterval: 60000, // 每分钟刷新一次概览 }); } export function useTokenRankings() { return useQuery({ queryKey: ['token', 'rankings'], queryFn: tokenApi.getRankings, }); } export function useTokenBalance() { return useQuery({ queryKey: ['token', 'balance'], queryFn: tokenApi.getBalance, }); } export function useTokenLogs(params: { start_date?: string; end_date?: string; model_id?: string; app_type?: string; page?: number; page_size?: number; }) { return useQuery({ queryKey: ['token', 'logs', params], queryFn: () => tokenApi.getLogs(params), placeholderData: (prev) => prev, // 翻页时保留旧数据,避免闪烁 }); }