Adisri99 commited on
Commit
b30fd30
·
verified ·
1 Parent(s): 9cbd247

Delete frontend

Browse files
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
- }