From 5ebbb82d669133a71b02a4874b26fab1644f98d5 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 15:23:54 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=9F=A5=E8=AF=86=E5=BA=93=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=A4=B9/=E6=A8=A1=E6=9D=BF=E3=80=81=E6=8A=80?= =?UTF-8?q?=E8=83=BD=E5=8E=8B=E7=BC=A9=E8=AE=BE=E7=BD=AE=E4=B8=8E=E6=91=98?= =?UTF-8?q?=E8=A6=81=E7=AA=97=E5=8F=A3=E8=B0=83=E4=BC=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 知识库新增文件夹与模板支持(模型/仓储/服务/迁移),前端知识管理组件 - 岗位管理模型与接口完善 - 技能新增压缩设置页与相关存储/工具能力 - 系统设置扩展配置项 - 摘要中间件 keep 窗口改用 token 预算(16000),减少重复压缩 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/components/wiki/KnowledgeManage.tsx | 429 ++++++++++++++++++ .../src/components/wiki/WikiContent.tsx | 26 +- .../src/components/wiki/WikiHeader.tsx | 16 + frontend-web/src/components/wiki/wikiTypes.ts | 2 + frontend-web/src/components/wiki/wikiUtils.ts | 104 ++++- .../admin/position-management-page.tsx | 2 +- .../settings/appearance-settings-page.tsx | 6 +- .../settings/skill-settings-page.tsx | 9 + .../workspace/skills/skill-detail-dialog.tsx | 39 ++ .../workspace/workspace-nav-menu.tsx | 13 + frontend-web/src/core/knowledge/api.ts | 112 ++++- frontend-web/src/core/knowledge/hooks.ts | 69 ++- frontend-web/src/core/knowledge/types.ts | 37 ++ frontend-web/src/core/positions/api.ts | 7 + frontend-web/src/core/positions/hooks.ts | 7 + frontend-web/src/core/skills/api.ts | 20 + frontend-web/src/core/skills/hooks.ts | 19 + frontend-web/src/core/skills/type.ts | 3 + frontend-web/src/core/system-settings/api.ts | 44 ++ .../src/core/system-settings/hooks.ts | 27 ++ frontend-web/src/pages/KnowledgeBasePage.tsx | 75 ++- .../pages/SkillCompressionSettingsPage.tsx | 141 ++++++ frontend-web/src/pages/WorkspaceRoutes.tsx | 13 + .../src/strategy-components/Header.tsx | 8 + offline-backend-20260512/backend/CLAUDE.md | 60 ++- .../backend/app/gateway/deps.py | 8 + .../backend/app/gateway/routers/knowledge.py | 168 ++++++- .../backend/app/gateway/routers/positions.py | 26 ++ .../backend/app/gateway/routers/skills.py | 59 +++ .../app/gateway/routers/system_settings.py | 59 +++ .../backend/docs/summarization.md | 1 + .../deerflow/agents/lead_agent/prompt.py | 108 +++-- .../middlewares/summarization_middleware.py | 74 +++ .../packages/harness/deerflow/client.py | 43 +- .../deerflow/config/extensions_config.py | 19 + .../deerflow/config/system_settings.py | 30 ++ .../harness/deerflow/knowledge/extractor.py | 16 +- .../deerflow/knowledge/llm_extractor.py | 12 +- .../harness/deerflow/knowledge/models.py | 51 +++ .../harness/deerflow/knowledge/repository.py | 198 ++++++++ .../harness/deerflow/knowledge/schemas.py | 2 + .../harness/deerflow/knowledge/service.py | 102 +++++ ...20260616_02_knowledge_folders_templates.py | 84 ++++ .../deerflow/persistence/models/__init__.py | 4 + .../deerflow/persistence/positions/model.py | 9 +- .../deerflow/persistence/positions/sql.py | 7 +- .../skills/storage/local_skill_storage.py | 58 ++- .../deerflow/skills/storage/skill_storage.py | 1 + .../packages/harness/deerflow/skills/types.py | 1 + .../deerflow/tools/builtins/skill_tools.py | 107 +++++ .../packages/harness/deerflow/tools/tools.py | 12 + .../scripts/cleanup_knowledge_related.py | 182 ++++++++ .../scripts/sql/column_additions_mysql.sql | 18 + offline-backend-20260512/config.yaml | 11 +- 54 files changed, 2658 insertions(+), 100 deletions(-) create mode 100644 frontend-web/src/components/wiki/KnowledgeManage.tsx create mode 100644 frontend-web/src/pages/SkillCompressionSettingsPage.tsx create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/persistence/migrations/versions/20260616_02_knowledge_folders_templates.py create mode 100644 offline-backend-20260512/backend/scripts/cleanup_knowledge_related.py diff --git a/frontend-web/src/components/wiki/KnowledgeManage.tsx b/frontend-web/src/components/wiki/KnowledgeManage.tsx new file mode 100644 index 00000000..808c3894 --- /dev/null +++ b/frontend-web/src/components/wiki/KnowledgeManage.tsx @@ -0,0 +1,429 @@ +import { Plus, Trash2 } from "lucide-react"; +import { useEffect, useMemo, useState } from "react"; +import { toast } from "sonner"; + +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Textarea } from "@/components/ui/textarea"; +import { + useExtractTemplateActions, + useExtractTemplates, + useFolderActions, + useKnowledgeFolders, +} from "@/core/knowledge/hooks"; +import type { + ExtractTemplateScope, + KnowledgeExtractTemplate, +} from "@/core/knowledge/types"; +import { cn } from "@/lib/utils"; + +const NONE = "__none__"; + +/** A dropdown of managed directories (+ an "unfiled" option). */ +export function FolderSelect({ + value, + onChange, + disabled, + triggerClassName, + allowNone = true, +}: { + value: string | null; + onChange: (v: string | null) => void; + disabled?: boolean; + triggerClassName?: string; + allowNone?: boolean; +}) { + const { data: folders = [] } = useKnowledgeFolders(); + return ( + + ); +} + +/** A dropdown of enabled extraction templates applicable to a scope. */ +export function TemplateSelect({ + value, + onChange, + scope, + disabled, + triggerClassName, +}: { + value: string | null; + onChange: (v: string | null) => void; + scope: ExtractTemplateScope; + disabled?: boolean; + triggerClassName?: string; +}) { + const { data: templates = [] } = useExtractTemplates(scope, true); + return ( + + ); +} + +/** Create / rename / delete knowledge directories. */ +export function FolderManageDialog({ + open, + onOpenChange, +}: { + open: boolean; + onOpenChange: (v: boolean) => void; +}) { + const { data: folders = [], isLoading } = useKnowledgeFolders(); + const { create, rename, remove } = useFolderActions(); + const [newPath, setNewPath] = useState(""); + + const handleCreate = async () => { + const path = newPath.trim(); + if (!path) { + toast.error("请输入目录路径"); + return; + } + try { + await create.mutateAsync(path); + toast.success("目录已创建"); + setNewPath(""); + } catch (error) { + toast.error(error instanceof Error ? error.message : "创建失败"); + } + }; + + const handleRename = async (folderId: string, current: string) => { + const next = window.prompt("重命名 / 移动目录(输入新的完整路径,斜杠分隔)", current); + if (next === null) return; + try { + await rename.mutateAsync({ folderId, path: next }); + toast.success("已更新"); + } catch (error) { + toast.error(error instanceof Error ? error.message : "更新失败"); + } + }; + + const handleDelete = async (folderId: string, path: string) => { + if (!window.confirm(`删除目录「${path}」?其中的知识将变为未归类(不会被删除)。`)) return; + try { + await remove.mutateAsync({ folderId }); + toast.success("目录已删除"); + } catch (error) { + toast.error(error instanceof Error ? error.message : "删除失败"); + } + }; + + return ( + + + + 目录管理 + + 自建知识目录,用斜杠分隔多级(如「投研/行业」)。删除目录只会让其中的知识变为未归类。 + + +
+ setNewPath(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") void handleCreate(); + }} + /> + +
+ + {isLoading ? ( +
加载中...
+ ) : folders.length === 0 ? ( +
+ 还没有目录,先在上方新建一个。 +
+ ) : ( +
    + {folders.map((f) => ( +
  • + {f.path} +
    + + +
    +
  • + ))} +
+ )} +
+ + + +
+
+ ); +} + +const SCOPE_LABELS: Record = { + thread: "对话", + document: "文档", + both: "通用", +}; + +const EMPTY_DRAFT = { + name: "", + scope: "both" as ExtractTemplateScope, + description: "", + system_prompt: "", + is_default: false, + enabled: true, +}; + +/** Create / edit / delete the editable extraction-prompt templates. */ +export function ExtractTemplateManageDialog({ + open, + onOpenChange, +}: { + open: boolean; + onOpenChange: (v: boolean) => void; +}) { + const { data: templates = [], isLoading } = useExtractTemplates(); + const { create, update, remove } = useExtractTemplateActions(); + const [selectedId, setSelectedId] = useState(null); + const [draft, setDraft] = useState({ ...EMPTY_DRAFT }); + + const selected = useMemo( + () => templates.find((t) => t.id === selectedId) ?? null, + [templates, selectedId], + ); + + // Load the selected template into the editable draft (or reset for "new"). + useEffect(() => { + if (selected) { + setDraft({ + name: selected.name, + scope: selected.scope, + description: selected.description ?? "", + system_prompt: selected.system_prompt, + is_default: selected.is_default, + enabled: selected.enabled, + }); + } else { + setDraft({ ...EMPTY_DRAFT }); + } + }, [selected]); + + const startNew = () => setSelectedId(null); + + const handleSave = async () => { + if (!draft.name.trim() || !draft.system_prompt.trim()) { + toast.error("请填写名称与提示词正文"); + return; + } + try { + if (selectedId) { + await update.mutateAsync({ templateId: selectedId, data: draft }); + toast.success("已保存"); + } else { + const created = await create.mutateAsync(draft); + setSelectedId(created.id); + toast.success("模板已创建"); + } + } catch (error) { + toast.error(error instanceof Error ? error.message : "保存失败"); + } + }; + + const handleDelete = async (t: KnowledgeExtractTemplate) => { + if (!window.confirm(`删除模板「${t.name}」?`)) return; + try { + await remove.mutateAsync(t.id); + if (selectedId === t.id) setSelectedId(null); + toast.success("已删除"); + } catch (error) { + toast.error(error instanceof Error ? error.message : "删除失败"); + } + }; + + return ( + + + + 抽取提示词模板 + + 编辑驱动「知识沉淀」的提炼提示词,可新建多套抽取逻辑。沉淀对话/导入文件时可选用对应模板;未选则用该类型的默认模板。 + + +
+ {/* template list */} +
+ + + {isLoading ? ( +
加载中...
+ ) : ( +
    + {templates.map((t) => ( +
  • + +
  • + ))} +
+ )} +
+
+ + {/* editor */} +
+
+ setDraft((d) => ({ ...d, name: e.target.value }))} + /> + +
+ setDraft((d) => ({ ...d, description: e.target.value }))} + /> +