313 lines
9.5 KiB
TypeScript
313 lines
9.5 KiB
TypeScript
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<T>(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<typeof profileApi.update>[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, // 翻页时保留旧数据,避免闪烁
|
|
});
|
|
}
|