import type { GenerationCapability, GenerationProvider } from "@/lib/types"; export const USAGE_TIME_ZONE = "Asia/Shanghai"; export const UNASSIGNED_ORGANIZATION_ID = "__unassigned__"; export type UsagePreset = "today" | "7d" | "30d" | "month"; export type UsageDateRange = { from: string; to: string; startDate: string; endDate: string; label: string; dayCount: number; }; export type UsageCountItem = { key: string; label: string; count: number; }; export type UsageRecordView = { id: string; jobId: string; ownerId: string; accountName: string; accountUsername?: string; organizationId: string; organizationName: string; capability: GenerationCapability; capabilityLabel: string; provider?: GenerationProvider; providerLabel: string; reqKey?: string; createdAt: string; }; export type PersonalUsageReport = { preset: UsagePreset; range: UsageDateRange; total: number; byCapability: UsageCountItem[]; recent: UsageRecordView[]; }; export type UsageTrendPoint = { date: string; label: string; count: number; }; export type UsageOrganizationRow = { organizationId: string; organizationName: string; count: number; accountCount: number; lastUsedAt?: string; }; export type UsageAccountRow = { ownerId: string; accountName: string; accountUsername?: string; organizationId: string; organizationName: string; count: number; lastUsedAt?: string; }; export type UsageFilterOption = { value: string; label: string; }; export type AdminUsageReport = { range: UsageDateRange; summary: { total: number; activeAccounts: number; activeOrganizations: number; averagePerDay: number; }; trend: UsageTrendPoint[]; byCapability: UsageCountItem[]; byProvider: UsageCountItem[]; organizations: UsageOrganizationRow[]; accounts: UsageAccountRow[]; recent: UsageRecordView[]; options: { organizations: UsageFilterOption[]; accounts: UsageFilterOption[]; capabilities: UsageFilterOption[]; providers: UsageFilterOption[]; }; warnings?: string[]; }; export const USAGE_PRESET_OPTIONS: Array<{ value: UsagePreset; label: string }> = [ { value: "today", label: "今天" }, { value: "7d", label: "近 7 天" }, { value: "30d", label: "近 30 天" }, { value: "month", label: "本月" } ]; export const CAPABILITY_OPTIONS: Array<{ value: GenerationCapability; label: string }> = [ { value: "image.generate", label: "图片生成" }, { value: "video.generate", label: "视频生成" } ]; export function capabilityLabel(capability: GenerationCapability): string { return CAPABILITY_OPTIONS.find((item) => item.value === capability)?.label || capability; } export function providerLabel(provider?: GenerationProvider): string { if (provider === "volcengine-visual") return "即梦图片"; if (provider === "evolink") return "EvoLink"; if (provider === "seedance") return "Seedance"; if (provider === "seedream") return "Seedream 5.0 Pro"; if (provider === "bailian") return "阿里云百炼"; if (provider === "mock") return "历史任务"; return "未知服务商"; } export function usagePresetRange(preset: UsagePreset, now = new Date()): UsageDateRange { const today = dateKeyInChina(now); let startDate = today; if (preset === "7d") startDate = shiftDateKey(today, -6); if (preset === "30d") startDate = shiftDateKey(today, -29); if (preset === "month") startDate = `${today.slice(0, 7)}-01`; return usageDateRange(startDate, today, USAGE_PRESET_OPTIONS.find((item) => item.value === preset)?.label); } export function usageDateRange(startDate: string, endDate: string, label?: string): UsageDateRange { if (!isDateKey(startDate) || !isDateKey(endDate)) throw badDateRange("日期格式必须为 YYYY-MM-DD。"); if (startDate > endDate) throw badDateRange("开始日期不能晚于结束日期。"); const dayCount = daysBetween(startDate, endDate) + 1; if (dayCount > 3660) throw badDateRange("单次查询最多支持 10 年。"); return { from: `${startDate}T00:00:00+08:00`, to: `${shiftDateKey(endDate, 1)}T00:00:00+08:00`, startDate, endDate, label: label || (startDate === endDate ? startDate : `${startDate} 至 ${endDate}`), dayCount }; } export function usageDateKey(iso: string): string { return dateKeyInChina(new Date(iso)); } export function shiftDateKey(date: string, days: number): string { const value = new Date(`${date}T00:00:00Z`); value.setUTCDate(value.getUTCDate() + days); return value.toISOString().slice(0, 10); } function dateKeyInChina(date: Date): string { const parts = new Intl.DateTimeFormat("en-CA", { timeZone: USAGE_TIME_ZONE, year: "numeric", month: "2-digit", day: "2-digit" }).formatToParts(date); const values = Object.fromEntries(parts.map((part) => [part.type, part.value])); return `${values.year}-${values.month}-${values.day}`; } function isDateKey(value: string): boolean { if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return false; return new Date(`${value}T00:00:00Z`).toISOString().slice(0, 10) === value; } function daysBetween(startDate: string, endDate: string): number { return Math.round((Date.parse(`${endDate}T00:00:00Z`) - Date.parse(`${startDate}T00:00:00Z`)) / 86_400_000); } function badDateRange(message: string): Error & { status: number } { return Object.assign(new Error(message), { status: 400 }); }