From 10e35055e04377065edb044db7624971972d784e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=89=E9=91=AB=E5=8D=87?= Date: Thu, 28 May 2026 17:16:15 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=99=BA=E8=83=BD=E4=BD=93?= =?UTF-8?q?=E6=94=B6=E8=97=8F=E3=80=81=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E5=BF=85=E9=A1=BB=E7=94=9F=E6=88=90MD=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=E3=80=81=E9=97=AE=E7=AD=94=E6=97=B6=E9=97=B4=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E5=8F=8A=E8=B4=A6=E5=8F=B7=E7=BB=91=E5=AE=9A=E7=9A=84=E5=B8=B8?= =?UTF-8?q?=E7=94=A8=E6=8F=90=E7=A4=BA=E8=AF=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 智能体支持从广场收藏到「我的」(新增 agent_favorites 表与收藏/取消收藏接口) - 定时任务新增「必须生成Markdown文件」开关:注入提示词并校验生成的 .md 文件且正文超过100字,否则记为失败(存于 execution_context,无需迁移) - 聊天问答展示时间(年月日 时分):问题在右下角、回答在左下角 - 常用提示词改为与账号绑定(新增 user_custom_prompts 表与 /api/user-prompts 接口),仅在首页输入框上方以居中标签 + 轮播展示,齿轮置于最右侧管理 Co-Authored-By: Claude Opus 4.7 (1M context) --- .../workspace/agents/agent-card.tsx | 36 ++- .../workspace/agents/agent-gallery.tsx | 7 +- .../components/workspace/chats/chat-box.tsx | 155 ++++----- .../workspace/custom-prompt-menu.tsx | 294 ++++++++++++++++++ .../src/components/workspace/input-box.tsx | 83 +++-- .../workspace/messages/message-list-item.tsx | 79 ++++- .../workspace/messages/message-list.tsx | 20 +- .../workspace/skill-display-adapter-panel.tsx | 1 + .../workspace/skill-display-block.tsx | 2 +- .../workspace/skill-display-sources.tsx | 3 +- frontend-web/src/core/agents/api.ts | 20 ++ frontend-web/src/core/agents/hooks.ts | 30 ++ frontend-web/src/core/agents/types.ts | 2 + .../src/core/chat-timestamps/index.ts | 105 +++++++ frontend-web/src/core/custom-prompts/index.ts | 112 +++++++ .../src/core/messages/search-results.ts | 21 +- frontend-web/src/core/scheduled-tasks/api.ts | 3 + .../src/core/scheduled-tasks/types.ts | 6 + .../src/core/skill-display/display-adapter.ts | 16 + .../src/core/skill-display/runtime.ts | 1 + frontend-web/src/core/skills/type.ts | 1 + frontend-web/src/core/threads/hooks.ts | 49 ++- frontend-web/src/pages/ScheduledTasksPage.tsx | 18 ++ .../backend/app/gateway/app.py | 3 + .../backend/app/gateway/deps.py | 4 + .../backend/app/gateway/routers/agents.py | 63 +++- .../app/gateway/routers/scheduled_tasks.py | 6 + .../backend/app/gateway/routers/threads.py | 24 +- .../app/gateway/routers/user_prompts.py | 108 +++++++ .../middlewares/rag_citation_middleware.py | 19 +- .../deerflow/config/extensions_config.py | 1 + .../deerflow/persistence/agents/__init__.py | 3 +- .../deerflow/persistence/agents/base.py | 22 ++ .../deerflow/persistence/agents/memory.py | 18 ++ .../deerflow/persistence/agents/model.py | 19 +- .../deerflow/persistence/agents/sql.py | 50 ++- .../versions/20260528_01_agent_favorites.py | 45 +++ .../20260528_02_user_custom_prompts.py | 45 +++ .../deerflow/persistence/models/__init__.py | 5 +- .../persistence/user_prompts/__init__.py | 18 ++ .../persistence/user_prompts/model.py | 23 ++ .../deerflow/persistence/user_prompts/sql.py | 74 +++++ .../harness/deerflow/runtime/references.py | 97 +++++- .../deerflow/runtime/scheduler/__init__.py | 10 +- .../deerflow/runtime/scheduler/service.py | 77 +++++ 45 files changed, 1653 insertions(+), 145 deletions(-) create mode 100644 frontend-web/src/components/workspace/custom-prompt-menu.tsx create mode 100644 frontend-web/src/core/chat-timestamps/index.ts create mode 100644 frontend-web/src/core/custom-prompts/index.ts create mode 100644 offline-backend-20260512/backend/app/gateway/routers/user_prompts.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/persistence/migrations/versions/20260528_01_agent_favorites.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/persistence/migrations/versions/20260528_02_user_custom_prompts.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/persistence/user_prompts/__init__.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/persistence/user_prompts/model.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/persistence/user_prompts/sql.py 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="名称(可选,例如:周报总结)" + /> +