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.
Files changed (137) hide show
  1. flowyml/assets/base.py +15 -0
  2. flowyml/assets/dataset.py +570 -17
  3. flowyml/assets/metrics.py +5 -0
  4. flowyml/assets/model.py +1052 -15
  5. flowyml/cli/main.py +709 -0
  6. flowyml/cli/stack_cli.py +138 -25
  7. flowyml/core/__init__.py +17 -0
  8. flowyml/core/executor.py +231 -37
  9. flowyml/core/image_builder.py +129 -0
  10. flowyml/core/log_streamer.py +227 -0
  11. flowyml/core/orchestrator.py +59 -4
  12. flowyml/core/pipeline.py +65 -13
  13. flowyml/core/routing.py +558 -0
  14. flowyml/core/scheduler.py +88 -5
  15. flowyml/core/step.py +9 -1
  16. flowyml/core/step_grouping.py +49 -35
  17. flowyml/core/types.py +407 -0
  18. flowyml/integrations/keras.py +247 -82
  19. flowyml/monitoring/alerts.py +10 -0
  20. flowyml/monitoring/notifications.py +104 -25
  21. flowyml/monitoring/slack_blocks.py +323 -0
  22. flowyml/plugins/__init__.py +251 -0
  23. flowyml/plugins/alerters/__init__.py +1 -0
  24. flowyml/plugins/alerters/slack.py +168 -0
  25. flowyml/plugins/base.py +752 -0
  26. flowyml/plugins/config.py +478 -0
  27. flowyml/plugins/deployers/__init__.py +22 -0
  28. flowyml/plugins/deployers/gcp_cloud_run.py +200 -0
  29. flowyml/plugins/deployers/sagemaker.py +306 -0
  30. flowyml/plugins/deployers/vertex.py +290 -0
  31. flowyml/plugins/integration.py +369 -0
  32. flowyml/plugins/manager.py +510 -0
  33. flowyml/plugins/model_registries/__init__.py +22 -0
  34. flowyml/plugins/model_registries/mlflow.py +159 -0
  35. flowyml/plugins/model_registries/sagemaker.py +489 -0
  36. flowyml/plugins/model_registries/vertex.py +386 -0
  37. flowyml/plugins/orchestrators/__init__.py +13 -0
  38. flowyml/plugins/orchestrators/sagemaker.py +443 -0
  39. flowyml/plugins/orchestrators/vertex_ai.py +461 -0
  40. flowyml/plugins/registries/__init__.py +13 -0
  41. flowyml/plugins/registries/ecr.py +321 -0
  42. flowyml/plugins/registries/gcr.py +313 -0
  43. flowyml/plugins/registry.py +454 -0
  44. flowyml/plugins/stack.py +494 -0
  45. flowyml/plugins/stack_config.py +537 -0
  46. flowyml/plugins/stores/__init__.py +13 -0
  47. flowyml/plugins/stores/gcs.py +460 -0
  48. flowyml/plugins/stores/s3.py +453 -0
  49. flowyml/plugins/trackers/__init__.py +11 -0
  50. flowyml/plugins/trackers/mlflow.py +316 -0
  51. flowyml/plugins/validators/__init__.py +3 -0
  52. flowyml/plugins/validators/deepchecks.py +119 -0
  53. flowyml/registry/__init__.py +2 -1
  54. flowyml/registry/model_environment.py +109 -0
  55. flowyml/registry/model_registry.py +241 -96
  56. flowyml/serving/__init__.py +17 -0
  57. flowyml/serving/model_server.py +628 -0
  58. flowyml/stacks/__init__.py +60 -0
  59. flowyml/stacks/aws.py +93 -0
  60. flowyml/stacks/base.py +62 -0
  61. flowyml/stacks/components.py +12 -0
  62. flowyml/stacks/gcp.py +44 -9
  63. flowyml/stacks/plugins.py +115 -0
  64. flowyml/stacks/registry.py +2 -1
  65. flowyml/storage/sql.py +401 -12
  66. flowyml/tracking/experiment.py +8 -5
  67. flowyml/ui/backend/Dockerfile +87 -16
  68. flowyml/ui/backend/auth.py +12 -2
  69. flowyml/ui/backend/main.py +149 -5
  70. flowyml/ui/backend/routers/ai_context.py +226 -0
  71. flowyml/ui/backend/routers/assets.py +23 -4
  72. flowyml/ui/backend/routers/auth.py +96 -0
  73. flowyml/ui/backend/routers/deployments.py +660 -0
  74. flowyml/ui/backend/routers/model_explorer.py +597 -0
  75. flowyml/ui/backend/routers/plugins.py +103 -51
  76. flowyml/ui/backend/routers/projects.py +91 -8
  77. flowyml/ui/backend/routers/runs.py +132 -1
  78. flowyml/ui/backend/routers/schedules.py +54 -29
  79. flowyml/ui/backend/routers/templates.py +319 -0
  80. flowyml/ui/backend/routers/websocket.py +2 -2
  81. flowyml/ui/frontend/Dockerfile +55 -6
  82. flowyml/ui/frontend/dist/assets/index-B5AsPTSz.css +1 -0
  83. flowyml/ui/frontend/dist/assets/index-dFbZ8wD8.js +753 -0
  84. flowyml/ui/frontend/dist/index.html +2 -2
  85. flowyml/ui/frontend/dist/logo.png +0 -0
  86. flowyml/ui/frontend/nginx.conf +65 -4
  87. flowyml/ui/frontend/package-lock.json +1415 -74
  88. flowyml/ui/frontend/package.json +4 -0
  89. flowyml/ui/frontend/public/logo.png +0 -0
  90. flowyml/ui/frontend/src/App.jsx +10 -7
  91. flowyml/ui/frontend/src/app/assets/page.jsx +890 -321
  92. flowyml/ui/frontend/src/app/auth/Login.jsx +90 -0
  93. flowyml/ui/frontend/src/app/dashboard/page.jsx +8 -8
  94. flowyml/ui/frontend/src/app/deployments/page.jsx +786 -0
  95. flowyml/ui/frontend/src/app/model-explorer/page.jsx +1031 -0
  96. flowyml/ui/frontend/src/app/pipelines/page.jsx +12 -2
  97. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectExperimentsList.jsx +19 -6
  98. flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectMetricsPanel.jsx +1 -1
  99. flowyml/ui/frontend/src/app/runs/[runId]/page.jsx +601 -101
  100. flowyml/ui/frontend/src/app/runs/page.jsx +8 -2
  101. flowyml/ui/frontend/src/app/settings/page.jsx +267 -253
  102. flowyml/ui/frontend/src/components/ArtifactViewer.jsx +62 -2
  103. flowyml/ui/frontend/src/components/AssetDetailsPanel.jsx +424 -29
  104. flowyml/ui/frontend/src/components/AssetTreeHierarchy.jsx +119 -11
  105. flowyml/ui/frontend/src/components/DatasetViewer.jsx +753 -0
  106. flowyml/ui/frontend/src/components/Layout.jsx +6 -0
  107. flowyml/ui/frontend/src/components/PipelineGraph.jsx +79 -29
  108. flowyml/ui/frontend/src/components/RunDetailsPanel.jsx +36 -6
  109. flowyml/ui/frontend/src/components/RunMetaPanel.jsx +113 -0
  110. flowyml/ui/frontend/src/components/TrainingHistoryChart.jsx +514 -0
  111. flowyml/ui/frontend/src/components/TrainingMetricsPanel.jsx +175 -0
  112. flowyml/ui/frontend/src/components/ai/AIAssistantButton.jsx +71 -0
  113. flowyml/ui/frontend/src/components/ai/AIAssistantPanel.jsx +420 -0
  114. flowyml/ui/frontend/src/components/header/Header.jsx +22 -0
  115. flowyml/ui/frontend/src/components/plugins/PluginManager.jsx +4 -4
  116. flowyml/ui/frontend/src/components/plugins/{ZenMLIntegration.jsx → StackImport.jsx} +38 -12
  117. flowyml/ui/frontend/src/components/sidebar/Sidebar.jsx +36 -13
  118. flowyml/ui/frontend/src/contexts/AIAssistantContext.jsx +245 -0
  119. flowyml/ui/frontend/src/contexts/AuthContext.jsx +108 -0
  120. flowyml/ui/frontend/src/hooks/useAIContext.js +156 -0
  121. flowyml/ui/frontend/src/hooks/useWebGPU.js +54 -0
  122. flowyml/ui/frontend/src/layouts/MainLayout.jsx +6 -0
  123. flowyml/ui/frontend/src/router/index.jsx +47 -20
  124. flowyml/ui/frontend/src/services/pluginService.js +3 -1
  125. flowyml/ui/server_manager.py +5 -5
  126. flowyml/ui/utils.py +157 -39
  127. flowyml/utils/config.py +37 -15
  128. flowyml/utils/model_introspection.py +123 -0
  129. flowyml/utils/observability.py +30 -0
  130. flowyml-1.8.0.dist-info/METADATA +174 -0
  131. {flowyml-1.7.1.dist-info → flowyml-1.8.0.dist-info}/RECORD +134 -73
  132. {flowyml-1.7.1.dist-info → flowyml-1.8.0.dist-info}/WHEEL +1 -1
  133. flowyml/ui/frontend/dist/assets/index-BqDQvp63.js +0 -630
  134. flowyml/ui/frontend/dist/assets/index-By4trVyv.css +0 -1
  135. flowyml-1.7.1.dist-info/METADATA +0 -477
  136. {flowyml-1.7.1.dist-info → flowyml-1.8.0.dist-info}/entry_points.txt +0 -0
  137. {flowyml-1.7.1.dist-info → flowyml-1.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -15,7 +15,9 @@ import {
15
15
  ChevronLeft,
16
16
  ChevronRight,
17
17
  Menu,
18
- Activity
18
+ Activity,
19
+ Rocket,
20
+ Microscope
19
21
  } from 'lucide-react';
20
22
  import { motion, AnimatePresence } from 'framer-motion';
21
23
 
@@ -33,6 +35,7 @@ const NAV_GROUPS = [
33
35
  { icon: PlayCircle, label: 'Pipelines', path: '/pipelines' },
34
36
  { icon: Calendar, label: 'Schedules', path: '/schedules' },
35
37
  { icon: PlayCircle, label: 'Runs', path: '/runs' },
38
+ { icon: Rocket, label: 'Deployments', path: '/deployments' },
36
39
  ],
37
40
  },
38
41
  {
@@ -40,6 +43,7 @@ const NAV_GROUPS = [
40
43
  items: [
41
44
  { icon: Trophy, label: 'Leaderboard', path: '/leaderboard' },
42
45
  { icon: FlaskConical, label: 'Experiments', path: '/experiments' },
46
+ { icon: Microscope, label: 'Model Explorer', path: '/model-explorer' },
43
47
  ],
44
48
  },
45
49
  {
@@ -61,6 +65,8 @@ const SETTINGS_LINKS = [
61
65
  export function Sidebar({ collapsed, setCollapsed }) {
62
66
  const location = useLocation();
63
67
 
68
+ const [logoError, setLogoError] = useState(false);
69
+
64
70
  return (
65
71
  <motion.aside
66
72
  initial={false}
@@ -68,20 +74,32 @@ export function Sidebar({ collapsed, setCollapsed }) {
68
74
  className="h-screen bg-white dark:bg-slate-800 border-r border-slate-200 dark:border-slate-700 flex flex-col shadow-sm z-20 relative"
69
75
  >
70
76
  {/* Logo Section */}
71
- <div className="p-6 border-b border-slate-100 dark:border-slate-700 flex items-center gap-3 h-[73px]">
72
- <div className="w-8 h-8 min-w-[32px] bg-primary-600 rounded-lg flex items-center justify-center shadow-lg shadow-primary-500/30">
73
- <PlayCircle className="text-white w-5 h-5" />
74
- </div>
77
+ <div className="p-4 border-b border-slate-100 dark:border-slate-700 flex items-center gap-3 h-[73px]">
78
+ {logoError ? (
79
+ <div className="w-12 h-12 min-w-[48px] rounded-lg shadow-lg bg-gradient-to-br from-primary-500 to-indigo-600 flex items-center justify-center text-white font-bold text-xl select-none">
80
+ F
81
+ </div>
82
+ ) : (
83
+ <img
84
+ src="/logo.png"
85
+ alt="FlowyML"
86
+ className="w-12 h-12 min-w-[48px] rounded-lg shadow-lg object-contain bg-white dark:bg-slate-800"
87
+ onError={() => setLogoError(true)}
88
+ />
89
+ )}
75
90
  <AnimatePresence>
76
91
  {!collapsed && (
77
- <motion.h1
92
+ <motion.div
78
93
  initial={{ opacity: 0, x: -10 }}
79
94
  animate={{ opacity: 1, x: 0 }}
80
95
  exit={{ opacity: 0, x: -10 }}
81
- className="text-xl font-bold text-slate-900 dark:text-white tracking-tight whitespace-nowrap overflow-hidden"
96
+ className="flex flex-col"
82
97
  >
83
- flowyml
84
- </motion.h1>
98
+ <h1 className="text-xl font-bold text-slate-900 dark:text-white tracking-tight whitespace-nowrap overflow-hidden">
99
+ FlowyML
100
+ </h1>
101
+ <span className="text-[10px] text-slate-400 dark:text-slate-500">by UnicoLab</span>
102
+ </motion.div>
85
103
  )}
86
104
  </AnimatePresence>
87
105
  </div>
@@ -123,14 +141,19 @@ export function Sidebar({ collapsed, setCollapsed }) {
123
141
 
124
142
  {/* Footer */}
125
143
  <div className="p-4 border-t border-slate-100 dark:border-slate-700">
126
- <div className={`bg-slate-50 dark:bg-slate-900 rounded-lg p-4 border border-slate-100 dark:border-slate-700 transition-all duration-200 ${collapsed ? 'p-2 flex justify-center' : ''}`}>
144
+ <div className={`bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800 rounded-lg p-3 border border-slate-200 dark:border-slate-700 transition-all duration-200 ${collapsed ? 'p-2 flex justify-center' : ''}`}>
127
145
  {!collapsed ? (
128
146
  <>
129
- <p className="text-xs font-medium text-slate-500 dark:text-slate-400 whitespace-nowrap">flowyml v1.3.0</p>
130
- <p className="text-xs text-slate-400 dark:text-slate-500 mt-1 whitespace-nowrap">Local Environment</p>
147
+ <div className="flex items-center gap-2 mb-2">
148
+ <div className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
149
+ <p className="text-xs font-semibold text-slate-600 dark:text-slate-300 whitespace-nowrap">FlowyML v1.3.0</p>
150
+ </div>
151
+ <p className="text-[10px] text-slate-400 dark:text-slate-500 whitespace-nowrap">
152
+ Made with ❤️ by <span className="font-medium text-primary-500">UnicoLab</span>
153
+ </p>
131
154
  </>
132
155
  ) : (
133
- <div className="w-2 h-2 rounded-full bg-emerald-500" title="Online" />
156
+ <div className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" title="Online" />
134
157
  )}
135
158
  </div>
136
159
  </div>
@@ -0,0 +1,245 @@
1
+ import { createContext, useContext, useState, useCallback, useEffect, useRef } from 'react';
2
+ import * as webllm from '@mlc-ai/web-llm';
3
+
4
+ // FlowyML System Prompt - comprehensive knowledge base for the AI assistant
5
+ const FLOWYML_SYSTEM_PROMPT = `You are FlowyML Assistant, an expert AI advisor for the FlowyML ML pipeline framework. You help users optimize their pipelines, debug issues, and follow best practices.
6
+
7
+ ## About FlowyML
8
+ FlowyML is an enterprise-grade ML pipeline framework that combines Python simplicity with powerful MLOps features:
9
+ - Zero-boilerplate orchestration with pure Python (no YAML/DSLs)
10
+ - Intelligent caching (code hash, input hash) to skip unchanged steps
11
+ - First-class Assets: Dataset, Model, Metrics, FeatureSet with auto-lineage
12
+ - Context auto-injection for parameters
13
+ - Step grouping for efficient execution
14
+ - Dynamic workflows with conditional logic (If/then/else)
15
+ - Human-in-the-loop approval gates
16
+ - Built-in experiment tracking and model leaderboard
17
+ - LLM/GenAI observability with @trace_llm decorator
18
+ - Data drift detection
19
+ - Pipeline scheduling and notifications
20
+
21
+ ## Key Concepts
22
+ 1. **Steps**: Use @step decorator with inputs/outputs. Cache with cache="input_hash" or cache="code_hash"
23
+ 2. **Pipelines**: Create with Pipeline(name, context=ctx), add steps with .add_step()
24
+ 3. **Context**: Define params with context(lr=0.01), auto-injected to steps
25
+ 4. **Assets**: Dataset.create(), Model.create(), Metrics.create() - tracked automatically
26
+ 5. **Execution Groups**: Group steps with execution_group="name" to share resources
27
+
28
+ ## Best Practices
29
+ - Use caching strategically: input_hash for data processing, code_hash for model training
30
+ - Group related steps in execution_group for efficiency
31
+ - Use conditional execution (If/then/else) for dynamic workflows
32
+ - Track experiments with Experiment class
33
+ - Monitor data drift with detect_drift()
34
+ - Use @trace_llm for LLM observability
35
+
36
+ When users share logs, metrics, or pipeline context, analyze them and provide:
37
+ 1. Specific, actionable recommendations
38
+ 2. Code examples when helpful
39
+ 3. Explanations of why certain optimizations work
40
+ 4. Links to relevant FlowyML features
41
+
42
+ Be concise but thorough. Format responses with markdown for clarity.`;
43
+
44
+ const AIAssistantContext = createContext(null);
45
+
46
+ // Model configuration - using a smaller, faster model for WebGPU
47
+ const MODEL_ID = "Qwen2.5-1.5B-Instruct-q4f16_1-MLC"; // Fast, lightweight model
48
+
49
+ export function AIAssistantProvider({ children }) {
50
+ const [isOpen, setIsOpen] = useState(false);
51
+ const [messages, setMessages] = useState([]);
52
+ const [isLoading, setIsLoading] = useState(false);
53
+ const [isModelLoading, setIsModelLoading] = useState(false);
54
+ const [loadProgress, setLoadProgress] = useState(0);
55
+ const [loadStatus, setLoadStatus] = useState('');
56
+ const [error, setError] = useState(null);
57
+ const [isWebGPUSupported, setIsWebGPUSupported] = useState(null);
58
+ const [pipelineContext, setPipelineContext] = useState(null);
59
+ const [contextEnabled, setContextEnabled] = useState(true); // User toggle for context sharing
60
+
61
+ const engineRef = useRef(null);
62
+ const abortControllerRef = useRef(null);
63
+
64
+ // Check WebGPU support on mount
65
+ useEffect(() => {
66
+ const checkWebGPU = async () => {
67
+ try {
68
+ if (!navigator.gpu) {
69
+ setIsWebGPUSupported(false);
70
+ return;
71
+ }
72
+ const adapter = await navigator.gpu.requestAdapter();
73
+ setIsWebGPUSupported(!!adapter);
74
+ } catch (e) {
75
+ console.warn('WebGPU check failed:', e);
76
+ setIsWebGPUSupported(false);
77
+ }
78
+ };
79
+ checkWebGPU();
80
+ }, []);
81
+
82
+ // Initialize WebLLM engine
83
+ const initEngine = useCallback(async () => {
84
+ if (engineRef.current || !isWebGPUSupported) return;
85
+
86
+ setIsModelLoading(true);
87
+ setError(null);
88
+ setLoadStatus('Initializing...');
89
+
90
+ try {
91
+ const engine = await webllm.CreateMLCEngine(MODEL_ID, {
92
+ initProgressCallback: (progress) => {
93
+ setLoadProgress(Math.round(progress.progress * 100));
94
+ setLoadStatus(progress.text || 'Loading model...');
95
+ }
96
+ });
97
+
98
+ engineRef.current = engine;
99
+ setLoadStatus('Model ready!');
100
+ setIsModelLoading(false);
101
+
102
+ // Add welcome message
103
+ setMessages([{
104
+ role: 'assistant',
105
+ content: `👋 Hi! I'm your FlowyML AI Assistant, running **locally** in your browser using WebGPU.
106
+
107
+ I have access to your current pipeline context and can help you:
108
+ - 🔧 **Optimize** your pipelines for better performance
109
+ - 🐛 **Debug** failing steps and understand errors
110
+ - 📊 **Analyze** run metrics and suggest improvements
111
+ - 💡 **Learn** FlowyML best practices and features
112
+
113
+ What would you like help with today?`
114
+ }]);
115
+
116
+ } catch (e) {
117
+ console.error('Failed to initialize WebLLM:', e);
118
+ setError(e.message || 'Failed to load AI model');
119
+ setIsModelLoading(false);
120
+ }
121
+ }, [isWebGPUSupported]);
122
+
123
+ // Send message and get streaming response
124
+ const sendMessage = useCallback(async (content) => {
125
+ if (!engineRef.current || isLoading) return;
126
+
127
+ // Add user message
128
+ const userMessage = { role: 'user', content };
129
+ setMessages(prev => [...prev, userMessage]);
130
+ setIsLoading(true);
131
+
132
+ // Create abort controller for cancellation
133
+ abortControllerRef.current = new AbortController();
134
+
135
+ try {
136
+ // Build context-aware prompt (only if enabled and context exists)
137
+ let contextInfo = '';
138
+ if (contextEnabled && pipelineContext) {
139
+ contextInfo = `\n\n## Current Page Context\nThe user is currently viewing: ${pipelineContext.pageType || 'unknown page'}\n\n### Context Data\n${JSON.stringify(pipelineContext, null, 2)}\n\nUse this context to provide specific, actionable advice about the current pipeline run.\n`;
140
+ }
141
+
142
+ const systemMessage = FLOWYML_SYSTEM_PROMPT + contextInfo;
143
+
144
+ // Build message history for context
145
+ const chatMessages = [
146
+ { role: 'system', content: systemMessage },
147
+ ...messages.slice(-6), // Keep last 6 messages for context
148
+ userMessage
149
+ ];
150
+
151
+ // Add placeholder for assistant response
152
+ const assistantMessage = { role: 'assistant', content: '' };
153
+ setMessages(prev => [...prev, assistantMessage]);
154
+
155
+ // Stream the response
156
+ const asyncGenerator = await engineRef.current.chat.completions.create({
157
+ messages: chatMessages,
158
+ temperature: 0.7,
159
+ max_tokens: 1024,
160
+ stream: true
161
+ });
162
+
163
+ let fullContent = '';
164
+ for await (const chunk of asyncGenerator) {
165
+ if (abortControllerRef.current?.signal.aborted) break;
166
+
167
+ const delta = chunk.choices[0]?.delta?.content || '';
168
+ fullContent += delta;
169
+
170
+ // Update the last message with streamed content
171
+ setMessages(prev => {
172
+ const updated = [...prev];
173
+ updated[updated.length - 1] = { role: 'assistant', content: fullContent };
174
+ return updated;
175
+ });
176
+ }
177
+
178
+ } catch (e) {
179
+ if (e.name !== 'AbortError') {
180
+ console.error('Chat error:', e);
181
+ setMessages(prev => [...prev, {
182
+ role: 'assistant',
183
+ content: `❌ Error: ${e.message || 'Failed to generate response'}`
184
+ }]);
185
+ }
186
+ } finally {
187
+ setIsLoading(false);
188
+ abortControllerRef.current = null;
189
+ }
190
+ }, [messages, pipelineContext, contextEnabled, isLoading]);
191
+
192
+ // Cancel current generation
193
+ const cancelGeneration = useCallback(() => {
194
+ if (abortControllerRef.current) {
195
+ abortControllerRef.current.abort();
196
+ }
197
+ }, []);
198
+
199
+ // Clear chat history
200
+ const clearChat = useCallback(() => {
201
+ setMessages([{
202
+ role: 'assistant',
203
+ content: '🗑️ Chat cleared. How can I help you with FlowyML?'
204
+ }]);
205
+ }, []);
206
+
207
+ // Update pipeline context (called from other components)
208
+ const updateContext = useCallback((context) => {
209
+ setPipelineContext(context);
210
+ }, []);
211
+
212
+ const value = {
213
+ isOpen,
214
+ setIsOpen,
215
+ messages,
216
+ isLoading,
217
+ isModelLoading,
218
+ loadProgress,
219
+ loadStatus,
220
+ error,
221
+ isWebGPUSupported,
222
+ initEngine,
223
+ sendMessage,
224
+ cancelGeneration,
225
+ clearChat,
226
+ updateContext,
227
+ pipelineContext,
228
+ contextEnabled,
229
+ setContextEnabled
230
+ };
231
+
232
+ return (
233
+ <AIAssistantContext.Provider value={value}>
234
+ {children}
235
+ </AIAssistantContext.Provider>
236
+ );
237
+ }
238
+
239
+ export function useAIAssistant() {
240
+ const context = useContext(AIAssistantContext);
241
+ if (!context) {
242
+ throw new Error('useAIAssistant must be used within AIAssistantProvider');
243
+ }
244
+ return context;
245
+ }
@@ -0,0 +1,108 @@
1
+ import React, { createContext, useState, useContext, useEffect } from 'react';
2
+ import { useNavigate, useLocation } from 'react-router-dom';
3
+
4
+ const AuthContext = createContext(null);
5
+
6
+ export const AuthProvider = ({ children }) => {
7
+ const [user, setUser] = useState(null);
8
+ const [loading, setLoading] = useState(true);
9
+ const navigate = useNavigate();
10
+ const location = useLocation();
11
+
12
+ // On mount, check if user is already logged in via cookie
13
+ useEffect(() => {
14
+ checkAuthStatus();
15
+ }, []);
16
+
17
+ const checkAuthStatus = async () => {
18
+ try {
19
+ const response = await fetch('/api/auth/me');
20
+ if (response.ok) {
21
+ const userData = await response.json();
22
+ setUser(userData);
23
+ } else {
24
+ // If 401/403, we are not logged in.
25
+ // If we get here, it means backend didn't auto-redirect or we are just verifying session.
26
+ setUser(null);
27
+ }
28
+ } catch (error) {
29
+ console.error('Auth check failed:', error);
30
+ setUser(null);
31
+ } finally {
32
+ setLoading(false);
33
+ }
34
+ };
35
+
36
+ const login = async (username, password) => {
37
+ try {
38
+ const response = await fetch('/api/auth/login', {
39
+ method: 'POST',
40
+ headers: { 'Content-Type': 'application/json' },
41
+ body: JSON.stringify({ username, password }),
42
+ });
43
+
44
+ if (response.ok) {
45
+ const data = await response.json();
46
+ await checkAuthStatus(); // Refresh user data
47
+
48
+ // Navigate to where they came from, or dashboard
49
+ const origin = location.state?.from?.pathname || '/';
50
+ navigate(origin);
51
+ return { success: true };
52
+ } else {
53
+ const errorData = await response.json();
54
+ return { success: false, error: errorData.detail || 'Login failed' };
55
+ }
56
+ } catch (error) {
57
+ return { success: false, error: 'Network error' };
58
+ }
59
+ };
60
+
61
+ const logout = async () => {
62
+ try {
63
+ await fetch('/api/auth/logout', { method: 'POST' });
64
+ setUser(null);
65
+ navigate('/login');
66
+ } catch (error) {
67
+ console.error('Logout failed:', error);
68
+ }
69
+ };
70
+
71
+ const value = {
72
+ user,
73
+ loading,
74
+ login,
75
+ logout,
76
+ checkAuthStatus
77
+ };
78
+
79
+ return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
80
+ };
81
+
82
+ export const useAuth = () => {
83
+ return useContext(AuthContext);
84
+ };
85
+
86
+ // Component to protect routes
87
+ export const RequireAuth = ({ children }) => {
88
+ const { user, loading } = useAuth();
89
+ const location = useLocation();
90
+
91
+ if (loading) {
92
+ return (
93
+ <div className="flex h-screen w-screen items-center justify-center bg-gray-900 text-white">
94
+ <div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-primary-500"></div>
95
+ </div>
96
+ );
97
+ }
98
+
99
+ if (!user) {
100
+ // Redirect to login page, but save the current location to redirect back after login
101
+ return <Navigate to="/login" state={{ from: location }} replace />;
102
+ }
103
+
104
+ return children;
105
+ };
106
+
107
+ // Helper for redirect (imported from rrd usually, but defined here for context usage)
108
+ import { Navigate } from 'react-router-dom';
@@ -0,0 +1,156 @@
1
+ import { useEffect, useCallback, useState } from 'react';
2
+ import { useLocation } from 'react-router-dom';
3
+ import { useAIAssistant } from '../contexts/AIAssistantContext';
4
+ import { fetchApi } from '../utils/api';
5
+
6
+ /**
7
+ * Hook to share page-specific context with the AI assistant.
8
+ * Fetches comprehensive context from the backend API.
9
+ * Automatically cleans up context when navigating away.
10
+ *
11
+ * @param {Object} options - Configuration options
12
+ * @param {string} options.pageType - Type of page ('run', 'pipeline', 'experiment', etc.)
13
+ * @param {string} options.resourceId - The resource ID (run_id, pipeline_name, etc.)
14
+ * @param {boolean} options.includeLogs - Whether to include logs (default: true)
15
+ * @param {boolean} options.includeCode - Whether to include step code (default: true)
16
+ * @param {boolean} options.includeMetrics - Whether to include metrics (default: true)
17
+ */
18
+ export function useAIContext({ pageType, resourceId, includeLogs = true, includeCode = true, includeMetrics = true } = {}) {
19
+ const location = useLocation();
20
+ const { updateContext, contextEnabled } = useAIAssistant();
21
+ const [isLoading, setIsLoading] = useState(false);
22
+ const [error, setError] = useState(null);
23
+
24
+ const fetchContext = useCallback(async () => {
25
+ if (!pageType || !resourceId || !contextEnabled) {
26
+ return;
27
+ }
28
+
29
+ setIsLoading(true);
30
+ setError(null);
31
+
32
+ try {
33
+ // Fetch comprehensive context from backend
34
+ const response = await fetchApi(`/api/ai/context/${pageType}/${resourceId}?include_logs=${includeLogs}&include_code=${includeCode}&include_metrics=${includeMetrics}`);
35
+
36
+ if (response.ok) {
37
+ const contextData = await response.json();
38
+
39
+ // Update context with backend data
40
+ updateContext({
41
+ pageType: contextData.page_type,
42
+ resourceId: contextData.resource_id,
43
+ ...contextData.summary,
44
+ details: contextData.details,
45
+ suggestions: contextData.suggestions,
46
+ timestamp: new Date().toISOString()
47
+ });
48
+ } else {
49
+ // Fallback: Use basic context if API fails
50
+ console.warn('AI context API failed, using basic context');
51
+ updateContext({
52
+ pageType,
53
+ resourceId,
54
+ timestamp: new Date().toISOString()
55
+ });
56
+ }
57
+ } catch (err) {
58
+ console.error('Failed to fetch AI context:', err);
59
+ setError(err.message);
60
+
61
+ // Fallback to basic context
62
+ updateContext({
63
+ pageType,
64
+ resourceId,
65
+ timestamp: new Date().toISOString()
66
+ });
67
+ } finally {
68
+ setIsLoading(false);
69
+ }
70
+ }, [pageType, resourceId, contextEnabled, includeLogs, includeCode, includeMetrics, updateContext]);
71
+
72
+ // Fetch context when enabled or resource changes
73
+ useEffect(() => {
74
+ if (contextEnabled && pageType && resourceId) {
75
+ fetchContext();
76
+ }
77
+ }, [contextEnabled, pageType, resourceId, fetchContext]);
78
+
79
+ // Cleanup when navigating away
80
+ useEffect(() => {
81
+ return () => {
82
+ updateContext(null);
83
+ };
84
+ }, [location.pathname, updateContext]);
85
+
86
+ return { isLoading, error, refetch: fetchContext };
87
+ }
88
+
89
+ /**
90
+ * Formats run data for AI context - client-side fallback.
91
+ * Used when backend API is not available.
92
+ */
93
+ export function formatRunContext(run, metrics = [], selectedStep = null, logs = {}) {
94
+ if (!run) return null;
95
+
96
+ // Summarize steps
97
+ const stepsSummary = run.steps ? Object.entries(run.steps).map(([name, step]) => ({
98
+ name,
99
+ status: step.success ? 'success' : (step.error ? 'failed' : 'pending'),
100
+ duration: step.duration?.toFixed(2) + 's',
101
+ cached: step.cached || false,
102
+ error: step.error || null,
103
+ inputs: step.inputs?.slice(0, 3),
104
+ outputs: step.outputs?.slice(0, 3)
105
+ })) : [];
106
+
107
+ // Format metrics - take top 10 most relevant
108
+ const metricsSummary = metrics.slice(0, 10).map(m => ({
109
+ name: m.name,
110
+ value: typeof m.value === 'number' ? m.value.toFixed(4) : m.value,
111
+ step: m.step
112
+ }));
113
+
114
+ // Format logs - truncate to last 500 chars per step
115
+ const logsSummary = {};
116
+ if (logs && typeof logs === 'object') {
117
+ Object.entries(logs).forEach(([stepName, stepLogs]) => {
118
+ if (typeof stepLogs === 'string') {
119
+ logsSummary[stepName] = stepLogs.slice(-500);
120
+ }
121
+ });
122
+ }
123
+
124
+ // Selected step details
125
+ const selectedStepDetails = selectedStep && run.steps?.[selectedStep] ? {
126
+ name: selectedStep,
127
+ ...run.steps[selectedStep],
128
+ fullError: run.steps[selectedStep].error,
129
+ sourceCode: run.steps[selectedStep].source_code?.slice(0, 1000)
130
+ } : null;
131
+
132
+ return {
133
+ pageType: 'run',
134
+ data: {
135
+ runId: run.run_id,
136
+ pipelineName: run.pipeline_name,
137
+ project: run.project,
138
+ status: run.status,
139
+ duration: run.duration?.toFixed(2) + 's',
140
+ startTime: run.start_time,
141
+ endTime: run.end_time,
142
+ totalSteps: stepsSummary.length,
143
+ successfulSteps: stepsSummary.filter(s => s.status === 'success').length,
144
+ failedSteps: stepsSummary.filter(s => s.status === 'failed').length,
145
+ cachedSteps: stepsSummary.filter(s => s.cached).length,
146
+ steps: stepsSummary,
147
+ metrics: metricsSummary,
148
+ selectedStep: selectedStepDetails,
149
+ recentLogs: logsSummary,
150
+ environment: run.environment || {},
151
+ context: run.context || {}
152
+ }
153
+ };
154
+ }
155
+
156
+ export default useAIContext;
@@ -0,0 +1,54 @@
1
+ import { useState, useEffect } from 'react';
2
+
3
+ /**
4
+ * Hook to detect WebGPU support in the browser.
5
+ * Returns loading state while checking and final support status.
6
+ */
7
+ export function useWebGPU() {
8
+ const [isSupported, setIsSupported] = useState(null);
9
+ const [isLoading, setIsLoading] = useState(true);
10
+ const [gpuInfo, setGpuInfo] = useState(null);
11
+
12
+ useEffect(() => {
13
+ const checkWebGPU = async () => {
14
+ try {
15
+ // Check if WebGPU API is available
16
+ if (!navigator.gpu) {
17
+ setIsSupported(false);
18
+ setIsLoading(false);
19
+ return;
20
+ }
21
+
22
+ // Try to get an adapter
23
+ const adapter = await navigator.gpu.requestAdapter();
24
+ if (!adapter) {
25
+ setIsSupported(false);
26
+ setIsLoading(false);
27
+ return;
28
+ }
29
+
30
+ // Get adapter info for debugging
31
+ const info = await adapter.requestAdapterInfo?.() || {};
32
+ setGpuInfo({
33
+ vendor: info.vendor || 'Unknown',
34
+ architecture: info.architecture || 'Unknown',
35
+ device: info.device || 'Unknown',
36
+ description: info.description || 'WebGPU Available'
37
+ });
38
+
39
+ setIsSupported(true);
40
+ } catch (error) {
41
+ console.warn('WebGPU detection error:', error);
42
+ setIsSupported(false);
43
+ } finally {
44
+ setIsLoading(false);
45
+ }
46
+ };
47
+
48
+ checkWebGPU();
49
+ }, []);
50
+
51
+ return { isSupported, isLoading, gpuInfo };
52
+ }
53
+
54
+ export default useWebGPU;
@@ -4,6 +4,8 @@ import { Sidebar } from '../components/sidebar/Sidebar';
4
4
  import { Header } from '../components/header/Header';
5
5
 
6
6
  import { ErrorBoundary } from '../components/ui/ErrorBoundary';
7
+ import { AIAssistantButton } from '../components/ai/AIAssistantButton';
8
+ import { AIAssistantPanel } from '../components/ai/AIAssistantPanel';
7
9
 
8
10
  export function MainLayout() {
9
11
  const [collapsed, setCollapsed] = useState(false);
@@ -22,6 +24,10 @@ export function MainLayout() {
22
24
  </div>
23
25
  </main>
24
26
  </div>
27
+
28
+ {/* AI Assistant - Floating button and slide-out panel */}
29
+ <AIAssistantButton />
30
+ <AIAssistantPanel />
25
31
  </div>
26
32
  );
27
33
  }