diff --git a/frontend-web/src/components/workspace/agents/agent-card.tsx b/frontend-web/src/components/workspace/agents/agent-card.tsx index 704ebd87..f00ccdc2 100644 --- a/frontend-web/src/components/workspace/agents/agent-card.tsx +++ b/frontend-web/src/components/workspace/agents/agent-card.tsx @@ -9,6 +9,7 @@ import { ShieldIcon, SparklesIcon, SquarePenIcon, + StarIcon, TagsIcon, Trash2Icon, } from "lucide-react"; @@ -44,6 +45,7 @@ import { useDeleteAgent, useSetAgentFeatured, useSetAgentPrivate, + useToggleAgentFavorite, useUpdateAgent, } from "@/core/agents"; import type { Agent } from "@/core/agents"; @@ -64,6 +66,7 @@ export function AgentCard({ agent }: AgentCardProps) { const updateAgent = useUpdateAgent(); const setFeatured = useSetAgentFeatured(); const setPrivate = useSetAgentPrivate(); + const toggleFavorite = useToggleAgentFavorite(); const { skills, isLoading: isSkillsLoading } = useSkills(); const [deleteOpen, setDeleteOpen] = useState(false); const [editOpen, setEditOpen] = useState(false); @@ -91,6 +94,10 @@ export function AgentCard({ agent }: AgentCardProps) { // everyone so toggling published is moot. const canEdit = isOwned || isAdmin; const canDelete = isOwned || isAdmin; + // Favoriting collects a square agent into "我的"; pointless for one you + // already own, so only offer it on built-in / others' agents. + const canFavorite = !isOwned; + const isFavorite = !!agent.is_favorite; useEffect(() => { if (!editOpen) { @@ -197,6 +204,16 @@ export function AgentCard({ agent }: AgentCardProps) { } } + async function handleToggleFavorite() { + const next = !isFavorite; + try { + await toggleFavorite.mutateAsync({ id: agent.id, favorite: next }); + toast.success(next ? "已收藏到「我的」" : "已取消收藏"); + } catch (err) { + toast.error(err instanceof Error ? err.message : String(err)); + } + } + async function handleToggleFeatured() { // One-click on/off — ordering is handled by the dedicated 管理 Agent 列表 // dialog. Featured agents get appended to the end on add (Date.now() is a @@ -347,8 +364,25 @@ export function AgentCard({ agent }: AgentCardProps) { )} - {(canEdit || canDelete || isOwned || isAdmin) && ( + {(canEdit || canDelete || isOwned || isAdmin || canFavorite) && (
+ {canFavorite && ( + + )} {isAdmin && ( + )} +
+
+ {prompts.map((prompt) => ( + + ))} +
+
+ {overflowing && ( + + )} + + )} + {prompts.length === 0 &&
} + +
+ + + + ); +} + +function ManagePromptsDialog({ + open, + onOpenChange, +}: { + open: boolean; + onOpenChange: (open: boolean) => void; +}) { + const prompts = useCustomPrompts(); + const createPrompt = useCreateCustomPrompt(); + const updatePrompt = useUpdateCustomPrompt(); + const deletePrompt = useDeleteCustomPrompt(); + const [editingId, setEditingId] = useState(null); + const [title, setTitle] = useState(""); + const [content, setContent] = useState(""); + + const saving = createPrompt.isPending || updatePrompt.isPending; + + const resetForm = () => { + setEditingId(null); + setTitle(""); + setContent(""); + }; + + const handleSubmit = async () => { + const trimmedTitle = title.trim(); + if (!content.trim()) { + toast.error("提示词内容不能为空"); + return; + } + try { + if (editingId) { + await updatePrompt.mutateAsync({ + id: editingId, + input: { title: trimmedTitle, content }, + }); + toast.success("提示词已更新"); + } else { + await createPrompt.mutateAsync({ title: trimmedTitle, content }); + toast.success("提示词已添加"); + } + resetForm(); + } catch (err) { + toast.error(err instanceof Error ? err.message : String(err)); + } + }; + + const handleEdit = (id: string) => { + const target = prompts.find((p) => p.id === id); + if (!target) return; + setEditingId(id); + setTitle(target.title); + setContent(target.content); + }; + + const handleDelete = async (id: string) => { + try { + await deletePrompt.mutateAsync(id); + if (editingId === id) resetForm(); + } catch (err) { + toast.error(err instanceof Error ? err.message : String(err)); + } + }; + + return ( + { + if (!next) resetForm(); + onOpenChange(next); + }} + > + + + 管理常用提示词 + + 保存常用的提示词,保存后会以小标签形式显示在输入框上方,点击即可填入。提示词与你的账号绑定,可跨设备使用。 + + + +
+ setTitle(e.target.value)} + placeholder="名称(可选,例如:周报总结)" + /> +