feat: 工具/技能调用统计 + 用户活动审计 + 标签多选筛选

- 任务1: 用户管理/定时任务管理移入左下角菜单, 从侧边栏删除原入口
- 任务2: 管理员查看用户问答(读 LangGraph checkpoint)与记忆, 新增 admin_users 路由与审计页
- 任务3: 标签筛选改为顶部多选标签栏(OR), agents/skills/scheduled_tasks 三端
- 任务4: 新增 tool_call_metrics 表 + ToolMetricsMiddleware + tool-metrics 路由,
  工具/技能调用统计并入大模型统计页, 含错误分类
- 技能识别: lead agent 经 read_file 加载 /mnt/skills/<cat>/<name>/SKILL.md 调用技能,
  中间件扫描工具参数里的 skills/{public,custom}/{name}/ 路径还原 skill_name
- 单元测试 tests/test_tool_metrics_middleware.py (18 项全过)
- 附: SQLite->MySQL 切换后清理脚本

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
吉鑫升andClaude Opus 4.7 committed 2026-05-19 00:59:15 +08:00
1 parent e3cde9be25
commit 4940e0d8ed
46 files changed
+3299 -315

No files matched your search

+72
View File
@@ -0,0 +1,72 @@
import { apiFetch } from "@/core/api";
import { getBackendBaseURL } from "@/core/config";
/** A conversation thread owned by some user (admin view). */
export interface AdminThread {
thread_id: string;
display_name: string;
status: string;
created_at: string;
updated_at: string;
}
/**
* One conversation message, as serialized from the LangGraph checkpoint —
* a LangChain message dump: ``type`` is "human" | "ai" | "tool" | "system",
* ``content`` is a string or a content-block list.
*/
export interface AdminMessage {
type?: string;
content?: unknown;
id?: string | null;
name?: string | null;
}
/** One memory bucket: the global file or a per-agent file. */
export interface AdminMemoryBucket {
name: string;
data: Record<string, unknown>;
}
export interface AdminMemoryResponse {
user_id: string;
buckets: AdminMemoryBucket[];
}
const base = () => `${getBackendBaseURL()}/api/admin/users`;
async function parse<T>(res: Response, what: string): Promise<T> {
if (!res.ok) {
const err = (await res.json().catch(() => ({}))) as { detail?: string };
throw new Error(err.detail ?? `${what} (HTTP ${res.status})`);
}
return res.json() as Promise<T>;
}
export async function listUserThreads(userId: string): Promise<AdminThread[]> {
return parse(
await apiFetch(`${base()}/${encodeURIComponent(userId)}/threads?limit=200`),
"Failed to load threads",
);
}
export async function listUserThreadMessages(
userId: string,
threadId: string,
): Promise<AdminMessage[]> {
return parse(
await apiFetch(
`${base()}/${encodeURIComponent(userId)}/threads/${encodeURIComponent(threadId)}/messages`,
),
"Failed to load messages",
);
}
export async function getUserMemory(
userId: string,
): Promise<AdminMemoryResponse> {
return parse(
await apiFetch(`${base()}/${encodeURIComponent(userId)}/memory`),
"Failed to load memory",
);
}