feat: 管理面板补齐(RAG导入/记忆添加/会话接history/诚实表述) + design/16 §4.6 IM多会话隔离+第三方身份+黑白名单
agent-98 面板批次:
- 会话管理面板改接 history.ListSessions(主web+次级) + 新建次级会话按钮
- 记忆面板手动添加入口 POST /admin/memories(owner)
- 知识库真正实现 IndexDocument(分块+落projectindex) + POST /admin/rag 导入端点
- 三面板空态诚实表述(去除'后端M10+提供')
- RAG嵌入降级到keyword/line(无直属embedder,不伪造语义,符合红线)
agent-99 design/16 §4.6:
- model.Message.SenderName + 迁移0006 sender_name 列
- 第三方身份边界: session.ResolveActor判定(非LLM猜) + [第三方·昵称]前缀(不改源content)
- 群聊/陌生人落独立sess-xxx(persistIsolated),不写主会话,不广播
- 黑/白名单(allowGroups/denyGroups/allowUsers/denyUsers)最前拦截,不进LLM
- 多账号 CHANNELS_JSON 配置 + 每平台多实例
- 管理只读 GET /admin/channels/{platform}/messages(该平台非主人消息流)
实机验证(JWT): 会话管理返回主会话web / 记忆添加成功 / RAG导入documents非空+Cyrene命中; 后端build/vet/test全绿 + 前端build绿
This commit is contained in:
1 parent
37e98ec584
commit
9c2a37c4ac
32 files changed
+1768
-136
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { useAsyncData } from "../../lib/useAsyncData";
|
||||
import { adminListMemories, adminMemorySearch } from "../../lib/api";
|
||||
import { adminAddMemory, adminListMemories, adminMemorySearch } from "../../lib/api";
|
||||
import type { MemoryRecord, MemorySearchHit } from "../../lib/types";
|
||||
import { useAutoReload } from "../hooks";
|
||||
import { useAuditStore } from "../audit";
|
||||
@@ -9,10 +9,12 @@ import { Button, Surface, Tag } from "../../components/primitives";
|
||||
|
||||
const scopeTone: Record<string, "ok" | "warn" | "accent" | "neutral"> = {
|
||||
user: "accent",
|
||||
habitual: "ok",
|
||||
persona: "ok",
|
||||
project: "warn",
|
||||
};
|
||||
|
||||
const SCOPES = ["user", "persona", "project"] as const;
|
||||
|
||||
function MemoryTable({ rows, loading, error }: { rows: MemoryRecord[] | null; loading: boolean; error: string | null }): React.ReactNode {
|
||||
const columns: AdminColumn<MemoryRecord>[] = [
|
||||
{ key: "scope", title: "作用域", render: (m) => <Tag tone={scopeTone[m.scope] ?? "neutral"}>{m.scope}</Tag> },
|
||||
@@ -20,7 +22,7 @@ function MemoryTable({ rows, loading, error }: { rows: MemoryRecord[] | null; lo
|
||||
{ key: "id", title: "ID", render: (m) => <span className="font-mono text-xs text-[var(--mu-text-lo)]">{m.id}</span> },
|
||||
];
|
||||
return (
|
||||
<AdminTable columns={columns} rows={rows} loading={loading} error={error} emptyTitle="无记忆数据" rowKey={(m) => m.id} />
|
||||
<AdminTable columns={columns} rows={rows} loading={loading} error={error} emptyTitle="当前无记忆:说出『记住…』会写入,或点『添加记忆』手动写入" rowKey={(m) => m.id} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -33,6 +35,10 @@ export function MemoriesModule(): React.ReactNode {
|
||||
const [hits, setHits] = useState<MemorySearchHit[] | null>(null);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [fail, setFail] = useState<string | null>(null);
|
||||
// Manual "添加记忆" form fields.
|
||||
const [addPersona, setAddPersona] = useState("cyrene");
|
||||
const [addScope, setAddScope] = useState<(typeof SCOPES)[number]>("persona");
|
||||
const [addContent, setAddContent] = useState("");
|
||||
const log = useAuditStore((s) => s.log);
|
||||
|
||||
const filtered = (data ?? []).filter((m) => {
|
||||
@@ -60,11 +66,36 @@ export function MemoriesModule(): React.ReactNode {
|
||||
.finally(() => setBusy(false));
|
||||
};
|
||||
|
||||
const addMemory = () => {
|
||||
if (!addContent.trim()) {
|
||||
setFail("记忆内容不能为空");
|
||||
return;
|
||||
}
|
||||
setBusy(true);
|
||||
setFail(null);
|
||||
adminAddMemory({
|
||||
personaId: addPersona.trim() || "cyrene",
|
||||
scope: addScope,
|
||||
content: addContent.trim(),
|
||||
})
|
||||
.then(() => {
|
||||
log("memory.add", "ok");
|
||||
setAddContent("");
|
||||
// Reflect the new record immediately (also refreshes the table).
|
||||
reload();
|
||||
})
|
||||
.catch((e: unknown) => {
|
||||
log("memory.add", "error");
|
||||
setFail(e instanceof Error ? e.message : "添加记忆失败");
|
||||
})
|
||||
.finally(() => setBusy(false));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
<PageHeader
|
||||
title="记忆管理"
|
||||
subtitle="GET /api/v1/admin/memories + POST /memories/search · 用户习惯(全局)/角色日常(只读)/项目(读写) 作用域隔离"
|
||||
subtitle="GET /api/v1/admin/memories + POST /memories + POST /memories/search · 用户习惯(全局)/角色日常(只读)/项目(读写) 作用域隔离"
|
||||
actions={
|
||||
<Button variant="soft" disabled={busy} onClick={search}>
|
||||
向量测试
|
||||
@@ -74,6 +105,42 @@ export function MemoriesModule(): React.ReactNode {
|
||||
|
||||
{fail ? <Notice tone="err">{fail}</Notice> : null}
|
||||
|
||||
<Surface className="space-y-3">
|
||||
<h3 className="text-sm font-semibold">添加记忆(写入后端持久化)</h3>
|
||||
<div className="flex flex-wrap items-start gap-2">
|
||||
<input
|
||||
value={addPersona}
|
||||
onChange={(e) => setAddPersona(e.target.value)}
|
||||
placeholder="personaId"
|
||||
className="w-40 rounded-[var(--mu-radius)] bg-[var(--mu-surface-2)] px-3 py-2 text-sm outline-none focus:ring-1 focus:ring-[var(--mu-accent)]"
|
||||
/>
|
||||
<select
|
||||
value={addScope}
|
||||
onChange={(e) => setAddScope(e.target.value as (typeof SCOPES)[number])}
|
||||
className="h-9 rounded-[var(--mu-radius)] bg-[var(--mu-surface-2)] px-3 text-sm outline-none focus:ring-1 focus:ring-[var(--mu-accent)]"
|
||||
>
|
||||
{SCOPES.map((s) => (
|
||||
<option key={s} value={s}>
|
||||
{s}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<input
|
||||
value={addContent}
|
||||
onChange={(e) => setAddContent(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && addContent.trim()) void addMemory();
|
||||
}}
|
||||
placeholder="记忆内容(如:她喜欢喝冰美式)…"
|
||||
className="min-w-0 flex-1 rounded-[var(--mu-radius)] bg-[var(--mu-surface-2)] px-3 py-2 text-sm outline-none focus:ring-1 focus:ring-[var(--mu-accent)]"
|
||||
/>
|
||||
<Button variant="primary" disabled={busy} onClick={addMemory}>
|
||||
添加记忆
|
||||
</Button>
|
||||
</div>
|
||||
<span className="text-[11px] text-[var(--mu-text-lo)]">作用域为 persona 时与「记住…」同路径;owner 私聊写入安全(群聊/陌生人隔离在 loop 层)。</span>
|
||||
</Surface>
|
||||
|
||||
<Surface className="space-y-3">
|
||||
<h3 className="text-sm font-semibold">语义/关键词检索测试(真实 retrieval)</h3>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
@@ -117,7 +184,9 @@ export function MemoriesModule(): React.ReactNode {
|
||||
<MemoryTable rows={loading ? null : filtered} loading={loading} error={error} />
|
||||
</Surface>
|
||||
|
||||
<Notice>编辑 / 删除 / 衰减 / 溯源为后端 M10+ 提供,当前只读 + 检索测试。群聊 / 陌生人会话查记忆时只读不写、不入主会话上下文、不泄露主人敏感信息。</Notice>
|
||||
<Notice>
|
||||
编辑 / 删除 / 衰减 / 溯源为后端 M10+ 提供,当前只读 + 手动添加 + 检索测试。群聊 / 陌生人会话查记忆时只读不写、不入主会话上下文、不泄露主人敏感信息。
|
||||
</Notice>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user