/** * portal-shell 适配:trackEvent/EventName 在客户端微前端不存在, * 服务端埋点由 BFF 负责;此处保留为 no-op 占位以保持类型契约。 */ type EventName = string; const trackEvent = async (_event: { event: EventName; userId: string; targetType?: string; properties?: Record; }): Promise => { /* 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 = { 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 ( userId: string, capability: AiUsageEvent["capability"], providerId: string | undefined, fn: () => Promise<{ result: T; model?: string; tokenUsage?: number }>, ): Promise => { 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; } };