import { CheckIcon } from "@chopin/icons"; import { ExcerptCorrection } from "./excerpt-correction"; import { answerText, questionName, SIGNALS } from "./analysis-labels"; import type { ConversationPlan } from "@chopin/protocol"; import type { CardLink } from "./links"; import type { ExcerptCorrectionAction } from "./analysis-action"; export type { ExcerptCorrectionAction } from "./analysis-action"; export function AnalysisOverview({ analysis, canEdit = false, links, messageId = "", messageText, onAddExcerpt, state, status, }: { analysis?: ConversationPlan.AnalysisRecord; canEdit?: boolean; links: CardLink[]; messageId?: string; messageText: string; onAddExcerpt?: (action: ExcerptCorrectionAction) => Promise; state?: ConversationPlan.State; status: ConversationPlan.AnalysisRecord["status"]; }) { let outcomes = analysis?.outcomes ?? []; let applied = outcomes.filter(outcome => outcome.status === "accepted").length; let targetIds = [...new Set(outcomes.map(outcome => outcome.targetId).filter(Boolean))]; if (targetIds.length === 0) targetIds = [...new Set(links.map(link => link.threadId))]; let target = targetIds.length === 1 ? state?.threads.find(thread => thread.id === targetIds[0])?.question : targetIds.length > 1 ? `${targetIds.length} decisions` : undefined; let heading = status === "applied" ? outcomes.length ? `${applied} finding${applied === 1 ? "" : "s"} applied` : "Plan updated" : status === "unlinked" ? "No plan change" : status === "failed" ? "Analysis failed" : status === "running" ? "Jev is analysing" : "Waiting for analysis"; let triage = analysis?.passes.find(pass => pass.stage === "triage"); let signals = Object.entries(triage?.answers ?? {}) .filter(([key, answer]) => SIGNALS[key] && answer.type === "noul" && answer.noul >= 0.55) .sort((a, b) => (b[1] as { noul: number }).noul - (a[1] as { noul: number }).noul) .map(([key]) => SIGNALS[key]); let rows = outcomes.map((outcome, index) => { let completeLink = links.find(item => item.source.start === outcome.start && item.source.end === outcome.end ); let linkedSubspans = links.filter(item => item.source.start >= outcome.start && item.source.end <= outcome.end && (item.source.start !== outcome.start || item.source.end !== outcome.end) ).map(item => item.source); let link = completeLink ?? links.find(item => item.source.start >= outcome.start && item.source.end <= outcome.end); let role = analysis?.passes.find(pass => pass.stage === "targeting")?.answers[ `c${index}_role` ]; let label = link?.label ?? (role?.type === "choice" ? role.choice.replaceAll("_", " ") : "Excerpt"); let quote = messageText.slice(outcome.start, outcome.end); return { outcome, label, link, completeLink: !!completeLink, linkedSubspans, quote: quote || link?.source.quote || `Excerpt ${index + 1}`, }; }); if (rows.length === 0 && links.length > 0) { rows = links.map(link => ({ outcome: { start: link.source.start, end: link.source.end, status: "accepted" as const, gate: "accepted", eventIds: [], }, label: link.label, link, completeLink: true, linkedSubspans: [], quote: link.source.quote, })); } let review = rows.filter(row => row.outcome.status === "review" && !row.completeLink).length; let ignored = rows.filter(row => row.outcome.status === "ignored" && !row.completeLink).length; return ( <>

{status === "applied" ? ( <> Chopin linked this message{target ? ( <> to {target} ) : ""}. ) : status === "unlinked" ? "Chopin did not apply a card change from this message." : status === "failed" ? analysis?.error || "Jev could not complete this analysis." : "The message has not finished processing yet."}

{signals.length > 0 && (

Jev detected

{signals.map(signal => ( {signal} ))}
)} {rows.length > 0 && (
{rows.map(({ outcome, label, link, completeLink, linkedSubspans, quote }, index) => (
{label} {link && outcome.status !== "accepted" ? "Added to card" : outcome.status === "accepted" ? "Applied" : outcome.status === "review" ? "Review" : "Not applied"}

“{quote}”

{outcome.status !== "accepted" && !link && (

{outcome.gate}

)} {["review", "ignored"].includes(outcome.status) && ( )}
))}
)} {outcomes.length > 0 && (review > 0 || ignored > 0) && (

{review} held for review · {ignored} not applied

)} {analysis && (
Model answers and run details

Model {analysis.modelVersion || "not recorded"} · Questions{" "} {analysis.questionSetVersion || "not recorded"} {analysis.latencyMs === undefined ? "" : ` · ${analysis.latencyMs} ms`}

{analysis.policyGate &&

Policy: {analysis.policyGate}

} {analysis.quoteValidation && (

Source quotes:{" "} {analysis.quoteValidation.filter(range => range.valid).length}/{analysis .quoteValidation.length} valid

)} {analysis.passes.map((pass, index) => (

{pass.stage}

{Object.entries(pass.answers).map(([key, answer]) => (
{questionName(key)}
{answerText(answer)}
))}
))}
)} ); }