flowyml 1.7.1__py3-none-any.whl → 1.8.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.
- flowyml/assets/base.py +15 -0
- flowyml/assets/dataset.py +570 -17
- flowyml/assets/metrics.py +5 -0
- flowyml/assets/model.py +1052 -15
- flowyml/cli/main.py +709 -0
- flowyml/cli/stack_cli.py +138 -25
- flowyml/core/__init__.py +17 -0
- flowyml/core/executor.py +231 -37
- flowyml/core/image_builder.py +129 -0
- flowyml/core/log_streamer.py +227 -0
- flowyml/core/orchestrator.py +59 -4
- flowyml/core/pipeline.py +65 -13
- flowyml/core/routing.py +558 -0
- flowyml/core/scheduler.py +88 -5
- flowyml/core/step.py +9 -1
- flowyml/core/step_grouping.py +49 -35
- flowyml/core/types.py +407 -0
- flowyml/integrations/keras.py +247 -82
- flowyml/monitoring/alerts.py +10 -0
- flowyml/monitoring/notifications.py +104 -25
- flowyml/monitoring/slack_blocks.py +323 -0
- flowyml/plugins/__init__.py +251 -0
- flowyml/plugins/alerters/__init__.py +1 -0
- flowyml/plugins/alerters/slack.py +168 -0
- flowyml/plugins/base.py +752 -0
- flowyml/plugins/config.py +478 -0
- flowyml/plugins/deployers/__init__.py +22 -0
- flowyml/plugins/deployers/gcp_cloud_run.py +200 -0
- flowyml/plugins/deployers/sagemaker.py +306 -0
- flowyml/plugins/deployers/vertex.py +290 -0
- flowyml/plugins/integration.py +369 -0
- flowyml/plugins/manager.py +510 -0
- flowyml/plugins/model_registries/__init__.py +22 -0
- flowyml/plugins/model_registries/mlflow.py +159 -0
- flowyml/plugins/model_registries/sagemaker.py +489 -0
- flowyml/plugins/model_registries/vertex.py +386 -0
- flowyml/plugins/orchestrators/__init__.py +13 -0
- flowyml/plugins/orchestrators/sagemaker.py +443 -0
- flowyml/plugins/orchestrators/vertex_ai.py +461 -0
- flowyml/plugins/registries/__init__.py +13 -0
- flowyml/plugins/registries/ecr.py +321 -0
- flowyml/plugins/registries/gcr.py +313 -0
- flowyml/plugins/registry.py +454 -0
- flowyml/plugins/stack.py +494 -0
- flowyml/plugins/stack_config.py +537 -0
- flowyml/plugins/stores/__init__.py +13 -0
- flowyml/plugins/stores/gcs.py +460 -0
- flowyml/plugins/stores/s3.py +453 -0
- flowyml/plugins/trackers/__init__.py +11 -0
- flowyml/plugins/trackers/mlflow.py +316 -0
- flowyml/plugins/validators/__init__.py +3 -0
- flowyml/plugins/validators/deepchecks.py +119 -0
- flowyml/registry/__init__.py +2 -1
- flowyml/registry/model_environment.py +109 -0
- flowyml/registry/model_registry.py +241 -96
- flowyml/serving/__init__.py +17 -0
- flowyml/serving/model_server.py +628 -0
- flowyml/stacks/__init__.py +60 -0
- flowyml/stacks/aws.py +93 -0
- flowyml/stacks/base.py +62 -0
- flowyml/stacks/components.py +12 -0
- flowyml/stacks/gcp.py +44 -9
- flowyml/stacks/plugins.py +115 -0
- flowyml/stacks/registry.py +2 -1
- flowyml/storage/sql.py +401 -12
- flowyml/tracking/experiment.py +8 -5
- flowyml/ui/backend/Dockerfile +87 -16
- flowyml/ui/backend/auth.py +12 -2
- flowyml/ui/backend/main.py +149 -5
- flowyml/ui/backend/routers/ai_context.py +226 -0
- flowyml/ui/backend/routers/assets.py +23 -4
- flowyml/ui/backend/routers/auth.py +96 -0
- flowyml/ui/backend/routers/deployments.py +660 -0
- flowyml/ui/backend/routers/model_explorer.py +597 -0
- flowyml/ui/backend/routers/plugins.py +103 -51
- flowyml/ui/backend/routers/projects.py +91 -8
- flowyml/ui/backend/routers/runs.py +132 -1
- flowyml/ui/backend/routers/schedules.py +54 -29
- flowyml/ui/backend/routers/templates.py +319 -0
- flowyml/ui/backend/routers/websocket.py +2 -2
- flowyml/ui/frontend/Dockerfile +55 -6
- flowyml/ui/frontend/dist/assets/index-B5AsPTSz.css +1 -0
- flowyml/ui/frontend/dist/assets/index-dFbZ8wD8.js +753 -0
- flowyml/ui/frontend/dist/index.html +2 -2
- flowyml/ui/frontend/dist/logo.png +0 -0
- flowyml/ui/frontend/nginx.conf +65 -4
- flowyml/ui/frontend/package-lock.json +1415 -74
- flowyml/ui/frontend/package.json +4 -0
- flowyml/ui/frontend/public/logo.png +0 -0
- flowyml/ui/frontend/src/App.jsx +10 -7
- flowyml/ui/frontend/src/app/assets/page.jsx +890 -321
- flowyml/ui/frontend/src/app/auth/Login.jsx +90 -0
- flowyml/ui/frontend/src/app/dashboard/page.jsx +8 -8
- flowyml/ui/frontend/src/app/deployments/page.jsx +786 -0
- flowyml/ui/frontend/src/app/model-explorer/page.jsx +1031 -0
- flowyml/ui/frontend/src/app/pipelines/page.jsx +12 -2
- flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectExperimentsList.jsx +19 -6
- flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectMetricsPanel.jsx +1 -1
- flowyml/ui/frontend/src/app/runs/[runId]/page.jsx +601 -101
- flowyml/ui/frontend/src/app/runs/page.jsx +8 -2
- flowyml/ui/frontend/src/app/settings/page.jsx +267 -253
- flowyml/ui/frontend/src/components/ArtifactViewer.jsx +62 -2
- flowyml/ui/frontend/src/components/AssetDetailsPanel.jsx +424 -29
- flowyml/ui/frontend/src/components/AssetTreeHierarchy.jsx +119 -11
- flowyml/ui/frontend/src/components/DatasetViewer.jsx +753 -0
- flowyml/ui/frontend/src/components/Layout.jsx +6 -0
- flowyml/ui/frontend/src/components/PipelineGraph.jsx +79 -29
- flowyml/ui/frontend/src/components/RunDetailsPanel.jsx +36 -6
- flowyml/ui/frontend/src/components/RunMetaPanel.jsx +113 -0
- flowyml/ui/frontend/src/components/TrainingHistoryChart.jsx +514 -0
- flowyml/ui/frontend/src/components/TrainingMetricsPanel.jsx +175 -0
- flowyml/ui/frontend/src/components/ai/AIAssistantButton.jsx +71 -0
- flowyml/ui/frontend/src/components/ai/AIAssistantPanel.jsx +420 -0
- flowyml/ui/frontend/src/components/header/Header.jsx +22 -0
- flowyml/ui/frontend/src/components/plugins/PluginManager.jsx +4 -4
- flowyml/ui/frontend/src/components/plugins/{ZenMLIntegration.jsx → StackImport.jsx} +38 -12
- flowyml/ui/frontend/src/components/sidebar/Sidebar.jsx +36 -13
- flowyml/ui/frontend/src/contexts/AIAssistantContext.jsx +245 -0
- flowyml/ui/frontend/src/contexts/AuthContext.jsx +108 -0
- flowyml/ui/frontend/src/hooks/useAIContext.js +156 -0
- flowyml/ui/frontend/src/hooks/useWebGPU.js +54 -0
- flowyml/ui/frontend/src/layouts/MainLayout.jsx +6 -0
- flowyml/ui/frontend/src/router/index.jsx +47 -20
- flowyml/ui/frontend/src/services/pluginService.js +3 -1
- flowyml/ui/server_manager.py +5 -5
- flowyml/ui/utils.py +157 -39
- flowyml/utils/config.py +37 -15
- flowyml/utils/model_introspection.py +123 -0
- flowyml/utils/observability.py +30 -0
- flowyml-1.8.0.dist-info/METADATA +174 -0
- {flowyml-1.7.1.dist-info → flowyml-1.8.0.dist-info}/RECORD +134 -73
- {flowyml-1.7.1.dist-info → flowyml-1.8.0.dist-info}/WHEEL +1 -1
- flowyml/ui/frontend/dist/assets/index-BqDQvp63.js +0 -630
- flowyml/ui/frontend/dist/assets/index-By4trVyv.css +0 -1
- flowyml-1.7.1.dist-info/METADATA +0 -477
- {flowyml-1.7.1.dist-info → flowyml-1.8.0.dist-info}/entry_points.txt +0 -0
- {flowyml-1.7.1.dist-info → flowyml-1.8.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -3,6 +3,8 @@ import { Link, useLocation } from 'react-router-dom';
|
|
|
3
3
|
import { LayoutDashboard, PlayCircle, FolderKanban, FlaskConical, Database, Settings, Trophy, Calendar, MessageSquare, Moon, Sun, Key, Package } from 'lucide-react';
|
|
4
4
|
import { useTheme } from '../contexts/ThemeContext';
|
|
5
5
|
import { ProjectSelector } from './ui/ProjectSelector';
|
|
6
|
+
import { AIAssistantButton } from './ai/AIAssistantButton';
|
|
7
|
+
import { AIAssistantPanel } from './ai/AIAssistantPanel';
|
|
6
8
|
|
|
7
9
|
function NavLink({ to, icon, label }) {
|
|
8
10
|
const location = useLocation();
|
|
@@ -103,6 +105,10 @@ export function Layout({ children }) {
|
|
|
103
105
|
{children}
|
|
104
106
|
</div>
|
|
105
107
|
</main>
|
|
108
|
+
|
|
109
|
+
{/* AI Assistant */}
|
|
110
|
+
<AIAssistantButton />
|
|
111
|
+
<AIAssistantPanel />
|
|
106
112
|
</div>
|
|
107
113
|
);
|
|
108
114
|
}
|
|
@@ -10,7 +10,7 @@ import ReactFlow, {
|
|
|
10
10
|
Position
|
|
11
11
|
} from 'reactflow';
|
|
12
12
|
import 'reactflow/dist/style.css';
|
|
13
|
-
import { CheckCircle, XCircle, Clock, Loader, Database, Box, BarChart2, FileText, Layers } from 'lucide-react';
|
|
13
|
+
import { CheckCircle, XCircle, Clock, Loader, Database, Box, BarChart2, FileText, Layers, GitFork, User } from 'lucide-react';
|
|
14
14
|
import dagre from 'dagre';
|
|
15
15
|
|
|
16
16
|
const stepNodeWidth = 240;
|
|
@@ -115,6 +115,11 @@ export function PipelineGraph({ dag, steps, selectedStep, onStepSelect, onArtifa
|
|
|
115
115
|
const executionGroup = stepData.execution_group;
|
|
116
116
|
const groupColor = executionGroup ? groupColors[executionGroup] : null;
|
|
117
117
|
|
|
118
|
+
// Detect special step types
|
|
119
|
+
const isConditional = node.name.toLowerCase().startsWith('if') || node.name.includes('condition');
|
|
120
|
+
const isHumanInLoop = node.name.toLowerCase().includes('approve') || node.name.includes('review') || node.name.includes('human');
|
|
121
|
+
|
|
122
|
+
|
|
118
123
|
nodes.push({
|
|
119
124
|
id: node.id,
|
|
120
125
|
type: 'step',
|
|
@@ -125,7 +130,11 @@ export function PipelineGraph({ dag, steps, selectedStep, onStepSelect, onArtifa
|
|
|
125
130
|
cached: stepData.cached,
|
|
126
131
|
selected: selectedStep === node.id,
|
|
127
132
|
execution_group: executionGroup,
|
|
128
|
-
groupColor: groupColor
|
|
133
|
+
groupColor: groupColor,
|
|
134
|
+
isConditional: isConditional,
|
|
135
|
+
isHumanInLoop: isHumanInLoop,
|
|
136
|
+
inputs: node.inputs || [],
|
|
137
|
+
outputs: node.outputs || []
|
|
129
138
|
}
|
|
130
139
|
});
|
|
131
140
|
|
|
@@ -250,7 +259,7 @@ function CustomStepNode({ data }) {
|
|
|
250
259
|
bg: 'bg-white dark:bg-slate-800',
|
|
251
260
|
border: 'border-amber-500 dark:border-amber-500',
|
|
252
261
|
ring: 'ring-amber-200 dark:ring-amber-900',
|
|
253
|
-
shadow: 'shadow-amber-100 dark:shadow-none'
|
|
262
|
+
shadow: 'shadow-amber-100 dark:shadow-none animate-pulse ring-2 ring-amber-400/50'
|
|
254
263
|
},
|
|
255
264
|
pending: {
|
|
256
265
|
icon: <Clock size={18} />,
|
|
@@ -266,6 +275,39 @@ function CustomStepNode({ data }) {
|
|
|
266
275
|
const groupColor = data.groupColor;
|
|
267
276
|
const hasGroup = data.execution_group && groupColor;
|
|
268
277
|
|
|
278
|
+
// Special styling for conditional nodes
|
|
279
|
+
if (data.isConditional) {
|
|
280
|
+
return (
|
|
281
|
+
<div
|
|
282
|
+
className={`
|
|
283
|
+
relative px-4 py-3 rounded-xl border-2 transition-all duration-200 flex flex-col justify-center items-center text-center
|
|
284
|
+
${config.bg} border-violet-400 dark:border-violet-500
|
|
285
|
+
${data.selected ? 'ring-4 ring-violet-200 dark:ring-violet-900 shadow-lg scale-105' : 'hover:shadow-md'}
|
|
286
|
+
transform rotate-0
|
|
287
|
+
`}
|
|
288
|
+
style={{ width: 180, height: 100 }} // Slightly different size for conditional
|
|
289
|
+
>
|
|
290
|
+
<Handle type="target" position={Position.Top} className="!bg-violet-400 !w-3 !h-3" />
|
|
291
|
+
|
|
292
|
+
<div className="bg-violet-100 dark:bg-violet-900/50 p-2 rounded-full mb-2">
|
|
293
|
+
<GitFork size={20} className="text-violet-600 dark:text-violet-300" />
|
|
294
|
+
</div>
|
|
295
|
+
<div className="font-bold text-xs text-violet-900 dark:text-violet-100 uppercase tracking-wider mb-1">Decision</div>
|
|
296
|
+
<div className="text-xs font-semibold text-slate-700 dark:text-slate-300 line-clamp-2 leading-tight">
|
|
297
|
+
{data.label}
|
|
298
|
+
</div>
|
|
299
|
+
|
|
300
|
+
{data.status !== 'pending' && (
|
|
301
|
+
<div className={`absolute -right-2 -top-2 rounded-full p-1 border-2 border-white dark:border-slate-900 ${config.bg}`}>
|
|
302
|
+
<div className={config.color}>{config.icon}</div>
|
|
303
|
+
</div>
|
|
304
|
+
)}
|
|
305
|
+
|
|
306
|
+
<Handle type="source" position={Position.Bottom} className="!bg-violet-400 !w-3 !h-3" />
|
|
307
|
+
</div>
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
|
|
269
311
|
return (
|
|
270
312
|
<div
|
|
271
313
|
className={`
|
|
@@ -273,6 +315,7 @@ function CustomStepNode({ data }) {
|
|
|
273
315
|
${hasGroup ? groupColor.bg : config.bg}
|
|
274
316
|
${hasGroup ? groupColor.border : config.border}
|
|
275
317
|
${data.selected ? `ring-4 ${config.ring} shadow-lg` : `hover:shadow-md ${config.shadow}`}
|
|
318
|
+
${data.isHumanInLoop ? 'border-dashed border-amber-400 dark:border-amber-500' : ''}
|
|
276
319
|
`}
|
|
277
320
|
style={{ width: stepNodeWidth, height: stepNodeHeight }}
|
|
278
321
|
>
|
|
@@ -280,8 +323,13 @@ function CustomStepNode({ data }) {
|
|
|
280
323
|
|
|
281
324
|
<div className="flex flex-col h-full justify-between">
|
|
282
325
|
<div className="flex items-start gap-3">
|
|
283
|
-
<div className={`p-1.5 rounded-md bg-slate-50 border border-slate-100 ${config.color}`}>
|
|
326
|
+
<div className={`p-1.5 rounded-md bg-slate-50 border border-slate-100 ${config.color} relative`}>
|
|
284
327
|
{config.icon}
|
|
328
|
+
{data.isHumanInLoop && (
|
|
329
|
+
<div className="absolute -bottom-1 -right-1 bg-amber-100 text-amber-600 rounded-full p-0.5 border border-white" title="Human in the loop">
|
|
330
|
+
<User size={10} />
|
|
331
|
+
</div>
|
|
332
|
+
)}
|
|
285
333
|
</div>
|
|
286
334
|
<div className="min-w-0 flex-1">
|
|
287
335
|
<h3 className={`font-bold text-sm truncate ${hasGroup ? groupColor.text : 'text-slate-900 dark:text-white'}`} title={data.label}>
|
|
@@ -298,18 +346,19 @@ function CustomStepNode({ data }) {
|
|
|
298
346
|
</div>
|
|
299
347
|
</div>
|
|
300
348
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
<span className="text-xs text-slate-400 font-mono">
|
|
304
|
-
{data.duration.toFixed(2)}s
|
|
349
|
+
<div className="flex items-center justify-between pt-2 border-t border-slate-100 mt-1">
|
|
350
|
+
{data.duration !== undefined ? (
|
|
351
|
+
<span className="text-xs text-slate-400 font-mono flex items-center gap-1">
|
|
352
|
+
<Clock size={10} /> {data.duration.toFixed(2)}s
|
|
305
353
|
</span>
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
354
|
+
) : <span className="text-xs text-slate-300">-</span>}
|
|
355
|
+
|
|
356
|
+
{data.cached && (
|
|
357
|
+
<span className="flex items-center gap-1 text-[10px] font-bold text-emerald-600 bg-emerald-50 border border-emerald-100 px-1.5 py-0.5 rounded-full uppercase tracking-wider shadow-sm">
|
|
358
|
+
<Database size={8} /> Cached
|
|
359
|
+
</span>
|
|
360
|
+
)}
|
|
361
|
+
</div>
|
|
313
362
|
</div>
|
|
314
363
|
|
|
315
364
|
<Handle type="source" position={Position.Bottom} className="!bg-slate-400 !w-2 !h-2" />
|
|
@@ -318,6 +367,7 @@ function CustomStepNode({ data }) {
|
|
|
318
367
|
}
|
|
319
368
|
|
|
320
369
|
|
|
370
|
+
|
|
321
371
|
function CustomArtifactNode({ data }) {
|
|
322
372
|
// Determine icon and styling based on artifact type (inferred from label)
|
|
323
373
|
const getArtifactStyle = (label) => {
|
|
@@ -326,8 +376,8 @@ function CustomArtifactNode({ data }) {
|
|
|
326
376
|
if (lowerLabel.includes('model') || lowerLabel.includes('weights')) {
|
|
327
377
|
return {
|
|
328
378
|
icon: Box,
|
|
329
|
-
bgColor: 'bg-purple-100 dark:bg-purple-900/
|
|
330
|
-
borderColor: 'border-purple-
|
|
379
|
+
bgColor: 'bg-purple-100 dark:bg-purple-900/40',
|
|
380
|
+
borderColor: 'border-purple-300 dark:border-purple-600',
|
|
331
381
|
iconColor: 'text-purple-600 dark:text-purple-400',
|
|
332
382
|
textColor: 'text-purple-900 dark:text-purple-100'
|
|
333
383
|
};
|
|
@@ -335,8 +385,8 @@ function CustomArtifactNode({ data }) {
|
|
|
335
385
|
if (lowerLabel.includes('feature') || lowerLabel.includes('train_set') || lowerLabel.includes('test_set')) {
|
|
336
386
|
return {
|
|
337
387
|
icon: Layers,
|
|
338
|
-
bgColor: 'bg-emerald-100 dark:bg-emerald-900/
|
|
339
|
-
borderColor: 'border-emerald-
|
|
388
|
+
bgColor: 'bg-emerald-100 dark:bg-emerald-900/40',
|
|
389
|
+
borderColor: 'border-emerald-300 dark:border-emerald-600',
|
|
340
390
|
iconColor: 'text-emerald-600 dark:text-emerald-400',
|
|
341
391
|
textColor: 'text-emerald-900 dark:text-emerald-100'
|
|
342
392
|
};
|
|
@@ -344,8 +394,8 @@ function CustomArtifactNode({ data }) {
|
|
|
344
394
|
if (lowerLabel.includes('data') || lowerLabel.includes('batch') || lowerLabel.includes('set')) {
|
|
345
395
|
return {
|
|
346
396
|
icon: Database,
|
|
347
|
-
bgColor: 'bg-blue-100 dark:bg-blue-900/
|
|
348
|
-
borderColor: 'border-blue-
|
|
397
|
+
bgColor: 'bg-blue-100 dark:bg-blue-900/40',
|
|
398
|
+
borderColor: 'border-blue-300 dark:border-blue-600',
|
|
349
399
|
iconColor: 'text-blue-600 dark:text-blue-400',
|
|
350
400
|
textColor: 'text-blue-900 dark:text-blue-100'
|
|
351
401
|
};
|
|
@@ -353,8 +403,8 @@ function CustomArtifactNode({ data }) {
|
|
|
353
403
|
if (lowerLabel.includes('metrics') || lowerLabel.includes('report') || lowerLabel.includes('status')) {
|
|
354
404
|
return {
|
|
355
405
|
icon: BarChart2,
|
|
356
|
-
bgColor: 'bg-orange-100 dark:bg-orange-900/
|
|
357
|
-
borderColor: 'border-orange-
|
|
406
|
+
bgColor: 'bg-orange-100 dark:bg-orange-900/40',
|
|
407
|
+
borderColor: 'border-orange-300 dark:border-orange-600',
|
|
358
408
|
iconColor: 'text-orange-600 dark:text-orange-400',
|
|
359
409
|
textColor: 'text-orange-900 dark:text-orange-100'
|
|
360
410
|
};
|
|
@@ -362,8 +412,8 @@ function CustomArtifactNode({ data }) {
|
|
|
362
412
|
if (lowerLabel.includes('image') || lowerLabel.includes('docker')) {
|
|
363
413
|
return {
|
|
364
414
|
icon: Box,
|
|
365
|
-
bgColor: 'bg-cyan-100 dark:bg-cyan-900/
|
|
366
|
-
borderColor: 'border-cyan-
|
|
415
|
+
bgColor: 'bg-cyan-100 dark:bg-cyan-900/40',
|
|
416
|
+
borderColor: 'border-cyan-300 dark:border-cyan-600',
|
|
367
417
|
iconColor: 'text-cyan-600 dark:text-cyan-400',
|
|
368
418
|
textColor: 'text-cyan-900 dark:text-cyan-100'
|
|
369
419
|
};
|
|
@@ -384,13 +434,13 @@ function CustomArtifactNode({ data }) {
|
|
|
384
434
|
|
|
385
435
|
return (
|
|
386
436
|
<div
|
|
387
|
-
className={`px-
|
|
388
|
-
style={{ height:
|
|
437
|
+
className={`px-4 py-1.5 rounded-full ${style.bgColor} border ${style.borderColor} flex items-center justify-center gap-2 shadow-sm hover:shadow-md transition-all min-w-[120px] cursor-pointer`}
|
|
438
|
+
style={{ height: 36 }}
|
|
389
439
|
>
|
|
390
440
|
<Handle type="target" position={Position.Top} className="!bg-slate-400 !w-2 !h-2" />
|
|
391
441
|
|
|
392
|
-
<Icon size={
|
|
393
|
-
<span className={`text-
|
|
442
|
+
<Icon size={12} className={style.iconColor} />
|
|
443
|
+
<span className={`text-[11px] font-bold ${style.textColor} truncate max-w-[120px] uppercase tracking-wide`} title={data.label}>
|
|
394
444
|
{data.label}
|
|
395
445
|
</span>
|
|
396
446
|
|
|
@@ -232,11 +232,41 @@ export function RunDetailsPanel({ run, onClose }) {
|
|
|
232
232
|
{step.duration ? `${step.duration.toFixed(2)}s` : '-'}
|
|
233
233
|
</span>
|
|
234
234
|
</div>
|
|
235
|
-
{
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
235
|
+
{
|
|
236
|
+
step.source_file && (
|
|
237
|
+
<div className="flex justify-end mb-2">
|
|
238
|
+
<a
|
|
239
|
+
href={`vscode://file/${step.source_file}:${step.source_line || 1}`}
|
|
240
|
+
className="text-xs flex items-center gap-1 text-slate-500 hover:text-blue-600 transition-colors"
|
|
241
|
+
title="Open in VS Code"
|
|
242
|
+
>
|
|
243
|
+
<Terminal size={12} />
|
|
244
|
+
<span className="font-mono">{step.source_file.split('/').pop()}:{step.source_line}</span>
|
|
245
|
+
</a>
|
|
246
|
+
</div>
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
{
|
|
250
|
+
step.metrics && (
|
|
251
|
+
<div className="flex gap-4 mb-2 justify-end text-xs text-slate-500 font-mono">
|
|
252
|
+
<div className="flex items-center gap-1.5" title="CPU Usage">
|
|
253
|
+
<Cpu size={12} className="text-slate-400" />
|
|
254
|
+
<span>{step.metrics.cpu_percent?.toFixed(1) || 0}%</span>
|
|
255
|
+
</div>
|
|
256
|
+
<div className="flex items-center gap-1.5" title="Memory Usage">
|
|
257
|
+
<Activity size={12} className="text-slate-400" />
|
|
258
|
+
<span>{step.metrics.memory_mb?.toFixed(0) || 0} MB</span>
|
|
259
|
+
</div>
|
|
260
|
+
</div>
|
|
261
|
+
)
|
|
262
|
+
}
|
|
263
|
+
{
|
|
264
|
+
step.error && (
|
|
265
|
+
<div className="text-xs text-rose-600 bg-rose-50 dark:bg-rose-900/20 p-2 rounded mt-2 font-mono">
|
|
266
|
+
{step.error}
|
|
267
|
+
</div>
|
|
268
|
+
)
|
|
269
|
+
}
|
|
240
270
|
</div>
|
|
241
271
|
))}
|
|
242
272
|
{(!runData.steps || Object.keys(runData.steps).length === 0) && (
|
|
@@ -280,7 +310,7 @@ export function RunDetailsPanel({ run, onClose }) {
|
|
|
280
310
|
)}
|
|
281
311
|
</div>
|
|
282
312
|
</div>
|
|
283
|
-
</div>
|
|
313
|
+
</div >
|
|
284
314
|
);
|
|
285
315
|
}
|
|
286
316
|
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Card } from './ui/Card';
|
|
3
|
+
import { Badge } from './ui/Badge';
|
|
4
|
+
import { Cpu, Box, Calendar, Clock, Container, Database, Tag } from 'lucide-react';
|
|
5
|
+
import { motion } from 'framer-motion';
|
|
6
|
+
|
|
7
|
+
export function RunMetaPanel({ run }) {
|
|
8
|
+
if (!run) return null;
|
|
9
|
+
|
|
10
|
+
// Simulate getting resource info from run metadata if available, else placeholders
|
|
11
|
+
const resources = run.metadata?.resources || {
|
|
12
|
+
cpu: "Standard (2 vCPU)",
|
|
13
|
+
memory: "8 GiB",
|
|
14
|
+
gpu: run.metadata?.resources?.gpu ? `${run.metadata.resources.gpu_count}x ${run.metadata.resources.gpu}` : null
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const dockerInfo = run.metadata?.docker || {
|
|
18
|
+
image: "flowyml/base:latest",
|
|
19
|
+
registry: "ghcr.io",
|
|
20
|
+
requirements: ["tensorflow", "scikit-learn"]
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const scheduleInfo = run.trigger ? {
|
|
24
|
+
type: run.trigger.type,
|
|
25
|
+
cron: run.trigger.cron,
|
|
26
|
+
next_run: run.trigger.next_run
|
|
27
|
+
} : null;
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<Card className="p-4 bg-white/50 dark:bg-slate-800/50 backdrop-blur-sm border border-slate-200 dark:border-slate-700/50 shadow-sm">
|
|
31
|
+
<h4 className="text-xs font-bold text-slate-400 uppercase tracking-wider mb-4 flex items-center gap-2">
|
|
32
|
+
<Tag size={12} /> Run Environment
|
|
33
|
+
</h4>
|
|
34
|
+
|
|
35
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
36
|
+
{/* Resources */}
|
|
37
|
+
<div className="space-y-3">
|
|
38
|
+
<div className="flex items-center gap-2 text-sm font-semibold text-slate-700 dark:text-slate-300">
|
|
39
|
+
<Cpu size={16} className="text-blue-500" />
|
|
40
|
+
Resources
|
|
41
|
+
</div>
|
|
42
|
+
<div className="space-y-2">
|
|
43
|
+
<div className="flex items-center justify-between text-xs">
|
|
44
|
+
<span className="text-slate-500">Compute</span>
|
|
45
|
+
<span className="font-mono text-slate-700 dark:text-slate-200">{resources.cpu}</span>
|
|
46
|
+
</div>
|
|
47
|
+
<div className="flex items-center justify-between text-xs">
|
|
48
|
+
<span className="text-slate-500">Memory</span>
|
|
49
|
+
<span className="font-mono text-slate-700 dark:text-slate-200">{resources.memory}</span>
|
|
50
|
+
</div>
|
|
51
|
+
{resources.gpu && (
|
|
52
|
+
<div className="flex items-center justify-between text-xs">
|
|
53
|
+
<span className="text-slate-500">GPU</span>
|
|
54
|
+
<Badge variant="secondary" className="text-[10px] bg-purple-50 text-purple-700 border-purple-200">
|
|
55
|
+
{resources.gpu}
|
|
56
|
+
</Badge>
|
|
57
|
+
</div>
|
|
58
|
+
)}
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
{/* Docker / Environment */}
|
|
63
|
+
<div className="space-y-3">
|
|
64
|
+
<div className="flex items-center gap-2 text-sm font-semibold text-slate-700 dark:text-slate-300">
|
|
65
|
+
<Container size={16} className="text-cyan-500" />
|
|
66
|
+
Environment
|
|
67
|
+
</div>
|
|
68
|
+
<div className="space-y-2">
|
|
69
|
+
<div className="flex flex-col gap-1">
|
|
70
|
+
<span className="text-xs text-slate-500">Base Image</span>
|
|
71
|
+
<code className="text-[10px] bg-slate-100 dark:bg-slate-700 px-1.5 py-1 rounded text-slate-600 dark:text-slate-300 truncate">
|
|
72
|
+
{dockerInfo.image}
|
|
73
|
+
</code>
|
|
74
|
+
</div>
|
|
75
|
+
<div className="flex items-center gap-2">
|
|
76
|
+
<span className="text-xs text-slate-500 w-16">Registry</span>
|
|
77
|
+
<span className="text-xs font-medium text-slate-700 dark:text-slate-300 truncate">{dockerInfo.registry}</span>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
{/* Schedule / Trigger */}
|
|
83
|
+
<div className="space-y-3">
|
|
84
|
+
<div className="flex items-center gap-2 text-sm font-semibold text-slate-700 dark:text-slate-300">
|
|
85
|
+
<Calendar size={16} className="text-emerald-500" />
|
|
86
|
+
Trigger Info
|
|
87
|
+
</div>
|
|
88
|
+
{scheduleInfo ? (
|
|
89
|
+
<div className="space-y-2">
|
|
90
|
+
<div className="flex items-center gap-2">
|
|
91
|
+
<Badge variant="outline" className="text-xs uppercase">{scheduleInfo.type}</Badge>
|
|
92
|
+
{scheduleInfo.cron && <code className="text-[10px] bg-slate-100 px-1 py-0.5 rounded">{scheduleInfo.cron}</code>}
|
|
93
|
+
</div>
|
|
94
|
+
{scheduleInfo.next_run && (
|
|
95
|
+
<div className="text-xs text-slate-500 flex items-center gap-1">
|
|
96
|
+
<Clock size={10} />
|
|
97
|
+
Next: {new Date(scheduleInfo.next_run).toLocaleString()}
|
|
98
|
+
</div>
|
|
99
|
+
)}
|
|
100
|
+
</div>
|
|
101
|
+
) : (
|
|
102
|
+
<div className="flex items-center gap-2 h-full py-2">
|
|
103
|
+
<div className="p-1.5 bg-slate-100 rounded-lg">
|
|
104
|
+
<Box size={14} className="text-slate-400" />
|
|
105
|
+
</div>
|
|
106
|
+
<span className="text-xs text-slate-500 italic">Manual Trigger</span>
|
|
107
|
+
</div>
|
|
108
|
+
)}
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
</Card>
|
|
112
|
+
);
|
|
113
|
+
}
|