"use client"; import { Alert, Card, Col, Progress, Row, Segmented, Space, Spin, Statistic, Table, Typography } from "antd"; import { useEffect, useState } from "react"; import { adminRequestJson } from "@/lib/admin/providers"; import { TELEMETRY_BIRTH_TIME_SOURCE_LABEL, TELEMETRY_GAP_BUCKET_LABEL, TELEMETRY_PERCENT_SHOWN_BUCKETS, TELEMETRY_QUESTION_BUCKET_LABEL, TELEMETRY_STOP_REASON_LABEL, TELEMETRY_SUMMARY_DEFAULT_WEEKS, TELEMETRY_SUMMARY_WEEK_OPTIONS, TELEMETRY_WIDTH_BUCKET_LABEL, share, type RectificationTelemetrySummary, type TelemetryBucket, type TelemetryVersionRow, type TelemetryWeek, } from "@/lib/admin/rectification-telemetry-summary"; type LoadState = | { status: "loading"; weeks: number } | { status: "ready"; weeks: number; summary: RectificationTelemetrySummary } | { status: "error"; weeks: number; message: string }; function formatNumber(value: number | null, suffix = "") { return value === null || !Number.isFinite(value) ? "—" : `${value.toLocaleString("zh-CN", { maximumFractionDigits: 1 })}${suffix}`; } function formatPercent(value: number | null) { return value === null ? "—" : `${(value * 100).toFixed(0)}%`; } function formatMinutes(seconds: number | null) { return seconds === null ? "—" : formatNumber(seconds / 60, " 分钟"); } function BucketTable({ rows, labels, total, emptyText, keyTitle = "区间", }: { keyTitle?: string; rows: readonly TelemetryBucket[]; labels: Readonly>; total: number; emptyText: string; }) { return ( size="small" pagination={false} rowKey="key" dataSource={[...rows]} locale={{ emptyText }} columns={[ { title: keyTitle, dataIndex: "key", render: (key: string) => labels[key] ?? key }, { title: "会话", dataIndex: "count", width: 64 }, { title: "占比", width: "36%", render: (_, row) => { const value = share(row.count, total); return ; }, }, ]} /> ); } export function RectificationTelemetrySummaryView() { const [state, setState] = useState({ status: "loading", weeks: TELEMETRY_SUMMARY_DEFAULT_WEEKS }); const weeks = state.weeks; useEffect(() => { let active = true; adminRequestJson<{ data: RectificationTelemetrySummary }>(`/api/admin/rectification-telemetry?weeks=${weeks}`) .then((payload) => { if (active) setState({ status: "ready", weeks, summary: payload.data }); }) .catch((error: unknown) => { if (active) { setState({ status: "error", weeks, message: error instanceof Error ? error.message : "统计暂时不可用", }); } }); return () => { active = false; }; }, [weeks]); const summary = state.status === "ready" ? state.summary : null; const total = summary?.sessions ?? 0; const gateKnown = summary ? summary.precisionGate.met + summary.precisionGate.notMet : 0; const percentShown = summary ? summary.gapDistribution .filter((bucket) => TELEMETRY_PERCENT_SHOWN_BUCKETS.includes(bucket.key)) .reduce((sum, bucket) => sum + bucket.count, 0) : 0; return ( ({ label: `近 ${value} 周`, value }))} onChange={(value) => setState({ status: "loading", weeks: Number(value) })} /> )} > 匿名汇总:每个校正会话在第一次给出范围卡时记一行,只有数字和类别,不含用户、出生资料或对话内容。 这里只看汇总,不能逐条查看或导出;数据保留 180 天。 {summary?.since ? ` 当前统计从 ${summary.since} 那一周起。` : ""} {state.status === "error" ? ( ) : null} {state.status === "loading" ? ( ) : null} {summary ? ( <> 0 ? `(${summary.precisionGate.met}/${gateKnown})` : undefined} /> 每个会话平均:定向 {formatNumber(summary.questionTypes.targeted)} · 引导 {formatNumber(summary.questionTypes.guided)} {" "}· 带年月探针 {formatNumber(summary.questionTypes.datedProbe)} · 性格 {formatNumber(summary.questionTypes.personality)} {" "}· 开放 {formatNumber(summary.questionTypes.open)} · 其他 {formatNumber(summary.questionTypes.other)}。 问过引导题的会话 {summary.questionTypes.guidedAskedSessions} 个。 size="small" pagination={false} rowKey="week" dataSource={[...summary.weekly]} scroll={{ x: 640 }} columns={[ { title: "周(周一起)", dataIndex: "week" }, { title: "会话", dataIndex: "sessions" }, { title: "宽度中位", dataIndex: "rangeWidthMinutesP50", render: (value: number | null) => formatNumber(value, " 分钟") }, { title: "提问中位", dataIndex: "questionsTotalP50", render: (value: number | null) => formatNumber(value, " 题") }, { title: "门槛达标", render: (_, row) => row.precisionGateKnown > 0 ? `${formatPercent(share(row.precisionGateMet, row.precisionGateKnown))}(${row.precisionGateMet}/${row.precisionGateKnown})` : "—", }, ]} /> 起始窗口半径中位 {formatNumber(summary.medians.windowRadiusMinutesP50, " 分钟")}。 size="small" pagination={false} rowKey={(row) => `${row.algorithmVersion}|${row.policyVersion}|${row.skillVersion}`} dataSource={[...summary.versions]} locale={{ emptyText: "暂无数据" }} columns={[ { title: "算法", dataIndex: "algorithmVersion", render: (value: string | null) => value ?? "—" }, { title: "策略", dataIndex: "policyVersion", render: (value: string | null) => value ?? "—" }, { title: "Skill", dataIndex: "skillVersion", render: (value: string | null) => value ?? "—" }, { title: "会话", dataIndex: "count", width: 72 }, ]} /> ) : null} ); }