import React from "react"; import { useTranslation } from "react-i18next"; import ReactMarkdown from "react-markdown"; import type { Components } from "react-markdown"; import remarkGfm from "remark-gfm"; import type { TaskDetail, TaskStep, WorkflowStepResult } from "@fusion/core"; import type { ModelPricingOverrides } from "../../../core/src/model-pricing"; import { createMermaidCodeComponent, sharedRehypePlugins } from "./markdownPipeline"; import { ProviderIcon } from "./ProviderIcon"; import { linkifyFilePaths, linkifyReactChildren } from "../utils/filePathLinkify"; import { inferProviderIconKey } from "../utils/providerIconKey"; import { buildTokenCostRows, formatCost, formatCount, totalCostForRows } from "../utils/taskTokenCost"; const EMPTY_MARKDOWN_CHILD_SEPARATOR = ""; const STRING_OBJECT_TAG = "[object String]"; const markdownLinkifyCodeComponent: NonNullable = ({ children, ...props }) => { const text = React.Children.toArray(children).join(EMPTY_MARKDOWN_CHILD_SEPARATOR); const linkedChildren = linkifyFilePaths(text); if (linkedChildren.length === 1 && Object.prototype.toString.call(linkedChildren[0]) === STRING_OBJECT_TAG) { return {children}; } return {linkedChildren}; }; const markdownLinkifyComponents: Components = { p: ({ children, ...props }) =>

{linkifyReactChildren(children)}

, li: ({ children, ...props }) =>
  • {linkifyReactChildren(children)}
  • , code: createMermaidCodeComponent("task-summary-mermaid-diagram", markdownLinkifyCodeComponent), }; interface TaskSummaryTabProps { task: TaskDetail; pricingOverrides?: ModelPricingOverrides; } function getCompletedSteps(steps: TaskStep[] | undefined): TaskStep[] { return (steps ?? []).filter((step) => step.status === "done" || step.status === "skipped"); } function getRenderableWorkflowResults(results: WorkflowStepResult[] | undefined): WorkflowStepResult[] { return (results ?? []).filter((result) => result.status !== "pending"); } /** * FNXC:TaskDetailSummaryTab 2026-06-27-00:00: * TaskSummaryTab aggregates read-only completion data already loaded on TaskDetail: agent-written summary, changed-file metadata, implementation steps, workflow-step outcomes, and retry counts. It does not fetch, persist, or generate AI content so done-task details remain a front-end composition only. */ export function TaskSummaryTab({ task, pricingOverrides }: TaskSummaryTabProps) { const { t } = useTranslation("app"); const summary = task.summary?.trim(); const changedFiles = task.mergeDetails?.landedFiles?.length ? task.mergeDetails.landedFiles : task.modifiedFiles ?? []; const completedSteps = getCompletedSteps(task.steps); const workflowResults = getRenderableWorkflowResults(task.workflowStepResults); const retryTotal = task.retrySummary?.total ?? 0; const hasChangedStats = task.mergeDetails?.filesChanged != null || task.mergeDetails?.insertions != null || task.mergeDetails?.deletions != null; const hasChangedContent = changedFiles.length > 0 || hasChangedStats || Boolean(task.mergeDetails?.commitSha); const hasAgentWork = completedSteps.length > 0 || workflowResults.length > 0 || retryTotal > 0; const tokenCostRows = buildTokenCostRows(task, t("taskDetail.summaryTab.unknownModel", "(unknown)"), pricingOverrides); const totalCost = totalCostForRows(tokenCostRows); return (

    {t("taskDetail.summaryTab.completionHeading", "Completion summary")}

    {summary ? (
    {summary}
    ) : (

    {t("taskDetail.summaryTab.noCompletionSummary", "No completion summary was recorded for this task.")}

    )}
    {hasChangedContent ? (

    {t("taskDetail.summaryTab.changedHeading", "What changed")}

    {(hasChangedStats || task.mergeDetails?.commitSha) && (
    {task.mergeDetails?.commitSha && (
    {t("taskDetail.summaryTab.commit", "Commit")}
    {task.mergeDetails.commitSha.slice(0, 7)}
    )} {task.mergeDetails?.filesChanged != null && (
    {t("taskDetail.summaryTab.filesChanged", "Files")}
    {task.mergeDetails.filesChanged}
    )} {task.mergeDetails?.insertions != null && (
    {t("taskDetail.summaryTab.insertions", "Added")}
    +{task.mergeDetails.insertions}
    )} {task.mergeDetails?.deletions != null && (
    {t("taskDetail.summaryTab.deletions", "Removed")}
    -{task.mergeDetails.deletions}
    )}
    )} {changedFiles.length > 0 ? (
      {changedFiles.map((path) => (
    • {path}
    • ))}
    ) : (

    {t("taskDetail.summaryTab.noChangedFiles", "No changed-file list is available for this task.")}

    )}
    ) : null} {task.tokenUsage ? (

    {t("taskDetail.summaryTab.tokenCostHeading", "Token usage & cost")}

    {tokenCostRows.map((row) => ( ))}
    {t("taskDetail.summaryTab.model", "Model")} {t("taskDetail.summaryTab.inputTokens", "Input")} {t("taskDetail.summaryTab.outputTokens", "Output")} {t("taskDetail.summaryTab.cachedTokens", "Cached")} {t("taskDetail.summaryTab.totalTokens", "Total")} {t("taskDetail.summaryTab.cost", "Cost")}
    {row.label} {formatCount(row.inputTokens)} {formatCount(row.outputTokens)} {formatCount(row.cachedTokens)} {formatCount(row.totalTokens)} {row.cost.unavailable || row.cost.usd === null ? ( — ) : ( formatCost(row.cost.usd, row.cost.unavailable) )}
    {t("taskDetail.summaryTab.totalCost", "Total cost")} {formatCost(totalCost.usd, totalCost.unavailable)}
    ) : null} {hasAgentWork ? (

    {t("taskDetail.summaryTab.agentWorkHeading", "Work done by agents")}

    {completedSteps.length > 0 && (
    {t("taskDetail.summaryTab.completedSteps", "Completed steps")}
      {completedSteps.map((step, index) => (
    • {step.status} {step.name}
    • ))}
    )} {workflowResults.length > 0 && (
    {t("taskDetail.summaryTab.workflowResults", "Workflow results")}
      {workflowResults.map((result) => { /* FNXC:WorkflowStepResults 2026-07-09-00:40: FN-7727: `priorAttempts` is bounded, read-only history populated by the shared `upsertWorkflowStepResult` core helper when a pre-merge review node (code-review, plan-review, browser-verification) is re-run after a prior failed attempt. Render it only when present — steps with no history render no extra affordance (no orphaned shell). */ const priorAttempts = result.priorAttempts ?? []; return (
    • {result.status.replace("_", " ")} {result.workflowStepName}
      {priorAttempts.length > 0 && (
      {t("taskDetail.summaryTab.priorAttempts", "{{count}} previous failed attempt{{plural}}", { count: priorAttempts.length, plural: priorAttempts.length === 1 ? "" : "s" })}
        {priorAttempts.map((attempt, index) => (
      • {attempt.status.replace("_", " ")} {attempt.startedAt && {attempt.startedAt}} {attempt.output &&

        {attempt.output}

        }
      • ))}
      )}
    • ); })}
    )} {retryTotal > 0 && (

    {t("taskDetail.summaryTab.retries", "Agents retried this task {{count}} time{{plural}}.", { count: retryTotal, plural: retryTotal === 1 ? "" : "s" })}

    )}
    ) : (

    {t("taskDetail.summaryTab.agentWorkHeading", "Work done by agents")}

    {t("taskDetail.summaryTab.noAgentWork", "No completed steps or workflow results are available for this task.")}

    )}
    ); }