From 646cb8caeb22005034161e07479c02a7cdf10534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=89=E9=91=AB=E5=8D=87?= Date: Fri, 15 May 2026 16:19:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=A0=87=E7=AD=BE=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=20+=20=E9=97=AE=E7=AD=94=E5=89=8D=E7=BC=80?= =?UTF-8?q?=E5=AF=B9=E7=94=A8=E6=88=B7=E9=9A=90=E8=97=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 标签管理: - 新增 tags/tag_assignments 持久化层、迁移、/api/tags 路由 - 标签分智能体/技能/定时任务三类, 仅同类型可互打, 仅 admin 可增删改与打标签 - 三类资源列表支持按名称模糊检索与按标签筛选(走后端查询参数) - 前端新增标签管理页、TagPickerDialog(点击即时绑定)、彩色标签 chip 问答前缀: - 前缀不再拼入消息 content, 改存 additional_kwargs 由 PromptPrefixMiddleware 在调模型前临时注入, 用户界面看不到前缀但模型仍收到 - 修正 thread_existed 闸门导致首轮前缀被跳过的问题 Co-Authored-By: Claude Opus 4.7 (1M context) --- .../workspace/admin/tag-management-page.tsx | 293 ++++++++++++++++++ .../workspace/agents/agent-card.tsx | 33 +- .../workspace/agents/agent-gallery.tsx | 23 +- .../settings/skill-settings-page.tsx | 65 +++- .../components/workspace/tags/tag-chip.tsx | 58 ++++ .../components/workspace/tags/tag-colors.ts | 30 ++ .../workspace/tags/tag-picker-dialog.tsx | 140 +++++++++ .../workspace/tags/tag-search-bar.tsx | 80 +++++ .../workspace/workspace-nav-menu.tsx | 9 + frontend-web/src/core/agents/api.ts | 15 +- frontend-web/src/core/agents/hooks.ts | 16 +- frontend-web/src/core/agents/types.ts | 3 + frontend-web/src/core/i18n/locales/en-US.ts | 34 ++ frontend-web/src/core/i18n/locales/types.ts | 37 +++ frontend-web/src/core/i18n/locales/zh-CN.ts | 34 ++ frontend-web/src/core/scheduled-tasks/api.ts | 15 +- .../src/core/scheduled-tasks/hooks.ts | 18 +- .../src/core/scheduled-tasks/types.ts | 3 + frontend-web/src/core/skills/api.ts | 15 +- frontend-web/src/core/skills/hooks.ts | 16 +- frontend-web/src/core/skills/type.ts | 4 + frontend-web/src/core/tags/api.ts | 83 +++++ frontend-web/src/core/tags/hooks.ts | 88 ++++++ frontend-web/src/core/tags/index.ts | 3 + frontend-web/src/core/tags/types.ts | 31 ++ frontend-web/src/pages/ChatPage.tsx | 21 +- frontend-web/src/pages/ScheduledTasksPage.tsx | 46 ++- frontend-web/src/pages/TagsPage.tsx | 5 + frontend-web/src/pages/WorkspaceRoutes.tsx | 13 + offline-backend-20260512/backend/CLAUDE.md | 26 +- .../backend/app/gateway/app.py | 4 + .../backend/app/gateway/deps.py | 13 + .../backend/app/gateway/routers/agents.py | 30 +- .../app/gateway/routers/scheduled_tasks.py | 24 +- .../backend/app/gateway/routers/skills.py | 33 +- .../backend/app/gateway/routers/tags.py | 212 +++++++++++++ .../backend/app/gateway/services.py | 40 ++- .../deerflow/agents/lead_agent/agent.py | 19 +- .../middlewares/prompt_prefix_middleware.py | 78 +++++ .../versions/20260515_01_tags_tables.py | 60 ++++ .../deerflow/persistence/models/__init__.py | 3 + .../deerflow/persistence/tags/__init__.py | 14 + .../harness/deerflow/persistence/tags/base.py | 73 +++++ .../deerflow/persistence/tags/memory.py | 138 +++++++++ .../deerflow/persistence/tags/model.py | 47 +++ .../harness/deerflow/persistence/tags/sql.py | 187 +++++++++++ .../backend/tests/test_prompt_prefix.py | 57 +++- 47 files changed, 2180 insertions(+), 109 deletions(-) create mode 100644 frontend-web/src/components/workspace/admin/tag-management-page.tsx create mode 100644 frontend-web/src/components/workspace/tags/tag-chip.tsx create mode 100644 frontend-web/src/components/workspace/tags/tag-colors.ts create mode 100644 frontend-web/src/components/workspace/tags/tag-picker-dialog.tsx create mode 100644 frontend-web/src/components/workspace/tags/tag-search-bar.tsx create mode 100644 frontend-web/src/core/tags/api.ts create mode 100644 frontend-web/src/core/tags/hooks.ts create mode 100644 frontend-web/src/core/tags/index.ts create mode 100644 frontend-web/src/core/tags/types.ts create mode 100644 frontend-web/src/pages/TagsPage.tsx create mode 100644 offline-backend-20260512/backend/app/gateway/routers/tags.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/agents/middlewares/prompt_prefix_middleware.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/persistence/migrations/versions/20260515_01_tags_tables.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/persistence/tags/__init__.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/persistence/tags/base.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/persistence/tags/memory.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/persistence/tags/model.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/persistence/tags/sql.py diff --git a/frontend-web/src/components/workspace/admin/tag-management-page.tsx b/frontend-web/src/components/workspace/admin/tag-management-page.tsx new file mode 100644 index 00000000..388cd1c3 --- /dev/null +++ b/frontend-web/src/components/workspace/admin/tag-management-page.tsx @@ -0,0 +1,293 @@ +"use client"; + +import { Loader2, PencilIcon, PlusIcon, Trash2Icon } from "lucide-react"; +import { useState } from "react"; +import { toast } from "sonner"; + +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Textarea } from "@/components/ui/textarea"; +import { SettingsSection } from "@/components/workspace/settings/settings-section"; +import { TagChip } from "@/components/workspace/tags/tag-chip"; +import { TAG_COLORS } from "@/components/workspace/tags/tag-colors"; +import { useI18n } from "@/core/i18n/hooks"; +import { + useCreateTag, + useDeleteTag, + useTags, + useUpdateTag, + type Tag, + type TagType, +} from "@/core/tags"; +import { cn } from "@/lib/utils"; + +function TagFormDialog({ + open, + onOpenChange, + tagType, + editing, +}: { + open: boolean; + onOpenChange: (open: boolean) => void; + tagType: TagType; + editing: Tag | null; +}) { + const { t } = useI18n(); + const createTag = useCreateTag(); + const updateTag = useUpdateTag(); + const [name, setName] = useState(""); + const [color, setColor] = useState("gray"); + const [description, setDescription] = useState(""); + + // Sync form state whenever the dialog (re)opens. + const [syncedFor, setSyncedFor] = useState(null); + const syncKey = `${open}:${editing?.id ?? "new"}`; + if (open && syncedFor !== syncKey) { + setName(editing?.name ?? ""); + setColor(editing?.color ?? "gray"); + setDescription(editing?.description ?? ""); + setSyncedFor(syncKey); + } + if (!open && syncedFor !== null) setSyncedFor(null); + + const pending = createTag.isPending || updateTag.isPending; + + const handleSave = async () => { + const trimmed = name.trim(); + if (!trimmed) return; + try { + if (editing) { + await updateTag.mutateAsync({ + id: editing.id, + input: { name: trimmed, color, description }, + }); + } else { + await createTag.mutateAsync({ + name: trimmed, + tag_type: tagType, + color, + description, + }); + } + onOpenChange(false); + } catch (e) { + toast.error(String(e instanceof Error ? e.message : e)); + } + }; + + return ( + + + + + {editing ? t.adminTags.editTitle : t.adminTags.createTitle} + + +
+
+ + setName(e.target.value)} + placeholder={t.adminTags.namePlaceholder} + maxLength={256} + /> +
+
+ +
+ {TAG_COLORS.map((c) => ( +
+
+
+ +