diff --git a/app/(admin)/settings/admin-settings-client.tsx b/app/(admin)/settings/admin-settings-client.tsx index ccc572573..cf482f75b 100644 --- a/app/(admin)/settings/admin-settings-client.tsx +++ b/app/(admin)/settings/admin-settings-client.tsx @@ -1445,6 +1445,8 @@ type RuntimeModelCandidate = RuntimeModelMeta & { }; const analyticsNumberFormatter = new Intl.NumberFormat("zh-CN"); +const UUID_LIKE_REGEX = + /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; function scoreRuntimeModelCandidate(candidate: RuntimeModelCandidate) { return ( @@ -1511,6 +1513,17 @@ function formatAnalyticsMonthLabel(value: string) { return `${year} 年 ${month} 月`; } +function formatAnalyticsModelLabel( + meta: RuntimeModelMeta | null | undefined, + modelId: string | null | undefined +) { + if (meta?.modelName) return meta.modelName; + const normalizedModelId = String(modelId || "").trim(); + if (!normalizedModelId) return "未知模型"; + if (UUID_LIKE_REGEX.test(normalizedModelId)) return "未映射模型"; + return normalizedModelId; +} + const AVAILABLE_TOOLS = [ { name: "getWeather", description: "获取天气信息" }, { name: "createDocument", description: "创建文档" }, @@ -1851,7 +1864,7 @@ export function AdminSettingsClient() { (modelsData?.models || []).forEach((model) => { const providerMeta = providerMetaById.get(model.providerId); - candidates.push({ + const sharedMeta = { modelId: model.modelId, modelName: model.displayName, providerName: providerMeta?.providerName || null, @@ -1861,6 +1874,11 @@ export function AdminSettingsClient() { isDefault: model.isDefault, isSubModel: model.isSubModel, sortOrder: model.sortOrder, + }; + candidates.push(sharedMeta); + candidates.push({ + ...sharedMeta, + modelId: model.id, }); }); @@ -8821,12 +8839,13 @@ export function AdminSettingsClient() {

- {resolveRuntimeModelMeta( - runtimeModelMetaById, + {formatAnalyticsModelLabel( + resolveRuntimeModelMeta( + runtimeModelMetaById, + selectedImage.modelId + ), selectedImage.modelId - ).modelName || - selectedImage.modelId || - "未知"} + )}

{selectedImage.recordType !== "video" ? ( @@ -8967,83 +8986,80 @@ export function AdminSettingsClient() { {/* 🆕 数据分析 */} -
- - -
-
- analytics overview + + +
+
+ analytics overview +
+
+
+ + 生成与运营总览 + + + 统一展示生成规模、活跃用户、趋势波动、模型分布和分辨率结构。 +
-
-
- - 生成与运营总览 - - - 当前面板聚合最近 30 天趋势、当日峰值时段和模型使用分布。 - {selectedDate - ? ` 当前已锁定 ${selectedDate} 的小时分布。` - : " 点击趋势柱可联动查看某一天的小时明细。"} - -
-
- - {formatAnalyticsMonthLabel(selectedMonth)} - {selectedDate ? ( - - {selectedDate} - - ) : null} -
+
+ + {formatAnalyticsMonthLabel(selectedMonth)} + {selectedDate ? ( + + {selectedDate} + + ) : null}
- - -
- {analyticsOverviewCards.map((item) => { - const Icon = item.icon; - return ( -
-
-
-
- {item.label} -
-
- {item.value} -
+
+ + +
+ {analyticsOverviewCards.map((item) => { + const Icon = item.icon; + return ( +
+
+
+
+ {item.label}
-
- +
+ {item.value}
-
- {item.note} +
+
- ); - })} -
- - +
+ {item.note} +
+
+ ); + })} +
+ + - - -
- filters & pulse -
- 时间筛选与月度脉冲 - - 用月份控制本月指标卡,并通过日期联动小时分布。 - -
- + + +
+ filters & pulse +
+ 统计筛选与核心信号 + + 保留月份筛选,同时把高峰时段、领先模型和最近月度走势铺满在同一块工作区里。 + +
+ +
-
-
-
- - 高峰时段 -
-
- {analyticsPeakHour - ? `${String(analyticsPeakHour.hour).padStart(2, "0")}:00` - : "--:--"} -
-
- {analyticsPeakHour - ? `${formatAnalyticsNumber(analyticsPeakHour.count)} 次生成` - : "暂无小时数据"} -
+
+
+ + 高峰时段
+
+ {analyticsPeakHour + ? `${String(analyticsPeakHour.hour).padStart(2, "0")}:00` + : "--:--"} +
+
+ {analyticsPeakHour + ? `${formatAnalyticsNumber(analyticsPeakHour.count)} 次生成` + : "暂无小时数据"} +
+
-
-
- - 领先模型 -
-
- {resolveRuntimeModelMeta( +
+
+ + 领先模型 +
+
+ {formatAnalyticsModelLabel( + resolveRuntimeModelMeta( runtimeModelMetaById, analyticsData?.modelDetailStats?.[0]?.modelId || null - ).modelName || - analyticsData?.modelDetailStats?.[0]?.modelId || - "暂无"} -
-
- {analyticsData?.modelDetailStats?.[0] - ? `${formatAnalyticsNumber( - analyticsData.modelDetailStats[0].count - )} 次调用` - : "还没有模型统计结果"} -
+ ), + analyticsData?.modelDetailStats?.[0]?.modelId || null + )} +
+
+ {analyticsData?.modelDetailStats?.[0] + ? `${formatAnalyticsNumber( + analyticsData.modelDetailStats[0].count + )} 次调用` + : "还没有模型统计结果"}
-
-
+
+
recent months
-
- {analyticsRecentMonths.map((stat) => ( +
+ {analyticsRecentMonths.slice(-3).map((stat) => (
- {stat.month} + {stat.month.slice(5)}
-
+
{formatAnalyticsNumber(stat.count)}
))}
- - -
+
+ + -
- - -
-
- throughput -
- 生成节奏 - - 左侧看最近 30 天总量,右侧看 - {selectedDate ? ` ${selectedDate} ` : "当前选择日期 "} - 的小时分布。 - -
- {selectedDate ? ( - - ) : null} -
- -
-
- 近 30 天生成量 - 点击柱体联动小时分布 -
-
- {analyticsData?.dailyStats?.map((stat, index) => { - const maxCount = Math.max( - ...(analyticsData.dailyStats?.map((s) => s.count) || [1]) - ); - const height = (stat.count / maxCount) * 100; - const isHovered = - hoveredBar?.type === "daily" && - hoveredBar?.index === index; - const isSelected = selectedDate === stat.date; - return ( -
- setSelectedDate( - selectedDate === stat.date ? null : stat.date - ) - } - onMouseEnter={() => - setHoveredBar({ - type: "daily", - index, - value: stat.count, - label: stat.date, - }) - } - onMouseLeave={() => setHoveredBar(null)} - style={{ height: `${Math.max(height, 5)}%` }} - > - {isHovered ? ( -
- {stat.date} · {formatAnalyticsNumber(stat.count)} 次 -
- ) : null} -
- ); - })} -
-
- {analyticsData?.dailyStats?.[0]?.date || "--"} - {analyticsData?.dailyStats?.at(-1)?.date || "--"} -
-
- -
-
- 24 小时分布 - {selectedDate || "默认最近样本"} -
-
- {Array.from({ length: 24 }, (_, hour) => { - const stat = analyticsData?.hourlyStats?.find( - (item) => item.hour === hour - ); - const count = stat?.count || 0; - const maxCount = Math.max( - ...(analyticsData?.hourlyStats?.map((s) => s.count) || [ - 1, - ]), - 1 - ); - const height = (count / maxCount) * 100; - const isHovered = - hoveredBar?.type === "hourly" && - hoveredBar?.index === hour; - return ( -
- setHoveredBar({ - type: "hourly", - index: hour, - value: count, - label: `${hour}:00`, - }) - } - onMouseLeave={() => setHoveredBar(null)} - style={{ height: `${Math.max(height, 4)}%` }} - > - {isHovered ? ( -
- {String(hour).padStart(2, "0")}:00 ·{" "} - {formatAnalyticsNumber(count)} 次 -
- ) : null} -
- ); - })} -
-
- 00:00 - 12:00 - 23:00 -
-
-
-
- -
- - -
- top users -
- 高活跃用户 - 按生成次数排序,便于识别主要使用群体。 -
- -
- {analyticsData?.userStats?.slice(0, 8).map((user, index) => ( +
+ {index + 1} +
+
+
+ {user.userName || user.userEmail?.split("@")[0]} +
+
+ {user.userEmail} +
+
+
+
+
+ {formatAnalyticsNumber(user.imageCount)} +
+
+ 次生成 +
+
+
+ ))} + {(!analyticsData?.userStats || analyticsData.userStats.length === 0) && ( +
+ 暂无用户排行数据 +
+ )} +
+ + + + + +
+ output sizes +
+ 分辨率分布 + + 统一按 4K、3K、2K、1K 四档归并统计,不再展示零散原始尺寸。 + +
+ +
+ {analyticsData?.sizeStats?.map((stat) => { + const percentage = formatAnalyticsRatio( + stat.count, + analyticsData?.overview.totalImages || 0 + ); + return ( +
+
+ {stat.imageSize} +
+
+ {formatAnalyticsNumber(stat.count)} +
+
+ 占比 {percentage} +
+
+
+
+
+ ); + })} +
+ + + + + +
+ model usage +
+ 模型使用概览 + + 兼容历史 UUID 与 runtime modelId,并按统一模型归并后展示后台配置名称。 + +
+ + {(analyticsData?.modelDetailStats || []).slice(0, 8).map((stat, index) => { + const resolved = resolveRuntimeModelMeta( + runtimeModelMetaById, + stat.modelId + ); + const modelLabel = formatAnalyticsModelLabel( + resolved, + stat.modelId + ); + return ( +
+
+
-
+ {index + 1} -
-
-
- {user.userName || user.userEmail?.split("@")[0]} -
-
- {user.userEmail} -
-
-
-
-
- {formatAnalyticsNumber(user.imageCount)} -
-
- 次生成 -
-
-
- ))} - {(!analyticsData?.userStats || - analyticsData.userStats.length === 0) && ( -
- 暂无用户排行数据 -
- )} -
- - - - - -
- output sizes -
- 尺寸分布 - 观察用户输出尺寸的偏好与占比。 -
- - {analyticsData?.sizeStats?.map((stat) => { - const percentage = formatAnalyticsRatio( - stat.count, - analyticsData?.overview.totalImages || 0 - ); - return ( -
-
- - {stat.imageSize || "未知"} - - {formatAnalyticsNumber(stat.count)} · {percentage} - -
-
-
-
-
- ); - })} - - -
-
- -
- - -
- model usage -
- 模型使用概览 - - 修正为按 runtime modelId 聚合后再解析名称,避免跨 provider 串名。 - -
- -
- {(analyticsData?.modelDetailStats || []).slice(0, 6).map((stat, index) => { - const resolved = resolveRuntimeModelMeta( - runtimeModelMetaById, - stat.modelId - ); - return ( -
-
-
- - {index + 1} - -
- {resolved.modelName || stat.modelId || "未知模型"} -
+
+ {modelLabel}
-
+
{resolved.providerName || "未知提供商"} {formatAnalyticsNumber(stat.userCount)} 位用户
-
-
- {formatAnalyticsNumber(stat.count)} -
-
- {formatAnalyticsRatio(stat.count, analyticsModelTotal)} -
-
-
-
-
- ); - })} - {(!analyticsData?.modelDetailStats || - analyticsData.modelDetailStats.length === 0) && ( -
- 暂无模型使用数据 +
+
+ {formatAnalyticsNumber(stat.count)} +
+
+ {formatAnalyticsRatio(stat.count, analyticsModelTotal)} +
+
- )} -
- -
-
-
- model traffic -
-
- {formatAnalyticsNumber(analyticsModelTotal)} -
-
- 当前榜单内模型累计调用次数 +
+
+ ); + })} + {(!analyticsData?.modelDetailStats || + analyticsData.modelDetailStats.length === 0) && ( +
+ 暂无模型使用数据 +
+ )} + + -
- {(analyticsData?.providerTypeStats || []).map((stat, index) => { - const total = - analyticsData?.providerTypeStats?.reduce( - (sum, item) => sum + item.count, - 0 - ) || 0; - const width = formatAnalyticsRatio(stat.count, total); - return ( + + +
+ provider distribution +
+ 提供商分布 + 按 provider 汇总模型调用量,使用整行进度条展示占比。 +
+ + {(analyticsData?.providerTypeStats || []).map((stat, index) => { + const total = + analyticsData?.providerTypeStats?.reduce( + (sum, item) => sum + item.count, + 0 + ) || 0; + const width = formatAnalyticsRatio(stat.count, total); + return ( +
+
+ + {stat.providerName || stat.providerType || "未知提供商"} + + + {formatAnalyticsNumber(stat.count)} · {width} + +
+
+
+
+
+ ); + })} + + + + + +
+ recent 7 days +
+ 模型日趋势 + 每天显示总量,并列出当日最活跃的模型名称。 +
+ + {analyticsTrendDays.map((day) => { + const topModels = day.models.slice(0, 3); + return ( +
+
+
+
+ {day.date} +
+
+ 当日共 {formatAnalyticsNumber(day.total)} 次生成 +
+
+
+ {formatAnalyticsNumber(day.models.length)} 个模型活跃 +
+
+
+ {topModels.map((stat) => (
-
- - {stat.providerName || stat.providerType || "未知提供商"} - - - {formatAnalyticsNumber(stat.count)} · {width} - -
-
-
-
+ + {formatAnalyticsModelLabel( + { + modelName: stat.modelName, + providerName: stat.providerName, + providerType: stat.providerType, + }, + stat.modelId + )} + + + {formatAnalyticsNumber(stat.count)} +
- ); - })} -
-
- - - - - -
- recent 7 days -
- 模型日趋势 - 每天显示总量,并直接列出当日最活跃的模型。 -
- - {analyticsTrendDays.map((day) => { - const topModels = day.models.slice(0, 3); - return ( -
-
-
-
- {day.date} -
-
- 当日共 {formatAnalyticsNumber(day.total)} 次生成 -
-
-
- {formatAnalyticsNumber(day.models.length)} 个模型活跃 -
-
-
- {topModels.map((stat) => ( -
- - {stat.modelName || stat.modelId || "未知模型"} - - - {formatAnalyticsNumber(stat.count)} - -
- ))} -
+ ))}
- ); - })} - {analyticsTrendDays.length === 0 ? ( -
- 暂无趋势数据
- ) : null} -
-
-
+ ); + })} + {analyticsTrendDays.length === 0 ? ( +
+ 暂无趋势数据 +
+ ) : null} +
+
{/* 网站设置 */} diff --git a/app/(chat)/gallery/page.tsx b/app/(chat)/gallery/page.tsx index f5e69863e..bb41768eb 100644 --- a/app/(chat)/gallery/page.tsx +++ b/app/(chat)/gallery/page.tsx @@ -538,13 +538,17 @@ export default function GalleryPage() { />
-
+
-

{selectedImage.alt}

-

- {formatBeijingTime(selectedImage.createdAt)} -

+
+ + 模型 ID: {selectedImage.modelId || "未知模型"} + + + {formatBeijingTime(selectedImage.createdAt)} + +