Spaces:
Sleeping
Sleeping
Delete frontend
Browse files- frontend/.DS_Store +0 -0
- frontend/app/compare/page.tsx +0 -26
- frontend/app/datasets/[id]/page.tsx +0 -9
- frontend/app/experiments/[id]/page.tsx +0 -32
- frontend/app/experiments/new/page.tsx +0 -96
- frontend/app/globals.css +0 -27
- frontend/app/layout.tsx +0 -22
- frontend/app/page.tsx +0 -19
- frontend/app/runs/page.tsx +0 -28
- frontend/app/upload/page.tsx +0 -4
- frontend/components/AlgorithmSelector.tsx +0 -23
- frontend/components/ClusterBarChart.tsx +0 -24
- frontend/components/ClusterScatterPlot.tsx +0 -35
- frontend/components/ClusterSummaryCard.tsx +0 -14
- frontend/components/ClusterTable.tsx +0 -22
- frontend/components/DatasetProfileCard.tsx +0 -36
- frontend/components/DynamicParamForm.tsx +0 -26
- frontend/components/FileUploadCard.tsx +0 -38
- frontend/components/MetricsGrid.tsx +0 -24
- frontend/components/Navbar.tsx +0 -13
- frontend/components/OutlierTable.tsx +0 -24
- frontend/components/RunComparisonTable.tsx +0 -30
- frontend/lib/api.ts +0 -36
- frontend/lib/constants.ts +0 -2
- frontend/lib/types.ts +0 -86
- frontend/next.config.js +0 -5
- frontend/package.json +0 -23
- frontend/tsconfig.json +0 -20
- frontend/types/react-plotly.js.d.ts +0 -21
frontend/.DS_Store
DELETED
|
Binary file (8.2 kB)
|
|
|
frontend/app/compare/page.tsx
DELETED
|
@@ -1,26 +0,0 @@
|
|
| 1 |
-
'use client';
|
| 2 |
-
import { useState } from 'react';
|
| 3 |
-
import RunComparisonTable from '../../components/RunComparisonTable';
|
| 4 |
-
import { apiGet } from '../../lib/api';
|
| 5 |
-
import type { CompareRunItem } from '../../lib/types';
|
| 6 |
-
|
| 7 |
-
export default function ComparePage() {
|
| 8 |
-
const [ids, setIds] = useState('');
|
| 9 |
-
const [runs, setRuns] = useState<CompareRunItem[]>([]);
|
| 10 |
-
async function compare() {
|
| 11 |
-
const data = await apiGet<{ runs: CompareRunItem[] }>(`/compare?experiment_ids=${encodeURIComponent(ids)}`);
|
| 12 |
-
setRuns(data.runs);
|
| 13 |
-
}
|
| 14 |
-
return (
|
| 15 |
-
<div className='grid'>
|
| 16 |
-
<div className='card'>
|
| 17 |
-
<h1>Compare Runs</h1>
|
| 18 |
-
<p className='small'>Enter experiment ids separated by commas.</p>
|
| 19 |
-
<input value={ids} onChange={(e) => setIds(e.target.value)} placeholder='exp_x,exp_y,exp_z' />
|
| 20 |
-
<div style={{ height: 12 }} />
|
| 21 |
-
<button onClick={compare}>Compare</button>
|
| 22 |
-
</div>
|
| 23 |
-
{runs.length > 0 ? <RunComparisonTable runs={runs} /> : null}
|
| 24 |
-
</div>
|
| 25 |
-
);
|
| 26 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/app/datasets/[id]/page.tsx
DELETED
|
@@ -1,9 +0,0 @@
|
|
| 1 |
-
import DatasetProfileCard from '../../../components/DatasetProfileCard';
|
| 2 |
-
import { apiGet } from '../../../lib/api';
|
| 3 |
-
import type { DatasetProfile } from '../../../lib/types';
|
| 4 |
-
|
| 5 |
-
export default async function DatasetProfilePage({ params }: { params: Promise<{ id: string }> }) {
|
| 6 |
-
const { id } = await params;
|
| 7 |
-
const profile = await apiGet<DatasetProfile>(`/datasets/${id}/profile`);
|
| 8 |
-
return <DatasetProfileCard profile={profile} />;
|
| 9 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/app/experiments/[id]/page.tsx
DELETED
|
@@ -1,32 +0,0 @@
|
|
| 1 |
-
import ClusterBarChart from '../../../components/ClusterBarChart';
|
| 2 |
-
import ClusterScatterPlot from '../../../components/ClusterScatterPlot';
|
| 3 |
-
import ClusterSummaryCard from '../../../components/ClusterSummaryCard';
|
| 4 |
-
import ClusterTable from '../../../components/ClusterTable';
|
| 5 |
-
import MetricsGrid from '../../../components/MetricsGrid';
|
| 6 |
-
import OutlierTable from '../../../components/OutlierTable';
|
| 7 |
-
import { apiGet } from '../../../lib/api';
|
| 8 |
-
import type { ExperimentResult } from '../../../lib/types';
|
| 9 |
-
|
| 10 |
-
export default async function ExperimentResultPage({ params }: { params: Promise<{ id: string }> }) {
|
| 11 |
-
const { id } = await params;
|
| 12 |
-
const data = await apiGet<ExperimentResult>(`/experiments/${id}/results`);
|
| 13 |
-
return (
|
| 14 |
-
<div className='grid' style={{ gap: 18 }}>
|
| 15 |
-
<div className='card'>
|
| 16 |
-
<div className='header-row'>
|
| 17 |
-
<h1>Experiment {data.experiment_id}</h1>
|
| 18 |
-
<div className='badge'>{data.algorithm}</div>
|
| 19 |
-
</div>
|
| 20 |
-
<p className='small'>Status: {data.status}</p>
|
| 21 |
-
</div>
|
| 22 |
-
<MetricsGrid metrics={data.metrics} />
|
| 23 |
-
<ClusterScatterPlot points={data.scatter_points} />
|
| 24 |
-
<ClusterBarChart summaries={data.cluster_summaries} />
|
| 25 |
-
<ClusterTable summaries={data.cluster_summaries} />
|
| 26 |
-
<OutlierTable points={data.scatter_points} />
|
| 27 |
-
<div className='grid grid-2'>
|
| 28 |
-
{data.cluster_summaries.map((item) => <ClusterSummaryCard key={item.cluster_label} item={item} />)}
|
| 29 |
-
</div>
|
| 30 |
-
</div>
|
| 31 |
-
);
|
| 32 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/app/experiments/new/page.tsx
DELETED
|
@@ -1,96 +0,0 @@
|
|
| 1 |
-
'use client';
|
| 2 |
-
import { useEffect, useState } from 'react';
|
| 3 |
-
import AlgorithmSelector from '../../../components/AlgorithmSelector';
|
| 4 |
-
import DynamicParamForm from '../../../components/DynamicParamForm';
|
| 5 |
-
import { apiGet, apiPost } from '../../../lib/api';
|
| 6 |
-
import type { AlgorithmPreset, DatasetItem, DatasetProfile } from '../../../lib/types';
|
| 7 |
-
|
| 8 |
-
export default function NewExperimentPage() {
|
| 9 |
-
const [datasets, setDatasets] = useState<DatasetItem[]>([]);
|
| 10 |
-
const [algorithms, setAlgorithms] = useState<AlgorithmPreset[]>([]);
|
| 11 |
-
const [datasetId, setDatasetId] = useState('');
|
| 12 |
-
const [profile, setProfile] = useState<DatasetProfile | null>(null);
|
| 13 |
-
const [algorithm, setAlgorithm] = useState('kmeans');
|
| 14 |
-
const [params, setParams] = useState<Record<string, unknown>>({});
|
| 15 |
-
const [featureColumns, setFeatureColumns] = useState<string[]>([]);
|
| 16 |
-
const [name, setName] = useState('Experiment');
|
| 17 |
-
const [message, setMessage] = useState('');
|
| 18 |
-
|
| 19 |
-
useEffect(() => {
|
| 20 |
-
apiGet<DatasetItem[]>('/datasets').then(setDatasets).catch(() => {});
|
| 21 |
-
apiGet<{ algorithms: AlgorithmPreset[] }>('/presets/algorithms').then((d) => {
|
| 22 |
-
setAlgorithms(d.algorithms);
|
| 23 |
-
if (d.algorithms.length > 0) {
|
| 24 |
-
setAlgorithm(d.algorithms[0].key);
|
| 25 |
-
setParams(d.algorithms[0].params);
|
| 26 |
-
}
|
| 27 |
-
}).catch(() => {});
|
| 28 |
-
}, []);
|
| 29 |
-
|
| 30 |
-
useEffect(() => {
|
| 31 |
-
if (!datasetId) return;
|
| 32 |
-
apiGet<DatasetProfile>(`/datasets/${datasetId}/profile`).then((data) => {
|
| 33 |
-
setProfile(data);
|
| 34 |
-
setFeatureColumns(data.suggested_feature_columns);
|
| 35 |
-
}).catch(() => {});
|
| 36 |
-
}, [datasetId]);
|
| 37 |
-
|
| 38 |
-
useEffect(() => {
|
| 39 |
-
const match = algorithms.find((a) => a.key === algorithm);
|
| 40 |
-
if (match) setParams(match.params);
|
| 41 |
-
}, [algorithm, algorithms]);
|
| 42 |
-
|
| 43 |
-
async function run() {
|
| 44 |
-
setMessage('');
|
| 45 |
-
const result = await apiPost<{ experiment_id: string; status: string }>('/experiments/run', {
|
| 46 |
-
dataset_id: datasetId,
|
| 47 |
-
name,
|
| 48 |
-
algorithm,
|
| 49 |
-
feature_columns: featureColumns,
|
| 50 |
-
preprocessing: { numeric_scaler: 'standard', categorical_encoder: 'onehot', dimensionality_reduction: 'umap' },
|
| 51 |
-
algorithm_params: params,
|
| 52 |
-
outlier_detection_enabled: true
|
| 53 |
-
});
|
| 54 |
-
setMessage(`Completed: ${result.experiment_id}`);
|
| 55 |
-
}
|
| 56 |
-
|
| 57 |
-
return (
|
| 58 |
-
<div className='grid grid-2'>
|
| 59 |
-
<div className='card'>
|
| 60 |
-
<h1>New Experiment</h1>
|
| 61 |
-
<div className='grid'>
|
| 62 |
-
<div><label>Experiment Name</label><input value={name} onChange={(e) => setName(e.target.value)} /></div>
|
| 63 |
-
<div>
|
| 64 |
-
<label>Dataset</label>
|
| 65 |
-
<select value={datasetId} onChange={(e) => setDatasetId(e.target.value)}>
|
| 66 |
-
<option value=''>Select dataset</option>
|
| 67 |
-
{datasets.map((d) => <option key={d.dataset_id} value={d.dataset_id}>{d.name}</option>)}
|
| 68 |
-
</select>
|
| 69 |
-
</div>
|
| 70 |
-
<AlgorithmSelector algorithms={algorithms} value={algorithm} onChange={setAlgorithm} />
|
| 71 |
-
<div>
|
| 72 |
-
<label>Feature Columns</label>
|
| 73 |
-
<textarea rows={8} value={featureColumns.join(',')} onChange={(e) => setFeatureColumns(e.target.value.split(',').map((s) => s.trim()).filter(Boolean))} />
|
| 74 |
-
</div>
|
| 75 |
-
<DynamicParamForm params={params} onChange={setParams} />
|
| 76 |
-
<button onClick={run}>Run Experiment</button>
|
| 77 |
-
{message ? <p>{message}</p> : null}
|
| 78 |
-
</div>
|
| 79 |
-
</div>
|
| 80 |
-
<div className='card'>
|
| 81 |
-
<h2>Dataset Hints</h2>
|
| 82 |
-
{profile ? (
|
| 83 |
-
<>
|
| 84 |
-
<p className='small'>Recommended algorithms: {profile.recommended_algorithms.join(', ')}</p>
|
| 85 |
-
<p className='small'>Suggested feature columns: {profile.suggested_feature_columns.join(', ')}</p>
|
| 86 |
-
<div className='grid'>
|
| 87 |
-
{profile.columns.slice(0, 20).map((col) => <div key={col.name} className='badge'>{col.name}: {col.inferred_type}</div>)}
|
| 88 |
-
</div>
|
| 89 |
-
</>
|
| 90 |
-
) : (
|
| 91 |
-
<p className='small'>Choose a dataset to load schema and recommendations.</p>
|
| 92 |
-
)}
|
| 93 |
-
</div>
|
| 94 |
-
</div>
|
| 95 |
-
);
|
| 96 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/app/globals.css
DELETED
|
@@ -1,27 +0,0 @@
|
|
| 1 |
-
html, body {
|
| 2 |
-
padding: 0;
|
| 3 |
-
margin: 0;
|
| 4 |
-
font-family: Arial, Helvetica, sans-serif;
|
| 5 |
-
background: #0b1020;
|
| 6 |
-
color: #f4f7fb;
|
| 7 |
-
}
|
| 8 |
-
a { color: inherit; text-decoration: none; }
|
| 9 |
-
* { box-sizing: border-box; }
|
| 10 |
-
.page { min-height: 100vh; padding: 24px; }
|
| 11 |
-
.container { max-width: 1200px; margin: 0 auto; }
|
| 12 |
-
.card { background: #121933; border: 1px solid #243054; border-radius: 14px; padding: 18px; }
|
| 13 |
-
.grid { display: grid; gap: 16px; }
|
| 14 |
-
.grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
| 15 |
-
.grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
| 16 |
-
input, select, textarea, button { font: inherit; }
|
| 17 |
-
input, select, textarea { width: 100%; padding: 10px 12px; border-radius: 10px; border: 1px solid #33406d; background: #0f1730; color: #f4f7fb; }
|
| 18 |
-
button { padding: 10px 14px; border-radius: 10px; border: none; background: #4f8cff; color: white; cursor: pointer; }
|
| 19 |
-
.table { width: 100%; border-collapse: collapse; }
|
| 20 |
-
.table th, .table td { padding: 10px; border-bottom: 1px solid #243054; text-align: left; }
|
| 21 |
-
.small { color: #b9c4e2; font-size: 14px; }
|
| 22 |
-
.header-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
| 23 |
-
.nav { display: flex; gap: 12px; margin-bottom: 22px; flex-wrap: wrap; }
|
| 24 |
-
.badge { display: inline-block; padding: 4px 10px; border-radius: 999px; background: #1e2a4e; color: #c4d7ff; font-size: 12px; }
|
| 25 |
-
.code { font-family: monospace; }
|
| 26 |
-
pre { overflow: auto; }
|
| 27 |
-
@media (max-width: 900px) { .grid-2, .grid-3 { grid-template-columns: 1fr; } }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/app/layout.tsx
DELETED
|
@@ -1,22 +0,0 @@
|
|
| 1 |
-
import './globals.css';
|
| 2 |
-
import Navbar from '../components/Navbar';
|
| 3 |
-
|
| 4 |
-
export const metadata = {
|
| 5 |
-
title: 'ClusterForge',
|
| 6 |
-
description: 'Interactive clustering intelligence platform'
|
| 7 |
-
};
|
| 8 |
-
|
| 9 |
-
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
| 10 |
-
return (
|
| 11 |
-
<html lang='en'>
|
| 12 |
-
<body>
|
| 13 |
-
<div className='page'>
|
| 14 |
-
<div className='container'>
|
| 15 |
-
<Navbar />
|
| 16 |
-
{children}
|
| 17 |
-
</div>
|
| 18 |
-
</div>
|
| 19 |
-
</body>
|
| 20 |
-
</html>
|
| 21 |
-
);
|
| 22 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/app/page.tsx
DELETED
|
@@ -1,19 +0,0 @@
|
|
| 1 |
-
import Link from 'next/link';
|
| 2 |
-
export default function HomePage() {
|
| 3 |
-
return (
|
| 4 |
-
<div className='grid' style={{ gap: 20 }}>
|
| 5 |
-
<div className='card'>
|
| 6 |
-
<h1>ClusterForge</h1>
|
| 7 |
-
<p>A full stack clustering intelligence platform. Upload data, choose the clustering algorithm, tune parameters, compare runs, inspect visualizations, and export labeled output.</p>
|
| 8 |
-
<div className='grid grid-3'>
|
| 9 |
-
<div className='card'><h3>Algorithms</h3><p className='small'>KMeans, MiniBatch KMeans, Agglomerative, DBSCAN, HDBSCAN, GMM, Spectral, Birch</p></div>
|
| 10 |
-
<div className='card'><h3>Backend</h3><p className='small'>FastAPI, SQLAlchemy, SQLite, scikit learn, UMAP</p></div>
|
| 11 |
-
<div className='card'><h3>Frontend</h3><p className='small'>Next.js, React, Plotly, TypeScript</p></div>
|
| 12 |
-
</div>
|
| 13 |
-
<div style={{ marginTop: 20 }}>
|
| 14 |
-
<Link href='/upload'><button>Upload Dataset</button></Link>
|
| 15 |
-
</div>
|
| 16 |
-
</div>
|
| 17 |
-
</div>
|
| 18 |
-
);
|
| 19 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/app/runs/page.tsx
DELETED
|
@@ -1,28 +0,0 @@
|
|
| 1 |
-
import Link from 'next/link';
|
| 2 |
-
import { apiGet } from '../../lib/api';
|
| 3 |
-
import type { ExperimentListItem } from '../../lib/types';
|
| 4 |
-
|
| 5 |
-
export default async function RunsPage() {
|
| 6 |
-
const runs = await apiGet<ExperimentListItem[]>('/experiments');
|
| 7 |
-
return (
|
| 8 |
-
<div className='card'>
|
| 9 |
-
<h1>Saved Runs</h1>
|
| 10 |
-
<table className='table'>
|
| 11 |
-
<thead>
|
| 12 |
-
<tr><th>Name</th><th>Algorithm</th><th>Status</th><th>Started</th><th>Open</th></tr>
|
| 13 |
-
</thead>
|
| 14 |
-
<tbody>
|
| 15 |
-
{runs.map((run) => (
|
| 16 |
-
<tr key={run.experiment_id}>
|
| 17 |
-
<td>{run.name}</td>
|
| 18 |
-
<td>{run.algorithm}</td>
|
| 19 |
-
<td>{run.status}</td>
|
| 20 |
-
<td>{run.started_at}</td>
|
| 21 |
-
<td><Link href={`/experiments/${run.experiment_id}`}>View</Link></td>
|
| 22 |
-
</tr>
|
| 23 |
-
))}
|
| 24 |
-
</tbody>
|
| 25 |
-
</table>
|
| 26 |
-
</div>
|
| 27 |
-
);
|
| 28 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/app/upload/page.tsx
DELETED
|
@@ -1,4 +0,0 @@
|
|
| 1 |
-
import FileUploadCard from '../../components/FileUploadCard';
|
| 2 |
-
export default function UploadPage() {
|
| 3 |
-
return <div className='grid'><FileUploadCard /></div>;
|
| 4 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/components/AlgorithmSelector.tsx
DELETED
|
@@ -1,23 +0,0 @@
|
|
| 1 |
-
'use client';
|
| 2 |
-
import type { AlgorithmPreset } from '../lib/types';
|
| 3 |
-
|
| 4 |
-
export default function AlgorithmSelector({
|
| 5 |
-
algorithms,
|
| 6 |
-
value,
|
| 7 |
-
onChange
|
| 8 |
-
}: {
|
| 9 |
-
algorithms: AlgorithmPreset[];
|
| 10 |
-
value: string;
|
| 11 |
-
onChange: (value: string) => void;
|
| 12 |
-
}) {
|
| 13 |
-
return (
|
| 14 |
-
<div>
|
| 15 |
-
<label>Algorithm</label>
|
| 16 |
-
<select value={value} onChange={(e) => onChange(e.target.value)}>
|
| 17 |
-
{algorithms.map((alg) => (
|
| 18 |
-
<option key={alg.key} value={alg.key}>{alg.label}</option>
|
| 19 |
-
))}
|
| 20 |
-
</select>
|
| 21 |
-
</div>
|
| 22 |
-
);
|
| 23 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/components/ClusterBarChart.tsx
DELETED
|
@@ -1,24 +0,0 @@
|
|
| 1 |
-
'use client';
|
| 2 |
-
import dynamic from 'next/dynamic';
|
| 3 |
-
import type { ClusterSummaryResponse } from '../lib/types';
|
| 4 |
-
const Plot = dynamic(() => import('react-plotly.js'), { ssr: false });
|
| 5 |
-
|
| 6 |
-
export default function ClusterBarChart({ summaries }: { summaries: ClusterSummaryResponse[] }) {
|
| 7 |
-
return (
|
| 8 |
-
<div className='card'>
|
| 9 |
-
<h2>Cluster Size Distribution</h2>
|
| 10 |
-
<Plot
|
| 11 |
-
data={[{ type: 'bar', x: summaries.map((s) => `Cluster ${s.cluster_label}`), y: summaries.map((s) => s.size) }]}
|
| 12 |
-
layout={{
|
| 13 |
-
width: 1000,
|
| 14 |
-
height: 420,
|
| 15 |
-
paper_bgcolor: '#121933',
|
| 16 |
-
plot_bgcolor: '#121933',
|
| 17 |
-
font: { color: '#f4f7fb' },
|
| 18 |
-
margin: { l: 30, r: 20, t: 30, b: 40 }
|
| 19 |
-
}}
|
| 20 |
-
config={{ responsive: true }}
|
| 21 |
-
/>
|
| 22 |
-
</div>
|
| 23 |
-
);
|
| 24 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/components/ClusterScatterPlot.tsx
DELETED
|
@@ -1,35 +0,0 @@
|
|
| 1 |
-
'use client';
|
| 2 |
-
import dynamic from 'next/dynamic';
|
| 3 |
-
import type { ScatterPoint } from '../lib/types';
|
| 4 |
-
const Plot = dynamic(() => import('react-plotly.js'), { ssr: false });
|
| 5 |
-
|
| 6 |
-
export default function ClusterScatterPlot({ points }: { points: ScatterPoint[] }) {
|
| 7 |
-
const traces = Array.from(new Set(points.map((p) => p.cluster_label))).map((label) => {
|
| 8 |
-
const rows = points.filter((p) => p.cluster_label === label);
|
| 9 |
-
return {
|
| 10 |
-
x: rows.map((r) => r.x),
|
| 11 |
-
y: rows.map((r) => r.y),
|
| 12 |
-
mode: 'markers',
|
| 13 |
-
type: 'scattergl',
|
| 14 |
-
name: `Cluster ${label}`,
|
| 15 |
-
text: rows.map((r) => `row ${r.row_index}, outlier=${r.is_outlier}`)
|
| 16 |
-
};
|
| 17 |
-
});
|
| 18 |
-
return (
|
| 19 |
-
<div className='card'>
|
| 20 |
-
<h2>Cluster Plot</h2>
|
| 21 |
-
<Plot
|
| 22 |
-
data={traces}
|
| 23 |
-
layout={{
|
| 24 |
-
width: 1000,
|
| 25 |
-
height: 520,
|
| 26 |
-
paper_bgcolor: '#121933',
|
| 27 |
-
plot_bgcolor: '#121933',
|
| 28 |
-
font: { color: '#f4f7fb' },
|
| 29 |
-
margin: { l: 30, r: 20, t: 30, b: 40 }
|
| 30 |
-
}}
|
| 31 |
-
config={{ responsive: true }}
|
| 32 |
-
/>
|
| 33 |
-
</div>
|
| 34 |
-
);
|
| 35 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/components/ClusterSummaryCard.tsx
DELETED
|
@@ -1,14 +0,0 @@
|
|
| 1 |
-
import type { ClusterSummaryResponse } from '../lib/types';
|
| 2 |
-
export default function ClusterSummaryCard({ item }: { item: ClusterSummaryResponse }) {
|
| 3 |
-
const profile = item.summary_json?.profile as Record<string, unknown> | undefined;
|
| 4 |
-
return (
|
| 5 |
-
<div className='card'>
|
| 6 |
-
<div className='header-row'>
|
| 7 |
-
<h3>Cluster {item.cluster_label}</h3>
|
| 8 |
-
<div className='badge'>{item.size} rows</div>
|
| 9 |
-
</div>
|
| 10 |
-
<p>{item.description_text}</p>
|
| 11 |
-
<pre className='small' style={{ whiteSpace: 'pre-wrap' }}>{JSON.stringify(profile, null, 2)}</pre>
|
| 12 |
-
</div>
|
| 13 |
-
);
|
| 14 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/components/ClusterTable.tsx
DELETED
|
@@ -1,22 +0,0 @@
|
|
| 1 |
-
import type { ClusterSummaryResponse } from '../lib/types';
|
| 2 |
-
export default function ClusterTable({ summaries }: { summaries: ClusterSummaryResponse[] }) {
|
| 3 |
-
return (
|
| 4 |
-
<div className='card'>
|
| 5 |
-
<h2>Cluster Summaries</h2>
|
| 6 |
-
<table className='table'>
|
| 7 |
-
<thead>
|
| 8 |
-
<tr><th>Cluster</th><th>Size</th><th>Description</th></tr>
|
| 9 |
-
</thead>
|
| 10 |
-
<tbody>
|
| 11 |
-
{summaries.map((s) => (
|
| 12 |
-
<tr key={s.cluster_label}>
|
| 13 |
-
<td>{s.cluster_label}</td>
|
| 14 |
-
<td>{s.size}</td>
|
| 15 |
-
<td>{s.description_text}</td>
|
| 16 |
-
</tr>
|
| 17 |
-
))}
|
| 18 |
-
</tbody>
|
| 19 |
-
</table>
|
| 20 |
-
</div>
|
| 21 |
-
);
|
| 22 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/components/DatasetProfileCard.tsx
DELETED
|
@@ -1,36 +0,0 @@
|
|
| 1 |
-
import type { DatasetProfile } from '../lib/types';
|
| 2 |
-
|
| 3 |
-
export default function DatasetProfileCard({ profile }: { profile: DatasetProfile }) {
|
| 4 |
-
return (
|
| 5 |
-
<div className='card'>
|
| 6 |
-
<div className='header-row'>
|
| 7 |
-
<h2>Dataset Profile</h2>
|
| 8 |
-
<div className='badge'>{profile.row_count} rows</div>
|
| 9 |
-
</div>
|
| 10 |
-
<p className='small'>Suggested features: {profile.suggested_feature_columns.join(', ') || 'None'}</p>
|
| 11 |
-
<p className='small'>Recommended algorithms: {profile.recommended_algorithms.join(', ') || 'None'}</p>
|
| 12 |
-
<table className='table'>
|
| 13 |
-
<thead>
|
| 14 |
-
<tr>
|
| 15 |
-
<th>Column</th>
|
| 16 |
-
<th>Type</th>
|
| 17 |
-
<th>Missing %</th>
|
| 18 |
-
<th>Cardinality</th>
|
| 19 |
-
<th>Samples</th>
|
| 20 |
-
</tr>
|
| 21 |
-
</thead>
|
| 22 |
-
<tbody>
|
| 23 |
-
{profile.columns.map((col) => (
|
| 24 |
-
<tr key={col.name}>
|
| 25 |
-
<td>{col.name}</td>
|
| 26 |
-
<td>{col.inferred_type}</td>
|
| 27 |
-
<td>{col.missing_pct}</td>
|
| 28 |
-
<td>{col.cardinality}</td>
|
| 29 |
-
<td className='small'>{col.sample_values.join(', ')}</td>
|
| 30 |
-
</tr>
|
| 31 |
-
))}
|
| 32 |
-
</tbody>
|
| 33 |
-
</table>
|
| 34 |
-
</div>
|
| 35 |
-
);
|
| 36 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/components/DynamicParamForm.tsx
DELETED
|
@@ -1,26 +0,0 @@
|
|
| 1 |
-
'use client';
|
| 2 |
-
export default function DynamicParamForm({
|
| 3 |
-
params,
|
| 4 |
-
onChange
|
| 5 |
-
}: {
|
| 6 |
-
params: Record<string, unknown>;
|
| 7 |
-
onChange: (next: Record<string, unknown>) => void;
|
| 8 |
-
}) {
|
| 9 |
-
return (
|
| 10 |
-
<div className='grid'>
|
| 11 |
-
{Object.entries(params).map(([key, value]) => (
|
| 12 |
-
<div key={key}>
|
| 13 |
-
<label>{key}</label>
|
| 14 |
-
<input
|
| 15 |
-
value={String(value)}
|
| 16 |
-
onChange={(e) => {
|
| 17 |
-
const raw = e.target.value;
|
| 18 |
-
const parsed = raw !== '' && !Number.isNaN(Number(raw)) ? Number(raw) : raw;
|
| 19 |
-
onChange({ ...params, [key]: parsed });
|
| 20 |
-
}}
|
| 21 |
-
/>
|
| 22 |
-
</div>
|
| 23 |
-
))}
|
| 24 |
-
</div>
|
| 25 |
-
);
|
| 26 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/components/FileUploadCard.tsx
DELETED
|
@@ -1,38 +0,0 @@
|
|
| 1 |
-
'use client';
|
| 2 |
-
import { useState } from 'react';
|
| 3 |
-
import { apiUpload } from '../lib/api';
|
| 4 |
-
import type { DatasetItem } from '../lib/types';
|
| 5 |
-
|
| 6 |
-
export default function FileUploadCard() {
|
| 7 |
-
const [file, setFile] = useState<File | null>(null);
|
| 8 |
-
const [result, setResult] = useState<DatasetItem | null>(null);
|
| 9 |
-
const [error, setError] = useState('');
|
| 10 |
-
async function onUpload() {
|
| 11 |
-
if (!file) return;
|
| 12 |
-
setError('');
|
| 13 |
-
try {
|
| 14 |
-
const data = await apiUpload<DatasetItem>('/datasets/upload', file);
|
| 15 |
-
setResult(data);
|
| 16 |
-
} catch (e) {
|
| 17 |
-
setError(e instanceof Error ? e.message : 'Upload failed');
|
| 18 |
-
}
|
| 19 |
-
}
|
| 20 |
-
return (
|
| 21 |
-
<div className='card'>
|
| 22 |
-
<h2>Upload CSV or Excel</h2>
|
| 23 |
-
<p className='small'>Upload your dataset and start profiling.</p>
|
| 24 |
-
<input type='file' accept='.csv,.xlsx,.xls' onChange={(e) => setFile(e.target.files?.[0] || null)} />
|
| 25 |
-
<div style={{ height: 12 }} />
|
| 26 |
-
<button onClick={onUpload}>Upload Dataset</button>
|
| 27 |
-
{error ? <p>{error}</p> : null}
|
| 28 |
-
{result ? (
|
| 29 |
-
<div style={{ marginTop: 16 }}>
|
| 30 |
-
<div className='badge'>Uploaded</div>
|
| 31 |
-
<p className='small code'>{result.dataset_id}</p>
|
| 32 |
-
<p>{result.name}</p>
|
| 33 |
-
<p className='small'>{result.row_count} rows, {result.column_count} columns</p>
|
| 34 |
-
</div>
|
| 35 |
-
) : null}
|
| 36 |
-
</div>
|
| 37 |
-
);
|
| 38 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/components/MetricsGrid.tsx
DELETED
|
@@ -1,24 +0,0 @@
|
|
| 1 |
-
import type { MetricResponse } from '../lib/types';
|
| 2 |
-
const entries: Array<[keyof MetricResponse, string]> = [
|
| 3 |
-
['cluster_count', 'Clusters'],
|
| 4 |
-
['noise_ratio', 'Noise Ratio'],
|
| 5 |
-
['silhouette_score', 'Silhouette'],
|
| 6 |
-
['davies_bouldin_score', 'Davies Bouldin'],
|
| 7 |
-
['calinski_harabasz_score', 'Calinski Harabasz'],
|
| 8 |
-
['stability_score', 'Stability'],
|
| 9 |
-
['interpretability_score', 'Interpretability']
|
| 10 |
-
];
|
| 11 |
-
export default function MetricsGrid({ metrics }: { metrics: MetricResponse }) {
|
| 12 |
-
return (
|
| 13 |
-
<div className='grid grid-3'>
|
| 14 |
-
{entries.map(([key, label]) => (
|
| 15 |
-
<div key={String(key)} className='card'>
|
| 16 |
-
<div className='small'>{label}</div>
|
| 17 |
-
<div style={{ fontSize: 28, fontWeight: 700 }}>
|
| 18 |
-
{metrics[key] === undefined || metrics[key] === null ? 'N/A' : String(metrics[key])}
|
| 19 |
-
</div>
|
| 20 |
-
</div>
|
| 21 |
-
))}
|
| 22 |
-
</div>
|
| 23 |
-
);
|
| 24 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/components/Navbar.tsx
DELETED
|
@@ -1,13 +0,0 @@
|
|
| 1 |
-
import Link from 'next/link';
|
| 2 |
-
|
| 3 |
-
export default function Navbar() {
|
| 4 |
-
return (
|
| 5 |
-
<div className='nav'>
|
| 6 |
-
<Link className='badge' href='/'>Home</Link>
|
| 7 |
-
<Link className='badge' href='/upload'>Upload</Link>
|
| 8 |
-
<Link className='badge' href='/experiments/new'>New Experiment</Link>
|
| 9 |
-
<Link className='badge' href='/runs'>Runs</Link>
|
| 10 |
-
<Link className='badge' href='/compare'>Compare</Link>
|
| 11 |
-
</div>
|
| 12 |
-
);
|
| 13 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/components/OutlierTable.tsx
DELETED
|
@@ -1,24 +0,0 @@
|
|
| 1 |
-
import type { ScatterPoint } from '../lib/types';
|
| 2 |
-
export default function OutlierTable({ points }: { points: ScatterPoint[] }) {
|
| 3 |
-
const outliers = points.filter((p) => p.is_outlier).slice(0, 100);
|
| 4 |
-
return (
|
| 5 |
-
<div className='card'>
|
| 6 |
-
<h2>Outliers</h2>
|
| 7 |
-
<table className='table'>
|
| 8 |
-
<thead>
|
| 9 |
-
<tr><th>Row</th><th>Cluster</th><th>X</th><th>Y</th></tr>
|
| 10 |
-
</thead>
|
| 11 |
-
<tbody>
|
| 12 |
-
{outliers.map((p) => (
|
| 13 |
-
<tr key={p.row_index}>
|
| 14 |
-
<td>{p.row_index}</td>
|
| 15 |
-
<td>{p.cluster_label}</td>
|
| 16 |
-
<td>{p.x.toFixed(3)}</td>
|
| 17 |
-
<td>{p.y.toFixed(3)}</td>
|
| 18 |
-
</tr>
|
| 19 |
-
))}
|
| 20 |
-
</tbody>
|
| 21 |
-
</table>
|
| 22 |
-
</div>
|
| 23 |
-
);
|
| 24 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/components/RunComparisonTable.tsx
DELETED
|
@@ -1,30 +0,0 @@
|
|
| 1 |
-
import type { CompareRunItem } from '../lib/types';
|
| 2 |
-
export default function RunComparisonTable({ runs }: { runs: CompareRunItem[] }) {
|
| 3 |
-
return (
|
| 4 |
-
<div className='card'>
|
| 5 |
-
<h2>Run Comparison</h2>
|
| 6 |
-
<table className='table'>
|
| 7 |
-
<thead>
|
| 8 |
-
<tr>
|
| 9 |
-
<th>Name</th><th>Algorithm</th><th>Clusters</th><th>Noise</th><th>Silhouette</th><th>DB</th><th>CH</th><th>Stability</th><th>Interpretability</th>
|
| 10 |
-
</tr>
|
| 11 |
-
</thead>
|
| 12 |
-
<tbody>
|
| 13 |
-
{runs.map((r) => (
|
| 14 |
-
<tr key={r.experiment_id}>
|
| 15 |
-
<td>{r.name}</td>
|
| 16 |
-
<td>{r.algorithm}</td>
|
| 17 |
-
<td>{r.cluster_count}</td>
|
| 18 |
-
<td>{r.noise_ratio}</td>
|
| 19 |
-
<td>{r.silhouette_score ?? 'N/A'}</td>
|
| 20 |
-
<td>{r.davies_bouldin_score ?? 'N/A'}</td>
|
| 21 |
-
<td>{r.calinski_harabasz_score ?? 'N/A'}</td>
|
| 22 |
-
<td>{r.stability_score ?? 'N/A'}</td>
|
| 23 |
-
<td>{r.interpretability_score ?? 'N/A'}</td>
|
| 24 |
-
</tr>
|
| 25 |
-
))}
|
| 26 |
-
</tbody>
|
| 27 |
-
</table>
|
| 28 |
-
</div>
|
| 29 |
-
);
|
| 30 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/lib/api.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
| 1 |
-
import { API_BASE_URL } from './constants';
|
| 2 |
-
|
| 3 |
-
export async function apiGet<T>(path: string): Promise<T> {
|
| 4 |
-
const res = await fetch(`${API_BASE_URL}${path}`, { cache: 'no-store' });
|
| 5 |
-
if (!res.ok) {
|
| 6 |
-
throw new Error(`GET ${path} failed`);
|
| 7 |
-
}
|
| 8 |
-
return res.json();
|
| 9 |
-
}
|
| 10 |
-
|
| 11 |
-
export async function apiPost<T>(path: string, body: unknown): Promise<T> {
|
| 12 |
-
const res = await fetch(`${API_BASE_URL}${path}`, {
|
| 13 |
-
method: 'POST',
|
| 14 |
-
headers: { 'Content-Type': 'application/json' },
|
| 15 |
-
body: JSON.stringify(body)
|
| 16 |
-
});
|
| 17 |
-
if (!res.ok) {
|
| 18 |
-
const text = await res.text();
|
| 19 |
-
throw new Error(text || `POST ${path} failed`);
|
| 20 |
-
}
|
| 21 |
-
return res.json();
|
| 22 |
-
}
|
| 23 |
-
|
| 24 |
-
export async function apiUpload<T>(path: string, file: File): Promise<T> {
|
| 25 |
-
const form = new FormData();
|
| 26 |
-
form.append('file', file);
|
| 27 |
-
const res = await fetch(`${API_BASE_URL}${path}`, {
|
| 28 |
-
method: 'POST',
|
| 29 |
-
body: form
|
| 30 |
-
});
|
| 31 |
-
if (!res.ok) {
|
| 32 |
-
const text = await res.text();
|
| 33 |
-
throw new Error(text || `UPLOAD ${path} failed`);
|
| 34 |
-
}
|
| 35 |
-
return res.json();
|
| 36 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/lib/constants.ts
DELETED
|
@@ -1,2 +0,0 @@
|
|
| 1 |
-
export const API_BASE_URL =
|
| 2 |
-
process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:8000/api';
|
|
|
|
|
|
|
|
|
frontend/lib/types.ts
DELETED
|
@@ -1,86 +0,0 @@
|
|
| 1 |
-
export type DatasetItem = {
|
| 2 |
-
dataset_id: string;
|
| 3 |
-
name: string;
|
| 4 |
-
row_count: number;
|
| 5 |
-
column_count: number;
|
| 6 |
-
};
|
| 7 |
-
|
| 8 |
-
export type ColumnProfile = {
|
| 9 |
-
name: string;
|
| 10 |
-
inferred_type: string;
|
| 11 |
-
missing_pct: number;
|
| 12 |
-
cardinality: number;
|
| 13 |
-
sample_values: unknown[];
|
| 14 |
-
};
|
| 15 |
-
|
| 16 |
-
export type DatasetProfile = {
|
| 17 |
-
dataset_id: string;
|
| 18 |
-
columns: ColumnProfile[];
|
| 19 |
-
suggested_feature_columns: string[];
|
| 20 |
-
recommended_algorithms: string[];
|
| 21 |
-
row_count: number;
|
| 22 |
-
column_count: number;
|
| 23 |
-
};
|
| 24 |
-
|
| 25 |
-
export type AlgorithmPreset = {
|
| 26 |
-
key: string;
|
| 27 |
-
label: string;
|
| 28 |
-
params: Record<string, unknown>;
|
| 29 |
-
};
|
| 30 |
-
|
| 31 |
-
export type ExperimentListItem = {
|
| 32 |
-
experiment_id: string;
|
| 33 |
-
dataset_id: string;
|
| 34 |
-
name: string;
|
| 35 |
-
algorithm: string;
|
| 36 |
-
status: string;
|
| 37 |
-
started_at: string;
|
| 38 |
-
completed_at?: string | null;
|
| 39 |
-
};
|
| 40 |
-
|
| 41 |
-
export type MetricResponse = {
|
| 42 |
-
cluster_count: number;
|
| 43 |
-
noise_ratio: number;
|
| 44 |
-
silhouette_score?: number | null;
|
| 45 |
-
davies_bouldin_score?: number | null;
|
| 46 |
-
calinski_harabasz_score?: number | null;
|
| 47 |
-
stability_score?: number | null;
|
| 48 |
-
interpretability_score?: number | null;
|
| 49 |
-
};
|
| 50 |
-
|
| 51 |
-
export type ScatterPoint = {
|
| 52 |
-
row_index: number;
|
| 53 |
-
cluster_label: number;
|
| 54 |
-
is_outlier: boolean;
|
| 55 |
-
x: number;
|
| 56 |
-
y: number;
|
| 57 |
-
};
|
| 58 |
-
|
| 59 |
-
export type ClusterSummaryResponse = {
|
| 60 |
-
cluster_label: number;
|
| 61 |
-
size: number;
|
| 62 |
-
description_text: string;
|
| 63 |
-
summary_json: Record<string, unknown>;
|
| 64 |
-
};
|
| 65 |
-
|
| 66 |
-
export type ExperimentResult = {
|
| 67 |
-
experiment_id: string;
|
| 68 |
-
algorithm: string;
|
| 69 |
-
metrics: MetricResponse;
|
| 70 |
-
scatter_points: ScatterPoint[];
|
| 71 |
-
cluster_summaries: ClusterSummaryResponse[];
|
| 72 |
-
status: string;
|
| 73 |
-
};
|
| 74 |
-
|
| 75 |
-
export type CompareRunItem = {
|
| 76 |
-
experiment_id: string;
|
| 77 |
-
name: string;
|
| 78 |
-
algorithm: string;
|
| 79 |
-
cluster_count: number;
|
| 80 |
-
noise_ratio: number;
|
| 81 |
-
silhouette_score?: number | null;
|
| 82 |
-
davies_bouldin_score?: number | null;
|
| 83 |
-
calinski_harabasz_score?: number | null;
|
| 84 |
-
stability_score?: number | null;
|
| 85 |
-
interpretability_score?: number | null;
|
| 86 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/next.config.js
DELETED
|
@@ -1,5 +0,0 @@
|
|
| 1 |
-
/** @type {import('next').NextConfig} */
|
| 2 |
-
const nextConfig = {
|
| 3 |
-
reactStrictMode: true
|
| 4 |
-
};
|
| 5 |
-
module.exports = nextConfig;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/package.json
DELETED
|
@@ -1,23 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"name": "clusterforge-frontend",
|
| 3 |
-
"private": true,
|
| 4 |
-
"version": "1.0.0",
|
| 5 |
-
"scripts": {
|
| 6 |
-
"dev": "next dev -p 3000",
|
| 7 |
-
"build": "next build",
|
| 8 |
-
"start": "next start -p 3000"
|
| 9 |
-
},
|
| 10 |
-
"dependencies": {
|
| 11 |
-
"next": "15.2.4",
|
| 12 |
-
"react": "19.0.0",
|
| 13 |
-
"react-dom": "19.0.0",
|
| 14 |
-
"plotly.js-dist-min": "3.0.1",
|
| 15 |
-
"react-plotly.js": "2.6.0"
|
| 16 |
-
},
|
| 17 |
-
"devDependencies": {
|
| 18 |
-
"@types/node": "22.14.1",
|
| 19 |
-
"@types/react": "19.0.12",
|
| 20 |
-
"@types/react-dom": "19.0.4",
|
| 21 |
-
"typescript": "5.8.3"
|
| 22 |
-
}
|
| 23 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/tsconfig.json
DELETED
|
@@ -1,20 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"compilerOptions": {
|
| 3 |
-
"target": "ES2017",
|
| 4 |
-
"lib": ["dom", "esnext"],
|
| 5 |
-
"allowJs": false,
|
| 6 |
-
"skipLibCheck": true,
|
| 7 |
-
"strict": true,
|
| 8 |
-
"noEmit": true,
|
| 9 |
-
"esModuleInterop": true,
|
| 10 |
-
"module": "esnext",
|
| 11 |
-
"moduleResolution": "bundler",
|
| 12 |
-
"resolveJsonModule": true,
|
| 13 |
-
"isolatedModules": true,
|
| 14 |
-
"jsx": "preserve",
|
| 15 |
-
"incremental": true,
|
| 16 |
-
"plugins": [{ "name": "next" }]
|
| 17 |
-
},
|
| 18 |
-
"include": ["**/*.ts", "**/*.tsx", "**/*.d.ts", ".next/types/**/*.ts"],
|
| 19 |
-
"exclude": ["node_modules"]
|
| 20 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/types/react-plotly.js.d.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
| 1 |
-
declare module 'react-plotly.js' {
|
| 2 |
-
import * as React from 'react';
|
| 3 |
-
|
| 4 |
-
export interface PlotParams {
|
| 5 |
-
data?: any[];
|
| 6 |
-
layout?: any;
|
| 7 |
-
config?: any;
|
| 8 |
-
frames?: any[];
|
| 9 |
-
style?: React.CSSProperties;
|
| 10 |
-
className?: string;
|
| 11 |
-
useResizeHandler?: boolean;
|
| 12 |
-
onInitialized?: (figure: any, graphDiv: any) => void;
|
| 13 |
-
onUpdate?: (figure: any, graphDiv: any) => void;
|
| 14 |
-
onPurge?: (figure: any, graphDiv: any) => void;
|
| 15 |
-
onError?: (err: Error) => void;
|
| 16 |
-
divId?: string;
|
| 17 |
-
}
|
| 18 |
-
|
| 19 |
-
const Plot: React.ComponentType<PlotParams>;
|
| 20 |
-
export default Plot;
|
| 21 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|