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:
1 parent
dd942cdd60
commit
d2be3b206d
26 files changed
+808
-40
No files matched your search
@@ -16,6 +16,7 @@ import {
|
||||
|
||||
import { apiFetch } from "@/core/api/fetch-client";
|
||||
import { getBackendBaseURL } from "@/core/config";
|
||||
import { OptType, writeYLog } from "@/core/log/y-log";
|
||||
import { parseThemeMap, stringifyThemeMap } from "@/strategy-components/lib/light-app";
|
||||
|
||||
export type LightAppType = "window_open" | "iframe";
|
||||
@@ -156,7 +157,15 @@ export async function listLightApps(params: ListLightAppsParams = {}): Promise<L
|
||||
const query = qs.toString();
|
||||
const res = await apiFetch(`${BASE()}${query ? `?${query}` : ""}`, { method: "GET" });
|
||||
const data = await readJson<{ apps: ServerApp[] }>(res, "list light apps");
|
||||
return (data.apps ?? []).map(toApp);
|
||||
const apps = (data.apps ?? []).map(toApp);
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-轻应用-列表",
|
||||
url: `${BASE()}${query ? `?${query}` : ""}`,
|
||||
reqParam: params,
|
||||
returnParam: apps,
|
||||
});
|
||||
return apps;
|
||||
}
|
||||
|
||||
export async function getLightApp(appId: string): Promise<LightApp> {
|
||||
@@ -170,7 +179,15 @@ export async function createLightApp(input: LightAppInput): Promise<LightApp> {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(toBody(input)),
|
||||
});
|
||||
return toApp(await readJson<ServerApp>(res, "create light app"));
|
||||
const app = toApp(await readJson<ServerApp>(res, "create light app"));
|
||||
writeYLog({
|
||||
optType: OptType.create,
|
||||
optDetail: "B1-CM助手-轻应用-注册",
|
||||
url: BASE(),
|
||||
reqParam: input,
|
||||
returnParam: app,
|
||||
});
|
||||
return app;
|
||||
}
|
||||
|
||||
export async function updateLightApp(
|
||||
@@ -182,12 +199,26 @@ export async function updateLightApp(
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(toBody(patch)),
|
||||
});
|
||||
return toApp(await readJson<ServerApp>(res, "update light app"));
|
||||
const app = toApp(await readJson<ServerApp>(res, "update light app"));
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-轻应用-修改",
|
||||
url: `${BASE()}/${encodeURIComponent(appId)}`,
|
||||
reqParam: { appId, patch },
|
||||
returnParam: app,
|
||||
});
|
||||
return app;
|
||||
}
|
||||
|
||||
export async function deleteLightApp(appId: string): Promise<void> {
|
||||
const res = await apiFetch(`${BASE()}/${encodeURIComponent(appId)}`, { method: "DELETE" });
|
||||
await readJson<{ success: boolean }>(res, "delete light app");
|
||||
writeYLog({
|
||||
optType: OptType.delete,
|
||||
optDetail: "B1-CM助手-轻应用-删除",
|
||||
url: `${BASE()}/${encodeURIComponent(appId)}`,
|
||||
reqParam: { appId },
|
||||
});
|
||||
}
|
||||
|
||||
// ── React Query hooks(管理页 / 列表页 / 侧边栏共用缓存) ───────────────────
|
||||
|
||||
@@ -10,6 +10,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { apiFetch } from "@/core/api/fetch-client";
|
||||
import { getBackendBaseURL } from "@/core/config";
|
||||
import { OptType, writeYLog } from "@/core/log/y-log";
|
||||
import type { MenuOverride } from "@/core/page-layout/menu-overrides";
|
||||
|
||||
const BASE = () => `${getBackendBaseURL()}/api/menu-overrides`;
|
||||
@@ -67,7 +68,15 @@ export async function replaceMenuOverrides(overrides: MenuOverride[]): Promise<M
|
||||
body: JSON.stringify({ overrides: overrides.map(toBody) }),
|
||||
});
|
||||
const data = await readJson<{ overrides: ServerOverride[] }>(res, "save menu overrides");
|
||||
return (data.overrides ?? []).map(toOverride);
|
||||
const saved = (data.overrides ?? []).map(toOverride);
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-菜单管理-保存",
|
||||
url: "/api/menu-overrides",
|
||||
reqParam: { overrides },
|
||||
returnParam: saved,
|
||||
});
|
||||
return saved;
|
||||
}
|
||||
|
||||
// ── React Query hooks(侧边栏 + 管理页共用缓存) ────────────────────────────
|
||||
|
||||
@@ -10,6 +10,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { apiFetch } from "@/core/api/fetch-client";
|
||||
import { getBackendBaseURL } from "@/core/config";
|
||||
import { OptType, writeYLog } from "@/core/log/y-log";
|
||||
|
||||
const BASE = () => `${getBackendBaseURL()}/api/sensitive-words`;
|
||||
|
||||
@@ -71,7 +72,14 @@ async function readJson<T>(res: Response, ctx: string): Promise<T> {
|
||||
export async function listSensitiveWords(): Promise<SensitiveWord[]> {
|
||||
const res = await apiFetch(BASE(), { method: "GET" });
|
||||
const data = await readJson<{ words: ServerWord[] }>(res, "list sensitive words");
|
||||
return (data.words ?? []).map(toWord);
|
||||
const words = (data.words ?? []).map(toWord);
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-敏感词管理-列表",
|
||||
url: "/api/sensitive-words",
|
||||
returnParam: words,
|
||||
});
|
||||
return words;
|
||||
}
|
||||
|
||||
export async function replaceSensitiveWords(words: SensitiveWord[]): Promise<SensitiveWord[]> {
|
||||
@@ -81,7 +89,15 @@ export async function replaceSensitiveWords(words: SensitiveWord[]): Promise<Sen
|
||||
body: JSON.stringify({ words: words.map(toBody) }),
|
||||
});
|
||||
const data = await readJson<{ words: ServerWord[] }>(res, "save sensitive words");
|
||||
return (data.words ?? []).map(toWord);
|
||||
const saved = (data.words ?? []).map(toWord);
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-敏感词管理-保存",
|
||||
url: "/api/sensitive-words",
|
||||
reqParam: { words },
|
||||
returnParam: saved,
|
||||
});
|
||||
return saved;
|
||||
}
|
||||
|
||||
/** 把「启用」的词条折叠成运行时脱敏所需的 `原始词 → 替换词` 映射。 */
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { apiFetch } from "@/core/api/fetch-client";
|
||||
import { getBackendBaseURL } from "@/core/config";
|
||||
import { OptType, writeYLog } from "@/core/log/y-log";
|
||||
|
||||
const BASE = () => `${getBackendBaseURL()}/api/skill-addresses`;
|
||||
|
||||
@@ -197,7 +198,15 @@ export async function scanAddresses(kinds?: AddressKind[], skill?: string): Prom
|
||||
if (skill) params.set("skill", skill);
|
||||
const qs = params.toString();
|
||||
const res = await apiFetch(`${BASE()}/scan${qs ? `?${qs}` : ""}`, { method: "GET" });
|
||||
return toScanResult(await readJson<any>(res, "扫描"));
|
||||
const result = toScanResult(await readJson<any>(res, "扫描"));
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-技能地址管理-扫描",
|
||||
url: `${BASE()}/scan${qs ? `?${qs}` : ""}`,
|
||||
reqParam: { kinds, skill },
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 流式扫描:逐技能上报进度,返回最终聚合结果。 */
|
||||
@@ -216,6 +225,13 @@ export async function scanAddressesStream(
|
||||
else if (ev.type === "result") result = toScanResult(ev);
|
||||
});
|
||||
if (!result) throw new Error("扫描未返回结果");
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-技能地址管理-扫描",
|
||||
url: `${BASE()}/scan/stream${qs ? `?${qs}` : ""}`,
|
||||
reqParam: { kinds: opts.kinds, skill: opts.skill },
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -251,6 +267,13 @@ export async function applyReplacementsStream(
|
||||
else if (ev.type === "result") result = toApplyResult(ev);
|
||||
});
|
||||
if (!result) throw new Error("应用未返回结果");
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-技能地址管理-应用替换",
|
||||
url: `${BASE()}/apply/stream`,
|
||||
reqParam: body,
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -269,7 +292,15 @@ export async function listBackups(): Promise<BackupEntry[]> {
|
||||
export async function rollbackBackup(backupId: string): Promise<{ restored: number }> {
|
||||
const res = await apiFetch(`${BASE()}/rollback/${encodeURIComponent(backupId)}`, { method: "POST" });
|
||||
const data = await readJson<{ restored_files: string[] }>(res, "撤销");
|
||||
return { restored: (data.restored_files ?? []).length };
|
||||
const result = { restored: (data.restored_files ?? []).length };
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-技能地址管理-回退",
|
||||
url: `${BASE()}/rollback/${encodeURIComponent(backupId)}`,
|
||||
reqParam: { backupId },
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// ── React Query ────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -10,6 +10,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { apiFetch } from "@/core/api/fetch-client";
|
||||
import { getBackendBaseURL } from "@/core/config";
|
||||
import { OptType, writeYLog } from "@/core/log/y-log";
|
||||
import type {
|
||||
TaskBusiness,
|
||||
TaskButton,
|
||||
@@ -87,7 +88,15 @@ export async function replaceTaskButtons(buttons: TaskButton[]): Promise<TaskBut
|
||||
body: JSON.stringify({ buttons: buttons.map(toBody) }),
|
||||
});
|
||||
const data = await readJson<{ buttons: ServerButton[] }>(res, "save task buttons");
|
||||
return (data.buttons ?? []).map(toButton);
|
||||
const saved = (data.buttons ?? []).map(toButton);
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-按钮管理-保存",
|
||||
url: "/api/task-buttons",
|
||||
reqParam: { buttons },
|
||||
returnParam: saved,
|
||||
});
|
||||
return saved;
|
||||
}
|
||||
|
||||
// ── React Query hooks(工作区 + 管理页共用缓存) ────────────────────────────
|
||||
|
||||
Reference in new issue
Block a user