- 学生域 32 页全量迁移(含作答/自动保存/提交/诊断) - 补齐 4 个 MSW mock 缺口,修 diagnostic case 名 - 修 4 处 Tailwind 任意值;新增共享组件与路由
133 lines
3.4 KiB
TypeScript
133 lines
3.4 KiB
TypeScript
/**
|
||
* portal-shell 适配:trackEvent/EventName 在客户端微前端不存在,
|
||
* 服务端埋点由 BFF 负责;此处保留为 no-op 占位以保持类型契约。
|
||
*/
|
||
type EventName = string;
|
||
const trackEvent = async (_event: {
|
||
event: EventName;
|
||
userId: string;
|
||
targetType?: string;
|
||
properties?: Record<string, unknown>;
|
||
}): Promise<void> => {
|
||
/* no-op: portal-shell client-side stub */
|
||
};
|
||
/**
|
||
* portal-shell 适配:data-access/recordAiEvent 在客户端微前端不存在,
|
||
* 由 BFF 维护事件存储;此处保留为 no-op 占位。
|
||
*/
|
||
const recordAiEvent = (_event: {
|
||
userId: string;
|
||
capability: string;
|
||
success: boolean;
|
||
durationMs: number;
|
||
timestamp: number;
|
||
errorMessage?: string;
|
||
}): void => {
|
||
/* no-op: portal-shell client-side stub */
|
||
};
|
||
|
||
export type AiUsageEvent = {
|
||
userId: string;
|
||
capability:
|
||
| "chat"
|
||
| "similar_question"
|
||
| "grading_assist"
|
||
| "lesson_content"
|
||
| "question_variant"
|
||
| "weakness_analysis"
|
||
| "child_summary"
|
||
| "study_path"
|
||
| "explain_error";
|
||
providerId?: string;
|
||
model?: string;
|
||
success: boolean;
|
||
durationMs: number;
|
||
tokenUsage?: number;
|
||
errorMessage?: string;
|
||
};
|
||
|
||
const AI_EVENT_MAP: Record<AiUsageEvent["capability"], EventName> = {
|
||
chat: "ai.chat",
|
||
similar_question: "ai.similar_question",
|
||
grading_assist: "ai.grading_assist",
|
||
lesson_content: "ai.lesson_content",
|
||
question_variant: "ai.question_variant",
|
||
weakness_analysis: "ai.weakness_analysis",
|
||
child_summary: "ai.child_summary",
|
||
study_path: "ai.study_path",
|
||
explain_error: "ai.explain_error",
|
||
};
|
||
|
||
/**
|
||
* AI 使用埋点
|
||
*
|
||
* 记录每次 AI 调用的元数据,用于监控、成本分析与异常排查。
|
||
* 同时写入 data-access 层的内存事件存储(供管理员仪表盘聚合查询)。
|
||
* 非阻塞,失败不影响主流程。
|
||
*/
|
||
export const trackAiUsage = (event: AiUsageEvent): void => {
|
||
const eventName = AI_EVENT_MAP[event.capability];
|
||
|
||
// 写入 data-access 层(供 getAiUsageStats 聚合)
|
||
recordAiEvent({
|
||
userId: event.userId,
|
||
capability: event.capability,
|
||
success: event.success,
|
||
durationMs: event.durationMs,
|
||
timestamp: Date.now(),
|
||
errorMessage: event.errorMessage,
|
||
});
|
||
|
||
// 写入全局 trackEvent(供外部监控系统)
|
||
void trackEvent({
|
||
event: eventName,
|
||
userId: event.userId,
|
||
targetType: event.capability,
|
||
properties: {
|
||
providerId: event.providerId,
|
||
model: event.model,
|
||
success: event.success,
|
||
durationMs: event.durationMs,
|
||
tokenUsage: event.tokenUsage,
|
||
errorMessage: event.errorMessage,
|
||
},
|
||
}).catch(() => {
|
||
// 静默失败:埋点不应影响业务流程
|
||
});
|
||
};
|
||
|
||
/**
|
||
* 测量 AI 调用耗时并自动埋点
|
||
*/
|
||
export const withAiTracking = async <T>(
|
||
userId: string,
|
||
capability: AiUsageEvent["capability"],
|
||
providerId: string | undefined,
|
||
fn: () => Promise<{ result: T; model?: string; tokenUsage?: number }>,
|
||
): Promise<T> => {
|
||
const start = Date.now();
|
||
try {
|
||
const { result, model, tokenUsage } = await fn();
|
||
trackAiUsage({
|
||
userId,
|
||
capability,
|
||
providerId,
|
||
model,
|
||
success: true,
|
||
durationMs: Date.now() - start,
|
||
tokenUsage,
|
||
});
|
||
return result;
|
||
} catch (error) {
|
||
trackAiUsage({
|
||
userId,
|
||
capability,
|
||
providerId,
|
||
success: false,
|
||
durationMs: Date.now() - start,
|
||
errorMessage: error instanceof Error ? error.message : String(error),
|
||
});
|
||
throw error;
|
||
}
|
||
};
|