feat(frontend/admin): M10 后台管理面板(/admin hash 路由 + 15 大模块 + RBAC(owner/admin/viewer) + 审计 + Miuix);Docs/api-client.md v0.3 契约补全;tsc+vite build 通过

This commit is contained in:
Aska committed 2026-09-06 07:55:04 +08:00
1 parent 4c48e94659
commit f43eb17030
26 files changed
+2253 -4

No files matched your search

@@ -0,0 +1,72 @@
import { useState } from "react";
import { useAsyncData } from "../../lib/useAsyncData";
import { adminListMemories } from "../../lib/api";
import type { MemoryRecord } from "../../lib/types";
import { useAutoReload } from "../hooks";
import { useAuditStore } from "../audit";
import { AdminTable, Notice, PageHeader, type AdminColumn } from "../components";
import { Button, Surface, Tag } from "../../components/primitives";
const scopeTone: Record<string, "ok" | "warn" | "accent" | "neutral"> = {
user: "accent",
habitual: "ok",
project: "warn",
};
const columns: AdminColumn<MemoryRecord>[] = [
{ key: "scope", title: "作用域", render: (m) => <Tag tone={scopeTone[m.scope] ?? "neutral"}>{m.scope}</Tag> },
{ key: "text", title: "内容", render: (m) => <span className="block max-w-md truncate">{m.text}</span> },
{ key: "id", title: "ID", render: (m) => <span className="font-mono text-xs text-[var(--mu-text-lo)]">{m.id}</span> },
];
export function MemoriesModule(): React.ReactNode {
const { data, loading, error, reload } = useAsyncData(adminListMemories, []);
useAutoReload(reload, 15000);
const [query, setQuery] = useState("");
const log = useAuditStore((s) => s.log);
const filtered = (data ?? []).filter((m) => {
if (!query.trim()) return true;
const q = query.toLowerCase();
return m.text.toLowerCase().includes(q) || m.scope.toLowerCase().includes(q) || m.id.toLowerCase().includes(q);
});
return (
<div className="space-y-5">
<PageHeader
title="记忆管理"
subtitle="GET /api/v1/admin/memories · 用户习惯(全局)/角色日常(只读)/项目(读写) 作用域隔离"
actions={
<Button
variant="soft"
onClick={() => {
log("memory.vectorTest", "ok");
alert("向量检索测试(占位):后端 M10+ 提供语义/向量路径。");
}}
>
向量测试
</Button>
}
/>
<Surface className="space-y-3">
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="按内容 / 作用域 / ID 检索…"
className="w-full 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)]"
/>
<AdminTable
columns={columns}
rows={loading ? null : filtered}
loading={loading}
error={error}
emptyTitle="无记忆数据"
rowKey={(m) => m.id}
/>
</Surface>
<Notice>编辑 / 删除 / 衰减 / 溯源为后端 M10+ 提供,当前只读。群聊 / 陌生人会话查记忆时只读不写、不入主会话上下文、不泄露主人敏感信息。</Notice>
</div>
);
}