From 3748513996e2140741ebaef8dd350fa043fde3fa Mon Sep 17 00:00:00 2001 From: xuhd Date: Thu, 21 May 2026 11:43:59 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=99=BA=E8=83=BD=E4=BD=93=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E6=B5=81=E7=A8=8B=E4=BC=98=E5=8C=96=E4=B8=8E=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=91=98=E8=AE=B0=E5=BF=86=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 创建智能体页面:保存后自动跳转列表页、技能列表增加搜索过滤框 - Bootstrap 模式:开放全部技能可见性,注入专属提示词(skill_list 匹配意图、禁止 skill_manage) - 管理员记忆页面:新增 AdminMemoryPage 及路由,支持查看/编辑用户记忆条目 - 内存配置与管理员用户接口同步更新 Co-Authored-By: Claude Sonnet 4.6 --- .../workspace/admin/admin-memory-page.tsx | 371 ++++++++++++++++++ .../settings/memory-settings-page.tsx | 147 ++----- .../workspace/workspace-nav-menu.tsx | 9 + frontend-web/src/core/admin/api.ts | 48 +++ frontend-web/src/core/admin/hooks.ts | 43 +- frontend-web/src/pages/AdminMemoryPage.tsx | 5 + frontend-web/src/pages/NewAgentPage.tsx | 32 +- frontend-web/src/pages/WorkspaceRoutes.tsx | 13 + .../app/gateway/routers/admin_users.py | 122 +++++- .../deerflow/agents/lead_agent/agent.py | 5 +- .../deerflow/agents/lead_agent/prompt.py | 28 ++ .../harness/deerflow/config/memory_config.py | 12 +- 12 files changed, 692 insertions(+), 143 deletions(-) create mode 100644 frontend-web/src/components/workspace/admin/admin-memory-page.tsx create mode 100644 frontend-web/src/pages/AdminMemoryPage.tsx diff --git a/frontend-web/src/components/workspace/admin/admin-memory-page.tsx b/frontend-web/src/components/workspace/admin/admin-memory-page.tsx new file mode 100644 index 00000000..eae8f24b --- /dev/null +++ b/frontend-web/src/components/workspace/admin/admin-memory-page.tsx @@ -0,0 +1,371 @@ +"use client"; + +import { useState } from "react"; +import { Check, Loader2, Pencil, Plus, Trash2, X } from "lucide-react"; +import { toast } from "sonner"; + +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Textarea } from "@/components/ui/textarea"; +import { + useAdminAddMemoryEntry, + useAdminDeleteMemoryEntry, + useAdminReplaceMemoryEntry, + useUserMemory, +} from "@/core/admin/hooks"; +import { useAllUsers } from "@/core/auth/hooks"; + +function getEntries(data: Record): string[] { + const raw = (data as { entries?: unknown }).entries; + if (!Array.isArray(raw)) return []; + return raw.filter((e): e is string => typeof e === "string" && e.trim().length > 0); +} + +// ── Memory entry list with inline editing ────────────────────────────────── + +function MemoryEntryList({ + entries, + userId, + agent, +}: { + entries: string[]; + userId: string; + agent: string | null; +}) { + const [editIndex, setEditIndex] = useState(null); + const [editDraft, setEditDraft] = useState(""); + const [addDraft, setAddDraft] = useState(""); + const [isAdding, setIsAdding] = useState(false); + + const addMutation = useAdminAddMemoryEntry(userId); + const replaceMutation = useAdminReplaceMemoryEntry(userId); + const deleteMutation = useAdminDeleteMemoryEntry(userId); + + const isPending = + addMutation.isPending || replaceMutation.isPending || deleteMutation.isPending; + + function startEdit(index: number) { + setEditIndex(index); + setEditDraft(entries[index] ?? ""); + } + + function cancelEdit() { + setEditIndex(null); + setEditDraft(""); + } + + async function confirmEdit(index: number) { + const oldText = entries[index]; + if (!oldText || editDraft.trim() === oldText.trim()) { + cancelEdit(); + return; + } + try { + await replaceMutation.mutateAsync({ oldText, content: editDraft.trim(), agent }); + cancelEdit(); + } catch (err) { + toast.error(err instanceof Error ? err.message : "修改失败"); + } + } + + async function handleDelete(entry: string) { + try { + await deleteMutation.mutateAsync({ oldText: entry, agent }); + } catch (err) { + toast.error(err instanceof Error ? err.message : "删除失败"); + } + } + + async function handleAdd() { + const content = addDraft.trim(); + if (!content) return; + try { + await addMutation.mutateAsync({ content, agent }); + setAddDraft(""); + setIsAdding(false); + } catch (err) { + toast.error(err instanceof Error ? err.message : "新增失败"); + } + } + + return ( +
+ {entries.length === 0 && !isAdding && ( +

暂无记忆条目

+ )} + + {entries.map((entry, i) => ( +
+ {editIndex === i ? ( +
+