parent: - Add parent-student-attendance-detail component auth: - Add actions, data-access, schema, services, types onboarding: - Add parent-children-form and hooks directory files: - Add actions, schema, hooks directory notifications: - Add schema and schema test adaptive-practice: - Add answer-input, answer-result, practice-result-view, practice-starter-with-nav - Add question-card, question-content, lib and services directories ai: - Add context/create-ai-client-service, hooks/use-drag-position, hooks/use-position-persistence
101 lines
2.7 KiB
TypeScript
101 lines
2.7 KiB
TypeScript
import "server-only"
|
||
|
||
import { trackEvent, type EventName } from "@/shared/lib/track-event"
|
||
import { recordAiEvent } from "../data-access"
|
||
|
||
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
|
||
}
|
||
}
|