feat(y-log): 操作日志上报(前端直连 + 后端公开只读下发地址)

- 后端新增 y_log 公开只读配置:YLogConfig + AppConfig.y_log +
  GET /api/public/config/y-log,地址写在 config.yaml(mtime 热重载)
- 前端核心 src/core/log/:writeYLog(双门控/异步即发即忘/永不抛错/
  去ip-host/对象序列化裁剪/optResult恒0)+ 配置加载 + token1 隔离存取
- 登录(token)持久化 token1 并报登录、登出先报后清;启动预热配置
- 各 API client 成功路径埋点:敏感词/菜单/按钮/技能地址/轻应用、
  会商草稿+任务草稿+后台任务、智能体/定时任务/知识库、Word导出,
  以及问答完成钩子(ChatPage/AgentChatPage onFinish)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
AdministratorandClaude Opus 4.8 committed 2026-06-23 17:46:08 +08:00
1 parent dd942cdd60
commit d2be3b206d
26 files changed
+808 -40

No files matched your search

+77 -7
View File
@@ -1,5 +1,6 @@
import { apiFetch } from "@/core/api";
import { getBackendBaseURL } from "@/core/config";
import { OptType, writeYLog } from "@/core/log/y-log";
import type {
BatchIngestPayload,
@@ -41,9 +42,17 @@ export async function listKnowledgeNotes(params: KnowledgeListParams = {}) {
if (params.folder !== undefined) qs.set("folder", params.folder);
qs.set("limit", String(params.limit ?? 20));
qs.set("offset", String(params.offset ?? 0));
return parseResponse<KnowledgeListResponse>(
const result = await parseResponse<KnowledgeListResponse>(
await apiFetch(`${baseUrl()}/notes?${qs.toString()}`),
);
writeYLog({
optType: OptType.search,
optDetail: "B1-CM助手-知识库-检索",
url: `${baseUrl()}/notes?${qs.toString()}`,
reqParam: params,
returnParam: result,
});
return result;
}
export async function getKnowledgeNote(noteId: string) {
@@ -67,6 +76,13 @@ export async function createKnowledgeNote(data: {
body: JSON.stringify(data),
}),
);
writeYLog({
optType: OptType.create,
optDetail: "B1-CM助手-知识库-新建",
url: `${baseUrl()}/notes`,
reqParam: data,
returnParam: res.note,
});
return res.note;
}
@@ -81,22 +97,38 @@ export async function updateKnowledgeNote(
folder: string | null;
}>,
) {
return parseResponse<KnowledgeNote>(
const result = await parseResponse<KnowledgeNote>(
await apiFetch(`${baseUrl()}/notes/${encodeURIComponent(noteId)}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
}),
);
writeYLog({
optType: OptType.edit,
optDetail: "B1-CM助手-知识库-修改",
url: `${baseUrl()}/notes/${encodeURIComponent(noteId)}`,
reqParam: { noteId, data },
returnParam: result,
});
return result;
}
export async function deleteKnowledgeNote(noteId: string, hard = false) {
return parseResponse<{ ok: boolean; note_id: string }>(
const result = await parseResponse<{ ok: boolean; note_id: string }>(
await apiFetch(
`${baseUrl()}/notes/${encodeURIComponent(noteId)}${hard ? "?hard=true" : ""}`,
{ method: "DELETE" },
),
);
writeYLog({
optType: OptType.delete,
optDetail: "B1-CM助手-知识库-删除",
url: `${baseUrl()}/notes/${encodeURIComponent(noteId)}${hard ? "?hard=true" : ""}`,
reqParam: { noteId, hard },
returnParam: result,
});
return result;
}
export async function searchKnowledge(query: string, limit = 8) {
@@ -107,11 +139,18 @@ export async function searchKnowledge(query: string, limit = 8) {
body: JSON.stringify({ query, mode: "keyword", limit }),
}),
);
writeYLog({
optType: OptType.search,
optDetail: "B1-CM助手-知识库-检索",
url: `${baseUrl()}/search`,
reqParam: { query, mode: "keyword", limit },
returnParam: res.items,
});
return res.items;
}
export async function ingestThread(threadId: string, payload: IngestThreadPayload = {}) {
return parseResponse<{
const result = await parseResponse<{
note?: KnowledgeNote;
created?: boolean;
queued?: boolean;
@@ -122,16 +161,32 @@ export async function ingestThread(threadId: string, payload: IngestThreadPayloa
body: JSON.stringify(payload),
}),
);
writeYLog({
optType: OptType.import,
optDetail: "B1-CM助手-知识库-导入",
url: `${baseUrl()}/ingest/thread/${encodeURIComponent(threadId)}`,
reqParam: { threadId, payload },
returnParam: result,
});
return result;
}
export async function ingestThreadsBatch(payload: BatchIngestPayload = {}) {
return parseResponse<BatchIngestResult>(
const result = await parseResponse<BatchIngestResult>(
await apiFetch(`${baseUrl()}/ingest/threads/batch`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
}),
);
writeYLog({
optType: OptType.import,
optDetail: "B1-CM助手-知识库-导入",
url: `${baseUrl()}/ingest/threads/batch`,
reqParam: payload,
returnParam: result,
});
return result;
}
export interface IngestFileResult {
@@ -162,9 +217,17 @@ export async function ingestKnowledgeFile(
if (opts.template_id) form.append("template_id", opts.template_id);
if (opts.folder) form.append("folder", opts.folder);
// No Content-Type header — the browser sets the multipart boundary itself.
return parseResponse<IngestFileResult>(
const result = await parseResponse<IngestFileResult>(
await apiFetch(`${baseUrl()}/ingest/file`, { method: "POST", body: form }),
);
writeYLog({
optType: OptType.import,
optDetail: "B1-CM助手-知识库-导入",
url: `${baseUrl()}/ingest/file`,
reqParam: { fileName: file.name, ...opts },
returnParam: result,
});
return result;
}
/** Resolve `[[wikilink]]` targets to note ids / vault pages. */
@@ -197,9 +260,16 @@ export async function fetchVaultPage(path: string) {
}
export async function exportKnowledgeGraph() {
return parseResponse<GraphExportResult>(
const result = await parseResponse<GraphExportResult>(
await apiFetch(`${baseUrl()}/export/graph`, { method: "POST" }),
);
writeYLog({
optType: OptType.export,
optDetail: "B1-CM助手-知识库-导出",
url: `${baseUrl()}/export/graph`,
returnParam: result,
});
return result;
}
/** Absolute URL of a served wiki-export file (for iframe / download). */