File size: 6,060 Bytes
403f212
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
import React, { useState, useRef } from "react";

/**
 * CropGuard.jsx — React frontend for the crop disease detection system.
 * Implements the four-step farmer flow from §3.10.4:
 *   home -> preview -> loading -> result
 * Talks to the FastAPI backend's POST /predict endpoint.
 *
 * Set the API base URL via Vite env: VITE_API_URL=http://localhost:8000
 */

const API = import.meta.env.VITE_API_URL || "http://localhost:8000";

const SEVERITY = {
  early:    { label: "Early stage",    urgency: "Routine",   color: "#3fa34d",
              desc: "Symptoms are small and localised. You have time, but act soon." },
  moderate: { label: "Moderate stage", urgency: "Urgent",    color: "#e9a625",
              desc: "The disease covers a good part of the leaf and may spread fast. Treat this week." },
  severe:   { label: "Severe stage",   urgency: "Emergency", color: "#cf3b2f",
              desc: "Most of the leaf or plant is affected. Act today to save the rest of your crop." },
};

export default function CropGuard() {
  const [screen, setScreen] = useState("home");   // home | preview | loading | result
  const [imgUrl, setImgUrl] = useState(null);
  const [file, setFile] = useState(null);
  const [result, setResult] = useState(null);
  const [error, setError] = useState(null);
  const camRef = useRef(null);
  const galRef = useRef(null);

  function pick(e) {
    const f = e.target.files?.[0];
    if (!f) return;
    setFile(f);
    setImgUrl(URL.createObjectURL(f));
    setScreen("preview");
    e.target.value = "";
  }

  async function analyse() {
    setScreen("loading");
    setError(null);
    try {
      const fd = new FormData();
      fd.append("file", file);
      const res = await fetch(`${API}/predict`, { method: "POST", body: fd });
      if (!res.ok) throw new Error("Server error");
      setResult(await res.json());
      setScreen("result");
    } catch (err) {
      setError("Could not reach the analysis server. Check your connection and try again.");
      setScreen("preview");
    }
  }

  function reset() { setScreen("home"); setResult(null); setImgUrl(null); setFile(null); }

  return (
    <div className="cg">
      <header className="cg-head">
        <div className="cg-logo">🌿</div>
        <div><h1>CropGuard GH</h1><span>Snap a leaf. Know the disease.</span></div>
      </header>

      {screen === "home" && (
        <section>
          <div className="cg-hero">
            <h2>Diagnose crop disease in seconds</h2>
            <p>Photograph a sick leaf and get the disease, its severity, and what to do — free.</p>
          </div>
          {error && <p className="cg-err">{error}</p>}
          <button className="cg-cta" onClick={() => camRef.current.click()}>📷 Take a photo of a leaf</button>
          <button className="cg-ghost" onClick={() => galRef.current.click()}>🖼️ Choose from gallery</button>
          <p className="cg-crops">Detects: Maize · Tomato · Cassava</p>
        </section>
      )}

      {screen === "preview" && (
        <section>
          <img className="cg-preview" src={imgUrl} alt="leaf" />
          <h3>Is this the right photo?</h3>
          <p>Make sure the diseased leaf fills the frame and is in focus.</p>
          {error && <p className="cg-err">{error}</p>}
          <div className="cg-row">
            <button className="cg-ghost" onClick={reset}>← Retake</button>
            <button className="cg-cta" onClick={analyse}>🔍 Analyse crop</button>
          </div>
        </section>
      )}

      {screen === "loading" && (
        <section className="cg-loading">
          <div className="cg-spinner" />
          <h3>Analysing the leaf…</h3>
          <p>Checking colour, spots and damage</p>
        </section>
      )}

      {screen === "result" && result && (
        <Result result={result} onReset={reset} />
      )}

      <input ref={camRef} type="file" accept="image/*" capture="environment" hidden onChange={pick} />
      <input ref={galRef} type="file" accept="image/*" hidden onChange={pick} />
    </div>
  );
}

function Result({ result, onReset }) {
  const d = result.disease || {};
  const healthy = !!d.healthy;
  const sev = !healthy && result.severity ? SEVERITY[result.severity] : null;

  return (
    <section>
      <div className={`cg-rhero ${healthy ? "ok" : "bad"}`}>
        <div className="cg-rlabel">Detected disease</div>
        <div className="cg-rname">{d.name}</div>
        <div className="cg-rcrop">🌿 {d.crop}</div>
        <div className="cg-conf">
          <span>Confidence</span><b>{Math.round(result.confidence * 100)}%</b>
        </div>
      </div>

      {!healthy && sev && (
        <div className="cg-badges">
          <div className="cg-badge">
            <small>Severity</small>
            <b style={{ color: sev.color }}>● {sev.label}</b>
          </div>
          <div className="cg-badge">
            <small>Urgency</small>
            <b style={{ color: sev.color }}>{sev.urgency}</b>
          </div>
        </div>
      )}
      {sev && <p className="cg-sevdesc">{sev.desc}</p>}

      {!healthy && (
        <div className="cg-card">
          <h4>✅ What to do now</h4>
          <ol>{(d.treatment || []).map((t, i) => <li key={i}>{t}</li>)}</ol>
          <div className="cg-products">
            {(d.products || []).map((p, i) => <span key={i}>🧪 {p}</span>)}
          </div>
        </div>
      )}

      {!healthy && (
        <div className="cg-card">
          <h4>ℹ️ About this disease</h4>
          <p>{d.cause}</p>
        </div>
      )}

      {healthy && (
        <div className="cg-card cg-ok">
          <h4>✅ No disease detected</h4>
          <p>This leaf looks healthy. Keep monitoring your field weekly.</p>
        </div>
      )}

      <p className="cg-disc">
        ⚠️ This is a diagnostic aid, not a replacement for an extension officer.
        For unusual or severe cases, consult MoFA.
      </p>
      <button className="cg-cta" onClick={onReset}>🍃 Scan another leaf</button>
    </section>
  );
}