#978 safe-align: 固化 frontend-v2 空白页修复真相源
- 新增 frontend-v2/ 为生产前端真相源(此前仅容器 dist,未入 git) - vite.config.ts: minify:false 根治压缩器压坏 react-router 6.28 (o.slice) - useApi.ts: asArray select 防御非裸数组返回值 - LandingPage/AppServices: Array.isArray coerce 双重防御 (apps.slice) - 关联: INCIDENT-2026-08-23-blank-page.md / SOP-FRONTEND-ITERATIVE-DEVELOPMENT.md - 公网验证通过 2026-08-23 09:31 (DOM 24493B, 无 slice 错误)
This commit is contained in:
1 parent
552a962123
commit
8f57956904
36 files changed
+7436
No files matched your search
@@ -0,0 +1,203 @@
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import {
|
||||
authApi,
|
||||
chatApi,
|
||||
knowledgeApi,
|
||||
marketApi,
|
||||
imBotApi,
|
||||
profileApi,
|
||||
dashboardApi,
|
||||
} 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) => asArray(d),
|
||||
});
|
||||
}
|
||||
|
||||
export function useMyApps() {
|
||||
return useQuery({
|
||||
queryKey: ['market', 'my-apps'],
|
||||
queryFn: marketApi.getMyApps,
|
||||
select: (d) => 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: Record<string, unknown>) => profileApi.update(data),
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: ['profile'] }),
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Dashboard ───────────────────────────────────────────────────────────────────
|
||||
|
||||
export function useDashboardStats() {
|
||||
return useQuery({
|
||||
queryKey: ['dashboard', 'stats'],
|
||||
queryFn: dashboardApi.getStats,
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user