flowyml 1.1.0__py3-none-any.whl → 1.3.0__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.
Files changed (92) hide show
  1. flowyml/__init__.py +3 -0
  2. flowyml/assets/base.py +10 -0
  3. flowyml/assets/metrics.py +6 -0
  4. flowyml/cli/main.py +108 -2
  5. flowyml/cli/run.py +9 -2
  6. flowyml/core/execution_status.py +52 -0
  7. flowyml/core/hooks.py +106 -0
  8. flowyml/core/observability.py +210 -0
  9. flowyml/core/orchestrator.py +274 -0
  10. flowyml/core/pipeline.py +193 -231
  11. flowyml/core/project.py +34 -2
  12. flowyml/core/remote_orchestrator.py +109 -0
  13. flowyml/core/resources.py +22 -5
  14. flowyml/core/retry_policy.py +80 -0
  15. flowyml/core/step.py +18 -1
  16. flowyml/core/submission_result.py +53 -0
  17. flowyml/core/versioning.py +2 -2
  18. flowyml/integrations/keras.py +95 -22
  19. flowyml/monitoring/alerts.py +2 -2
  20. flowyml/stacks/__init__.py +15 -0
  21. flowyml/stacks/aws.py +599 -0
  22. flowyml/stacks/azure.py +295 -0
  23. flowyml/stacks/components.py +24 -2
  24. flowyml/stacks/gcp.py +158 -11
  25. flowyml/stacks/local.py +5 -0
  26. flowyml/storage/artifacts.py +15 -5
  27. flowyml/storage/materializers/__init__.py +2 -0
  28. flowyml/storage/materializers/cloudpickle.py +74 -0
  29. flowyml/storage/metadata.py +166 -5
  30. flowyml/ui/backend/main.py +41 -1
  31. flowyml/ui/backend/routers/assets.py +356 -15
  32. flowyml/ui/backend/routers/client.py +46 -0
  33. flowyml/ui/backend/routers/execution.py +13 -2
  34. flowyml/ui/backend/routers/experiments.py +48 -12
  35. flowyml/ui/backend/routers/metrics.py +213 -0
  36. flowyml/ui/backend/routers/pipelines.py +63 -7
  37. flowyml/ui/backend/routers/projects.py +33 -7
  38. flowyml/ui/backend/routers/runs.py +150 -8
  39. flowyml/ui/frontend/dist/assets/index-DcYwrn2j.css +1 -0
  40. flowyml/ui/frontend/dist/assets/index-Dlz_ygOL.js +592 -0
  41. flowyml/ui/frontend/dist/index.html +2 -2
  42. flowyml/ui/frontend/src/App.jsx +4 -1
  43. flowyml/ui/frontend/src/app/assets/page.jsx +260 -230
  44. flowyml/ui/frontend/src/app/dashboard/page.jsx +38 -7
  45. flowyml/ui/frontend/src/app/experiments/page.jsx +61 -314
  46. flowyml/ui/frontend/src/app/observability/page.jsx +277 -0
  47. flowyml/ui/frontend/src/app/pipelines/page.jsx +79 -402
  48. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectArtifactsList.jsx +151 -0
  49. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectExperimentsList.jsx +145 -0
  50. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectHeader.jsx +45 -0
  51. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectHierarchy.jsx +467 -0
  52. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectMetricsPanel.jsx +253 -0
  53. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectPipelinesList.jsx +105 -0
  54. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectRelations.jsx +189 -0
  55. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectRunsList.jsx +136 -0
  56. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectTabs.jsx +95 -0
  57. flowyml/ui/frontend/src/app/projects/[projectId]/page.jsx +326 -0
  58. flowyml/ui/frontend/src/app/projects/page.jsx +13 -3
  59. flowyml/ui/frontend/src/app/runs/[runId]/page.jsx +79 -10
  60. flowyml/ui/frontend/src/app/runs/page.jsx +82 -424
  61. flowyml/ui/frontend/src/app/settings/page.jsx +1 -0
  62. flowyml/ui/frontend/src/app/tokens/page.jsx +62 -16
  63. flowyml/ui/frontend/src/components/AssetDetailsPanel.jsx +373 -0
  64. flowyml/ui/frontend/src/components/AssetLineageGraph.jsx +291 -0
  65. flowyml/ui/frontend/src/components/AssetStatsDashboard.jsx +302 -0
  66. flowyml/ui/frontend/src/components/AssetTreeHierarchy.jsx +477 -0
  67. flowyml/ui/frontend/src/components/ExperimentDetailsPanel.jsx +227 -0
  68. flowyml/ui/frontend/src/components/NavigationTree.jsx +401 -0
  69. flowyml/ui/frontend/src/components/PipelineDetailsPanel.jsx +239 -0
  70. flowyml/ui/frontend/src/components/PipelineGraph.jsx +67 -3
  71. flowyml/ui/frontend/src/components/ProjectSelector.jsx +115 -0
  72. flowyml/ui/frontend/src/components/RunDetailsPanel.jsx +298 -0
  73. flowyml/ui/frontend/src/components/header/Header.jsx +48 -1
  74. flowyml/ui/frontend/src/components/plugins/ZenMLIntegration.jsx +106 -0
  75. flowyml/ui/frontend/src/components/sidebar/Sidebar.jsx +52 -26
  76. flowyml/ui/frontend/src/components/ui/DataView.jsx +35 -17
  77. flowyml/ui/frontend/src/components/ui/ErrorBoundary.jsx +118 -0
  78. flowyml/ui/frontend/src/contexts/ProjectContext.jsx +2 -2
  79. flowyml/ui/frontend/src/contexts/ToastContext.jsx +116 -0
  80. flowyml/ui/frontend/src/layouts/MainLayout.jsx +5 -1
  81. flowyml/ui/frontend/src/router/index.jsx +4 -0
  82. flowyml/ui/frontend/src/utils/date.js +10 -0
  83. flowyml/ui/frontend/src/utils/downloads.js +11 -0
  84. flowyml/utils/config.py +6 -0
  85. flowyml/utils/stack_config.py +45 -3
  86. {flowyml-1.1.0.dist-info → flowyml-1.3.0.dist-info}/METADATA +113 -12
  87. {flowyml-1.1.0.dist-info → flowyml-1.3.0.dist-info}/RECORD +90 -53
  88. {flowyml-1.1.0.dist-info → flowyml-1.3.0.dist-info}/licenses/LICENSE +1 -1
  89. flowyml/ui/frontend/dist/assets/index-DFNQnrUj.js +0 -448
  90. flowyml/ui/frontend/dist/assets/index-pWI271rZ.css +0 -1
  91. {flowyml-1.1.0.dist-info → flowyml-1.3.0.dist-info}/WHEEL +0 -0
  92. {flowyml-1.1.0.dist-info → flowyml-1.3.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,302 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { fetchApi } from '../utils/api';
3
+ import { Database, Box, BarChart2, FileText, TrendingUp, HardDrive, Activity, Package, ChevronDown } from 'lucide-react';
4
+ import { motion } from 'framer-motion';
5
+ import { Card } from './ui/Card';
6
+
7
+ const AnimatedCounter = ({ value, duration = 1000 }) => {
8
+ const [count, setCount] = useState(0);
9
+
10
+ useEffect(() => {
11
+ let startTime;
12
+ let animationFrame;
13
+
14
+ const animate = (timestamp) => {
15
+ if (!startTime) startTime = timestamp;
16
+ const progress = Math.min((timestamp - startTime) / duration, 1);
17
+
18
+ setCount(Math.floor(progress * value));
19
+
20
+ if (progress < 1) {
21
+ animationFrame = requestAnimationFrame(animate);
22
+ }
23
+ };
24
+
25
+ animationFrame = requestAnimationFrame(animate);
26
+ return () => cancelAnimationFrame(animationFrame);
27
+ }, [value, duration]);
28
+
29
+ return <span>{count.toLocaleString()}</span>;
30
+ };
31
+
32
+ const StatCard = ({ icon: Icon, label, value, subtitle, gradient, isLoading }) => {
33
+ if (isLoading) {
34
+ return (
35
+ <Card className="p-6 animate-pulse">
36
+ <div className="h-20 bg-slate-200 dark:bg-slate-700 rounded"></div>
37
+ </Card>
38
+ );
39
+ }
40
+
41
+ return (
42
+ <motion.div
43
+ initial={{ opacity: 0, y: 20 }}
44
+ animate={{ opacity: 1, y: 0 }}
45
+ transition={{ duration: 0.3 }}
46
+ >
47
+ <Card className="p-6 hover:shadow-xl transition-all duration-300 border-2 hover:border-primary-300 dark:hover:border-primary-700 group overflow-hidden relative">
48
+ {/* Gradient background effect */}
49
+ <div className={`absolute inset-0 bg-gradient-to-br ${gradient} opacity-0 group-hover:opacity-5 transition-opacity duration-300`}></div>
50
+
51
+ <div className="relative">
52
+ <div className="flex items-start justify-between mb-3">
53
+ <div className={`p-3 rounded-xl bg-gradient-to-br ${gradient} text-white shadow-lg group-hover:scale-110 transition-transform duration-300`}>
54
+ <Icon size={24} />
55
+ </div>
56
+ </div>
57
+
58
+ <div className="space-y-1">
59
+ <p className="text-sm text-slate-500 dark:text-slate-400 font-medium">{label}</p>
60
+ <p className="text-3xl font-bold text-slate-900 dark:text-white">
61
+ {typeof value === 'number' ? <AnimatedCounter value={value} /> : value}
62
+ </p>
63
+ {subtitle && (
64
+ <p className="text-xs text-slate-400 dark:text-slate-500">{subtitle}</p>
65
+ )}
66
+ </div>
67
+ </div>
68
+ </Card>
69
+ </motion.div>
70
+ );
71
+ };
72
+
73
+ const TypeBreakdownChart = ({ typeData }) => {
74
+ const typeConfig = {
75
+ model: { icon: Box, color: 'from-purple-500 to-pink-500', text: 'text-purple-600 dark:text-purple-400' },
76
+ dataset: { icon: Database, color: 'from-blue-500 to-cyan-500', text: 'text-blue-600 dark:text-blue-400' },
77
+ metrics: { icon: BarChart2, color: 'from-emerald-500 to-teal-500', text: 'text-emerald-600 dark:text-emerald-400' },
78
+ default: { icon: FileText, color: 'from-slate-500 to-slate-600', text: 'text-slate-600 dark:text-slate-400' }
79
+ };
80
+
81
+ const total = Object.values(typeData).reduce((sum, count) => sum + count, 0);
82
+
83
+ return (
84
+ <div className="space-y-3">
85
+ {Object.entries(typeData).map(([type, count]) => {
86
+ const config = typeConfig[type.toLowerCase()] || typeConfig.default;
87
+ const Icon = config.icon;
88
+ const percentage = total > 0 ? (count / total) * 100 : 0;
89
+
90
+ return (
91
+ <motion.div
92
+ key={type}
93
+ initial={{ opacity: 0, x: -20 }}
94
+ animate={{ opacity: 1, x: 0 }}
95
+ className="group"
96
+ >
97
+ <div className="flex items-center justify-between mb-1">
98
+ <div className="flex items-center gap-2">
99
+ <div className={`p-1.5 rounded-lg bg-gradient-to-br ${config.color} text-white`}>
100
+ <Icon size={14} />
101
+ </div>
102
+ <span className="text-sm font-medium text-slate-700 dark:text-slate-300 capitalize">{type}</span>
103
+ </div>
104
+ <span className="text-sm font-semibold text-slate-900 dark:text-white">{count}</span>
105
+ </div>
106
+ <div className="h-2 bg-slate-100 dark:bg-slate-700 rounded-full overflow-hidden">
107
+ <motion.div
108
+ initial={{ width: 0 }}
109
+ animate={{ width: `${percentage}%` }}
110
+ transition={{ duration: 0.8, ease: "easeOut" }}
111
+ className={`h-full bg-gradient-to-r ${config.color} group-hover:shadow-lg transition-shadow`}
112
+ />
113
+ </div>
114
+ </motion.div>
115
+ );
116
+ })}
117
+ </div>
118
+ );
119
+ };
120
+
121
+ export function AssetStatsDashboard({ projectId }) {
122
+ const [stats, setStats] = useState(null);
123
+ const [loading, setLoading] = useState(true);
124
+ const [showDistribution, setShowDistribution] = useState(false);
125
+ const [showRecent, setShowRecent] = useState(false);
126
+
127
+ useEffect(() => {
128
+ const fetchStats = async () => {
129
+ setLoading(true);
130
+ try {
131
+ const url = projectId
132
+ ? `/api/assets/stats?project=${encodeURIComponent(projectId)}`
133
+ : '/api/assets/stats';
134
+ const res = await fetchApi(url);
135
+ const data = await res.json();
136
+ setStats(data);
137
+ } catch (err) {
138
+ console.error('Failed to fetch asset stats:', err);
139
+ } finally {
140
+ setLoading(false);
141
+ }
142
+ };
143
+
144
+ fetchStats();
145
+ }, [projectId]);
146
+
147
+ const formatBytes = (bytes) => {
148
+ if (bytes === 0) return '0 Bytes';
149
+ const k = 1024;
150
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
151
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
152
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
153
+ };
154
+
155
+ return (
156
+ <div className="space-y-6">
157
+ {/* Main Stats Grid */}
158
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
159
+ <StatCard
160
+ icon={Package}
161
+ label="Total Assets"
162
+ value={stats?.total_assets || 0}
163
+ subtitle="Across all pipelines"
164
+ gradient="from-blue-500 to-cyan-500"
165
+ isLoading={loading}
166
+ />
167
+ <StatCard
168
+ icon={Box}
169
+ label="Models"
170
+ value={stats?.by_type?.model || stats?.by_type?.Model || 0}
171
+ subtitle="Trained models"
172
+ gradient="from-purple-500 to-pink-500"
173
+ isLoading={loading}
174
+ />
175
+ <StatCard
176
+ icon={Database}
177
+ label="Datasets"
178
+ value={stats?.by_type?.dataset || stats?.by_type?.Dataset || 0}
179
+ subtitle="Data artifacts"
180
+ gradient="from-emerald-500 to-teal-500"
181
+ isLoading={loading}
182
+ />
183
+ <StatCard
184
+ icon={HardDrive}
185
+ label="Storage Used"
186
+ value={stats ? formatBytes(stats.total_storage_bytes) : '0 MB'}
187
+ subtitle="Total size"
188
+ gradient="from-orange-500 to-red-500"
189
+ isLoading={loading}
190
+ />
191
+ </div>
192
+
193
+ {/* Type Breakdown - Collapsible */}
194
+ {stats && Object.keys(stats.by_type).length > 0 && (
195
+ <motion.div
196
+ initial={{ opacity: 0, y: 20 }}
197
+ animate={{ opacity: 1, y: 0 }}
198
+ transition={{ delay: 0.2 }}
199
+ >
200
+ <Card className="overflow-hidden">
201
+ <div
202
+ className="flex items-center justify-between p-4 cursor-pointer hover:bg-slate-50 dark:hover:bg-slate-800 transition-colors"
203
+ onClick={() => setShowDistribution(!showDistribution)}
204
+ >
205
+ <div className="flex items-center gap-2">
206
+ <TrendingUp className="text-primary-600" size={20} />
207
+ <h3 className="text-lg font-semibold text-slate-900 dark:text-white">Asset Distribution</h3>
208
+ </div>
209
+ <motion.div
210
+ animate={{ rotate: showDistribution ? 180 : 0 }}
211
+ transition={{ duration: 0.2 }}
212
+ >
213
+ <ChevronDown className="text-slate-400" size={20} />
214
+ </motion.div>
215
+ </div>
216
+ {showDistribution && (
217
+ <motion.div
218
+ initial={{ opacity: 0, height: 0 }}
219
+ animate={{ opacity: 1, height: 'auto' }}
220
+ exit={{ opacity: 0, height: 0 }}
221
+ transition={{ duration: 0.2 }}
222
+ className="px-6 pb-6"
223
+ >
224
+ <TypeBreakdownChart typeData={stats.by_type} />
225
+ </motion.div>
226
+ )}
227
+ </Card>
228
+ </motion.div>
229
+ )}
230
+
231
+ {/* Recent Activity - Collapsible */}
232
+ {stats && stats.recent_assets && stats.recent_assets.length > 0 && (
233
+ <motion.div
234
+ initial={{ opacity: 0, y: 20 }}
235
+ animate={{ opacity: 1, y: 0 }}
236
+ transition={{ delay: 0.3 }}
237
+ >
238
+ <Card className="overflow-hidden">
239
+ <div
240
+ className="flex items-center justify-between p-4 cursor-pointer hover:bg-slate-50 dark:hover:bg-slate-800 transition-colors"
241
+ onClick={() => setShowRecent(!showRecent)}
242
+ >
243
+ <div className="flex items-center gap-2">
244
+ <Activity className="text-primary-600" size={20} />
245
+ <h3 className="text-lg font-semibold text-slate-900 dark:text-white">Recent Assets</h3>
246
+ </div>
247
+ <motion.div
248
+ animate={{ rotate: showRecent ? 180 : 0 }}
249
+ transition={{ duration: 0.2 }}
250
+ >
251
+ <ChevronDown className="text-slate-400" size={20} />
252
+ </motion.div>
253
+ </div>
254
+ {showRecent && (
255
+ <motion.div
256
+ initial={{ opacity: 0, height: 0 }}
257
+ animate={{ opacity: 1, height: 'auto' }}
258
+ exit={{ opacity: 0, height: 0 }}
259
+ transition={{ duration: 0.2 }}
260
+ className="px-6 pb-6"
261
+ >
262
+ <div className="space-y-2">
263
+ {stats.recent_assets.slice(0, 5).map((asset, idx) => {
264
+ const typeConfig = {
265
+ model: { icon: Box, color: 'text-purple-600' },
266
+ Model: { icon: Box, color: 'text-purple-600' },
267
+ dataset: { icon: Database, color: 'text-blue-600' },
268
+ Dataset: { icon: Database, color: 'text-blue-600' },
269
+ default: { icon: FileText, color: 'text-slate-600' }
270
+ };
271
+ const config = typeConfig[asset.type] || typeConfig.default;
272
+ const Icon = config.icon;
273
+
274
+ return (
275
+ <motion.div
276
+ key={asset.artifact_id || idx}
277
+ initial={{ opacity: 0, x: -20 }}
278
+ animate={{ opacity: 1, x: 0 }}
279
+ transition={{ delay: idx * 0.05 }}
280
+ className="flex items-center gap-3 p-3 rounded-lg hover:bg-slate-50 dark:hover:bg-slate-800 transition-colors"
281
+ >
282
+ <Icon className={config.color} size={16} />
283
+ <div className="flex-1 min-w-0">
284
+ <p className="text-sm font-medium text-slate-900 dark:text-white truncate">
285
+ {asset.name}
286
+ </p>
287
+ <p className="text-xs text-slate-500 dark:text-slate-400">
288
+ {asset.type} • {asset.step || 'N/A'}
289
+ </p>
290
+ </div>
291
+ </motion.div>
292
+ );
293
+ })}
294
+ </div>
295
+ </motion.div>
296
+ )}
297
+ </Card>
298
+ </motion.div>
299
+ )}
300
+ </div>
301
+ );
302
+ }