feat(awooop): surface telegram callback coverage
This commit is contained in:
@@ -138,6 +138,31 @@ interface CallbackEvidenceCaptureStatus {
|
||||
event_at?: string | null;
|
||||
}
|
||||
|
||||
interface CallbackReplyAuditSummary {
|
||||
schema_version?: string;
|
||||
project_id?: string;
|
||||
outbound_total?: number;
|
||||
outbound_source_envelope_total?: number;
|
||||
outbound_source_refs_total?: number;
|
||||
outbound_incident_ref_total?: number;
|
||||
outbound_failed_total?: number;
|
||||
callback_total?: number;
|
||||
callback_sent_total?: number;
|
||||
callback_fallback_total?: number;
|
||||
callback_rescue_total?: number;
|
||||
callback_failed_total?: number;
|
||||
callback_detail_total?: number;
|
||||
callback_history_total?: number;
|
||||
callback_snapshot_captured_total?: number;
|
||||
callback_snapshot_partial_total?: number;
|
||||
callback_snapshot_missing_total?: number;
|
||||
callback_incident_total?: number;
|
||||
snapshot_status?: CallbackEvidenceCaptureState | "no_callback" | string | null;
|
||||
next_action?: string | null;
|
||||
latest_outbound_at?: string | null;
|
||||
latest_callback_at?: string | null;
|
||||
}
|
||||
|
||||
interface Run {
|
||||
run_id: string;
|
||||
project_id: string;
|
||||
@@ -405,6 +430,7 @@ interface CallbackRepliesResponse {
|
||||
total: number;
|
||||
page: number;
|
||||
per_page: number;
|
||||
summary?: CallbackReplyAuditSummary | null;
|
||||
}
|
||||
|
||||
interface AiRoutePolicyItem {
|
||||
@@ -1908,13 +1934,148 @@ function CallbackAwoooPStatusChainSnapshot({
|
||||
);
|
||||
}
|
||||
|
||||
function formatCoveragePercent(value: number, total: number) {
|
||||
if (total <= 0) return "0%";
|
||||
return `${Math.round((value / total) * 100)}%`;
|
||||
}
|
||||
|
||||
function normalizeCallbackAuditSnapshotStatus(statusValue?: string | null) {
|
||||
if (
|
||||
statusValue === "captured" ||
|
||||
statusValue === "partial" ||
|
||||
statusValue === "not_captured" ||
|
||||
statusValue === "no_callback"
|
||||
) {
|
||||
return statusValue;
|
||||
}
|
||||
return "observed";
|
||||
}
|
||||
|
||||
function CallbackReplyAuditSummaryPanel({
|
||||
summary,
|
||||
}: {
|
||||
summary?: CallbackReplyAuditSummary | null;
|
||||
}) {
|
||||
const t = useTranslations("awooop.callbackReply.events.summary");
|
||||
if (!summary) return null;
|
||||
|
||||
const outboundTotal = summary.outbound_total ?? 0;
|
||||
const callbackTotal = summary.callback_total ?? 0;
|
||||
const snapshotStatus = normalizeCallbackAuditSnapshotStatus(summary.snapshot_status);
|
||||
const nextActionRaw = summary.next_action ?? "observed";
|
||||
const nextActionKey = (
|
||||
nextActionRaw === "none" ||
|
||||
nextActionRaw === "press_telegram_detail_or_history" ||
|
||||
nextActionRaw === "press_telegram_detail_or_history_after_rollout" ||
|
||||
nextActionRaw === "review_outbound_source_refs"
|
||||
) ? nextActionRaw : "observed";
|
||||
const sourceRefCoverage = formatCoveragePercent(
|
||||
summary.outbound_incident_ref_total ?? 0,
|
||||
outboundTotal
|
||||
);
|
||||
const snapshotCoverage = formatCoveragePercent(
|
||||
summary.callback_snapshot_captured_total ?? 0,
|
||||
callbackTotal
|
||||
);
|
||||
const latestCallback = summary.latest_callback_at
|
||||
? new Date(summary.latest_callback_at).toLocaleString("zh-TW", {
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
})
|
||||
: "--";
|
||||
const snapshotClass = {
|
||||
captured: "border-[#9bc7a4] bg-[#f0faf2] text-[#17602a]",
|
||||
partial: "border-[#d9b36f] bg-[#fff7e8] text-[#8a5a08]",
|
||||
not_captured: "border-[#e2a29b] bg-[#fff0ef] text-[#9f2f25]",
|
||||
no_callback: "border-[#d8d3c7] bg-[#faf9f3] text-[#5f5b52]",
|
||||
observed: "border-[#9bb6d9] bg-[#eef5ff] text-[#1f5b9b]",
|
||||
}[snapshotStatus];
|
||||
|
||||
return (
|
||||
<div className="grid gap-px border-b border-[#e0ddd4] bg-[#e0ddd4] md:grid-cols-2 xl:grid-cols-5">
|
||||
<div className="bg-white px-4 py-3">
|
||||
<p className="text-xs font-semibold text-[#77736a]">{t("outbound")}</p>
|
||||
<p className="mt-2 font-mono text-xl font-semibold text-[#141413]">
|
||||
{outboundTotal}
|
||||
</p>
|
||||
<p className="mt-1 text-xs leading-5 text-[#5f5b52]">
|
||||
{t("outboundDetail", {
|
||||
sourceRefs: summary.outbound_source_refs_total ?? 0,
|
||||
incidentRefs: summary.outbound_incident_ref_total ?? 0,
|
||||
coverage: sourceRefCoverage,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-white px-4 py-3">
|
||||
<p className="text-xs font-semibold text-[#77736a]">{t("callbacks")}</p>
|
||||
<p className="mt-2 font-mono text-xl font-semibold text-[#141413]">
|
||||
{callbackTotal}
|
||||
</p>
|
||||
<p className="mt-1 text-xs leading-5 text-[#5f5b52]">
|
||||
{t("callbackDetail", {
|
||||
detail: summary.callback_detail_total ?? 0,
|
||||
history: summary.callback_history_total ?? 0,
|
||||
incidents: summary.callback_incident_total ?? 0,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-white px-4 py-3">
|
||||
<p className="text-xs font-semibold text-[#77736a]">{t("snapshots")}</p>
|
||||
<span
|
||||
className={cn(
|
||||
"mt-2 inline-flex border px-2 py-0.5 text-xs font-semibold",
|
||||
snapshotClass
|
||||
)}
|
||||
>
|
||||
{t(`statuses.${snapshotStatus}` as never)}
|
||||
</span>
|
||||
<p className="mt-2 text-xs leading-5 text-[#5f5b52]">
|
||||
{t("snapshotDetail", {
|
||||
captured: summary.callback_snapshot_captured_total ?? 0,
|
||||
partial: summary.callback_snapshot_partial_total ?? 0,
|
||||
missing: summary.callback_snapshot_missing_total ?? 0,
|
||||
coverage: snapshotCoverage,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-white px-4 py-3">
|
||||
<p className="text-xs font-semibold text-[#77736a]">{t("delivery")}</p>
|
||||
<p className="mt-2 font-mono text-xl font-semibold text-[#141413]">
|
||||
{summary.callback_failed_total ?? 0}
|
||||
</p>
|
||||
<p className="mt-1 text-xs leading-5 text-[#5f5b52]">
|
||||
{t("deliveryDetail", {
|
||||
sent: summary.callback_sent_total ?? 0,
|
||||
fallback: (summary.callback_fallback_total ?? 0)
|
||||
+ (summary.callback_rescue_total ?? 0),
|
||||
outboundFailed: summary.outbound_failed_total ?? 0,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-white px-4 py-3">
|
||||
<p className="text-xs font-semibold text-[#77736a]">{t("next")}</p>
|
||||
<p className="mt-2 text-xs font-semibold leading-5 text-[#141413]">
|
||||
{t(`nextActions.${nextActionKey}` as never)}
|
||||
</p>
|
||||
<p className="mt-1 text-xs leading-5 text-[#5f5b52]">
|
||||
{t("latest", { time: latestCallback })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CallbackReplyEvidencePanel({
|
||||
events,
|
||||
total,
|
||||
summary,
|
||||
error,
|
||||
}: {
|
||||
events: CallbackReplyEvent[];
|
||||
total: number;
|
||||
summary?: CallbackReplyAuditSummary | null;
|
||||
error: string | null;
|
||||
}) {
|
||||
const t = useTranslations("awooop.callbackReply.events");
|
||||
@@ -1935,6 +2096,8 @@ function CallbackReplyEvidencePanel({
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<CallbackReplyAuditSummaryPanel summary={summary} />
|
||||
|
||||
{error ? (
|
||||
<div className="px-4 py-4 text-sm text-[#9f2f25]">
|
||||
{t("error", { error })}
|
||||
@@ -2440,6 +2603,7 @@ export default function RunsPage() {
|
||||
const [eventRecurrenceError, setEventRecurrenceError] = useState<string | null>(null);
|
||||
const [callbackEvents, setCallbackEvents] = useState<CallbackReplyEvent[]>([]);
|
||||
const [callbackEventsTotal, setCallbackEventsTotal] = useState(0);
|
||||
const [callbackAuditSummary, setCallbackAuditSummary] = useState<CallbackReplyAuditSummary | null>(null);
|
||||
const [callbackEventsError, setCallbackEventsError] = useState<string | null>(null);
|
||||
const [aiRouteStatus, setAiRouteStatus] = useState<AiRouteStatusResponse | null>(null);
|
||||
const [aiRouteStatusError, setAiRouteStatusError] = useState<string | null>(null);
|
||||
@@ -2568,10 +2732,12 @@ export default function RunsPage() {
|
||||
const callbackData: CallbackRepliesResponse = await callbackRes.json();
|
||||
setCallbackEvents(Array.isArray(callbackData.items) ? callbackData.items : []);
|
||||
setCallbackEventsTotal(callbackData.total ?? 0);
|
||||
setCallbackAuditSummary(callbackData.summary ?? null);
|
||||
setCallbackEventsError(null);
|
||||
} else {
|
||||
setCallbackEvents([]);
|
||||
setCallbackEventsTotal(0);
|
||||
setCallbackAuditSummary(null);
|
||||
setCallbackEventsError(`HTTP ${callbackRes.status}`);
|
||||
}
|
||||
|
||||
@@ -2817,6 +2983,7 @@ export default function RunsPage() {
|
||||
<CallbackReplyEvidencePanel
|
||||
events={callbackEvents}
|
||||
total={callbackEventsTotal}
|
||||
summary={callbackAuditSummary}
|
||||
error={callbackEventsError}
|
||||
/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user