File size: 4,230 Bytes
6b3f603
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import { useEffect, useState, useRef } from "react";
import { useParams, Link } from "react-router-dom";
import { forgesight } from "@/lib/api";
import AgentTranscript from "@/components/AgentTranscript";
import ReportDownloader from "@/components/ReportDownloader";
import { ArrowLeft, Loader2 } from "lucide-react";

export default function ReportView() {
  const { id } = useParams();
  const [result, setResult] = useState(null);
  const [loading, setLoading] = useState(true);
  const reportRef = useRef(null);

  useEffect(() => {
    async function load() {
      try {
        const data = await forgesight.getInspection(id);
        setResult(data);
      } catch (e) {
        console.error("Failed to load inspection", e);
      } finally {
        setLoading(false);
      }
    }
    load();
  }, [id]);

  if (loading) {
    return (
      <div className="mx-auto max-w-[1400px] px-6 py-20 flex items-center justify-center">
        <Loader2 className="w-8 h-8 animate-spin text-zinc-500" />
      </div>
    );
  }

  if (!result) {
    return (
      <div className="mx-auto max-w-[1400px] px-6 py-20 text-center text-zinc-500 font-mono">
        Inspection not found.
      </div>
    );
  }

  // Same logic to extract summary as in backend, since frontend might not have `result.summary` populated if we just fetched raw doc
  // Wait, backend does not attach `.summary` to raw doc, it's generated by `_summarize(doc)` for Feed.
  // We can write a quick helper here.
  const agents = result?.transcript?.agents || [];
  const inspector = agents.find((a) => a.role === "inspector")?.output?.parsed || {};
  const reporter = agents.find((a) => a.role === "reporter")?.output?.parsed || {};
  const action = agents.find((a) => a.role === "action")?.output?.parsed || {};
  
  const defects = inspector.defects || [];
  const summary = {
    verdict: inspector.verdict || "warn",
    confidence: inspector.confidence || 0,
    defect_count: defects.length,
    priority: action.priority || "P2",
  };

  return (
    <div className="mx-auto max-w-[1400px] px-6 py-10" data-testid="report-view-page">
      <header className="mb-8 flex items-center justify-between">
        <div>
          <Link to="/feed" className="inline-flex items-center gap-2 text-zinc-400 hover:text-white transition-colors mb-4 fs-label">
            <ArrowLeft className="w-4 h-4" /> Back to Feed
          </Link>
          <h1 className="font-display font-black tracking-tighter text-4xl md:text-5xl">
            Inspection Report
          </h1>
          <p className="text-zinc-400 mt-3 font-mono text-sm">ID: {result.id}</p>
        </div>
      </header>

      <div className="grid lg:grid-cols-12 gap-6">
        {/* RIGHT — transcript (Using 12 columns since we don't have the image input UI here) */}
        <div className="lg:col-span-10 lg:col-start-2 space-y-6" ref={reportRef}>
          <div className="border border-white/10 bg-[#141416] p-5 fs-rise" data-testid="summary-panel">
            <div className="flex items-start justify-between gap-4 flex-wrap mb-4">
              <div className="grid grid-cols-2 md:grid-cols-4 gap-4 flex-1">
                <SummaryStat label="Verdict" value={summary.verdict.toUpperCase()} kind={summary.verdict} />
                <SummaryStat label="Confidence" value={`${Math.round(summary.confidence * 100)}%`} />
                <SummaryStat label="Defects" value={summary.defect_count} />
                <SummaryStat label="Priority" value={summary.priority} />
              </div>
              <ReportDownloader
                targetRef={reportRef}
                inspectionId={result?.id}
                disabled={!result}
              />
            </div>
          </div>

          <AgentTranscript transcript={result.transcript} />
        </div>
      </div>
    </div>
  );
}

function SummaryStat({ label, value, kind }) {
  const color =
    kind === "pass" ? "text-[#10B981]" :
    kind === "warn" ? "text-[#F59E0B]" :
    kind === "fail" ? "text-[#ED1C24]" : "text-white";
  return (
    <div>
      <div className="fs-label mb-1">{label}</div>
      <div className={`font-display font-black text-2xl tabular-nums ${color}`}>{value}</div>
    </div>
  );
}