From 3855c30eddebc7ad1a39e9db2550d8ed76904390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=89=E9=91=AB=E5=8D=87?= Date: Tue, 16 Jun 2026 10:49:36 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=B2=97=E4=BD=8D?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E3=80=81=E6=8F=90=E7=A4=BA=E8=AF=8D=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E4=B8=8E=E8=B5=84=E6=BA=90=E6=A0=87=E7=AD=BE=EF=BC=8C?= =?UTF-8?q?=E5=AE=8C=E5=96=84=E7=94=A8=E6=88=B7=E6=8F=90=E7=A4=BA=E8=AF=8D?= =?UTF-8?q?=E4=B8=8E=E6=8E=A8=E8=8D=90=E9=97=AE=E9=A2=98=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../admin/position-management-page.tsx | 479 ++++++++++++++++++ .../workspace/admin/prompt-templates-page.tsx | 258 ++++++++++ .../workspace/admin/tag-management-page.tsx | 11 +- .../src/components/workspace/input-box.tsx | 48 +- .../settings/skill-settings-page.tsx | 13 +- .../workspace/tags/resource-tag-dialog.tsx | 98 ++++ frontend-web/src/core/custom-prompts/index.ts | 91 ++++ .../src/core/page-layout/sidebar-menu.ts | 15 + frontend-web/src/core/positions/api.ts | 123 +++++ frontend-web/src/core/positions/hooks.ts | 124 +++++ frontend-web/src/core/positions/index.ts | 3 + frontend-web/src/core/positions/types.ts | 86 ++++ .../src/core/recommended-questions/api.ts | 6 +- .../src/core/recommended-questions/hooks.ts | 12 +- frontend-web/src/core/skills/api.ts | 3 + frontend-web/src/core/skills/hooks.ts | 5 +- frontend-web/src/core/tags/types.ts | 24 +- frontend-web/src/core/threads/hooks.ts | 17 + frontend-web/src/pages/PositionsPage.tsx | 5 + .../src/pages/PromptTemplatesPage.tsx | 5 + .../src/pages/RecommendedQuestionsPage.tsx | 28 +- frontend-web/src/pages/StrategyRoutes.tsx | 22 + frontend-web/src/pages/WorkspaceRoutes.tsx | 26 + offline-backend-20260512/backend/CLAUDE.md | 59 ++- .../backend/app/gateway/app.py | 4 + .../backend/app/gateway/deps.py | 24 + .../backend/app/gateway/routers/auth.py | 19 + .../backend/app/gateway/routers/positions.py | 311 ++++++++++++ .../gateway/routers/recommended_questions.py | 29 +- .../backend/app/gateway/routers/skills.py | 14 + .../app/gateway/routers/user_prompts.py | 108 ++++ .../backend/docs/summarization.md | 4 +- .../middlewares/summarization_middleware.py | 91 +++- .../deerflow/persistence/agents/model.py | 9 +- .../versions/20260616_01_positions.py | 108 ++++ .../deerflow/persistence/models/__init__.py | 3 + .../persistence/positions/__init__.py | 26 + .../deerflow/persistence/positions/base.py | 112 ++++ .../deerflow/persistence/positions/memory.py | 143 ++++++ .../deerflow/persistence/positions/model.py | 73 +++ .../deerflow/persistence/positions/sql.py | 196 +++++++ .../persistence/scheduled_tasks/model.py | 5 + .../harness/deerflow/persistence/tags/base.py | 11 + .../deerflow/persistence/tags/memory.py | 12 + .../deerflow/persistence/tags/model.py | 4 +- .../harness/deerflow/persistence/tags/sql.py | 12 + .../deerflow/persistence/user/model.py | 5 + .../persistence/user_prompts/model.py | 15 +- .../deerflow/persistence/user_prompts/sql.py | 63 ++- .../harness/deerflow/services/__init__.py | 1 + .../deerflow/services/position_sync.py | 220 ++++++++ .../scripts/sql/column_additions_mysql.sql | 17 + 52 files changed, 3143 insertions(+), 57 deletions(-) create mode 100644 frontend-web/src/components/workspace/admin/position-management-page.tsx create mode 100644 frontend-web/src/components/workspace/admin/prompt-templates-page.tsx create mode 100644 frontend-web/src/components/workspace/tags/resource-tag-dialog.tsx create mode 100644 frontend-web/src/core/positions/api.ts create mode 100644 frontend-web/src/core/positions/hooks.ts create mode 100644 frontend-web/src/core/positions/index.ts create mode 100644 frontend-web/src/core/positions/types.ts create mode 100644 frontend-web/src/pages/PositionsPage.tsx create mode 100644 frontend-web/src/pages/PromptTemplatesPage.tsx create mode 100644 offline-backend-20260512/backend/app/gateway/routers/positions.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/persistence/migrations/versions/20260616_01_positions.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/persistence/positions/__init__.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/persistence/positions/base.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/persistence/positions/memory.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/persistence/positions/model.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/persistence/positions/sql.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/services/__init__.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/services/position_sync.py diff --git a/frontend-web/src/components/workspace/admin/position-management-page.tsx b/frontend-web/src/components/workspace/admin/position-management-page.tsx new file mode 100644 index 00000000..07ed6e18 --- /dev/null +++ b/frontend-web/src/components/workspace/admin/position-management-page.tsx @@ -0,0 +1,479 @@ +"use client"; + +import { + CheckIcon, + Loader2, + PencilIcon, + PlusIcon, + RefreshCwIcon, + Trash2Icon, + UserPlusIcon, + UsersIcon, +} from "lucide-react"; +import { useEffect, useMemo, 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 { Textarea } from "@/components/ui/textarea"; +import { resolveTagColor } from "@/components/workspace/tags/tag-colors"; +import { + POSITION_RESOURCE_HINT, + POSITION_RESOURCE_LABELS, + POSITION_RESOURCE_TYPES, + resourceTypeToTagType, + useAddMembers, + useCreatePosition, + useDeletePosition, + usePositionMembers, + usePositionTagBindings, + usePositionUsers, + usePositions, + useRemoveMembers, + useResyncPosition, + useSetTagBindings, + useUpdatePosition, + type Position, + type PositionResourceType, + type TagBindings, +} from "@/core/positions"; +import { useTags } from "@/core/tags"; +import { cn } from "@/lib/utils"; + +function errMsg(e: unknown): string { + return String(e instanceof Error ? e.message : e); +} + +/** Inline multi-select chip row for the tags of one resource type. */ +function TagMultiSelect({ + resourceType, + value, + onChange, +}: { + resourceType: PositionResourceType; + value: string[]; + onChange: (next: string[]) => void; +}) { + const { data: tags, isLoading } = useTags(resourceTypeToTagType(resourceType)); + const toggle = (id: string) => + onChange(value.includes(id) ? value.filter((x) => x !== id) : [...value, id]); + + if (isLoading) { + return ; + } + if (!tags || tags.length === 0) { + return ( + + 暂无{POSITION_RESOURCE_LABELS[resourceType]}标签,请先到「标签管理」创建。 + + ); + } + return ( +
+ {tags.map((tag) => { + const checked = value.includes(tag.id); + const color = resolveTagColor(tag.color); + return ( + + ); + })} +
+ ); +} + +function PositionFormDialog({ + open, + onOpenChange, + editing, +}: { + open: boolean; + onOpenChange: (open: boolean) => void; + editing: Position | null; +}) { + const createPosition = useCreatePosition(); + const updatePosition = useUpdatePosition(); + const [name, setName] = useState(""); + const [description, setDescription] = useState(""); + const [isDefault, setIsDefault] = useState(false); + + const [syncedFor, setSyncedFor] = useState(null); + const syncKey = `${open}:${editing?.id ?? "new"}`; + if (open && syncedFor !== syncKey) { + setName(editing?.name ?? ""); + setDescription(editing?.description ?? ""); + setIsDefault(editing?.is_default ?? false); + setSyncedFor(syncKey); + } + if (!open && syncedFor !== null) setSyncedFor(null); + + const pending = createPosition.isPending || updatePosition.isPending; + const handleSave = async () => { + const trimmed = name.trim(); + if (!trimmed) return; + try { + if (editing) { + await updatePosition.mutateAsync({ id: editing.id, input: { name: trimmed, description, is_default: isDefault } }); + } else { + await createPosition.mutateAsync({ name: trimmed, description, is_default: isDefault }); + } + onOpenChange(false); + } catch (e) { + toast.error(errMsg(e)); + } + }; + + return ( + + + + {editing ? "编辑岗位" : "新增岗位"} + +
+
+ + setName(e.target.value)} placeholder="如:理赔专员" maxLength={191} /> +
+
+ +