aml_project / frontend /src /pages /DbscanLab.jsx
Antigravity
Redesign UI with premium interactive glassmorphic dark theme
ef886da
import { useState } from 'react';
import axios from 'axios';
import { Braces } from 'lucide-react';
import { motion } from 'framer-motion';
import { PageHeader, ResultBox, ErrorBox, SubmitButton, UploadZone, SectionLabel } from '../components/UI';
export default function DbscanLab() {
const [file, setFile] = useState(null);
const [eps, setEps] = useState(0.5);
const [minSamples, setMinSamples] = useState(5);
const [result, setResult] = useState(null);
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
if (!file) return setError('Please upload a file');
setLoading(true); setError(''); setResult(null);
const fd = new FormData();
fd.append('file', file);
fd.append('eps', eps);
fd.append('min_samples', minSamples);
try {
const res = await axios.post('/api/dbscan', fd);
setResult(res.data);
} catch (err) {
setError(err.response?.data?.error || 'Request failed');
} finally { setLoading(false); }
};
return (
<div className="max-w-3xl mx-auto">
<PageHeader icon={Braces} title="DBSCAN Lab" subtitle="Density-based spatial clustering to identify complex patterns and outliers." />
<form onSubmit={handleSubmit} className="glass-card p-6 sm:p-8 space-y-6">
<UploadZone accept=".csv,.xlsx" onChange={(e) => setFile(e.target.files[0])} label="Upload Data Structure" sublabel=".CSV or .XLSX datasets" />
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-semibold text-slate-300 mb-2.5">Epsilon (eps)</label>
<input type="number" value={eps} onChange={(e) => setEps(e.target.value)} step="0.01" min="0.01" className="quantum-input" />
</div>
<div>
<label className="block text-sm font-semibold text-slate-300 mb-2.5">Min Samples</label>
<input type="number" value={minSamples} onChange={(e) => setMinSamples(e.target.value)} min="1" className="quantum-input" />
</div>
</div>
<SubmitButton loading={loading}>
<Braces size={18} /> Run DBSCAN
</SubmitButton>
</form>
<ErrorBox message={error} />
{result && (
<ResultBox>
<SectionLabel>DBSCAN Visualization</SectionLabel>
<motion.div initial={{ opacity: 0, scale: 0.95 }} animate={{ opacity: 1, scale: 1 }} className="bg-white p-4 rounded-2xl">
<img src={`data:image/png;base64,${result.plot}`} alt="DBSCAN Plot" className="w-full rounded-xl" />
</motion.div>
{result.cluster_info && (
<div className="mt-5 grid grid-cols-2 sm:grid-cols-3 gap-3">
{Object.entries(result.cluster_info).map(([k, v], i) => (
<motion.div key={k} initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: i * 0.1 }}
className="stat-glow p-4 rounded-2xl bg-white/[0.02] border border-white/[0.05]">
<span className="text-[10px] font-bold text-purple-400 uppercase tracking-widest">{k === '-1' ? 'Noise Points' : `Cluster ${k}`}</span>
<p className="text-xl font-black mt-1">{v}</p>
<span className="text-[11px] text-slate-500">Entities</span>
</motion.div>
))}
</div>
)}
</ResultBox>
)}
</div>
);
}