Files
deerflow-code/frontend-web/src/core/agents/hooks.ts
T
吉鑫升andClaude Opus 4.7 10e35055e0 新增智能体收藏、定时任务必须生成MD校验、问答时间显示及账号绑定的常用提示词
- 智能体支持从广场收藏到「我的」(新增 agent_favorites 表与收藏/取消收藏接口)
- 定时任务新增「必须生成Markdown文件」开关:注入提示词并校验生成的 .md 文件且正文超过100字,否则记为失败(存于 execution_context,无需迁移)
- 聊天问答展示时间(年月日 时分):问题在右下角、回答在左下角
- 常用提示词改为与账号绑定(新增 user_custom_prompts 表与 /api/user-prompts 接口),仅在首页输入框上方以居中标签 + 轮播展示,齿轮置于最右侧管理

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 17:16:15 +08:00

226 lines
6.8 KiB
TypeScript

import {
keepPreviousData,
useMutation,
useQuery,
useQueryClient,
} from "@tanstack/react-query";
import {
adminSyncAgentSkills,
createAgent,
deleteAgent,
favoriteAgent,
getAgent,
getAgentSelector,
listAgents,
listAllAgentsAdmin,
listPrivateAgents,
reorderFeaturedAgents,
setAgentFeatured,
setAgentPrivate,
unfavoriteAgent,
updateAgent,
} from "./api";
import type { Agent, CreateAgentRequest, UpdateAgentRequest } from "./types";
/** Patch an updated agent into every cached agent listing immediately so
* cards re-render before the background refetch finishes. Without this the
* mutation just invalidates the query and the user has to wait for the
* round-trip + render — which on a big list feels like nothing happened.
*/
function applyAgentToCache(
queryClient: ReturnType<typeof useQueryClient>,
next: Agent,
) {
queryClient.setQueriesData<Agent[] | undefined>(
{ queryKey: ["agents"] },
(old) => {
if (!Array.isArray(old)) return old;
return old.map((a) => (a.id === next.id ? { ...a, ...next } : a));
},
);
}
export function useAgents(params: { search?: string; tagIds?: string[] } = {}) {
const search = params.search ?? "";
const tagIds = params.tagIds ?? [];
const { data, isLoading, error } = useQuery({
queryKey: ["agents", search, [...tagIds].sort()],
queryFn: () => listAgents({ search, tagIds }),
placeholderData: keepPreviousData,
});
return { agents: data ?? [], isLoading, error };
}
/**
* Admin-only listing — returns every agent including unpublished agents owned
* by other users and private-square entries. Caller must be ``system_role
* === "admin"`` or the backend will 403.
*/
export function useAllAgentsAdmin(enabled: boolean) {
const { data, isLoading, error } = useQuery({
queryKey: ["agents", "admin", "all"],
queryFn: () => listAllAgentsAdmin(),
enabled,
staleTime: 30_000,
});
return { agents: data ?? [], isLoading, error };
}
export function useAgent(id: string | null | undefined) {
const { data, isLoading, error } = useQuery({
queryKey: ["agents", id],
queryFn: () => getAgent(id!),
enabled: !!id,
});
return { agent: data ?? null, isLoading, error };
}
export function useCreateAgent() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (request: CreateAgentRequest) => createAgent(request),
onSuccess: () => {
void queryClient.invalidateQueries({ queryKey: ["agents"] });
},
});
}
export function useUpdateAgent() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
id,
request,
}: {
id: string;
request: UpdateAgentRequest;
}) => updateAgent(id, request),
onSuccess: (data, { id }) => {
applyAgentToCache(queryClient, data);
void queryClient.invalidateQueries({ queryKey: ["agents"] });
void queryClient.invalidateQueries({ queryKey: ["agents", id] });
},
});
}
export function useDeleteAgent() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) => deleteAgent(id),
onSuccess: () => {
void queryClient.invalidateQueries({ queryKey: ["agents"] });
},
});
}
/** Toggle a user's favorite (收藏) on an agent, with an optimistic flip of
* ``is_favorite`` across every cached agent listing + detail so the star
* reacts instantly before the round-trip completes. */
export function useToggleAgentFavorite() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, favorite }: { id: string; favorite: boolean }) =>
favorite ? favoriteAgent(id) : unfavoriteAgent(id),
onMutate: ({ id, favorite }) => {
queryClient.setQueriesData<Agent[] | undefined>(
{ queryKey: ["agents"] },
(old) =>
Array.isArray(old)
? old.map((a) =>
a.id === id ? { ...a, is_favorite: favorite } : a,
)
: old,
);
queryClient.setQueryData<Agent | undefined>(["agents", id], (old) =>
old ? { ...old, is_favorite: favorite } : old,
);
},
onSettled: () => {
void queryClient.invalidateQueries({ queryKey: ["agents"] });
},
});
}
export function useAgentSelector() {
const { data, isLoading, error } = useQuery({
queryKey: ["agents", "selector"],
queryFn: () => getAgentSelector(),
staleTime: 60_000,
});
return {
agents: data?.agents ?? [],
isLoading,
error,
};
}
function invalidateAgentQueries(queryClient: ReturnType<typeof useQueryClient>) {
void queryClient.invalidateQueries({ queryKey: ["agents"] });
}
export function useSetAgentFeatured() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, order }: { id: string; order: number | null }) =>
setAgentFeatured(id, order),
onSuccess: (data, { id }) => {
applyAgentToCache(queryClient, data);
invalidateAgentQueries(queryClient);
void queryClient.invalidateQueries({ queryKey: ["agents", id] });
},
});
}
export function useReorderFeaturedAgents() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (agentIds: string[]) => reorderFeaturedAgents(agentIds),
onSuccess: () => invalidateAgentQueries(queryClient),
});
}
/**
* Query the password-gated private square. ``password`` is sent on every
* fetch — wire ``enabled`` to false until the user has typed it so we don't
* hit the endpoint with an empty body and get a 403.
*/
export function usePrivateAgents(password: string | null) {
const { data, isLoading, error, refetch } = useQuery({
queryKey: ["agents", "private-square", password ?? ""],
queryFn: () => listPrivateAgents(password!),
enabled: !!password,
staleTime: 30_000,
retry: false,
});
return { agents: data ?? [], isLoading, error, refetch };
}
export function useSetAgentPrivate() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, isPrivate }: { id: string; isPrivate: boolean }) =>
setAgentPrivate(id, isPrivate),
onSuccess: (data, { id }) => {
applyAgentToCache(queryClient, data);
invalidateAgentQueries(queryClient);
void queryClient.invalidateQueries({ queryKey: ["agents", id] });
void queryClient.invalidateQueries({
queryKey: ["agents", "private-square"],
});
},
});
}
export function useAdminSyncAgentSkills() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: adminSyncAgentSkills,
onSuccess: (result) => {
if (result.modified > 0) {
invalidateAgentQueries(queryClient);
}
},
});
}