MSU576 commited on
Commit
4eb0848
·
verified ·
1 Parent(s): 2a27667

Rename frontend/src/app.js to frontend/src/App.js

Browse files
Files changed (2) hide show
  1. frontend/src/App.js +243 -0
  2. frontend/src/app.js +0 -55
frontend/src/App.js ADDED
@@ -0,0 +1,243 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useEffect, useState } from "react";
2
+
3
+ const API_BASE = ""; // empty when served by same container; or set to server origin
4
+
5
+ function Sidebar({ page, setPage, sites, activeSite, setActiveSite, createSite }) {
6
+ const [name, setName] = useState("");
7
+ return (
8
+ <div className="sidebar">
9
+ <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
10
+ <div style={{ width: 44, height: 44, background: "linear-gradient(135deg,#ff7a00,#ff3a3a)", borderRadius: 12, display: "flex", alignItems: "center", justifyContent: "center" }}>
11
+ <span>🛰️</span>
12
+ </div>
13
+ <div>
14
+ <div style={{ color: "#FF8C00", fontWeight: 700 }}>GeoMate V3</div>
15
+ <div style={{ color: "#9aa7bf", fontSize: 12 }}>AI geotechnical copilot</div>
16
+ </div>
17
+ </div>
18
+
19
+ <hr style={{ margin: "12px 0", borderColor: "rgba(255,122,0,0.06)" }} />
20
+
21
+ <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
22
+ {["Home", "Soil Recognizer", "Soil Classifier", "GSD Curve", "Locator", "GeoMate Ask", "Reports"].map(p => (
23
+ <button key={p} className="btn" onClick={() => setPage(p)} style={{ justifyContent: "flex-start", background: page === p ? "rgba(255,122,0,0.2)" : undefined }}>
24
+ {p}
25
+ </button>
26
+ ))}
27
+ </div>
28
+
29
+ <hr style={{ margin: "12px 0" }} />
30
+ <div style={{ color: "#9aa7bf", marginBottom: 6 }}>Project Sites</div>
31
+ <div style={{ display: "flex", gap: 8 }}>
32
+ <input value={name} onChange={(e) => setName(e.target.value)} placeholder="New site name" />
33
+ <button onClick={() => { if (name) { createSite(name); setName(""); } }} className="btn">Add</button>
34
+ </div>
35
+ <div style={{ marginTop: 8 }}>
36
+ <select value={activeSite || ""} onChange={(e) => setActiveSite(e.target.value)}>
37
+ <option value="">-- Active site --</option>
38
+ {sites.map(s => <option key={s.id} value={s.id}>{s["Site Name"]}</option>)}
39
+ </select>
40
+ </div>
41
+
42
+ <div style={{ marginTop: 12 }}>
43
+ <details>
44
+ <summary style={{ color: "#9aa7bf" }}>Show site JSON</summary>
45
+ <pre style={{ maxHeight: 220, overflow: "auto", background: "#000", padding: 8, borderRadius: 8 }}>
46
+ {activeSite ? JSON.stringify(sites.find(s => s.id === activeSite), null, 2) : "Select an active site"}
47
+ </pre>
48
+ </details>
49
+ </div>
50
+ </div>
51
+ );
52
+ }
53
+
54
+ function Home() {
55
+ return (
56
+ <div>
57
+ <div className="panel">
58
+ <div className="headline">Welcome to GeoMate V3</div>
59
+ <div style={{ color: "#9aa7bf", marginTop: 6 }}>Use the sidebar to navigate. Configure secrets in your HF Space to enable Groq and Earth Engine features.</div>
60
+ </div>
61
+ </div>
62
+ );
63
+ }
64
+
65
+ function SoilClassifier({ activeSite }) {
66
+ const [chat, setChat] = useState([]);
67
+ const [input, setInput] = useState("");
68
+ useEffect(() => {
69
+ // initial greeting
70
+ setChat([{ role: "bot", text: "Hello — I am the GeoMate Soil Classifier. Is the soil organic (contains high organic matter or smells/spongy)? Please answer Yes/No." }]);
71
+ }, []);
72
+
73
+ const pushUser = (text) => {
74
+ setChat(prev => [...prev, { role: "user", text }]);
75
+ };
76
+
77
+ return (
78
+ <div>
79
+ <div className="panel">
80
+ <div style={{ fontWeight: 700 }}>Soil Classifier (chat style)</div>
81
+ <div style={{ marginTop: 12 }}>
82
+ {chat.map((m, i) => (
83
+ <div key={i} className={`bubble ${m.role === "bot" ? "bot" : "user"}`}>
84
+ {m.text}
85
+ </div>
86
+ ))}
87
+ </div>
88
+ <div style={{ marginTop: 12, display: "flex", gap: 8 }}>
89
+ <input style={{ flex: 1 }} value={input} onChange={(e) => setInput(e.target.value)} placeholder="Type your answer..." />
90
+ <button className="btn" onClick={() => { if (input.trim()) { pushUser(input.trim()); setInput(""); } }}>➤</button>
91
+ </div>
92
+ </div>
93
+ </div>
94
+ );
95
+ }
96
+
97
+ function GSDCurve({ activeSite }) {
98
+ const [diam, setDiam] = useState("75,50,37.5,25,19,12.5,9.5,4.75,2,0.85,0.425,0.25,0.18,0.15,0.075");
99
+ const [pass, setPass] = useState("100,98,96,90,85,78,72,65,55,45,35,25,18,14,8");
100
+ const [result, setResult] = useState(null);
101
+
102
+ const compute = async () => {
103
+ const diams = diam.split(",").map(s => parseFloat(s.trim())).filter(Boolean);
104
+ const passing = pass.split(",").map(s => parseFloat(s.trim())).filter(Boolean);
105
+ const res = await fetch(`${API_BASE}/api/gsd`, {
106
+ method: "POST",
107
+ headers: { "Content-Type": "application/json" },
108
+ body: JSON.stringify({ diameters: diams, passing })
109
+ });
110
+ const data = await res.json();
111
+ setResult(data.gsd);
112
+ };
113
+
114
+ return (
115
+ <div>
116
+ <div className="panel">
117
+ <div style={{ fontWeight: 700 }}>GSD Curve</div>
118
+ <textarea rows={3} value={diam} onChange={(e) => setDiam(e.target.value)} />
119
+ <textarea rows={3} value={pass} onChange={(e) => setPass(e.target.value)} />
120
+ <div style={{ marginTop: 8 }}>
121
+ <button className="btn" onClick={compute}>Compute & Save</button>
122
+ </div>
123
+ {result && (
124
+ <div style={{ marginTop: 12 }}>
125
+ <div>D10: {result.D10}</div>
126
+ <div>D30: {result.D30}</div>
127
+ <div>D60: {result.D60}</div>
128
+ <div>Cu: {result.Cu}</div>
129
+ <img alt="GSD" src={`data:image/png;base64,${result.plot_png}`} style={{ maxWidth: "100%", marginTop: 8 }} />
130
+ </div>
131
+ )}
132
+ </div>
133
+ </div>
134
+ );
135
+ }
136
+
137
+ function Locator({ activeSite }) {
138
+ return (
139
+ <div>
140
+ <div className="panel">
141
+ <div style={{ fontWeight: 700 }}>Locator</div>
142
+ <p style={{ color: "#9aa7bf" }}>Provide lat/lon or draw AOI on the map (map drawing will be implemented in future iterations — currently we accept lat/lon).</p>
143
+ </div>
144
+ </div>
145
+ );
146
+ }
147
+
148
+ function GeoMateAsk({ activeSite }) {
149
+ const [input, setInput] = useState("");
150
+ const [chat, setChat] = useState([]);
151
+ const ask = async () => {
152
+ if (!input.trim()) return;
153
+ setChat(prev => [...prev, { role: "user", text: input }]);
154
+ const res = await fetch(`${API_BASE}/api/chat`, {
155
+ method: "POST",
156
+ headers: { "Content-Type": "application/json" },
157
+ body: JSON.stringify({ site_id: activeSite, message: input, history: chat.map(c => ({ role: c.role, content: c.text })) })
158
+ });
159
+ const data = await res.json();
160
+ setChat(prev => [...prev, { role: "user", text: input }, { role: "assistant", text: data.reply }]);
161
+ setInput("");
162
+ };
163
+ return (
164
+ <div className="panel">
165
+ <div style={{ fontWeight: 700 }}>GeoMate Ask (RAG)</div>
166
+ <div style={{ marginTop: 8 }}>
167
+ {chat.map((c, i) => <div key={i} className={`bubble ${c.role === "assistant" ? "bot" : "user"}`}>{c.text}</div>)}
168
+ </div>
169
+ <div style={{ marginTop: 8, display: "flex", gap: 8 }}>
170
+ <input style={{ flex: 1 }} value={input} onChange={(e) => setInput(e.target.value)} />
171
+ <button className="btn" onClick={ask}>Ask</button>
172
+ </div>
173
+ </div>
174
+ );
175
+ }
176
+
177
+ function Reports({ activeSite, sites }) {
178
+ const [downUrl, setDownUrl] = useState(null);
179
+ const generate = async (classificationOnly=false) => {
180
+ if (!activeSite) { alert("Select active site"); return; }
181
+ const formData = new FormData();
182
+ formData.append("site_id", activeSite);
183
+ formData.append("classification_only", classificationOnly ? "true" : "false");
184
+ const res = await fetch(`${API_BASE}/api/report`, { method: "POST", body: formData });
185
+ if (res.ok) {
186
+ const blob = await res.blob();
187
+ const url = URL.createObjectURL(blob);
188
+ setDownUrl(url);
189
+ } else {
190
+ alert("Report generation failed");
191
+ }
192
+ };
193
+ return (
194
+ <div>
195
+ <div className="panel">
196
+ <div style={{ fontWeight: 700 }}>Reports</div>
197
+ <div style={{ marginTop: 8 }}>
198
+ <button className="btn" onClick={() => generate(false)}>Generate Full Geotechnical Report</button>
199
+ <button className="btn" style={{ marginLeft: 8 }} onClick={() => generate(true)}>Generate Classification-only Report</button>
200
+ </div>
201
+ {downUrl && (
202
+ <div style={{ marginTop: 12 }}>
203
+ <a href={downUrl} download>Download report</a>
204
+ </div>
205
+ )}
206
+ </div>
207
+ </div>
208
+ );
209
+ }
210
+
211
+ export default function App() {
212
+ const [page, setPage] = useState("Home");
213
+ const [sites, setSites] = useState([]);
214
+ const [activeSite, setActiveSite] = useState("");
215
+
216
+ useEffect(() => {
217
+ // fetch sites
218
+ fetch(`${API_BASE}/api/sites`).then(r => r.json()).then(d => setSites(d.sites || []));
219
+ }, []);
220
+
221
+ const createSite = async (name) => {
222
+ const form = new FormData();
223
+ form.append("name", name);
224
+ const r = await fetch(`${API_BASE}/api/sites`, { method: "POST", body: form });
225
+ const j = await r.json();
226
+ setSites(prev => [...prev, j.site]);
227
+ };
228
+
229
+ return (
230
+ <div className="app">
231
+ <Sidebar page={page} setPage={setPage} sites={sites} activeSite={activeSite} setActiveSite={setActiveSite} createSite={createSite} />
232
+ <div className="main">
233
+ {page === "Home" && <Home />}
234
+ {page === "Soil Recognizer" && <div className="panel">Soil Recognizer (OCR & model placeholder)</div>}
235
+ {page === "Soil Classifier" && <SoilClassifier activeSite={activeSite} />}
236
+ {page === "GSD Curve" && <GSDCurve activeSite={activeSite} />}
237
+ {page === "Locator" && <Locator activeSite={activeSite} />}
238
+ {page === "GeoMate Ask" && <GeoMateAsk activeSite={activeSite} />}
239
+ {page === "Reports" && <Reports activeSite={activeSite} sites={sites} />}
240
+ </div>
241
+ </div>
242
+ );
243
+ }
frontend/src/app.js DELETED
@@ -1,55 +0,0 @@
1
- import React, { useState } from "react";
2
-
3
- function App() {
4
- const [page, setPage] = useState("home");
5
- const [chat, setChat] = useState([]);
6
- const [input, setInput] = useState("");
7
-
8
- const sendChat = async () => {
9
- const res = await fetch("http://localhost:7860/api/chat", {
10
- method: "POST",
11
- headers: { "Content-Type": "application/json" },
12
- body: JSON.stringify({ site: "Site A", message: input })
13
- });
14
- const data = await res.json();
15
- setChat([...chat, { role: "user", msg: input }, { role: "bot", msg: data.reply }]);
16
- setInput("");
17
- };
18
-
19
- return (
20
- <div className="bg-black text-white h-screen flex">
21
- <div className="w-1/5 bg-gray-900 p-4">
22
- <h1 className="text-2xl text-orange-500 font-bold">🌍 GeoMate V3</h1>
23
- <ul>
24
- <li onClick={() => setPage("home")}>Home</li>
25
- <li onClick={() => setPage("classify")}>Classifier</li>
26
- <li onClick={() => setPage("locator")}>Locator</li>
27
- <li onClick={() => setPage("chat")}>Ask GeoMate</li>
28
- <li onClick={() => setPage("report")}>Reports</li>
29
- </ul>
30
- </div>
31
- <div className="flex-1 p-6 overflow-auto">
32
- {page === "home" && <h2>Welcome to GeoMate 🚀</h2>}
33
- {page === "chat" &&
34
- <div>
35
- <div className="space-y-2">
36
- {chat.map((c, i) => (
37
- <div key={i} className={c.role === "user" ? "text-right" : "text-left"}>
38
- <span className={c.role === "user" ? "bg-orange-500 p-2 rounded-xl" : "bg-blue-500 p-2 rounded-xl"}>
39
- {c.msg}
40
- </span>
41
- </div>
42
- ))}
43
- </div>
44
- <div className="mt-4 flex">
45
- <input value={input} onChange={e => setInput(e.target.value)} className="flex-1 text-black p-2 rounded" />
46
- <button onClick={sendChat} className="bg-orange-600 px-4 ml-2 rounded">➤</button>
47
- </div>
48
- </div>
49
- }
50
- </div>
51
- </div>
52
- );
53
- }
54
-
55
- export default App;