Spaces:
Sleeping
Sleeping
| import React, { useState } from 'react'; | |
| import { UploadCloud, Activity, CheckCircle, Layers, BarChart2, ArrowRight, ShieldCheck, Zap, Globe, Cpu, Settings, User } from 'lucide-react'; | |
| import './App.css'; | |
| interface HistoryRecord { | |
| id: number; | |
| timestamp: string; | |
| filename: string; | |
| confidence: number; | |
| thumbnail: string; | |
| } | |
| function LandingPage({ onEnter }: { onEnter: () => void }) { | |
| return ( | |
| <div className="landing-container"> | |
| <nav className="nav-bar"> | |
| <div className="logo"> | |
| <div className="logo-icon"><Activity size={20} color="#fff" /></div> | |
| DeepOceans | |
| </div> | |
| <div className="nav-actions"> | |
| <button className="btn-text">Documentation</button> | |
| <button className="btn-text">API Features</button> | |
| <button className="btn-primary" onClick={onEnter}>View Dashboard</button> | |
| </div> | |
| </nav> | |
| <main> | |
| <section className="hero-section"> | |
| <div className="hero-badge">DeepOceans v1.2 Engine Live</div> | |
| <h1 className="hero-title">Protecting Marine Life with Precision AI.</h1> | |
| <p className="hero-description"> | |
| Enterprise-grade anomaly detection for satellite telemetry. Monitor coastal regions and detect oceanic oil spills automatically using our state-of-the-art segmentation network. | |
| </p> | |
| <div className="hero-buttons"> | |
| <button className="btn-primary-lg" onClick={onEnter}>Start Monitoring <ArrowRight size={20} /></button> | |
| <button className="btn-secondary-lg">Talk to Sales</button> | |
| </div> | |
| <div className="hero-background-glow"></div> | |
| </section> | |
| <section className="features-section"> | |
| <div className="feature-grid"> | |
| <div className="feature-card"> | |
| <div className="feature-icon-wrapper"><Zap size={24} className="feature-icon" /></div> | |
| <h3>Real-Time Inference</h3> | |
| <p>Achieve sub-200ms latency on 256x256 image segmentation masks utilizing optimized T4 compute instances.</p> | |
| </div> | |
| <div className="feature-card"> | |
| <div className="feature-icon-wrapper"><Globe size={24} className="feature-icon" /></div> | |
| <h3>Satellite Agnostic</h3> | |
| <p>Process telemetry from SAR, Sentinel-1, or multi-spectral sources effortlessly through our robust API.</p> | |
| </div> | |
| <div className="feature-card"> | |
| <div className="feature-icon-wrapper"><ShieldCheck size={24} className="feature-icon" /></div> | |
| <h3>Validated Accuracy</h3> | |
| <p>Averaging 0.84 IoU against validation datasets, ensuring you capture maximum spill events with minimal noise.</p> | |
| </div> | |
| <div className="feature-card"> | |
| <div className="feature-icon-wrapper"><Cpu size={24} className="feature-icon" /></div> | |
| <h3>Seamless Integration</h3> | |
| <p>Plug predictions directly into your existing command center via standard REST protocols.</p> | |
| </div> | |
| </div> | |
| </section> | |
| <section className="how-it-works-section"> | |
| <div className="section-header"> | |
| <h2>Streamlined Operations</h2> | |
| <p>From raw signal to actionable intelligence in three steps.</p> | |
| </div> | |
| <div className="steps-container"> | |
| <div className="step-card"> | |
| <span className="step-numeral">01</span> | |
| <h4>Data Ingestion</h4> | |
| <p>Upload your optical or synthetic aperture radar imagery directly into our secure pipeline.</p> | |
| </div> | |
| <div className="step-card"> | |
| <span className="step-numeral">02</span> | |
| <h4>Neural Processing</h4> | |
| <p>Our proprietary U-Net architecture isolates hydrocarbon signatures instantly.</p> | |
| </div> | |
| <div className="step-card"> | |
| <span className="step-numeral">03</span> | |
| <h4>Spatial Analytics</h4> | |
| <p>Generate highly accurate masks and probability scores to dispatch cleanup crews faster.</p> | |
| </div> | |
| </div> | |
| </section> | |
| <section className="preview-section"> | |
| <div className="preview-app-window"> | |
| <div className="app-window-header"> | |
| <div className="mac-dots"><div className="mac-dot red"></div><div className="mac-dot yellow"></div><div className="mac-dot green"></div></div> | |
| <div className="app-title">app.deepoceans.ai/workspace</div> | |
| </div> | |
| <div className="app-window-body"> | |
| <div className="app-mockup-skeleton"> | |
| <div className="mock-nav"></div> | |
| <div className="mock-layout"> | |
| <div className="mock-top"> | |
| <div className="mock-stat"></div> | |
| <div className="mock-stat"></div> | |
| <div className="mock-stat"></div> | |
| <div className="mock-stat"></div> | |
| </div> | |
| <div className="mock-main"> | |
| <div className="mock-side"></div> | |
| <div className="mock-center"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <section className="cta-section"> | |
| <h2>Ready to revolutionize your monitoring?</h2> | |
| <p>Join the marine conservation operations running on DeepOceans.</p> | |
| <button className="btn-primary-lg" onClick={onEnter}>Launch Product <ArrowRight size={20} /></button> | |
| </section> | |
| </main> | |
| <footer className="landing-footer"> | |
| <div className="footer-content"> | |
| <div className="logo"><Activity size={20} color="var(--accent)" /> DeepOceans</div> | |
| <p>© 2026 DeepOceans AI. Securing marine futures.</p> | |
| </div> | |
| </footer> | |
| </div> | |
| ); | |
| } | |
| function Dashboard() { | |
| const [selectedFile, setSelectedFile] = useState<File | null>(null); | |
| const [preview, setPreview] = useState<string | null>(null); | |
| const [maskSrc, setMaskSrc] = useState<string | null>(null); | |
| const [isLoading, setIsLoading] = useState(false); | |
| const [confidence, setConfidence] = useState<number | null>(null); | |
| const [latency, setLatency] = useState<number | null>(null); | |
| const [threshold, setThreshold] = useState<number>(0.5); | |
| const [history, setHistory] = useState<HistoryRecord[]>([ | |
| { | |
| id: 1, | |
| timestamp: new Date(Date.now() - 14400000).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}), | |
| filename: 'sentinel_scan_alpha.png', | |
| confidence: 84.2, | |
| thumbnail: 'https://images.unsplash.com/photo-1621213038477-9dfaf942dcde?auto=format&fit=crop&w=48&h=48' | |
| } | |
| ]); | |
| const [isDragActive, setIsDragActive] = useState(false); | |
| const handleFile = (file: File) => { | |
| setSelectedFile(file); | |
| const url = URL.createObjectURL(file); | |
| setPreview(url); | |
| setMaskSrc(null); | |
| setConfidence(null); | |
| setLatency(null); | |
| }; | |
| const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => { | |
| if (e.target.files && e.target.files.length > 0) handleFile(e.target.files[0]); | |
| }; | |
| const onDragOver = (e: React.DragEvent) => { e.preventDefault(); setIsDragActive(true); }; | |
| const onDragLeave = () => { setIsDragActive(false); }; | |
| const onDrop = (e: React.DragEvent) => { | |
| e.preventDefault(); | |
| setIsDragActive(false); | |
| if (e.dataTransfer.files && e.dataTransfer.files.length > 0) handleFile(e.dataTransfer.files[0]); | |
| }; | |
| const handleUpload = async () => { | |
| if (!selectedFile || !preview) return; | |
| setIsLoading(true); | |
| const formData = new FormData(); | |
| formData.append("file", selectedFile); | |
| const API_URL = import.meta.env.VITE_API_URL || ""; | |
| try { | |
| const response = await fetch(`${API_URL}/predict`, { | |
| method: "POST", | |
| body: formData, | |
| }); | |
| if (response.ok) { | |
| const confHeader = response.headers.get("X-Confidence-Score"); | |
| const latHeader = response.headers.get("X-Inference-Latency-Ms"); | |
| let confValue = 0; | |
| if (confHeader) { | |
| confValue = parseFloat(confHeader); | |
| setConfidence(confValue); | |
| } | |
| if (latHeader) setLatency(parseInt(latHeader)); | |
| const blob = await response.blob(); | |
| const maskUrl = URL.createObjectURL(blob); | |
| setMaskSrc(maskUrl); | |
| const newRecord: HistoryRecord = { | |
| id: Date.now(), | |
| timestamp: new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}), | |
| filename: selectedFile.name, | |
| confidence: confValue, | |
| thumbnail: preview | |
| }; | |
| setHistory(prev => [newRecord, ...prev].slice(0, 10)); | |
| } else { | |
| alert("Inference server returned an error. Ensure backend is running."); | |
| } | |
| } catch (error) { | |
| console.error(error); | |
| alert("Connection failure to inference node."); | |
| } finally { | |
| setIsLoading(false); | |
| } | |
| }; | |
| return ( | |
| <div className="app-layout"> | |
| {/* SaaS App Header */} | |
| <header className="app-header"> | |
| <div className="app-logo"> | |
| <div className="logo-icon"><Activity size={18} color="#fff" /></div> | |
| DeepOceans <span className="app-badge">Workspace</span> | |
| </div> | |
| <div className="header-actions"> | |
| <div className="connection-status"> | |
| <div className="status-orb"></div> Node Operational | |
| </div> | |
| <button className="icon-btn"><Settings size={18} /></button> | |
| <button className="profile-btn"><User size={18} /></button> | |
| </div> | |
| </header> | |
| <main className="dashboard-content"> | |
| <div className="dashboard-grid"> | |
| {/* Top Row: Metrics Grid */} | |
| <div className="metrics-row"> | |
| <div className="stat-card"> | |
| <div className="stat-icon-wrapper"><BarChart2 size={20} className="stat-icon" /></div> | |
| <div className="stat-content"> | |
| <div className="stat-label">Spill Probability</div> | |
| <div className="stat-value" style={{ color: confidence && confidence > 50 ? 'var(--alert)' : 'var(--text-primary)' }}> | |
| {confidence !== null ? `${confidence.toFixed(1)}%` : '--'} | |
| </div> | |
| </div> | |
| </div> | |
| <div className="stat-card"> | |
| <div className="stat-icon-wrapper"><Activity size={20} className="stat-icon" /></div> | |
| <div className="stat-content"> | |
| <div className="stat-label">Inference Latency</div> | |
| <div className="stat-value">{latency !== null ? `${latency}ms` : '--'}</div> | |
| </div> | |
| </div> | |
| <div className="stat-card"> | |
| <div className="stat-icon-wrapper"><CheckCircle size={20} className="stat-icon" /></div> | |
| <div className="stat-content"> | |
| <div className="stat-label">Model IoU (Validation)</div> | |
| <div className="stat-value">0.842</div> | |
| </div> | |
| </div> | |
| <div className="stat-card"> | |
| <div className="stat-icon-wrapper"><Layers size={20} className="stat-icon" /></div> | |
| <div className="stat-content"> | |
| <div className="stat-label">Detection Threshold</div> | |
| <div className="stat-control"> | |
| <input type="range" min="0.1" max="0.9" step="0.1" value={threshold} onChange={(e) => setThreshold(parseFloat(e.target.value))} /> | |
| <span className="range-val">{threshold.toFixed(2)}</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div className="main-interaction-row"> | |
| {/* Left: Uploader */} | |
| <div className="upload-container panel"> | |
| <h3 className="panel-title">Data Ingestion</h3> | |
| <div | |
| className={`dropzone ${isDragActive ? 'drag-active' : ''} ${selectedFile ? 'has-file' : ''}`} | |
| onDragOver={onDragOver} | |
| onDragLeave={onDragLeave} | |
| onDrop={onDrop} | |
| > | |
| <input type="file" id="file-upload" accept="image/*" onChange={handleFileChange} /> | |
| <UploadCloud size={48} className="dropzone-icon" /> | |
| <div className="dropzone-text"> | |
| <span className="dropzone-primary">Click to upload</span> or drag and drop | |
| </div> | |
| <div className="dropzone-secondary">JPG or PNG (Optimal size: 256x256)</div> | |
| {selectedFile && <div className="selected-file-chip">{selectedFile.name}</div>} | |
| </div> | |
| <button | |
| className="btn-action-lg" | |
| onClick={handleUpload} | |
| disabled={!selectedFile || isLoading} | |
| > | |
| {isLoading ? <span className="loader" style={{marginRight: '8px'}}></span> : null} | |
| {isLoading ? "Running Prediction Pipeline..." : "Execute Detection"} | |
| </button> | |
| </div> | |
| {/* Right: History Log */} | |
| <div className="history-container panel"> | |
| <div className="panel-header"> | |
| <h3 className="panel-title">Analysis History</h3> | |
| <span className="record-count">{history.length} Records</span> | |
| </div> | |
| <div className="history-list"> | |
| {history.length === 0 ? ( | |
| <div className="empty-state">No telemetry analyzed yet.</div> | |
| ) : ( | |
| history.map(record => ( | |
| <div key={record.id} className="history-row fade-in"> | |
| <img src={record.thumbnail} alt="snapshot" className="history-thumb" /> | |
| <div className="history-details"> | |
| <div className="history-filename" title={record.filename}>{record.filename}</div> | |
| <div className="history-time">{record.timestamp}</div> | |
| </div> | |
| <div className="history-status"> | |
| <div className={`status-badge ${record.confidence > 50 ? 'alert' : 'clear'}`}> | |
| {record.confidence > 50 ? 'Anomaly Detected' : 'All Clear'} | |
| </div> | |
| <div className="history-score">{record.confidence.toFixed(1)}%</div> | |
| </div> | |
| </div> | |
| )) | |
| )} | |
| </div> | |
| </div> | |
| </div> | |
| {/* Bottom Row: Visualizations */} | |
| <div className="visualization-container panel"> | |
| <div className="panel-header" style={{ marginBottom: '24px' }}> | |
| <h3 className="panel-title">Spatial Rendering Analysis</h3> | |
| <span className="badge-pill">U-Net Feature Map</span> | |
| </div> | |
| <div className="frames-grid"> | |
| <div className="frame-card"> | |
| <div className="frame-header">Source Imagery</div> | |
| <div className="frame-content"> | |
| {preview ? <img src={preview} alt="Raw" className="fade-in" /> : <div className="empty-state">Awaiting Feed</div>} | |
| </div> | |
| </div> | |
| <div className="frame-card"> | |
| <div className="frame-header">Segmentation Mask</div> | |
| <div className="frame-content dark-mode"> | |
| {maskSrc ? <img src={maskSrc} alt="Mask" className="fade-in invert-render" /> : <div className="empty-state">No Output</div>} | |
| </div> | |
| </div> | |
| <div className="frame-card"> | |
| <div className="frame-header">Anomaly Overlay</div> | |
| <div className="frame-content"> | |
| {preview && maskSrc ? ( | |
| <> | |
| <img src={preview} alt="Base" /> | |
| <img src={maskSrc} alt="Overlay" className="fade-in feature-overlay" /> | |
| </> | |
| ) : <div className="empty-state">No Output</div>} | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| </div> | |
| ); | |
| } | |
| function App() { | |
| const [view, setView] = useState<'landing' | 'dashboard'>('landing'); | |
| return ( | |
| <> | |
| {view === 'landing' ? <LandingPage onEnter={() => setView('dashboard')} /> : <Dashboard />} | |
| </> | |
| ); | |
| } | |
| export default App; | |