Oill_split / frontend /src /App.tsx
Utkarshres32's picture
Prepare frontend for Vercel deployment
fcc8c39
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;