more-compute 0.1.4__py3-none-any.whl → 0.2.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 (55) hide show
  1. frontend/app/globals.css +322 -77
  2. frontend/app/layout.tsx +98 -82
  3. frontend/components/Cell.tsx +234 -95
  4. frontend/components/Notebook.tsx +430 -199
  5. frontend/components/{AddCellButton.tsx → cell/AddCellButton.tsx} +0 -2
  6. frontend/components/cell/MonacoCell.tsx +726 -0
  7. frontend/components/layout/ConnectionBanner.tsx +41 -0
  8. frontend/components/{Sidebar.tsx → layout/Sidebar.tsx} +16 -11
  9. frontend/components/modals/ConfirmModal.tsx +154 -0
  10. frontend/components/modals/SuccessModal.tsx +140 -0
  11. frontend/components/output/MarkdownRenderer.tsx +116 -0
  12. frontend/components/popups/ComputePopup.tsx +674 -365
  13. frontend/components/popups/MetricsPopup.tsx +11 -7
  14. frontend/components/popups/SettingsPopup.tsx +11 -13
  15. frontend/contexts/PodWebSocketContext.tsx +247 -0
  16. frontend/eslint.config.mjs +11 -0
  17. frontend/lib/monaco-themes.ts +160 -0
  18. frontend/lib/settings.ts +128 -26
  19. frontend/lib/themes.json +9973 -0
  20. frontend/lib/websocket-native.ts +19 -8
  21. frontend/lib/websocket.ts +59 -11
  22. frontend/next.config.ts +8 -0
  23. frontend/package-lock.json +1705 -3
  24. frontend/package.json +8 -1
  25. frontend/styling_README.md +18 -0
  26. kernel_run.py +159 -42
  27. more_compute-0.2.0.dist-info/METADATA +126 -0
  28. more_compute-0.2.0.dist-info/RECORD +100 -0
  29. morecompute/__version__.py +1 -1
  30. morecompute/execution/executor.py +31 -20
  31. morecompute/execution/worker.py +68 -7
  32. morecompute/models/__init__.py +31 -0
  33. morecompute/models/api_models.py +197 -0
  34. morecompute/notebook.py +50 -7
  35. morecompute/server.py +574 -94
  36. morecompute/services/data_manager.py +379 -0
  37. morecompute/services/lsp_service.py +335 -0
  38. morecompute/services/pod_manager.py +122 -20
  39. morecompute/services/pod_monitor.py +138 -0
  40. morecompute/services/prime_intellect.py +87 -63
  41. morecompute/utils/config_util.py +59 -0
  42. morecompute/utils/special_commands.py +11 -5
  43. morecompute/utils/zmq_util.py +51 -0
  44. frontend/components/MarkdownRenderer.tsx +0 -84
  45. frontend/components/popups/PythonPopup.tsx +0 -292
  46. more_compute-0.1.4.dist-info/METADATA +0 -173
  47. more_compute-0.1.4.dist-info/RECORD +0 -86
  48. /frontend/components/{CellButton.tsx → cell/CellButton.tsx} +0 -0
  49. /frontend/components/{ErrorModal.tsx → modals/ErrorModal.tsx} +0 -0
  50. /frontend/components/{CellOutput.tsx → output/CellOutput.tsx} +0 -0
  51. /frontend/components/{ErrorDisplay.tsx → output/ErrorDisplay.tsx} +0 -0
  52. {more_compute-0.1.4.dist-info → more_compute-0.2.0.dist-info}/WHEEL +0 -0
  53. {more_compute-0.1.4.dist-info → more_compute-0.2.0.dist-info}/entry_points.txt +0 -0
  54. {more_compute-0.1.4.dist-info → more_compute-0.2.0.dist-info}/licenses/LICENSE +0 -0
  55. {more_compute-0.1.4.dist-info → more_compute-0.2.0.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
1
- import React, { useState, useEffect } from "react";
1
+ import React, { useState, useEffect, useMemo } from "react";
2
2
  import {
3
3
  Zap,
4
4
  ExternalLink,
@@ -24,8 +24,11 @@ import {
24
24
  CreatePodRequest,
25
25
  PodConnectionStatus,
26
26
  } from "@/lib/api";
27
- import ErrorModal from "@/components/ErrorModal";
27
+ import ErrorModal from "@/components/modals/ErrorModal";
28
+ import SuccessModal from "@/components/modals/SuccessModal";
29
+ import ConfirmModal from "@/components/modals/ConfirmModal";
28
30
  import FilterPopup from "./FilterPopup";
31
+ import { usePodWebSocket } from "@/contexts/PodWebSocketContext";
29
32
 
30
33
  interface GPUPod {
31
34
  id: string;
@@ -34,6 +37,7 @@ interface GPUPod {
34
37
  gpuType: string;
35
38
  region: string;
36
39
  costPerHour: number;
40
+ sshConnection: string | null;
37
41
  }
38
42
 
39
43
  interface ComputePopupProps {
@@ -41,7 +45,17 @@ interface ComputePopupProps {
41
45
  }
42
46
 
43
47
  const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
44
- const [gpuPods, setGpuPods] = useState<GPUPod[]>([]);
48
+ const {
49
+ gpuPods,
50
+ setPods,
51
+ registerAutoConnect,
52
+ connectionState,
53
+ setConnectionState,
54
+ connectingPodId,
55
+ setConnectingPodId,
56
+ connectedPodId,
57
+ setConnectedPodId,
58
+ } = usePodWebSocket();
45
59
  const [loading, setLoading] = useState(false);
46
60
  const [kernelStatus, setKernelStatus] = useState(false);
47
61
  const [apiConfigured, setApiConfigured] = useState<boolean | null>(null);
@@ -56,9 +70,10 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
56
70
  const [filters, setFilters] = useState<GpuAvailabilityParams>({});
57
71
  const [creatingPodId, setCreatingPodId] = useState<string | null>(null);
58
72
  const [podCreationError, setPodCreationError] = useState<string | null>(null);
59
- const [connectingPodId, setConnectingPodId] = useState<string | null>(null);
60
- const [connectedPodId, setConnectedPodId] = useState<string | null>(null);
61
73
  const [deletingPodId, setDeletingPodId] = useState<string | null>(null);
74
+ const [connectionHealth, setConnectionHealth] = useState<"healthy" | "unhealthy" | "unknown">("unknown");
75
+ const [searchQuery, setSearchQuery] = useState("");
76
+ const [discoveredPod, setDiscoveredPod] = useState<GPUPod | null>(null);
62
77
 
63
78
  // Filter popup state
64
79
  const [showFilterPopup, setShowFilterPopup] = useState(false);
@@ -76,6 +91,63 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
76
91
  message: "",
77
92
  });
78
93
 
94
+ // Success modal state
95
+ const [successModal, setSuccessModal] = useState<{
96
+ isOpen: boolean;
97
+ title: string;
98
+ message: string;
99
+ }>({
100
+ isOpen: false,
101
+ title: "",
102
+ message: "",
103
+ });
104
+
105
+ // Confirm modal state
106
+ const [confirmModal, setConfirmModal] = useState<{
107
+ isOpen: boolean;
108
+ title: string;
109
+ message: string;
110
+ onConfirm: () => void;
111
+ }>({
112
+ isOpen: false,
113
+ title: "",
114
+ message: "",
115
+ onConfirm: () => {},
116
+ });
117
+
118
+ // Health check effect - runs every 10 seconds when connected
119
+ useEffect(() => {
120
+ if (!connectedPodId) {
121
+ setConnectionHealth("unknown");
122
+ return;
123
+ }
124
+
125
+ const checkConnectionHealth = async () => {
126
+ try {
127
+ const status: PodConnectionStatus = await getPodConnectionStatus();
128
+ if (status.connected && status.pod?.id === connectedPodId) {
129
+ setConnectionHealth("healthy");
130
+ } else {
131
+ setConnectionHealth("unhealthy");
132
+ // Auto-disconnect if connection is dead
133
+ setConnectedPodId(null);
134
+ setKernelStatus(false);
135
+ }
136
+ } catch (err) {
137
+ console.error("Connection health check failed:", err);
138
+ setConnectionHealth("unhealthy");
139
+ }
140
+ };
141
+
142
+ // Initial check
143
+ checkConnectionHealth();
144
+
145
+ // Set up periodic health checks
146
+ const interval = setInterval(checkConnectionHealth, 10000); // Check every 10 seconds
147
+
148
+ return () => clearInterval(interval);
149
+ }, [connectedPodId]);
150
+
79
151
  useEffect(() => {
80
152
  const checkApiConfig = async () => {
81
153
  try {
@@ -83,11 +155,32 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
83
155
  setApiConfigured(config.configured);
84
156
  if (config.configured) {
85
157
  await loadGPUPods();
158
+ // Load available GPUs automatically
159
+ await loadAvailableGPUs();
86
160
  // Check if already connected to a pod
87
161
  const status: PodConnectionStatus = await getPodConnectionStatus();
88
162
  if (status.connected && status.pod) {
89
163
  setConnectedPodId(status.pod.id);
90
164
  setKernelStatus(true); // Kernel is running when connected to pod
165
+ setConnectionHealth("healthy");
166
+ } else if (!status.connected && status.pod) {
167
+ // Discovered running pod but not connected (backend restart scenario)
168
+ console.log("[COMPUTE POPUP] Discovered running pod after restart:", status.pod);
169
+ // Only show discovered pod warning if it's not already in the pods list
170
+ // (This can happen if pods haven't loaded yet)
171
+ const pod = status.pod;
172
+ const alreadyInList = gpuPods.some(p => p.id === pod.id);
173
+ if (!alreadyInList) {
174
+ setDiscoveredPod({
175
+ id: pod.id,
176
+ name: pod.name,
177
+ status: "running",
178
+ gpuType: pod.gpu_type || "Unknown",
179
+ region: "Unknown",
180
+ costPerHour: pod.price_hr || 0,
181
+ sshConnection: pod.ssh_connection || null,
182
+ });
183
+ }
91
184
  }
92
185
  }
93
186
  } catch (err) {
@@ -96,16 +189,7 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
96
189
  }
97
190
  };
98
191
  checkApiConfig();
99
-
100
- // Poll pod list every 10 seconds if configured
101
- const pollInterval = setInterval(async () => {
102
- if (apiConfigured) {
103
- await loadGPUPods();
104
- }
105
- }, 10000);
106
-
107
- return () => clearInterval(pollInterval);
108
- }, [apiConfigured]);
192
+ }, []);
109
193
 
110
194
  const loadGPUPods = async (params?: PodsListParams) => {
111
195
  setLoading(true);
@@ -113,10 +197,11 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
113
197
  const response = await fetchGpuPods(params || { limit: 100 });
114
198
  const pods = (response.data || []).map((pod: PodResponse) => {
115
199
  // Map API status to UI status
200
+ // Pod must be ACTIVE *and* have SSH connection info to be "running"
116
201
  let uiStatus: "running" | "stopped" | "starting" = "stopped";
117
- if (pod.status === "ACTIVE") {
202
+ if (pod.status === "ACTIVE" && pod.sshConnection) {
118
203
  uiStatus = "running";
119
- } else if (pod.status === "PROVISIONING" || pod.status === "PENDING") {
204
+ } else if (pod.status === "ACTIVE" || pod.status === "PROVISIONING" || pod.status === "PENDING") {
120
205
  uiStatus = "starting";
121
206
  }
122
207
 
@@ -127,9 +212,10 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
127
212
  gpuType: pod.gpuName,
128
213
  region: "Unknown", //look at later
129
214
  costPerHour: pod.priceHr,
215
+ sshConnection: pod.sshConnection,
130
216
  };
131
217
  });
132
- setGpuPods(pods);
218
+ setPods(pods);
133
219
  } catch (err) {
134
220
  console.error("Failed to load GPU pods:", err);
135
221
  } finally {
@@ -187,14 +273,23 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
187
273
 
188
274
  const newPod = await createGpuPod(podRequest);
189
275
 
276
+ // Register auto-connect callback for when pod becomes ready
277
+ registerAutoConnect(newPod.id, handleConnectToPod);
278
+
279
+ // Set provisioning state for the banner
280
+ setConnectingPodId(newPod.id);
281
+ setConnectionState("provisioning"); // Show "PROVISIONING" banner
282
+
190
283
  // Refresh the pods list
191
284
  await loadGPUPods();
192
285
 
193
286
  // Close browse section and show success
194
287
  setShowBrowseGPUs(false);
195
- alert(
196
- `Pod "${newPod.name}" created successfully! Wait for provisioning (~2-5 min).`,
197
- );
288
+ setSuccessModal({
289
+ isOpen: true,
290
+ title: "Pod Created Successfully!",
291
+ message: `Pod "${newPod.name}" created successfully! Wait for provisioning (~2-5 min).`,
292
+ });
198
293
  } catch (err) {
199
294
  let errorMsg = "Failed to create pod";
200
295
 
@@ -210,6 +305,9 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
210
305
  "Insufficient funds. Please add credits to your Prime Intellect wallet:\nhttps://app.primeintellect.ai/dashboard/billing";
211
306
  } else if (errorMsg.includes("401") || errorMsg.includes("403")) {
212
307
  errorMsg = "Authentication failed. Check your API key configuration.";
308
+ } else if (errorMsg.includes("503") || errorMsg.includes("not available")) {
309
+ errorMsg =
310
+ "This GPU type is currently unavailable. Please try selecting a different GPU from the list below.";
213
311
  } else if (errorMsg.includes("data_center_id")) {
214
312
  errorMsg =
215
313
  "Pod configuration error: Missing data center ID. Try a different GPU or provider.";
@@ -240,27 +338,112 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
240
338
  };
241
339
 
242
340
  const handleConnectToPod = async (podId: string) => {
341
+ // Prevent double-connecting
342
+ if (connectingPodId === podId || connectedPodId === podId) {
343
+ console.log(`[CONNECT] Already connecting/connected to pod ${podId}, skipping`);
344
+ return;
345
+ }
346
+
243
347
  setConnectingPodId(podId);
348
+ setConnectionState("deploying"); // Show "Deploying worker..." banner
349
+ setConnectionHealth("unknown"); // Reset health status during connection
244
350
  try {
351
+ // Initiate connection (now returns immediately with "connecting" status)
245
352
  const result = await connectToPod(podId);
246
- if (result.status === "ok") {
353
+
354
+ if (result.status === "connecting") {
355
+ console.log("[CONNECT] Connection initiated, polling for completion...");
356
+
357
+ // Poll connection status until it's connected or fails
358
+ const maxAttempts = 30; // 30 seconds max
359
+ let attempts = 0;
360
+ let isComplete = false; // Track if polling is complete to prevent race conditions
361
+
362
+ const pollInterval = setInterval(async () => {
363
+ if (isComplete) return; // Skip if already completed
364
+ attempts++;
365
+
366
+ try {
367
+ const status: PodConnectionStatus = await getPodConnectionStatus();
368
+
369
+ if (status.connected && status.pod?.id === podId && !isComplete) {
370
+ // Successfully connected!
371
+ isComplete = true;
372
+ clearInterval(pollInterval);
373
+
374
+ setConnectedPodId(podId);
375
+ setKernelStatus(true);
376
+ setConnectionHealth("healthy");
377
+ setConnectingPodId(null);
378
+ setConnectionState("connected"); // Show "Connected!" banner
379
+ setDiscoveredPod(null); // Clear discovered pod state
380
+
381
+ setErrorModal({
382
+ isOpen: true,
383
+ title: "✓ Connected!",
384
+ message: "Successfully connected to GPU pod. You can now run code on the remote GPU.",
385
+ });
386
+
387
+ // Hide the connected banner after 3 seconds
388
+ setTimeout(() => {
389
+ setConnectionState(null);
390
+ }, 3000);
391
+ } else if (attempts >= maxAttempts && !isComplete) {
392
+ // Timeout
393
+ clearInterval(pollInterval);
394
+ setConnectionHealth("unhealthy");
395
+ setConnectingPodId(null);
396
+ setConnectionState(null); // Hide banner on failure
397
+
398
+ setErrorModal({
399
+ isOpen: true,
400
+ title: "Connection Timeout",
401
+ message: "Connection took too long. The pod may not be ready yet. Check the pod status and try again.",
402
+ });
403
+ }
404
+ } catch (err) {
405
+ // Error during polling
406
+ clearInterval(pollInterval);
407
+ setConnectionHealth("unhealthy");
408
+ setConnectingPodId(null);
409
+ setConnectionState(null); // Hide banner on error
410
+
411
+ setErrorModal({
412
+ isOpen: true,
413
+ title: "Connection Failed",
414
+ message: "Failed to establish connection. Please try again.",
415
+ });
416
+ }
417
+ }, 1000); // Poll every second
418
+
419
+ } else if (result.status === "ok") {
420
+ // Immediate success (backwards compatible)
247
421
  setConnectedPodId(podId);
248
- setKernelStatus(true); // Mark kernel as running
422
+ setKernelStatus(true);
423
+ setConnectionHealth("healthy");
424
+ setConnectingPodId(null);
425
+ setConnectionState("connected"); // Show "Connected!" banner
426
+ setDiscoveredPod(null); // Clear discovered pod state
427
+
249
428
  setErrorModal({
250
429
  isOpen: true,
251
430
  title: "✓ Connected!",
252
- message:
253
- "Successfully connected to GPU pod. You can now run code on the remote GPU.",
431
+ message: "Successfully connected to GPU pod. You can now run code on the remote GPU.",
254
432
  });
433
+
434
+ // Hide the connected banner after 3 seconds
435
+ setTimeout(() => {
436
+ setConnectionState(null);
437
+ }, 3000);
255
438
  } else {
256
- // Show detailed error message from backend
439
+ // Error
440
+ setConnectionHealth("unhealthy");
441
+ setConnectingPodId(null);
442
+ setConnectionState(null); // Hide banner on error
443
+
257
444
  let errorMsg = result.message || "Connection failed";
258
445
 
259
- // Check for SSH key issues
260
- if (
261
- errorMsg.includes("SSH authentication") ||
262
- errorMsg.includes("SSH public key")
263
- ) {
446
+ if (errorMsg.includes("SSH authentication") || errorMsg.includes("SSH public key")) {
264
447
  setErrorModal({
265
448
  isOpen: true,
266
449
  title: "SSH Key Required",
@@ -277,15 +460,16 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
277
460
  }
278
461
  }
279
462
  } catch (err) {
280
- const errorMsg =
281
- err instanceof Error ? err.message : "Failed to connect to pod";
463
+ const errorMsg = err instanceof Error ? err.message : "Failed to connect to pod";
464
+ setConnectionHealth("unhealthy");
465
+ setConnectingPodId(null);
466
+ setConnectionState(null); // Hide banner on error
467
+
282
468
  setErrorModal({
283
469
  isOpen: true,
284
470
  title: "Connection Error",
285
- message: errorMsg,
471
+ message: `${errorMsg}\n\nThis could be due to:\n• Network connectivity issues\n• Pod may have stopped running\n• SSH tunnel creation failed`,
286
472
  });
287
- } finally {
288
- setConnectingPodId(null);
289
473
  }
290
474
  };
291
475
 
@@ -294,7 +478,15 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
294
478
  await disconnectFromPod();
295
479
  setConnectedPodId(null);
296
480
  setKernelStatus(false); // Mark kernel as not running
297
- alert("Disconnected from pod");
481
+ setConnectionHealth("unknown"); // Reset health status
482
+ setConnectionState(null); // Hide banner
483
+
484
+ // Show warning that pod is still running
485
+ setErrorModal({
486
+ isOpen: true,
487
+ title: "⚠️ Disconnected",
488
+ message: "Disconnected from pod. Note: The pod is still running and incurring costs. You can reconnect or terminate it below.",
489
+ });
298
490
  } catch (err) {
299
491
  const errorMsg =
300
492
  err instanceof Error ? err.message : "Failed to disconnect";
@@ -303,29 +495,54 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
303
495
  };
304
496
 
305
497
  const handleDeletePod = async (podId: string, podName: string) => {
306
- if (!confirm(`Are you sure you want to terminate pod "${podName}"?`)) {
307
- return;
308
- }
498
+ // Show confirmation modal instead of native confirm dialog
499
+ setConfirmModal({
500
+ isOpen: true,
501
+ title: "Terminate Pod?",
502
+ message: `Are you sure you want to terminate pod "${podName}"?\n\nThis will stop the pod and you will no longer be charged for it.`,
503
+ onConfirm: async () => {
504
+ setDeletingPodId(podId);
505
+ try {
506
+ // Disconnect if this is the connected pod
507
+ if (connectedPodId === podId) {
508
+ await disconnectFromPod();
509
+ setConnectedPodId(null);
510
+ setKernelStatus(false);
511
+ setConnectionHealth("unknown");
512
+ }
309
513
 
310
- setDeletingPodId(podId);
311
- try {
312
- // Disconnect if this is the connected pod
313
- if (connectedPodId === podId) {
314
- await disconnectFromPod();
315
- setConnectedPodId(null);
316
- setKernelStatus(false);
317
- }
514
+ // Clear connection state if deleting the connecting pod
515
+ if (connectingPodId === podId) {
516
+ setConnectingPodId(null);
517
+ setConnectionState(null);
518
+ }
318
519
 
319
- await deleteGpuPod(podId);
320
- alert(`Pod "${podName}" terminated successfully`);
321
- await loadGPUPods();
322
- } catch (err) {
323
- const errorMsg =
324
- err instanceof Error ? err.message : "Failed to terminate pod";
325
- alert(`Terminate error: ${errorMsg}`);
326
- } finally {
327
- setDeletingPodId(null);
328
- }
520
+ await deleteGpuPod(podId);
521
+
522
+ // Clear discovered pod if it was the one we just deleted
523
+ if (discoveredPod && discoveredPod.id === podId) {
524
+ setDiscoveredPod(null);
525
+ }
526
+
527
+ setSuccessModal({
528
+ isOpen: true,
529
+ title: "Pod Terminated",
530
+ message: `Pod "${podName}" terminated successfully.`,
531
+ });
532
+ await loadGPUPods();
533
+ } catch (err) {
534
+ const errorMsg =
535
+ err instanceof Error ? err.message : "Failed to terminate pod";
536
+ setErrorModal({
537
+ isOpen: true,
538
+ title: "Termination Failed",
539
+ message: `Failed to terminate pod: ${errorMsg}`,
540
+ });
541
+ } finally {
542
+ setDeletingPodId(null);
543
+ }
544
+ },
545
+ });
329
546
  };
330
547
 
331
548
  const handleConnectToPrimeIntellect = () => {
@@ -355,6 +572,19 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
355
572
  }
356
573
  };
357
574
 
575
+ // Filter GPUs based on search query (memoized for performance)
576
+ const filteredGPUs = useMemo(() => {
577
+ if (!searchQuery.trim()) {
578
+ return availableGPUs;
579
+ }
580
+
581
+ const query = searchQuery.toLowerCase();
582
+ return availableGPUs.filter((gpu) => {
583
+ const gpuType = gpu.gpuType.toLowerCase();
584
+ return gpuType.includes(query);
585
+ });
586
+ }, [searchQuery, availableGPUs]);
587
+
358
588
  return (
359
589
  <>
360
590
  <ErrorModal
@@ -365,11 +595,27 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
365
595
  actionLabel={errorModal.actionLabel}
366
596
  actionUrl={errorModal.actionUrl}
367
597
  />
598
+ <SuccessModal
599
+ isOpen={successModal.isOpen}
600
+ onClose={() => setSuccessModal({ ...successModal, isOpen: false })}
601
+ title={successModal.title}
602
+ message={successModal.message}
603
+ />
604
+ <ConfirmModal
605
+ isOpen={confirmModal.isOpen}
606
+ onClose={() => setConfirmModal({ ...confirmModal, isOpen: false })}
607
+ onConfirm={confirmModal.onConfirm}
608
+ title={confirmModal.title}
609
+ message={confirmModal.message}
610
+ confirmLabel="Terminate"
611
+ cancelLabel="Cancel"
612
+ isDangerous={true}
613
+ />
368
614
  <div className="runtime-popup">
369
615
  {/* Kernel Status Section */}
370
616
  <section
371
617
  className="runtime-section"
372
- style={{ padding: "6px 12px", marginBottom: "16px" }}
618
+ style={{ padding: "16px 20px" }}
373
619
  >
374
620
  <div
375
621
  style={{
@@ -378,57 +624,80 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
378
624
  alignItems: "center",
379
625
  }}
380
626
  >
381
- <h3 className="runtime-section-title" style={{ fontSize: "12px" }}>
382
- Kernel:{" "}
383
- <span
384
- className={
385
- kernelStatus
386
- ? "kernel-status-active"
387
- : "kernel-status-inactive"
388
- }
389
- >
390
- {kernelStatus ? "running" : "not running"}
391
- </span>
392
- </h3>
393
- <button
394
- className="runtime-btn runtime-btn-secondary"
395
- style={{ fontSize: "11px", padding: "3px 8px" }}
396
- >
397
- Stop kernel
398
- </button>
627
+ <div>
628
+ <div style={{ fontSize: "11px", color: "var(--text-secondary)", marginBottom: "4px" }}>
629
+ The kernel is currently: {kernelStatus ? "Running" : "Stopped"}
630
+ </div>
631
+ </div>
399
632
  </div>
400
633
  </section>
401
634
 
635
+ {/* Divider */}
636
+ <div style={{
637
+ height: "1px",
638
+ backgroundColor: "rgba(128, 128, 128, 0.2)",
639
+ margin: "0 16px"
640
+ }} />
641
+
402
642
  {/* Compute Profile Section */}
403
- <section className="runtime-section" style={{ padding: "6px 12px" }}>
643
+ <section className="runtime-section" style={{ padding: "12px 16px" }}>
404
644
  <div
405
645
  style={{
406
646
  display: "flex",
407
647
  justifyContent: "space-between",
408
648
  alignItems: "center",
409
- marginBottom: "3px",
410
649
  }}
411
650
  >
412
- <h3 className="runtime-section-title" style={{ fontSize: "12px" }}>
413
- Compute profile
651
+ <h3 className="runtime-section-title" style={{ fontSize: "12px", fontWeight: 500 }}>
652
+ Compute Profile
414
653
  </h3>
415
- <span className="runtime-cost" style={{ fontSize: "11px" }}>
416
- $0.00 / hour
654
+ <span className="runtime-cost" style={{ fontSize: "12px", fontWeight: 500 }}>
655
+ {(() => {
656
+ const runningPods = gpuPods.filter(p => p.status === "running");
657
+ const totalCost = runningPods.reduce((sum, pod) => sum + pod.costPerHour, 0);
658
+ return `$${totalCost.toFixed(2)} / hour`;
659
+ })()}
417
660
  </span>
418
661
  </div>
662
+ </section>
663
+
664
+ {/* Divider */}
665
+ <div style={{
666
+ height: "1px",
667
+ backgroundColor: "rgba(128, 128, 128, 0.2)",
668
+ margin: "0 16px"
669
+ }} />
419
670
 
420
671
  {/* GPU Pods Section */}
421
- <div className="runtime-subsection" style={{ marginTop: "30px" }}>
672
+ <section className="runtime-section" style={{ padding: "12px 16px" }}>
422
673
  <div
423
- className="runtime-subsection-header"
424
- style={{ marginBottom: "4px" }}
674
+ style={{
675
+ display: "flex",
676
+ justifyContent: "space-between",
677
+ alignItems: "center",
678
+ marginBottom: "12px"
679
+ }}
425
680
  >
426
- <h4
427
- className="runtime-subsection-title"
428
- style={{ fontSize: "11px" }}
429
- >
681
+ <h3 className="runtime-section-title" style={{ fontSize: "12px", fontWeight: 500 }}>
430
682
  Remote GPU Pods
431
- </h4>
683
+ </h3>
684
+ {apiConfigured && (
685
+ <button
686
+ className="runtime-btn runtime-btn-secondary"
687
+ onClick={handleConnectToPrimeIntellect}
688
+ style={{
689
+ fontSize: "11px",
690
+ padding: "6px 12px",
691
+ backgroundColor: "#000",
692
+ color: "white",
693
+ border: "none",
694
+ borderRadius: "8px",
695
+ cursor: "pointer"
696
+ }}
697
+ >
698
+ Manage
699
+ </button>
700
+ )}
432
701
  </div>
433
702
 
434
703
  {apiConfigured === false ? (
@@ -451,8 +720,8 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
451
720
  onKeyPress={(e) => e.key === "Enter" && handleSaveApiKey()}
452
721
  style={{
453
722
  width: "100%",
454
- padding: "4px 6px",
455
- borderRadius: "3px",
723
+ padding: "6px 12px",
724
+ borderRadius: "8px",
456
725
  border: "1px solid var(--border-color)",
457
726
  backgroundColor: "var(--background)",
458
727
  color: "var(--text)",
@@ -477,14 +746,31 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
477
746
  className="runtime-btn runtime-btn-primary"
478
747
  onClick={handleSaveApiKey}
479
748
  disabled={saving}
480
- style={{ flex: 1, fontSize: "11px", padding: "4px 8px" }}
749
+ style={{
750
+ flex: 1,
751
+ fontSize: "11px",
752
+ padding: "6px 12px",
753
+ backgroundColor: "#000",
754
+ color: "white",
755
+ border: "none",
756
+ borderRadius: "8px",
757
+ cursor: "pointer"
758
+ }}
481
759
  >
482
760
  {saving ? "Saving..." : "Save"}
483
761
  </button>
484
762
  <button
485
763
  className="runtime-btn runtime-btn-secondary"
486
764
  onClick={handleConnectToPrimeIntellect}
487
- style={{ fontSize: "11px", padding: "4px 8px" }}
765
+ style={{
766
+ fontSize: "11px",
767
+ padding: "6px 12px",
768
+ backgroundColor: "#000",
769
+ color: "white",
770
+ border: "none",
771
+ borderRadius: "8px",
772
+ cursor: "pointer"
773
+ }}
488
774
  >
489
775
  <ExternalLink size={10} style={{ marginRight: "3px" }} />
490
776
  Get Key
@@ -492,185 +778,222 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
492
778
  </div>
493
779
  </div>
494
780
  ) : loading || apiConfigured === null ? (
495
- <div className="runtime-empty-state" style={{ padding: "6px" }}>
496
- <p style={{ color: "var(--text-secondary)", fontSize: "10px" }}>
497
- Loading...
498
- </p>
781
+ <div style={{ padding: "8px 0", color: "var(--text-secondary)", fontSize: "11px" }}>
782
+ Loading...
499
783
  </div>
500
- ) : gpuPods.length === 0 ? (
501
- <div className="runtime-empty-state" style={{ padding: "6px" }}>
502
- <p
503
- style={{
504
- marginBottom: "4px",
505
- color: "var(--text-secondary)",
506
- fontSize: "10px",
507
- }}
508
- >
509
- No GPU pods. Browse GPUs to create.
510
- </p>
511
- <div style={{ display: "flex", gap: "4px", width: "100%" }}>
512
- <button
513
- className="runtime-btn runtime-btn-primary"
514
- onClick={() => {
515
- setShowBrowseGPUs(!showBrowseGPUs);
516
- if (!showBrowseGPUs && availableGPUs.length === 0) {
517
- loadAvailableGPUs();
518
- }
519
- }}
520
- style={{ flex: 1, fontSize: "11px", padding: "4px 8px" }}
521
- >
522
- <Search size={10} style={{ marginRight: "3px" }} />
523
- Browse GPUs
524
- </button>
525
- <button
526
- className="runtime-btn runtime-btn-secondary"
527
- onClick={handleConnectToPrimeIntellect}
528
- style={{ fontSize: "11px", padding: "4px 8px" }}
529
- >
530
- <ExternalLink size={10} style={{ marginRight: "3px" }} />
531
- Manage
532
- </button>
784
+ ) : gpuPods.filter(p => p.status === "running").length === 0 && !discoveredPod ? (
785
+ <div style={{ padding: "8px 0", color: "var(--text-secondary)", fontSize: "11px" }}>
786
+ Currently not connected to any.
787
+ </div>
788
+ ) : !connectedPodId && discoveredPod ? (
789
+ <div style={{ padding: "8px 0" }}>
790
+ <div style={{
791
+ backgroundColor: "#fff3cd",
792
+ border: "1px solid #ffc107",
793
+ borderRadius: "8px",
794
+ padding: "12px",
795
+ marginBottom: "8px"
796
+ }}>
797
+ <div style={{ fontSize: "11px", fontWeight: 600, color: "#856404", marginBottom: "4px" }}>
798
+ ⚠️ Running Pod Detected
799
+ </div>
800
+ <div style={{ fontSize: "10px", color: "#856404", marginBottom: "8px" }}>
801
+ Found a running pod but not connected (backend may have restarted). This pod is still costing money!
802
+ </div>
803
+ <div style={{ fontSize: "10px", marginBottom: "8px" }}>
804
+ <span style={{ fontWeight: 500 }}>{discoveredPod.name}</span> {discoveredPod.gpuType} ${discoveredPod.costPerHour.toFixed(2)}/hour
805
+ </div>
806
+ <div style={{ display: "flex", gap: "6px" }}>
807
+ <button
808
+ className="runtime-btn runtime-btn-secondary"
809
+ onClick={() => handleConnectToPod(discoveredPod.id)}
810
+ style={{
811
+ flex: 1,
812
+ fontSize: "10px",
813
+ padding: "6px 12px",
814
+ backgroundColor: "#ffc107",
815
+ color: "#000",
816
+ border: "none",
817
+ borderRadius: "8px",
818
+ cursor: "pointer",
819
+ fontWeight: 600
820
+ }}
821
+ >
822
+ Reconnect
823
+ </button>
824
+ <button
825
+ className="runtime-btn runtime-btn-secondary"
826
+ onClick={() => handleDeletePod(discoveredPod.id, discoveredPod.name)}
827
+ disabled={deletingPodId === discoveredPod.id}
828
+ style={{
829
+ fontSize: "10px",
830
+ padding: "6px 12px",
831
+ backgroundColor: "#dc2626",
832
+ color: "white",
833
+ border: "none",
834
+ borderRadius: "8px",
835
+ cursor: "pointer"
836
+ }}
837
+ >
838
+ {deletingPodId === discoveredPod.id ? "..." : "Terminate"}
839
+ </button>
840
+ </div>
533
841
  </div>
534
842
  </div>
535
843
  ) : (
536
- <>
537
- <div className="runtime-gpu-list">
538
- {gpuPods.map((pod) => (
539
- <div key={pod.id} className="runtime-gpu-item">
540
- <div className="runtime-gpu-info">
541
- <div className="runtime-gpu-header">
542
- <span className="runtime-gpu-name">{pod.name}</span>
543
- <span
544
- className={`runtime-status-badge runtime-status-${pod.status}`}
545
- >
546
- <Activity size={10} />
547
- {pod.status}
548
- </span>
549
- </div>
550
- <div className="runtime-gpu-details">
551
- <span className="runtime-gpu-type">
552
- {pod.gpuType}
553
- </span>
554
- <span className="runtime-gpu-region">
555
- {pod.region}
556
- </span>
557
- <span className="runtime-gpu-cost">
558
- ${pod.costPerHour.toFixed(2)}/hour
559
- </span>
844
+ gpuPods
845
+ .filter((pod) => pod.status === "running")
846
+ .map((pod) => {
847
+ const isConnected = pod.id === connectedPodId;
848
+ return (
849
+ <div key={pod.id} style={{ padding: "8px 0" }}>
850
+ <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: "8px" }}>
851
+ <div>
852
+ <div style={{ fontSize: "11px", marginBottom: "4px" }}>
853
+ <span style={{ fontWeight: 500 }}>{pod.name}</span>
854
+ {isConnected && (
855
+ <span style={{
856
+ marginLeft: "6px",
857
+ fontSize: "9px",
858
+ backgroundColor: "#10b981",
859
+ color: "white",
860
+ padding: "2px 6px",
861
+ borderRadius: "4px"
862
+ }}>
863
+ Connected
864
+ </span>
865
+ )}
866
+ </div>
867
+ <div style={{ fontSize: "10px", color: "var(--text-secondary)" }}>
868
+ {pod.gpuType} • ${pod.costPerHour.toFixed(2)}/hour
869
+ </div>
560
870
  </div>
561
- </div>
562
- <div style={{ display: "flex", gap: "4px" }}>
563
- {pod.status === "running" ? (
564
- connectedPodId === pod.id ? (
565
- <button
566
- className="runtime-btn runtime-btn-sm"
567
- onClick={handleDisconnect}
568
- style={{
569
- fontSize: "11px",
570
- padding: "4px 8px",
571
- backgroundColor: "var(--success)",
572
- }}
573
- >
574
- Disconnect
575
- </button>
871
+ <div style={{ display: "flex", gap: "6px" }}>
872
+ {isConnected ? (
873
+ <>
874
+ <button
875
+ className="runtime-btn runtime-btn-secondary"
876
+ onClick={handleDisconnect}
877
+ style={{
878
+ fontSize: "10px",
879
+ padding: "6px 12px",
880
+ backgroundColor: "#000",
881
+ color: "white",
882
+ border: "none",
883
+ borderRadius: "8px",
884
+ cursor: "pointer"
885
+ }}
886
+ >
887
+ Disconnect
888
+ </button>
889
+ <button
890
+ className="runtime-btn runtime-btn-secondary"
891
+ onClick={() => handleDeletePod(pod.id, pod.name)}
892
+ disabled={deletingPodId === pod.id}
893
+ style={{
894
+ fontSize: "10px",
895
+ padding: "6px 12px",
896
+ backgroundColor: "#dc2626",
897
+ color: "white",
898
+ border: "none",
899
+ borderRadius: "8px",
900
+ cursor: "pointer"
901
+ }}
902
+ >
903
+ {deletingPodId === pod.id ? "..." : "Terminate"}
904
+ </button>
905
+ </>
576
906
  ) : (
577
- <button
578
- className="runtime-btn runtime-btn-sm"
579
- onClick={() => handleConnectToPod(pod.id)}
580
- disabled={connectingPodId === pod.id}
581
- style={{ fontSize: "11px", padding: "4px 8px" }}
582
- >
583
- {connectingPodId === pod.id
584
- ? "Connecting..."
585
- : "Connect"}
586
- </button>
587
- )
588
- ) : (
589
- <button
590
- className="runtime-btn runtime-btn-sm runtime-btn-secondary"
591
- style={{ fontSize: "11px", padding: "4px 8px" }}
592
- disabled
593
- >
594
- {pod.status === "starting"
595
- ? "Starting..."
596
- : "Stopped"}
597
- </button>
598
- )}
599
- <button
600
- className="runtime-btn runtime-btn-sm"
601
- onClick={() => handleDeletePod(pod.id, pod.name)}
602
- disabled={deletingPodId === pod.id}
603
- style={{
604
- fontSize: "11px",
605
- padding: "4px 8px",
606
- backgroundColor: "var(--error-color)",
607
- color: "white",
608
- }}
609
- >
610
- {deletingPodId === pod.id ? "..." : "×"}
611
- </button>
907
+ <>
908
+ <button
909
+ className="runtime-btn runtime-btn-secondary"
910
+ onClick={() => handleConnectToPod(pod.id)}
911
+ disabled={connectingPodId === pod.id}
912
+ style={{
913
+ fontSize: "10px",
914
+ padding: "6px 12px",
915
+ backgroundColor: "#10b981",
916
+ color: "white",
917
+ border: "none",
918
+ borderRadius: "8px",
919
+ cursor: "pointer"
920
+ }}
921
+ >
922
+ {connectingPodId === pod.id ? "Connecting..." : "Connect"}
923
+ </button>
924
+ <button
925
+ className="runtime-btn runtime-btn-secondary"
926
+ onClick={() => handleDeletePod(pod.id, pod.name)}
927
+ disabled={deletingPodId === pod.id}
928
+ style={{
929
+ fontSize: "10px",
930
+ padding: "6px 12px",
931
+ backgroundColor: "#dc2626",
932
+ color: "white",
933
+ border: "none",
934
+ borderRadius: "8px",
935
+ cursor: "pointer"
936
+ }}
937
+ >
938
+ {deletingPodId === pod.id ? "..." : "Terminate"}
939
+ </button>
940
+ </>
941
+ )}
942
+ </div>
612
943
  </div>
613
944
  </div>
614
- ))}
615
- </div>
616
- <div style={{ display: "flex", gap: "4px" }}>
617
- <button
618
- className="runtime-btn runtime-btn-link"
619
- onClick={() => loadGPUPods()}
620
- style={{ fontSize: "12px", padding: "6px 8px", flex: 1 }}
621
- >
622
- Refresh
623
- </button>
624
- <button
625
- className="runtime-btn runtime-btn-link"
626
- onClick={() => {
627
- setShowBrowseGPUs(!showBrowseGPUs);
628
- if (!showBrowseGPUs && availableGPUs.length === 0) {
629
- loadAvailableGPUs();
630
- }
631
- }}
632
- style={{ fontSize: "12px", padding: "6px 8px", flex: 1 }}
633
- >
634
- <Plus size={12} style={{ marginRight: "4px" }} />
635
- Browse GPUs
636
- </button>
637
- </div>
638
- </>
945
+ );
946
+ })
639
947
  )}
640
- </div>
948
+ </section>
641
949
 
642
- {/* Browse Available GPUs Section */}
643
- {apiConfigured && showBrowseGPUs && (
644
- <div className="runtime-subsection" style={{ marginTop: "6px" }}>
645
- <div
646
- className="runtime-subsection-header"
647
- style={{ marginBottom: "4px" }}
648
- >
649
- <h4
650
- className="runtime-subsection-title"
651
- style={{ fontSize: "11px" }}
652
- >
653
- <Filter size={10} style={{ marginRight: "2px" }} />
654
- Browse GPUs
655
- </h4>
656
- </div>
950
+ {/* Divider */}
951
+ <div style={{
952
+ height: "1px",
953
+ backgroundColor: "rgba(128, 128, 128, 0.2)",
954
+ margin: "0 16px"
955
+ }} />
657
956
 
658
- {/* Filter and Search Bar */}
957
+ {/* Browse Available GPUs Section */}
958
+ {apiConfigured && (
959
+ <section className="runtime-section" style={{ padding: "12px 16px" }}>
960
+ {/* Search and Filter Bar */}
659
961
  <div
660
962
  style={{
661
- marginBottom: "6px",
963
+ marginBottom: "20px",
662
964
  display: "flex",
663
- gap: "4px",
965
+ gap: "8px",
664
966
  alignItems: "center",
665
967
  }}
666
968
  >
969
+ <input
970
+ type="text"
971
+ placeholder="Search GPU (e.g., H100, A100, RTX 4090)"
972
+ value={searchQuery}
973
+ onChange={(e) => setSearchQuery(e.target.value)}
974
+ style={{
975
+ flex: 1,
976
+ padding: "6px 12px",
977
+ fontSize: "11px",
978
+ border: "1px solid #d1d5db",
979
+ borderRadius: "8px",
980
+ backgroundColor: "var(--background)",
981
+ color: "var(--text)",
982
+ outline: "none",
983
+ }}
984
+ />
667
985
  <button
668
986
  className="runtime-btn runtime-btn-secondary"
669
987
  onClick={() => setShowFilterPopup(!showFilterPopup)}
670
988
  style={{
671
- padding: "4px 8px",
989
+ padding: "6px 12px",
672
990
  fontSize: "11px",
673
991
  position: "relative",
992
+ backgroundColor: "#000",
993
+ color: "white",
994
+ border: "none",
995
+ borderRadius: "8px",
996
+ cursor: "pointer"
674
997
  }}
675
998
  >
676
999
  <Filter size={10} style={{ marginRight: "3px" }} />
@@ -692,19 +1015,6 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
692
1015
  />
693
1016
  )}
694
1017
  </button>
695
- <button
696
- className="runtime-btn runtime-btn-primary"
697
- onClick={loadAvailableGPUs}
698
- disabled={loadingAvailability}
699
- style={{
700
- flex: 1,
701
- padding: "4px 8px",
702
- fontSize: "11px",
703
- }}
704
- >
705
- <Search size={10} style={{ marginRight: "3px" }} />
706
- {loadingAvailability ? "Searching..." : "Search"}
707
- </button>
708
1018
  </div>
709
1019
 
710
1020
  {/* Filter Popup */}
@@ -718,40 +1028,39 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
718
1028
 
719
1029
  {/* Results */}
720
1030
  {loadingAvailability ? (
721
- <div className="runtime-empty-state" style={{ padding: "6px" }}>
722
- <p
723
- style={{ color: "var(--text-secondary)", fontSize: "10px" }}
724
- >
725
- Loading...
726
- </p>
1031
+ <div style={{ padding: "16px 0", textAlign: "center", color: "var(--text-secondary)", fontSize: "11px" }}>
1032
+ Loading...
727
1033
  </div>
728
1034
  ) : availableGPUs.length === 0 ? (
729
- <div className="runtime-empty-state" style={{ padding: "6px" }}>
730
- <p
731
- style={{ color: "var(--text-secondary)", fontSize: "10px" }}
732
- >
733
- Click Search to find GPUs
734
- </p>
1035
+ <div style={{ padding: "16px 0", textAlign: "center", color: "var(--text-secondary)", fontSize: "11px" }}>
1036
+ Use filters to find available GPUs
1037
+ </div>
1038
+ ) : filteredGPUs.length === 0 ? (
1039
+ <div style={{ padding: "16px 0", textAlign: "center", color: "var(--text-secondary)", fontSize: "11px" }}>
1040
+ No GPUs match "{searchQuery}"
735
1041
  </div>
736
1042
  ) : (
737
- <div style={{ maxHeight: "300px", overflowY: "auto" }}>
738
- {availableGPUs.map((gpu, index) => (
739
- <div
740
- key={`${gpu.cloudId}-${index}`}
741
- style={{
742
- padding: "4px 6px",
743
- borderRadius: "3px",
744
- border: "1px solid var(--border-color)",
745
- marginBottom: "3px",
746
- backgroundColor: "var(--background-secondary)",
747
- }}
748
- >
1043
+ <div style={{ maxHeight: "calc(100vh - 400px)", overflowY: "auto", paddingRight: "12px" }}>
1044
+ {filteredGPUs.map((gpu, index) => (
1045
+ <React.Fragment key={`${gpu.cloudId}-${index}`}>
1046
+ {index > 0 && (
1047
+ <div style={{
1048
+ height: "1px",
1049
+ backgroundColor: "rgba(128, 128, 128, 0.15)",
1050
+ margin: "8px 0"
1051
+ }} />
1052
+ )}
1053
+ <div
1054
+ style={{
1055
+ padding: "8px 0",
1056
+ }}
1057
+ >
749
1058
  <div
750
1059
  style={{
751
1060
  display: "flex",
752
1061
  justifyContent: "space-between",
753
1062
  alignItems: "flex-start",
754
- marginBottom: "3px",
1063
+ marginBottom: "8px",
755
1064
  }}
756
1065
  >
757
1066
  <div>
@@ -773,30 +1082,54 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
773
1082
  {gpu.provider} - {gpu.socket} - {gpu.gpuMemory}GB
774
1083
  </div>
775
1084
  </div>
776
- <div style={{ textAlign: "right" }}>
777
- <div
778
- style={{
779
- fontWeight: 600,
780
- fontSize: "11px",
781
- color: "var(--accent)",
782
- }}
783
- >
784
- ${gpu.prices?.onDemand?.toFixed(2) || "N/A"}/hr
785
- </div>
786
- {gpu.stockStatus && (
1085
+ <div style={{ textAlign: "right", display: "flex", flexDirection: "column", alignItems: "flex-end", gap: "6px" }}>
1086
+ <div>
787
1087
  <div
788
1088
  style={{
789
- fontSize: "9px",
790
- color:
791
- gpu.stockStatus === "Available"
792
- ? "var(--success)"
793
- : "var(--text-secondary)",
794
- marginTop: "1px",
1089
+ fontWeight: 600,
1090
+ fontSize: "11px",
1091
+ color: "var(--accent)",
795
1092
  }}
796
1093
  >
797
- {gpu.stockStatus}
1094
+ ${gpu.prices?.onDemand?.toFixed(2) || "N/A"}/hr
798
1095
  </div>
799
- )}
1096
+ {gpu.stockStatus && (
1097
+ <div
1098
+ style={{
1099
+ fontSize: "9px",
1100
+ color:
1101
+ gpu.stockStatus === "Available"
1102
+ ? "var(--success)"
1103
+ : "var(--text-secondary)",
1104
+ marginTop: "1px",
1105
+ }}
1106
+ >
1107
+ {gpu.stockStatus}
1108
+ </div>
1109
+ )}
1110
+ </div>
1111
+ <button
1112
+ className="runtime-btn runtime-btn-sm runtime-btn-primary"
1113
+ onClick={(e) => {
1114
+ e.stopPropagation();
1115
+ createPodFromGpu(gpu);
1116
+ }}
1117
+ disabled={creatingPodId === gpu.cloudId}
1118
+ style={{
1119
+ fontSize: "10px",
1120
+ padding: "6px 16px",
1121
+ whiteSpace: "nowrap",
1122
+ backgroundColor: "#000",
1123
+ color: "white",
1124
+ border: "none",
1125
+ borderRadius: "8px",
1126
+ cursor: "pointer"
1127
+ }}
1128
+ >
1129
+ {creatingPodId === gpu.cloudId
1130
+ ? "Selecting..."
1131
+ : "Select"}
1132
+ </button>
800
1133
  </div>
801
1134
  </div>
802
1135
  <div
@@ -806,71 +1139,47 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
806
1139
  fontSize: "9px",
807
1140
  color: "var(--text-secondary)",
808
1141
  alignItems: "center",
809
- justifyContent: "space-between",
810
1142
  }}
811
1143
  >
812
- <div
813
- style={{
814
- display: "flex",
815
- gap: "4px",
816
- flexWrap: "wrap",
817
- flex: 1,
818
- }}
819
- >
820
- {gpu.region && (
821
- <span style={{ marginRight: "8px" }}>
822
- {gpu.region}
823
- </span>
824
- )}
825
- {gpu.dataCenter && (
826
- <span style={{ marginRight: "8px" }}>
827
- {gpu.dataCenter}
828
- </span>
829
- )}
830
- {gpu.security && (
831
- <span
832
- style={{
833
- backgroundColor:
834
- gpu.security === "secure_cloud"
835
- ? "var(--success-bg)"
836
- : "var(--info-bg)",
837
- color:
838
- gpu.security === "secure_cloud"
839
- ? "var(--success)"
840
- : "var(--info)",
841
- padding: "1px 4px",
842
- borderRadius: "2px",
843
- fontSize: "9px",
844
- }}
845
- >
846
- {gpu.security === "secure_cloud"
847
- ? "Secure"
848
- : "Community"}
849
- </span>
850
- )}
851
- </div>
852
- <button
853
- className="runtime-btn runtime-btn-sm runtime-btn-primary"
854
- onClick={() => createPodFromGpu(gpu)}
855
- disabled={creatingPodId === gpu.cloudId}
856
- style={{
857
- fontSize: "10px",
858
- padding: "3px 6px",
859
- whiteSpace: "nowrap",
860
- }}
861
- >
862
- {creatingPodId === gpu.cloudId
863
- ? "Creating..."
864
- : "Create"}
865
- </button>
1144
+ {gpu.region && (
1145
+ <span style={{ marginRight: "8px" }}>
1146
+ {gpu.region}
1147
+ </span>
1148
+ )}
1149
+ {gpu.dataCenter && (
1150
+ <span style={{ marginRight: "8px" }}>
1151
+ {gpu.dataCenter}
1152
+ </span>
1153
+ )}
1154
+ {gpu.security && (
1155
+ <span
1156
+ style={{
1157
+ backgroundColor:
1158
+ gpu.security === "secure_cloud"
1159
+ ? "var(--success-bg)"
1160
+ : "var(--info-bg)",
1161
+ color:
1162
+ gpu.security === "secure_cloud"
1163
+ ? "var(--success)"
1164
+ : "var(--info)",
1165
+ padding: "1px 4px",
1166
+ borderRadius: "2px",
1167
+ fontSize: "9px",
1168
+ }}
1169
+ >
1170
+ {gpu.security === "secure_cloud"
1171
+ ? "Secure"
1172
+ : "Community"}
1173
+ </span>
1174
+ )}
866
1175
  </div>
867
1176
  </div>
1177
+ </React.Fragment>
868
1178
  ))}
869
1179
  </div>
870
1180
  )}
871
- </div>
1181
+ </section>
872
1182
  )}
873
- </section>
874
1183
  </div>
875
1184
  </>
876
1185
  );