flowyml 1.7.0__py3-none-any.whl → 1.7.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flowyml/assets/dataset.py +570 -17
- flowyml/assets/model.py +1052 -15
- flowyml/core/executor.py +70 -11
- flowyml/core/orchestrator.py +37 -2
- flowyml/core/pipeline.py +32 -4
- flowyml/core/scheduler.py +88 -5
- flowyml/integrations/keras.py +247 -82
- flowyml/storage/sql.py +24 -6
- flowyml/ui/backend/routers/runs.py +112 -0
- flowyml/ui/backend/routers/schedules.py +35 -15
- flowyml/ui/frontend/dist/assets/index-B40RsQDq.css +1 -0
- flowyml/ui/frontend/dist/assets/index-CjI0zKCn.js +685 -0
- flowyml/ui/frontend/dist/index.html +2 -2
- flowyml/ui/frontend/package-lock.json +11 -0
- flowyml/ui/frontend/package.json +1 -0
- flowyml/ui/frontend/src/app/assets/page.jsx +890 -321
- flowyml/ui/frontend/src/app/dashboard/page.jsx +1 -1
- flowyml/ui/frontend/src/app/experiments/[experimentId]/page.jsx +1 -1
- flowyml/ui/frontend/src/app/leaderboard/page.jsx +1 -1
- flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectMetricsPanel.jsx +1 -1
- flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectRunsList.jsx +3 -3
- flowyml/ui/frontend/src/app/runs/[runId]/page.jsx +590 -102
- flowyml/ui/frontend/src/components/ArtifactViewer.jsx +62 -2
- flowyml/ui/frontend/src/components/AssetDetailsPanel.jsx +401 -28
- flowyml/ui/frontend/src/components/AssetTreeHierarchy.jsx +119 -11
- flowyml/ui/frontend/src/components/DatasetViewer.jsx +753 -0
- flowyml/ui/frontend/src/components/TrainingHistoryChart.jsx +514 -0
- flowyml/ui/frontend/src/components/TrainingMetricsPanel.jsx +175 -0
- {flowyml-1.7.0.dist-info → flowyml-1.7.2.dist-info}/METADATA +1 -1
- {flowyml-1.7.0.dist-info → flowyml-1.7.2.dist-info}/RECORD +33 -30
- flowyml/ui/frontend/dist/assets/index-By4trVyv.css +0 -1
- flowyml/ui/frontend/dist/assets/index-CX5RV2C9.js +0 -630
- {flowyml-1.7.0.dist-info → flowyml-1.7.2.dist-info}/WHEEL +0 -0
- {flowyml-1.7.0.dist-info → flowyml-1.7.2.dist-info}/entry_points.txt +0 -0
- {flowyml-1.7.0.dist-info → flowyml-1.7.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -10,9 +10,13 @@ import {
|
|
|
10
10
|
Code,
|
|
11
11
|
Table as TableIcon,
|
|
12
12
|
AlertCircle,
|
|
13
|
-
Download
|
|
13
|
+
Download,
|
|
14
|
+
TrendingUp,
|
|
15
|
+
Database,
|
|
14
16
|
} from 'lucide-react';
|
|
15
|
-
import { Button } from './ui/Button';
|
|
17
|
+
import { Button } from './ui/Button';
|
|
18
|
+
import { TrainingHistoryChart } from './TrainingHistoryChart';
|
|
19
|
+
import { DatasetViewer } from './DatasetViewer';
|
|
16
20
|
|
|
17
21
|
SyntaxHighlighter.registerLanguage('json', json);
|
|
18
22
|
SyntaxHighlighter.registerLanguage('python', python);
|
|
@@ -23,6 +27,17 @@ export function ArtifactViewer({ artifact }) {
|
|
|
23
27
|
const [loading, setLoading] = useState(false);
|
|
24
28
|
const [error, setError] = useState(null);
|
|
25
29
|
|
|
30
|
+
// Check if this is a Dataset artifact FIRST - before any async operations
|
|
31
|
+
// This ensures Dataset gets its own specialized viewer with histograms
|
|
32
|
+
const isDataset = artifact?.type?.toLowerCase() === 'dataset' ||
|
|
33
|
+
artifact?.asset_type?.toLowerCase() === 'dataset';
|
|
34
|
+
|
|
35
|
+
// Render Dataset viewer immediately if it's a dataset
|
|
36
|
+
// Don't wait for content fetch - dataset data is in properties
|
|
37
|
+
if (isDataset) {
|
|
38
|
+
return <DatasetViewer artifact={artifact} />;
|
|
39
|
+
}
|
|
40
|
+
|
|
26
41
|
useEffect(() => {
|
|
27
42
|
if (!artifact?.artifact_id) return;
|
|
28
43
|
fetchContent();
|
|
@@ -110,6 +125,51 @@ export function ArtifactViewer({ artifact }) {
|
|
|
110
125
|
);
|
|
111
126
|
}
|
|
112
127
|
|
|
128
|
+
// Check if artifact has training history (from Keras callback)
|
|
129
|
+
const hasTrainingHistory = artifact?.training_history &&
|
|
130
|
+
artifact.training_history.epochs &&
|
|
131
|
+
artifact.training_history.epochs.length > 0;
|
|
132
|
+
|
|
133
|
+
// Render training history chart for model artifacts with training data
|
|
134
|
+
if (hasTrainingHistory) {
|
|
135
|
+
return (
|
|
136
|
+
<div className="space-y-6">
|
|
137
|
+
{/* Training History Visualization */}
|
|
138
|
+
<div>
|
|
139
|
+
<div className="flex items-center gap-2 mb-4 pb-3 border-b border-slate-200 dark:border-slate-700">
|
|
140
|
+
<TrendingUp className="text-primary-500" size={20} />
|
|
141
|
+
<h4 className="text-lg font-bold text-slate-900 dark:text-white">Training History</h4>
|
|
142
|
+
<span className="text-xs text-slate-500 bg-slate-100 dark:bg-slate-800 px-2 py-1 rounded-full">
|
|
143
|
+
{artifact.training_history.epochs.length} epochs
|
|
144
|
+
</span>
|
|
145
|
+
</div>
|
|
146
|
+
<TrainingHistoryChart trainingHistory={artifact.training_history} compact={false} />
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
{/* Also show JSON content if available */}
|
|
150
|
+
{content && contentType === 'json' && (
|
|
151
|
+
<div className="mt-6 pt-6 border-t border-slate-200 dark:border-slate-700">
|
|
152
|
+
<h5 className="text-sm font-semibold text-slate-600 dark:text-slate-400 mb-3 flex items-center gap-2">
|
|
153
|
+
<Code size={14} />
|
|
154
|
+
Raw Metadata
|
|
155
|
+
</h5>
|
|
156
|
+
<div className="rounded-lg overflow-hidden border border-slate-200 dark:border-slate-700 shadow-sm">
|
|
157
|
+
<div className="max-h-[30vh] overflow-y-auto bg-white dark:bg-slate-900">
|
|
158
|
+
<SyntaxHighlighter
|
|
159
|
+
language="json"
|
|
160
|
+
style={docco}
|
|
161
|
+
customStyle={{ margin: 0, padding: '1rem', fontSize: '0.75rem' }}
|
|
162
|
+
>
|
|
163
|
+
{JSON.stringify(content, null, 2)}
|
|
164
|
+
</SyntaxHighlighter>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
)}
|
|
169
|
+
</div>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
113
173
|
if (contentType === 'image') {
|
|
114
174
|
return (
|
|
115
175
|
<div className="flex flex-col items-center bg-slate-50 dark:bg-slate-900 p-4 rounded-lg border border-slate-200 dark:border-slate-700">
|
|
@@ -14,7 +14,17 @@ import {
|
|
|
14
14
|
Layers,
|
|
15
15
|
HardDrive,
|
|
16
16
|
ExternalLink,
|
|
17
|
-
Info
|
|
17
|
+
Info,
|
|
18
|
+
Cpu,
|
|
19
|
+
Zap,
|
|
20
|
+
Settings,
|
|
21
|
+
Table,
|
|
22
|
+
PieChart,
|
|
23
|
+
TrendingUp,
|
|
24
|
+
Hash,
|
|
25
|
+
Gauge,
|
|
26
|
+
LineChart,
|
|
27
|
+
Eye
|
|
18
28
|
} from 'lucide-react';
|
|
19
29
|
import { Card } from './ui/Card';
|
|
20
30
|
import { Badge } from './ui/Badge';
|
|
@@ -25,8 +35,10 @@ import { Link } from 'react-router-dom';
|
|
|
25
35
|
import { motion } from 'framer-motion';
|
|
26
36
|
import { ProjectSelector } from './ProjectSelector';
|
|
27
37
|
import { fetchApi } from '../utils/api';
|
|
38
|
+
import { DatasetViewer } from './DatasetViewer';
|
|
39
|
+
import { TrainingHistoryChart } from './TrainingHistoryChart';
|
|
28
40
|
|
|
29
|
-
export function AssetDetailsPanel({ asset, onClose }) {
|
|
41
|
+
export function AssetDetailsPanel({ asset, onClose, hideHeader = false }) {
|
|
30
42
|
const [activeTab, setActiveTab] = useState('overview');
|
|
31
43
|
const [currentProject, setCurrentProject] = useState(asset?.project);
|
|
32
44
|
|
|
@@ -98,43 +110,54 @@ export function AssetDetailsPanel({ asset, onClose }) {
|
|
|
98
110
|
className="h-full flex flex-col"
|
|
99
111
|
>
|
|
100
112
|
<Card className="flex-1 flex flex-col overflow-hidden">
|
|
101
|
-
{/* Header */}
|
|
102
|
-
|
|
103
|
-
<div className=
|
|
104
|
-
<div className=
|
|
105
|
-
<
|
|
113
|
+
{/* Header - can be hidden when embedded */}
|
|
114
|
+
{!hideHeader && (
|
|
115
|
+
<div className={`p-6 ${config.bgColor} border-b ${config.borderColor}`}>
|
|
116
|
+
<div className="flex items-start justify-between mb-4">
|
|
117
|
+
<div className={`p-3 rounded-xl bg-gradient-to-br ${config.color} text-white shadow-lg`}>
|
|
118
|
+
<Icon size={32} />
|
|
119
|
+
</div>
|
|
120
|
+
<button
|
|
121
|
+
onClick={onClose}
|
|
122
|
+
className="p-2 hover:bg-white/50 dark:hover:bg-slate-800/50 rounded-lg transition-colors"
|
|
123
|
+
>
|
|
124
|
+
<X size={20} className="text-slate-400" />
|
|
125
|
+
</button>
|
|
106
126
|
</div>
|
|
107
|
-
<button
|
|
108
|
-
onClick={onClose}
|
|
109
|
-
className="p-2 hover:bg-white/50 dark:hover:bg-slate-800/50 rounded-lg transition-colors"
|
|
110
|
-
>
|
|
111
|
-
<X size={20} className="text-slate-400" />
|
|
112
|
-
</button>
|
|
113
|
-
</div>
|
|
114
127
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
128
|
+
<h2 className="text-2xl font-bold text-slate-900 dark:text-white mb-2">
|
|
129
|
+
{asset.name}
|
|
130
|
+
</h2>
|
|
131
|
+
|
|
132
|
+
<div className="flex items-center gap-2 flex-wrap">
|
|
133
|
+
<Badge className={`bg-gradient-to-r ${config.color} text-white border-0`}>
|
|
134
|
+
{asset.type}
|
|
135
|
+
</Badge>
|
|
136
|
+
<ProjectSelector
|
|
137
|
+
currentProject={currentProject}
|
|
138
|
+
onUpdate={handleProjectUpdate}
|
|
139
|
+
/>
|
|
140
|
+
</div>
|
|
127
141
|
</div>
|
|
128
|
-
|
|
142
|
+
)}
|
|
129
143
|
|
|
130
144
|
{/* Tabs */}
|
|
131
|
-
<div className="flex items-center gap-1 p-2 border-b border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-900">
|
|
145
|
+
<div className="flex items-center gap-1 p-2 border-b border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-900 overflow-x-auto">
|
|
132
146
|
<TabButton
|
|
133
147
|
active={activeTab === 'overview'}
|
|
134
148
|
onClick={() => setActiveTab('overview')}
|
|
135
149
|
icon={Info}
|
|
136
150
|
label="Overview"
|
|
137
151
|
/>
|
|
152
|
+
{/* Show Visualization tab for Datasets and Models */}
|
|
153
|
+
{(asset.type?.toLowerCase() === 'dataset' || asset.type?.toLowerCase() === 'model') && (
|
|
154
|
+
<TabButton
|
|
155
|
+
active={activeTab === 'visualization'}
|
|
156
|
+
onClick={() => setActiveTab('visualization')}
|
|
157
|
+
icon={asset.type?.toLowerCase() === 'model' ? LineChart : BarChart2}
|
|
158
|
+
label={asset.type?.toLowerCase() === 'model' ? 'Training' : 'Statistics'}
|
|
159
|
+
/>
|
|
160
|
+
)}
|
|
138
161
|
<TabButton
|
|
139
162
|
active={activeTab === 'properties'}
|
|
140
163
|
onClick={() => setActiveTab('properties')}
|
|
@@ -151,8 +174,38 @@ export function AssetDetailsPanel({ asset, onClose }) {
|
|
|
151
174
|
|
|
152
175
|
{/* Content */}
|
|
153
176
|
<div className="flex-1 overflow-y-auto p-6 space-y-6">
|
|
177
|
+
{/* Visualization Tab - Rich interactive visualizations */}
|
|
178
|
+
{activeTab === 'visualization' && (
|
|
179
|
+
<div className="space-y-6">
|
|
180
|
+
{/* Dataset Visualization with full DatasetViewer */}
|
|
181
|
+
{asset.type?.toLowerCase() === 'dataset' && (
|
|
182
|
+
<DatasetViewer artifact={asset} />
|
|
183
|
+
)}
|
|
184
|
+
|
|
185
|
+
{/* Model Visualization with Training History */}
|
|
186
|
+
{asset.type?.toLowerCase() === 'model' && (
|
|
187
|
+
<ModelVisualization asset={asset} />
|
|
188
|
+
)}
|
|
189
|
+
</div>
|
|
190
|
+
)}
|
|
191
|
+
|
|
154
192
|
{activeTab === 'overview' && (
|
|
155
193
|
<>
|
|
194
|
+
{/* Model-specific Auto-extracted Info */}
|
|
195
|
+
{asset.type?.toLowerCase() === 'model' && asset.properties && (
|
|
196
|
+
<ModelOverview properties={asset.properties} />
|
|
197
|
+
)}
|
|
198
|
+
|
|
199
|
+
{/* Dataset-specific Auto-extracted Info */}
|
|
200
|
+
{asset.type?.toLowerCase() === 'dataset' && asset.properties && (
|
|
201
|
+
<DatasetOverview properties={asset.properties} />
|
|
202
|
+
)}
|
|
203
|
+
|
|
204
|
+
{/* Metrics-specific Info */}
|
|
205
|
+
{asset.type?.toLowerCase() === 'metrics' && asset.properties && (
|
|
206
|
+
<MetricsOverview properties={asset.properties} />
|
|
207
|
+
)}
|
|
208
|
+
|
|
156
209
|
{/* Key Information */}
|
|
157
210
|
<div>
|
|
158
211
|
<h3 className="text-sm font-semibold text-slate-700 dark:text-slate-300 uppercase tracking-wider mb-3 flex items-center gap-2">
|
|
@@ -371,3 +424,323 @@ function TabButton({ active, onClick, icon: Icon, label }) {
|
|
|
371
424
|
</button>
|
|
372
425
|
);
|
|
373
426
|
}
|
|
427
|
+
|
|
428
|
+
// Model-specific overview with auto-extracted metadata
|
|
429
|
+
function ModelOverview({ properties }) {
|
|
430
|
+
const framework = properties.framework;
|
|
431
|
+
const parameters = properties.parameters;
|
|
432
|
+
const trainableParams = properties.trainable_parameters;
|
|
433
|
+
const optimizer = properties.optimizer;
|
|
434
|
+
const learningRate = properties.learning_rate;
|
|
435
|
+
const lossFunction = properties.loss_function;
|
|
436
|
+
const numLayers = properties.num_layers;
|
|
437
|
+
const layerTypes = properties.layer_types;
|
|
438
|
+
const architecture = properties.architecture || properties.model_class;
|
|
439
|
+
const isAutoExtracted = properties._auto_extracted;
|
|
440
|
+
|
|
441
|
+
if (!framework && !parameters) return null;
|
|
442
|
+
|
|
443
|
+
const formatNumber = (num) => {
|
|
444
|
+
if (!num) return 'N/A';
|
|
445
|
+
if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M';
|
|
446
|
+
if (num >= 1000) return (num / 1000).toFixed(1) + 'K';
|
|
447
|
+
return num.toLocaleString();
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
return (
|
|
451
|
+
<div className="space-y-4">
|
|
452
|
+
<div className="flex items-center justify-between">
|
|
453
|
+
<h3 className="text-sm font-semibold text-slate-700 dark:text-slate-300 uppercase tracking-wider flex items-center gap-2">
|
|
454
|
+
<Cpu size={16} className="text-purple-500" />
|
|
455
|
+
Model Details
|
|
456
|
+
</h3>
|
|
457
|
+
{isAutoExtracted && (
|
|
458
|
+
<span className="text-[10px] bg-purple-100 dark:bg-purple-900/30 text-purple-600 dark:text-purple-400 px-2 py-0.5 rounded-full">
|
|
459
|
+
Auto-extracted
|
|
460
|
+
</span>
|
|
461
|
+
)}
|
|
462
|
+
</div>
|
|
463
|
+
|
|
464
|
+
{/* Framework & Architecture */}
|
|
465
|
+
<div className="grid grid-cols-2 gap-3">
|
|
466
|
+
{framework && (
|
|
467
|
+
<div className="bg-gradient-to-br from-purple-50 to-pink-50 dark:from-purple-900/20 dark:to-pink-900/20 rounded-lg p-3 border border-purple-100 dark:border-purple-800">
|
|
468
|
+
<div className="flex items-center gap-2 mb-1">
|
|
469
|
+
<Zap size={12} className="text-purple-500" />
|
|
470
|
+
<span className="text-[10px] text-purple-600 dark:text-purple-400 font-medium uppercase">Framework</span>
|
|
471
|
+
</div>
|
|
472
|
+
<p className="text-lg font-bold text-purple-700 dark:text-purple-300 capitalize">{framework}</p>
|
|
473
|
+
</div>
|
|
474
|
+
)}
|
|
475
|
+
{architecture && (
|
|
476
|
+
<div className="bg-slate-50 dark:bg-slate-800/50 rounded-lg p-3 border border-slate-200 dark:border-slate-700">
|
|
477
|
+
<div className="flex items-center gap-2 mb-1">
|
|
478
|
+
<Settings size={12} className="text-slate-500" />
|
|
479
|
+
<span className="text-[10px] text-slate-500 font-medium uppercase">Architecture</span>
|
|
480
|
+
</div>
|
|
481
|
+
<p className="text-sm font-semibold text-slate-900 dark:text-white truncate">{architecture}</p>
|
|
482
|
+
</div>
|
|
483
|
+
)}
|
|
484
|
+
</div>
|
|
485
|
+
|
|
486
|
+
{/* Parameters */}
|
|
487
|
+
{parameters && (
|
|
488
|
+
<div className="grid grid-cols-2 gap-3">
|
|
489
|
+
<div className="bg-slate-50 dark:bg-slate-800/50 rounded-lg p-3 border border-slate-200 dark:border-slate-700">
|
|
490
|
+
<div className="flex items-center gap-2 mb-1">
|
|
491
|
+
<Hash size={12} className="text-slate-500" />
|
|
492
|
+
<span className="text-[10px] text-slate-500 font-medium uppercase">Parameters</span>
|
|
493
|
+
</div>
|
|
494
|
+
<p className="text-xl font-bold text-slate-900 dark:text-white">{formatNumber(parameters)}</p>
|
|
495
|
+
</div>
|
|
496
|
+
{trainableParams && (
|
|
497
|
+
<div className="bg-slate-50 dark:bg-slate-800/50 rounded-lg p-3 border border-slate-200 dark:border-slate-700">
|
|
498
|
+
<div className="flex items-center gap-2 mb-1">
|
|
499
|
+
<TrendingUp size={12} className="text-slate-500" />
|
|
500
|
+
<span className="text-[10px] text-slate-500 font-medium uppercase">Trainable</span>
|
|
501
|
+
</div>
|
|
502
|
+
<p className="text-xl font-bold text-slate-900 dark:text-white">{formatNumber(trainableParams)}</p>
|
|
503
|
+
</div>
|
|
504
|
+
)}
|
|
505
|
+
</div>
|
|
506
|
+
)}
|
|
507
|
+
|
|
508
|
+
{/* Training Config */}
|
|
509
|
+
{(optimizer || lossFunction || learningRate) && (
|
|
510
|
+
<div className="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-3 border border-blue-100 dark:border-blue-800">
|
|
511
|
+
<h4 className="text-[10px] text-blue-600 dark:text-blue-400 font-medium uppercase mb-2">Training Configuration</h4>
|
|
512
|
+
<div className="grid grid-cols-3 gap-2 text-xs">
|
|
513
|
+
{optimizer && (
|
|
514
|
+
<div>
|
|
515
|
+
<span className="text-blue-500">Optimizer:</span>
|
|
516
|
+
<span className="ml-1 font-medium text-blue-700 dark:text-blue-300">{optimizer}</span>
|
|
517
|
+
</div>
|
|
518
|
+
)}
|
|
519
|
+
{learningRate && (
|
|
520
|
+
<div>
|
|
521
|
+
<span className="text-blue-500">LR:</span>
|
|
522
|
+
<span className="ml-1 font-medium text-blue-700 dark:text-blue-300">
|
|
523
|
+
{typeof learningRate === 'number' ? learningRate.toExponential(2) : learningRate}
|
|
524
|
+
</span>
|
|
525
|
+
</div>
|
|
526
|
+
)}
|
|
527
|
+
{lossFunction && (
|
|
528
|
+
<div>
|
|
529
|
+
<span className="text-blue-500">Loss:</span>
|
|
530
|
+
<span className="ml-1 font-medium text-blue-700 dark:text-blue-300">{lossFunction}</span>
|
|
531
|
+
</div>
|
|
532
|
+
)}
|
|
533
|
+
</div>
|
|
534
|
+
</div>
|
|
535
|
+
)}
|
|
536
|
+
|
|
537
|
+
{/* Layers */}
|
|
538
|
+
{(numLayers || layerTypes) && (
|
|
539
|
+
<div className="bg-slate-50 dark:bg-slate-800/50 rounded-lg p-3 border border-slate-200 dark:border-slate-700">
|
|
540
|
+
<div className="flex items-center justify-between mb-2">
|
|
541
|
+
<h4 className="text-[10px] text-slate-500 font-medium uppercase">Layers</h4>
|
|
542
|
+
{numLayers && <span className="text-sm font-bold text-slate-900 dark:text-white">{numLayers}</span>}
|
|
543
|
+
</div>
|
|
544
|
+
{layerTypes && (
|
|
545
|
+
<div className="flex flex-wrap gap-1">
|
|
546
|
+
{(Array.isArray(layerTypes) ? layerTypes : JSON.parse(layerTypes || '[]')).map((type, i) => (
|
|
547
|
+
<span key={i} className="text-[10px] bg-slate-200 dark:bg-slate-700 text-slate-600 dark:text-slate-300 px-2 py-0.5 rounded">
|
|
548
|
+
{type}
|
|
549
|
+
</span>
|
|
550
|
+
))}
|
|
551
|
+
</div>
|
|
552
|
+
)}
|
|
553
|
+
</div>
|
|
554
|
+
)}
|
|
555
|
+
</div>
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Dataset-specific overview with auto-extracted metadata
|
|
560
|
+
function DatasetOverview({ properties }) {
|
|
561
|
+
const samples = properties.samples || properties.num_samples;
|
|
562
|
+
const features = properties.num_features;
|
|
563
|
+
const columns = properties.columns || properties.feature_columns;
|
|
564
|
+
const framework = properties.framework;
|
|
565
|
+
const labelColumn = properties.label_column;
|
|
566
|
+
const columnStats = properties.column_stats;
|
|
567
|
+
const isAutoExtracted = properties._auto_extracted;
|
|
568
|
+
|
|
569
|
+
if (!samples && !features && !columns) return null;
|
|
570
|
+
|
|
571
|
+
return (
|
|
572
|
+
<div className="space-y-4">
|
|
573
|
+
<div className="flex items-center justify-between">
|
|
574
|
+
<h3 className="text-sm font-semibold text-slate-700 dark:text-slate-300 uppercase tracking-wider flex items-center gap-2">
|
|
575
|
+
<Database size={16} className="text-blue-500" />
|
|
576
|
+
Dataset Details
|
|
577
|
+
</h3>
|
|
578
|
+
{isAutoExtracted && (
|
|
579
|
+
<span className="text-[10px] bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400 px-2 py-0.5 rounded-full">
|
|
580
|
+
Auto-extracted
|
|
581
|
+
</span>
|
|
582
|
+
)}
|
|
583
|
+
</div>
|
|
584
|
+
|
|
585
|
+
{/* Key Stats */}
|
|
586
|
+
<div className="grid grid-cols-3 gap-3">
|
|
587
|
+
{samples && (
|
|
588
|
+
<div className="bg-gradient-to-br from-blue-50 to-cyan-50 dark:from-blue-900/20 dark:to-cyan-900/20 rounded-lg p-3 border border-blue-100 dark:border-blue-800">
|
|
589
|
+
<div className="flex items-center gap-2 mb-1">
|
|
590
|
+
<Table size={12} className="text-blue-500" />
|
|
591
|
+
<span className="text-[10px] text-blue-600 dark:text-blue-400 font-medium uppercase">Samples</span>
|
|
592
|
+
</div>
|
|
593
|
+
<p className="text-xl font-bold text-blue-700 dark:text-blue-300">{samples.toLocaleString()}</p>
|
|
594
|
+
</div>
|
|
595
|
+
)}
|
|
596
|
+
{features && (
|
|
597
|
+
<div className="bg-slate-50 dark:bg-slate-800/50 rounded-lg p-3 border border-slate-200 dark:border-slate-700">
|
|
598
|
+
<div className="flex items-center gap-2 mb-1">
|
|
599
|
+
<PieChart size={12} className="text-slate-500" />
|
|
600
|
+
<span className="text-[10px] text-slate-500 font-medium uppercase">Features</span>
|
|
601
|
+
</div>
|
|
602
|
+
<p className="text-xl font-bold text-slate-900 dark:text-white">{features}</p>
|
|
603
|
+
</div>
|
|
604
|
+
)}
|
|
605
|
+
{framework && (
|
|
606
|
+
<div className="bg-slate-50 dark:bg-slate-800/50 rounded-lg p-3 border border-slate-200 dark:border-slate-700">
|
|
607
|
+
<div className="flex items-center gap-2 mb-1">
|
|
608
|
+
<Zap size={12} className="text-slate-500" />
|
|
609
|
+
<span className="text-[10px] text-slate-500 font-medium uppercase">Format</span>
|
|
610
|
+
</div>
|
|
611
|
+
<p className="text-sm font-bold text-slate-900 dark:text-white capitalize">{framework}</p>
|
|
612
|
+
</div>
|
|
613
|
+
)}
|
|
614
|
+
</div>
|
|
615
|
+
|
|
616
|
+
{/* Columns */}
|
|
617
|
+
{columns && (
|
|
618
|
+
<div className="bg-slate-50 dark:bg-slate-800/50 rounded-lg p-3 border border-slate-200 dark:border-slate-700">
|
|
619
|
+
<div className="flex items-center justify-between mb-2">
|
|
620
|
+
<h4 className="text-[10px] text-slate-500 font-medium uppercase">Columns</h4>
|
|
621
|
+
<span className="text-xs text-slate-400">{Array.isArray(columns) ? columns.length : 0} total</span>
|
|
622
|
+
</div>
|
|
623
|
+
<div className="flex flex-wrap gap-1">
|
|
624
|
+
{(Array.isArray(columns) ? columns : []).slice(0, 10).map((col, i) => (
|
|
625
|
+
<span
|
|
626
|
+
key={i}
|
|
627
|
+
className={`text-[10px] px-2 py-0.5 rounded ${
|
|
628
|
+
col === labelColumn
|
|
629
|
+
? 'bg-emerald-100 dark:bg-emerald-900/30 text-emerald-600 dark:text-emerald-400 font-medium'
|
|
630
|
+
: 'bg-slate-200 dark:bg-slate-700 text-slate-600 dark:text-slate-300'
|
|
631
|
+
}`}
|
|
632
|
+
>
|
|
633
|
+
{col}{col === labelColumn && ' (target)'}
|
|
634
|
+
</span>
|
|
635
|
+
))}
|
|
636
|
+
{Array.isArray(columns) && columns.length > 10 && (
|
|
637
|
+
<span className="text-[10px] text-slate-400">+{columns.length - 10} more</span>
|
|
638
|
+
)}
|
|
639
|
+
</div>
|
|
640
|
+
</div>
|
|
641
|
+
)}
|
|
642
|
+
|
|
643
|
+
{/* Column Stats Preview */}
|
|
644
|
+
{columnStats && Object.keys(columnStats).length > 0 && (
|
|
645
|
+
<div className="bg-emerald-50 dark:bg-emerald-900/20 rounded-lg p-3 border border-emerald-100 dark:border-emerald-800">
|
|
646
|
+
<h4 className="text-[10px] text-emerald-600 dark:text-emerald-400 font-medium uppercase mb-2">Column Statistics Available</h4>
|
|
647
|
+
<p className="text-xs text-emerald-700 dark:text-emerald-300">
|
|
648
|
+
{Object.keys(columnStats).length} columns with statistics (mean, std, min, max, etc.)
|
|
649
|
+
</p>
|
|
650
|
+
</div>
|
|
651
|
+
)}
|
|
652
|
+
</div>
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Metrics-specific overview
|
|
657
|
+
function MetricsOverview({ properties }) {
|
|
658
|
+
// Filter out internal properties
|
|
659
|
+
const metricEntries = Object.entries(properties).filter(([key]) =>
|
|
660
|
+
!key.startsWith('_') && !['data', 'samples', 'source'].includes(key)
|
|
661
|
+
);
|
|
662
|
+
|
|
663
|
+
if (metricEntries.length === 0) return null;
|
|
664
|
+
|
|
665
|
+
const formatValue = (value) => {
|
|
666
|
+
if (typeof value === 'number') {
|
|
667
|
+
if (value < 0.01 && value !== 0) return value.toExponential(3);
|
|
668
|
+
if (value > 1000) return value.toLocaleString();
|
|
669
|
+
return value.toFixed(4);
|
|
670
|
+
}
|
|
671
|
+
return String(value);
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
return (
|
|
675
|
+
<div className="space-y-4">
|
|
676
|
+
<h3 className="text-sm font-semibold text-slate-700 dark:text-slate-300 uppercase tracking-wider flex items-center gap-2">
|
|
677
|
+
<Gauge size={16} className="text-emerald-500" />
|
|
678
|
+
Evaluation Metrics
|
|
679
|
+
</h3>
|
|
680
|
+
|
|
681
|
+
<div className="grid grid-cols-2 gap-3">
|
|
682
|
+
{metricEntries.slice(0, 8).map(([key, value]) => (
|
|
683
|
+
<div
|
|
684
|
+
key={key}
|
|
685
|
+
className="bg-gradient-to-br from-emerald-50 to-teal-50 dark:from-emerald-900/20 dark:to-teal-900/20 rounded-lg p-3 border border-emerald-100 dark:border-emerald-800"
|
|
686
|
+
>
|
|
687
|
+
<span className="text-[10px] text-emerald-600 dark:text-emerald-400 font-medium uppercase block mb-1">
|
|
688
|
+
{key.replace(/_/g, ' ')}
|
|
689
|
+
</span>
|
|
690
|
+
<p className="text-lg font-bold text-emerald-700 dark:text-emerald-300 font-mono">
|
|
691
|
+
{formatValue(value)}
|
|
692
|
+
</p>
|
|
693
|
+
</div>
|
|
694
|
+
))}
|
|
695
|
+
</div>
|
|
696
|
+
</div>
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// Model Visualization with Training History
|
|
701
|
+
function ModelVisualization({ asset }) {
|
|
702
|
+
// Check for training history in different locations
|
|
703
|
+
const trainingHistory = asset.training_history ||
|
|
704
|
+
asset.properties?.training_history ||
|
|
705
|
+
null;
|
|
706
|
+
|
|
707
|
+
const hasTrainingHistory = trainingHistory &&
|
|
708
|
+
trainingHistory.epochs &&
|
|
709
|
+
trainingHistory.epochs.length > 0;
|
|
710
|
+
|
|
711
|
+
if (!hasTrainingHistory) {
|
|
712
|
+
return (
|
|
713
|
+
<div className="text-center py-12 bg-slate-50 dark:bg-slate-800/50 rounded-xl border border-slate-200 dark:border-slate-700">
|
|
714
|
+
<div className="inline-flex p-4 bg-white dark:bg-slate-800 rounded-full mb-4 shadow-sm">
|
|
715
|
+
<LineChart size={32} className="text-slate-400" />
|
|
716
|
+
</div>
|
|
717
|
+
<h4 className="text-lg font-bold text-slate-900 dark:text-white mb-2">No Training History Available</h4>
|
|
718
|
+
<p className="text-sm text-slate-500 dark:text-slate-400 max-w-md mx-auto">
|
|
719
|
+
This model artifact doesn't have training history data.
|
|
720
|
+
<br />
|
|
721
|
+
Use <code className="bg-slate-100 dark:bg-slate-700 px-1.5 py-0.5 rounded text-xs">FlowymlKerasCallback</code> during training to capture metrics.
|
|
722
|
+
</p>
|
|
723
|
+
</div>
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
return (
|
|
728
|
+
<div className="space-y-6">
|
|
729
|
+
{/* Header */}
|
|
730
|
+
<div className="flex items-center gap-3 pb-4 border-b border-slate-200 dark:border-slate-700">
|
|
731
|
+
<div className="p-3 bg-gradient-to-br from-purple-500 to-pink-600 rounded-xl shadow-lg">
|
|
732
|
+
<LineChart size={24} className="text-white" />
|
|
733
|
+
</div>
|
|
734
|
+
<div>
|
|
735
|
+
<h3 className="text-xl font-bold text-slate-900 dark:text-white">Training History</h3>
|
|
736
|
+
<p className="text-sm text-slate-500 dark:text-slate-400">
|
|
737
|
+
{trainingHistory.epochs.length} epochs recorded
|
|
738
|
+
</p>
|
|
739
|
+
</div>
|
|
740
|
+
</div>
|
|
741
|
+
|
|
742
|
+
{/* Training History Chart */}
|
|
743
|
+
<TrainingHistoryChart trainingHistory={trainingHistory} compact={false} />
|
|
744
|
+
</div>
|
|
745
|
+
);
|
|
746
|
+
}
|