Files
Genarrative/apps/ai-game-creator-shell/src/services/clientAnalytics.ts
T
k88936 0270cd601e DirectProject 埋点成绩改由宿主结算,删掉候选/settle 两阶段与 analyticsAttemptId
- analytics/run.rs:删 Request::DirectCandidate / Request::Settle 与 settle();direct_finished 改收放行代次,终态再读一次 identity_generation,不同则整条不记,相同则直接落 Request::Terminal
- analytics/store.rs:删 pending_runs / pending_run_bytes 与两条候选分支(连带 16 条上限),run_request 不再需要字节预算参数
- analytics/gui.rs + main.rs:删 settle_direct_run_analytics 命令与注册
- 放行侧:kick_queue_dispatch 在读 claim 之后读一次平台会话代次并随整轮传下去;user_input 命令不再收 analyticsAttemptId,队列条目不再持有埋点句柄
- 渲染层:删 beginDirectRunAnalytics、句柄表与结算 effect、settle 调用与命令参数;删除 tests/directRunAnalytics.test.ts,appSurface 两处断言同步
- 判据口径随放行搬家:从“发送时与终态同代”改成“放行时与终态同代”,入队后放行前的账号切换不再丢弃成绩
- 验证:cargo test -- analytics::(45 passed,含新增的直写与代际变化两条)、cargo test -- agent::(949 passed)、analytics_real_file_write 用例、npx vitest run tests/appSurface.test.ts(194 passed)、相关 chat/direct 单测 139 passed、typecheck 通过
2026-09-30 16:25:05 +08:00

101 lines
2.7 KiB
TypeScript

import type { TauriInvoke } from '../app/types';
// 宿主签发的无凭据上下文:前端只传回原值,不从当前登录态补身份。
type AnalyticsContext = {
route: { user_id: string | null; destination_origin: string | null };
editor_session_id: string;
client_version: string;
};
export type ProjectOpenAnalytics = ReturnType<typeof beginProjectOpenAnalytics>;
export function beginProjectOpenAnalytics(
invoke: TauriInvoke,
openSource: 'create' | 'picker' | 'recent',
) {
try {
const operationId = crypto.randomUUID();
const context = invoke<AnalyticsContext | null>(
'capture_analytics_context',
).catch(() => null);
let recorded = false;
return {
record(projectPath: string) {
if (recorded) return;
recorded = true;
const eventTime = new Date().toISOString();
void context
.then((captured) => {
if (!captured) return;
return invoke('record_analytics_project_open', {
context: captured,
projectPath,
operationId,
openSource,
eventTime,
});
})
.catch(() => undefined);
},
};
} catch {
// UUID、桥接与写入失败都不能改变业务操作结果。
return null;
}
}
export function recordAnalyticsProjectLeave(
invoke: TauriInvoke,
projectPath: string,
) {
try {
void invoke('record_analytics_project_leave', { projectPath }).catch(
() => undefined,
);
} catch {
// 离开项目不能等待或依赖埋点。
}
}
// 保存开始冻结身份,完成时冻结时间;观察器不能影响保存结果。
export function beginUiSaveAnalytics(
invoke: TauriInvoke,
projectPath: string,
saveSource: 'manual' | 'auto',
) {
try {
const operationId = crypto.randomUUID();
const context = invoke<AnalyticsContext | null>(
'capture_analytics_context',
).catch(() => null);
let recorded = false;
return {
record(changed: boolean) {
if (recorded) return;
recorded = true;
if (saveSource === 'auto' && !changed) return;
try {
const eventTime = new Date().toISOString();
void context
.then((captured) => {
if (!captured) return;
return invoke('record_analytics_ui_save', {
context: captured,
projectPath,
operationId,
saveSource,
changed,
eventTime,
});
})
.catch(() => undefined);
} catch {
// 时间或桥接失败仅丢弃本条埋点。
}
},
};
} catch {
return null;
}
}