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

+16
View File
@@ -30,6 +30,7 @@ import { useNotification } from "@/core/notification/hooks";
import { saveLastAgentId, useThreadSettings } from "@/core/settings";
import { stopThreadRun, useThreadStream } from "@/core/threads/hooks";
import { textOfMessage } from "@/core/threads/utils";
import { OptType, writeYLog } from "@/core/log/y-log";
import { env } from "@/env";
import { readTaskContext, taskThreadMetadata } from "@/core/auth/task-purpose";
import { TaskPurposePanel } from "@/strategy-components/components/task-components/TaskPurposePanel";
@@ -157,6 +158,21 @@ export default function AgentChatPage({
);
},
onFinish: (state) => {
// 操作日志(Y-Log):专家代理问答完成(成功)后,用最终汇总文本上报一条。
try {
const msgs = state.messages ?? [];
const lastAi = [...msgs].reverse().find((m) => (m as { type?: string }).type === "ai");
const lastHuman = [...msgs].reverse().find((m) => (m as { type?: string }).type === "human");
writeYLog({
optType: OptType.other,
optDetail: "B1-CM助手-专家代理问答",
url: `/api/threads/${threadId ?? ""}/runs/stream`,
reqParam: { agent_id, question: lastHuman ? textOfMessage(lastHuman) : "" },
returnParam: { data: lastAi ? textOfMessage(lastAi) : "" },
});
} catch {
/* 上报绝不影响问答 */
}
if (document.hidden || !document.hasFocus()) {
let body = "Conversation finished";
const lastMessage = state.messages[state.messages.length - 1];
+16
View File
@@ -49,6 +49,7 @@ import { useThreadStream } from "@/core/threads/hooks";
import { useLayoutMode } from "@/core/system-settings/hooks";
import { useRouteBase } from "@/core/route-base";
import { textOfMessage } from "@/core/threads/utils";
import { OptType, writeYLog } from "@/core/log/y-log";
import { env } from "@/env";
import { cn } from "@/lib/utils";
@@ -183,6 +184,21 @@ export default function ChatPage() {
);
},
onFinish: (state) => {
// 操作日志(Y-Log):问答完成(成功)后,用最终汇总文本上报一条。
try {
const msgs = state.messages ?? [];
const lastAi = [...msgs].reverse().find((m) => (m as { type?: string }).type === "ai");
const lastHuman = [...msgs].reverse().find((m) => (m as { type?: string }).type === "human");
writeYLog({
optType: OptType.other,
optDetail: "B1-CM助手-专家代理问答",
url: `/api/threads/${threadId ?? ""}/runs/stream`,
reqParam: { agent_id: selectedAgentId, question: lastHuman ? textOfMessage(lastHuman) : "" },
returnParam: { data: lastAi ? textOfMessage(lastAi) : "" },
});
} catch {
/* 上报绝不影响问答 */
}
if (document.hidden || !document.hasFocus()) {
let body = "Conversation finished";
const lastMessage = state.messages.at(-1);
+16
View File
@@ -31,6 +31,9 @@ import {
isSidebarEnabledFromSearch,
persistSidebarEnabled,
} from '@/core/page-layout/sidebar-enabled';
import { fetchYLogConfig } from '@/core/log/y-log-config';
import { persistYLogToken } from '@/core/log/y-log-token';
import { OptType, writeYLog } from '@/core/log/y-log';
const DEFAULT_USERNAME = 'guest';
const IFRAME_LOGIN_SOURCE = 'iframe';
@@ -160,6 +163,19 @@ export default function LoginPage() {
return;
}
setStoredAuth(auth);
// 操作日志(Y-Log):只有 token 登录(存在 token1=authToken)才上报。
// 持久化 token1(走 authStorage,嵌入态自动隔离)+ 预热上报配置 + 上报登录。
if (authToken) {
persistYLogToken(authToken);
void fetchYLogConfig();
writeYLog({
optType: OptType.login,
optDetail: 'B1-CM助手-登录',
url: '/api/v1/auth/login/token',
reqParam: { username: loginUserName || auth.user_id },
returnParam: { user_id: auth.user_id },
});
}
// 标准登录把用户名写入 userInfo,供顶栏用户区显示(已有则不覆盖)
setStoredUserName(loginUserName);
// 内网透传的 access_token 合并进 userInfo(在 loginByYUserId 覆盖之后),