more-compute 0.4.4__py3-none-any.whl → 0.5.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 (57) hide show
  1. frontend/app/globals.css +734 -27
  2. frontend/app/layout.tsx +13 -3
  3. frontend/components/Notebook.tsx +2 -14
  4. frontend/components/cell/MonacoCell.tsx +99 -5
  5. frontend/components/layout/Sidebar.tsx +39 -4
  6. frontend/components/panels/ClaudePanel.tsx +461 -0
  7. frontend/components/popups/ComputePopup.tsx +738 -447
  8. frontend/components/popups/FilterPopup.tsx +305 -189
  9. frontend/components/popups/MetricsPopup.tsx +20 -1
  10. frontend/components/popups/ProviderConfigModal.tsx +322 -0
  11. frontend/components/popups/ProviderDropdown.tsx +398 -0
  12. frontend/components/popups/SettingsPopup.tsx +1 -1
  13. frontend/contexts/ClaudeContext.tsx +392 -0
  14. frontend/contexts/PodWebSocketContext.tsx +16 -21
  15. frontend/hooks/useInlineDiff.ts +269 -0
  16. frontend/lib/api.ts +323 -12
  17. frontend/lib/settings.ts +5 -0
  18. frontend/lib/websocket-native.ts +4 -8
  19. frontend/lib/websocket.ts +1 -2
  20. frontend/package-lock.json +733 -36
  21. frontend/package.json +2 -0
  22. frontend/public/assets/icons/providers/lambda_labs.svg +22 -0
  23. frontend/public/assets/icons/providers/prime_intellect.svg +18 -0
  24. frontend/public/assets/icons/providers/runpod.svg +9 -0
  25. frontend/public/assets/icons/providers/vastai.svg +1 -0
  26. frontend/settings.md +54 -0
  27. frontend/tsconfig.tsbuildinfo +1 -0
  28. frontend/types/claude.ts +194 -0
  29. kernel_run.py +13 -0
  30. {more_compute-0.4.4.dist-info → more_compute-0.5.0.dist-info}/METADATA +53 -11
  31. {more_compute-0.4.4.dist-info → more_compute-0.5.0.dist-info}/RECORD +56 -37
  32. {more_compute-0.4.4.dist-info → more_compute-0.5.0.dist-info}/WHEEL +1 -1
  33. morecompute/__init__.py +1 -1
  34. morecompute/__version__.py +1 -1
  35. morecompute/execution/executor.py +24 -67
  36. morecompute/execution/worker.py +6 -72
  37. morecompute/models/api_models.py +62 -0
  38. morecompute/notebook.py +11 -0
  39. morecompute/server.py +641 -133
  40. morecompute/services/claude_service.py +392 -0
  41. morecompute/services/pod_manager.py +168 -67
  42. morecompute/services/pod_monitor.py +67 -39
  43. morecompute/services/prime_intellect.py +0 -4
  44. morecompute/services/providers/__init__.py +92 -0
  45. morecompute/services/providers/base_provider.py +336 -0
  46. morecompute/services/providers/lambda_labs_provider.py +394 -0
  47. morecompute/services/providers/provider_factory.py +194 -0
  48. morecompute/services/providers/runpod_provider.py +504 -0
  49. morecompute/services/providers/vastai_provider.py +407 -0
  50. morecompute/utils/cell_magics.py +0 -3
  51. morecompute/utils/config_util.py +93 -3
  52. morecompute/utils/special_commands.py +5 -32
  53. morecompute/utils/version_check.py +117 -0
  54. frontend/styling_README.md +0 -23
  55. {more_compute-0.4.4.dist-info/licenses → more_compute-0.5.0.dist-info}/LICENSE +0 -0
  56. {more_compute-0.4.4.dist-info → more_compute-0.5.0.dist-info}/entry_points.txt +0 -0
  57. {more_compute-0.4.4.dist-info → more_compute-0.5.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,269 @@
1
+ "use client";
2
+
3
+ import { useEffect, useRef, useCallback } from "react";
4
+ import * as monaco from "monaco-editor";
5
+ import * as Diff from "diff";
6
+ import type { ProposedEdit } from "@/types/claude";
7
+
8
+ interface UseInlineDiffProps {
9
+ editor: monaco.editor.IStandaloneCodeEditor | null;
10
+ cellIndex: number;
11
+ pendingEdit: ProposedEdit | undefined;
12
+ onApply: (editId: string) => void;
13
+ onReject: (editId: string) => void;
14
+ }
15
+
16
+ interface DiffDecoration {
17
+ range: monaco.Range;
18
+ options: monaco.editor.IModelDecorationOptions;
19
+ }
20
+
21
+ /**
22
+ * Hook to manage inline diff decorations in Monaco editor.
23
+ * Shows red/green line highlighting for proposed edits with accept/reject buttons.
24
+ */
25
+ export function useInlineDiff({
26
+ editor,
27
+ cellIndex,
28
+ pendingEdit,
29
+ onApply,
30
+ onReject,
31
+ }: UseInlineDiffProps) {
32
+ const decorationsRef = useRef<string[]>([]);
33
+ const widgetRef = useRef<monaco.editor.IContentWidget | null>(null);
34
+ const commandsRef = useRef<monaco.IDisposable[]>([]);
35
+
36
+ // Clear decorations and widgets
37
+ const clearDecorations = useCallback(() => {
38
+ if (!editor) return;
39
+
40
+ // Remove decorations
41
+ if (decorationsRef.current.length > 0) {
42
+ editor.deltaDecorations(decorationsRef.current, []);
43
+ decorationsRef.current = [];
44
+ }
45
+
46
+ // Remove widget
47
+ if (widgetRef.current) {
48
+ editor.removeContentWidget(widgetRef.current);
49
+ widgetRef.current = null;
50
+ }
51
+
52
+ // Dispose commands
53
+ commandsRef.current.forEach((d) => d.dispose());
54
+ commandsRef.current = [];
55
+ }, [editor]);
56
+
57
+ // Apply diff decorations
58
+ const applyDiffDecorations = useCallback(() => {
59
+ if (!editor || !pendingEdit || pendingEdit.status !== "pending") {
60
+ clearDecorations();
61
+ return;
62
+ }
63
+
64
+ const model = editor.getModel();
65
+ if (!model) return;
66
+
67
+ // Clear previous decorations first
68
+ clearDecorations();
69
+
70
+ // Compute diff between original and new code
71
+ const originalCode = pendingEdit.originalCode || "";
72
+ const newCode = pendingEdit.newCode || "";
73
+ const changes = Diff.diffLines(originalCode, newCode);
74
+
75
+ const decorations: DiffDecoration[] = [];
76
+
77
+ // Track line numbers in the new code (what's displayed)
78
+ let newLineNumber = 1;
79
+ // Track position for removed line indicators
80
+ let removedLinesBuffer: number[] = [];
81
+
82
+ // Calculate decorations based on diff
83
+ for (const change of changes) {
84
+ // Count actual lines (handle trailing newline edge case)
85
+ const lines = change.value.split('\n');
86
+ const lineCount = change.value.endsWith('\n') ? lines.length - 1 : lines.length;
87
+
88
+ if (change.added) {
89
+ // Added lines - green background (these lines exist in the editor)
90
+ for (let i = 0; i < lineCount; i++) {
91
+ const targetLine = newLineNumber + i;
92
+ if (targetLine <= model.getLineCount()) {
93
+ decorations.push({
94
+ range: new monaco.Range(targetLine, 1, targetLine, 1),
95
+ options: {
96
+ isWholeLine: true,
97
+ className: "monaco-diff-added",
98
+ glyphMarginClassName: "monaco-diff-added-glyph",
99
+ overviewRuler: {
100
+ color: "#22c55e",
101
+ position: monaco.editor.OverviewRulerLane.Left,
102
+ },
103
+ },
104
+ });
105
+ }
106
+ }
107
+ // Flush any pending removed lines indicator at this position
108
+ if (removedLinesBuffer.length > 0) {
109
+ const removedCount = removedLinesBuffer.reduce((a, b) => a + b, 0);
110
+ if (newLineNumber <= model.getLineCount()) {
111
+ decorations.push({
112
+ range: new monaco.Range(newLineNumber, 1, newLineNumber, 1),
113
+ options: {
114
+ isWholeLine: false,
115
+ glyphMarginClassName: "monaco-diff-removed-glyph",
116
+ overviewRuler: {
117
+ color: "#ef4444",
118
+ position: monaco.editor.OverviewRulerLane.Left,
119
+ },
120
+ },
121
+ });
122
+ }
123
+ removedLinesBuffer = [];
124
+ }
125
+ newLineNumber += lineCount;
126
+ } else if (change.removed) {
127
+ // Removed lines - track for indicator (these lines don't exist in editor)
128
+ removedLinesBuffer.push(lineCount);
129
+ } else {
130
+ // Unchanged lines - flush any removed indicator first
131
+ if (removedLinesBuffer.length > 0) {
132
+ const removedCount = removedLinesBuffer.reduce((a, b) => a + b, 0);
133
+ if (newLineNumber <= model.getLineCount()) {
134
+ decorations.push({
135
+ range: new monaco.Range(newLineNumber, 1, newLineNumber, 1),
136
+ options: {
137
+ isWholeLine: false,
138
+ glyphMarginClassName: "monaco-diff-removed-glyph",
139
+ overviewRuler: {
140
+ color: "#ef4444",
141
+ position: monaco.editor.OverviewRulerLane.Left,
142
+ },
143
+ },
144
+ });
145
+ }
146
+ removedLinesBuffer = [];
147
+ }
148
+ newLineNumber += lineCount;
149
+ }
150
+ }
151
+
152
+ // Handle any remaining removed lines at the end
153
+ if (removedLinesBuffer.length > 0) {
154
+ const lastLine = Math.min(newLineNumber, model.getLineCount());
155
+ if (lastLine >= 1) {
156
+ decorations.push({
157
+ range: new monaco.Range(lastLine, 1, lastLine, 1),
158
+ options: {
159
+ isWholeLine: false,
160
+ glyphMarginClassName: "monaco-diff-removed-glyph",
161
+ overviewRuler: {
162
+ color: "#ef4444",
163
+ position: monaco.editor.OverviewRulerLane.Left,
164
+ },
165
+ },
166
+ });
167
+ }
168
+ }
169
+
170
+ // Apply decorations
171
+ decorationsRef.current = editor.deltaDecorations(
172
+ [],
173
+ decorations.map((d) => ({
174
+ range: d.range,
175
+ options: d.options,
176
+ }))
177
+ );
178
+
179
+ // Add content widget for accept/reject buttons at the top of the editor
180
+ const widget: monaco.editor.IContentWidget = {
181
+ getId: () => `claude-diff-widget-${cellIndex}`,
182
+ getDomNode: () => {
183
+ const container = document.createElement("div");
184
+ container.className = "diff-widget-overlay";
185
+
186
+ const keepBtn = document.createElement("button");
187
+ keepBtn.className = "diff-widget-btn accept";
188
+ keepBtn.innerHTML = `<span>Keep</span> <kbd>Cmd+Y</kbd>`;
189
+ keepBtn.onclick = () => onApply(pendingEdit.id);
190
+
191
+ const undoBtn = document.createElement("button");
192
+ undoBtn.className = "diff-widget-btn reject";
193
+ undoBtn.innerHTML = `<span>Undo</span> <kbd>Cmd+N</kbd>`;
194
+ undoBtn.onclick = () => onReject(pendingEdit.id);
195
+
196
+ container.appendChild(keepBtn);
197
+ container.appendChild(undoBtn);
198
+
199
+ return container;
200
+ },
201
+ getPosition: () => ({
202
+ position: { lineNumber: 1, column: 1 },
203
+ preference: [
204
+ monaco.editor.ContentWidgetPositionPreference.ABOVE,
205
+ monaco.editor.ContentWidgetPositionPreference.BELOW,
206
+ ],
207
+ }),
208
+ };
209
+
210
+ editor.addContentWidget(widget);
211
+ widgetRef.current = widget;
212
+
213
+ // Add keyboard shortcuts
214
+ const acceptCommand = editor.addCommand(
215
+ monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyY,
216
+ () => onApply(pendingEdit.id)
217
+ );
218
+ const rejectCommand = editor.addCommand(
219
+ monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyN,
220
+ () => onReject(pendingEdit.id)
221
+ );
222
+
223
+ // Note: addCommand returns a command ID (string), not disposable
224
+ // We'll track via decoration cleanup instead
225
+ }, [editor, pendingEdit, cellIndex, onApply, onReject, clearDecorations]);
226
+
227
+ // Effect to apply/update decorations when edit changes
228
+ useEffect(() => {
229
+ applyDiffDecorations();
230
+
231
+ return () => {
232
+ clearDecorations();
233
+ };
234
+ }, [applyDiffDecorations, clearDecorations]);
235
+
236
+ // Return whether there's an active diff
237
+ return {
238
+ hasDiff: pendingEdit?.status === "pending",
239
+ clearDecorations,
240
+ };
241
+ }
242
+
243
+ /**
244
+ * Utility function to compute unified diff for display
245
+ */
246
+ export function computeUnifiedDiff(
247
+ originalCode: string,
248
+ newCode: string
249
+ ): { type: "added" | "removed" | "unchanged"; content: string }[] {
250
+ const changes = Diff.diffLines(originalCode, newCode);
251
+ const result: { type: "added" | "removed" | "unchanged"; content: string }[] =
252
+ [];
253
+
254
+ for (const change of changes) {
255
+ const lines = change.value.split("\n").filter((l) => l !== "");
256
+
257
+ for (const line of lines) {
258
+ if (change.added) {
259
+ result.push({ type: "added", content: line });
260
+ } else if (change.removed) {
261
+ result.push({ type: "removed", content: line });
262
+ } else {
263
+ result.push({ type: "unchanged", content: line });
264
+ }
265
+ }
266
+ }
267
+
268
+ return result;
269
+ }
frontend/lib/api.ts CHANGED
@@ -171,8 +171,13 @@ export interface GpuAvailabilityParams {
171
171
  regions?: string[];
172
172
  gpu_count?: number;
173
173
  gpu_type?: string;
174
- socket?: string;
175
- security?: string;
174
+ // RunPod specific
175
+ secure_cloud?: boolean;
176
+ community_cloud?: boolean;
177
+ // Vast.ai specific
178
+ verified?: boolean;
179
+ min_reliability?: number;
180
+ min_gpu_ram?: number;
176
181
  }
177
182
 
178
183
  export interface PodsListParams {
@@ -187,18 +192,26 @@ export interface GpuAvailability {
187
192
  socket: string;
188
193
  provider: string;
189
194
  gpuCount: number;
190
- gpuMemory: number;
191
- security: string;
192
- prices: {
195
+ gpuMemory?: number;
196
+ security?: string;
197
+ // Prime Intellect format
198
+ prices?: {
193
199
  onDemand: number;
194
200
  communityPrice: number | null;
195
201
  isVariable: boolean | null;
196
202
  currency: string;
197
203
  };
198
- images: string[];
204
+ // Lambda Labs / other providers format
205
+ priceHr?: number;
206
+ gpuName?: string;
207
+ regionDescription?: string;
208
+ vcpus?: number;
209
+ memoryGb?: number;
210
+ storageGb?: number;
211
+ images?: string[];
199
212
  region: string | null;
200
- dataCenter: string | null;
201
- country: string | null;
213
+ dataCenter?: string | null;
214
+ country?: string | null;
202
215
  disk?: {
203
216
  minCount: number | null;
204
217
  defaultCount: number | null;
@@ -299,11 +312,22 @@ export async function fetchGpuAvailability(
299
312
  if (params?.gpu_type) {
300
313
  queryParams.set("gpu_type", params.gpu_type);
301
314
  }
302
- if (params?.socket) {
303
- queryParams.set("socket", params.socket);
315
+ // RunPod specific
316
+ if (params?.secure_cloud !== undefined) {
317
+ queryParams.set("secure_cloud", String(params.secure_cloud));
318
+ }
319
+ if (params?.community_cloud !== undefined) {
320
+ queryParams.set("community_cloud", String(params.community_cloud));
321
+ }
322
+ // Vast.ai specific
323
+ if (params?.verified !== undefined) {
324
+ queryParams.set("verified", String(params.verified));
304
325
  }
305
- if (params?.security) {
306
- queryParams.set("security", params.security);
326
+ if (params?.min_reliability !== undefined) {
327
+ queryParams.set("min_reliability", String(params.min_reliability));
328
+ }
329
+ if (params?.min_gpu_ram !== undefined) {
330
+ queryParams.set("min_gpu_ram", String(params.min_gpu_ram));
307
331
  }
308
332
 
309
333
  const query = queryParams.toString();
@@ -471,3 +495,290 @@ export async function fixIndentation(code: string): Promise<string> {
471
495
  const data = await response.json();
472
496
  return data.fixed_code;
473
497
  }
498
+
499
+ // ============================================================================
500
+ // Multi-Provider GPU API Types & Functions
501
+ // ============================================================================
502
+
503
+ export interface ProviderInfo {
504
+ name: string;
505
+ display_name: string;
506
+ api_key_env_name: string;
507
+ supports_ssh: boolean;
508
+ dashboard_url: string;
509
+ configured: boolean;
510
+ is_active: boolean;
511
+ }
512
+
513
+ export interface ProvidersListResponse {
514
+ providers: ProviderInfo[];
515
+ active_provider: string | null;
516
+ }
517
+
518
+ export interface ProviderConfigRequest {
519
+ api_key: string;
520
+ make_active?: boolean;
521
+ }
522
+
523
+ export interface ProviderConfigResponse {
524
+ configured: boolean;
525
+ provider: string;
526
+ is_active: boolean;
527
+ }
528
+
529
+ export interface SetActiveProviderRequest {
530
+ provider: string;
531
+ }
532
+
533
+ export interface SetActiveProviderResponse {
534
+ active_provider: string;
535
+ success: boolean;
536
+ }
537
+
538
+ // Extended pod response with provider info
539
+ export interface PodResponseWithProvider extends PodResponse {
540
+ provider?: string;
541
+ }
542
+
543
+ // Extended connection status with provider info
544
+ export interface PodConnectionStatusWithProvider extends PodConnectionStatus {
545
+ provider?: string;
546
+ }
547
+
548
+ /**
549
+ * Fetch list of all available GPU providers with their configuration status.
550
+ */
551
+ export async function fetchGpuProviders(): Promise<ProvidersListResponse> {
552
+ const response = await fetch("/api/gpu/providers");
553
+ if (!response.ok) {
554
+ throw new Error(`Failed to fetch GPU providers: ${response.status}`);
555
+ }
556
+ return response.json();
557
+ }
558
+
559
+ /**
560
+ * Configure a GPU provider with an API key.
561
+ */
562
+ export async function configureGpuProvider(
563
+ providerName: string,
564
+ config: ProviderConfigRequest
565
+ ): Promise<ProviderConfigResponse> {
566
+ const response = await fetch(`/api/gpu/providers/${providerName}/config`, {
567
+ method: "POST",
568
+ headers: {
569
+ "Content-Type": "application/json",
570
+ },
571
+ body: JSON.stringify(config),
572
+ });
573
+
574
+ if (!response.ok) {
575
+ const errorText = await response.text();
576
+ throw new Error(
577
+ `Failed to configure provider: ${response.status} - ${errorText}`
578
+ );
579
+ }
580
+
581
+ return response.json();
582
+ }
583
+
584
+ /**
585
+ * Set the active GPU provider.
586
+ */
587
+ export async function setActiveGpuProvider(
588
+ providerName: string
589
+ ): Promise<SetActiveProviderResponse> {
590
+ const response = await fetch("/api/gpu/providers/active", {
591
+ method: "POST",
592
+ headers: {
593
+ "Content-Type": "application/json",
594
+ },
595
+ body: JSON.stringify({ provider: providerName }),
596
+ });
597
+
598
+ if (!response.ok) {
599
+ const errorText = await response.text();
600
+ throw new Error(
601
+ `Failed to set active provider: ${response.status} - ${errorText}`
602
+ );
603
+ }
604
+
605
+ return response.json();
606
+ }
607
+
608
+ /**
609
+ * Fetch GPU availability from a specific provider.
610
+ */
611
+ export async function fetchProviderGpuAvailability(
612
+ providerName: string,
613
+ params?: GpuAvailabilityParams
614
+ ): Promise<GpuAvailabilityResponse & { provider: string }> {
615
+ const queryParams = new URLSearchParams();
616
+
617
+ if (params?.regions) {
618
+ params.regions.forEach((region) => queryParams.append("regions", region));
619
+ }
620
+ if (params?.gpu_count !== undefined) {
621
+ queryParams.set("gpu_count", String(params.gpu_count));
622
+ }
623
+ if (params?.gpu_type) {
624
+ queryParams.set("gpu_type", params.gpu_type);
625
+ }
626
+ // RunPod specific
627
+ if (params?.secure_cloud !== undefined) {
628
+ queryParams.set("secure_cloud", String(params.secure_cloud));
629
+ }
630
+ if (params?.community_cloud !== undefined) {
631
+ queryParams.set("community_cloud", String(params.community_cloud));
632
+ }
633
+ // Vast.ai specific
634
+ if (params?.verified !== undefined) {
635
+ queryParams.set("verified", String(params.verified));
636
+ }
637
+ if (params?.min_reliability !== undefined) {
638
+ queryParams.set("min_reliability", String(params.min_reliability));
639
+ }
640
+ if (params?.min_gpu_ram !== undefined) {
641
+ queryParams.set("min_gpu_ram", String(params.min_gpu_ram));
642
+ }
643
+
644
+ const query = queryParams.toString();
645
+ const url = query
646
+ ? `/api/gpu/providers/${providerName}/availability?${query}`
647
+ : `/api/gpu/providers/${providerName}/availability`;
648
+ const response = await fetch(url);
649
+
650
+ if (!response.ok) {
651
+ throw new Error(
652
+ `Failed to fetch GPU availability from ${providerName}: ${response.status}`
653
+ );
654
+ }
655
+
656
+ return response.json();
657
+ }
658
+
659
+ /**
660
+ * Fetch pods from a specific provider.
661
+ */
662
+ export async function fetchProviderPods(
663
+ providerName: string,
664
+ params?: PodsListParams
665
+ ): Promise<PodsListResponse & { provider: string }> {
666
+ const queryParams = new URLSearchParams();
667
+
668
+ if (params?.status) {
669
+ queryParams.set("status", params.status);
670
+ }
671
+ if (params?.limit !== undefined) {
672
+ queryParams.set("limit", String(params.limit));
673
+ }
674
+ if (params?.offset !== undefined) {
675
+ queryParams.set("offset", String(params.offset));
676
+ }
677
+
678
+ const query = queryParams.toString();
679
+ const url = query
680
+ ? `/api/gpu/providers/${providerName}/pods?${query}`
681
+ : `/api/gpu/providers/${providerName}/pods`;
682
+ const response = await fetch(url);
683
+
684
+ if (!response.ok) {
685
+ throw new Error(
686
+ `Failed to fetch pods from ${providerName}: ${response.status}`
687
+ );
688
+ }
689
+
690
+ return response.json();
691
+ }
692
+
693
+ /**
694
+ * Create a pod with a specific provider.
695
+ */
696
+ export async function createProviderPod(
697
+ providerName: string,
698
+ podRequest: CreatePodRequest
699
+ ): Promise<PodResponseWithProvider> {
700
+ const response = await fetch(`/api/gpu/providers/${providerName}/pods`, {
701
+ method: "POST",
702
+ headers: {
703
+ "Content-Type": "application/json",
704
+ },
705
+ body: JSON.stringify(podRequest),
706
+ });
707
+
708
+ if (!response.ok) {
709
+ const errorText = await response.text();
710
+ throw new Error(
711
+ `Failed to create pod with ${providerName}: ${response.status} - ${errorText}`
712
+ );
713
+ }
714
+
715
+ return response.json();
716
+ }
717
+
718
+ /**
719
+ * Fetch a specific pod from a provider.
720
+ */
721
+ export async function fetchProviderPod(
722
+ providerName: string,
723
+ podId: string
724
+ ): Promise<PodResponseWithProvider> {
725
+ const response = await fetch(
726
+ `/api/gpu/providers/${providerName}/pods/${podId}`
727
+ );
728
+
729
+ if (!response.ok) {
730
+ throw new Error(
731
+ `Failed to fetch pod from ${providerName}: ${response.status}`
732
+ );
733
+ }
734
+
735
+ return response.json();
736
+ }
737
+
738
+ /**
739
+ * Delete a pod from a specific provider.
740
+ */
741
+ export async function deleteProviderPod(
742
+ providerName: string,
743
+ podId: string
744
+ ): Promise<DeletePodResponse & { provider: string }> {
745
+ const response = await fetch(
746
+ `/api/gpu/providers/${providerName}/pods/${podId}`,
747
+ {
748
+ method: "DELETE",
749
+ }
750
+ );
751
+
752
+ if (!response.ok) {
753
+ const errorText = await response.text();
754
+ throw new Error(
755
+ `Failed to delete pod from ${providerName}: ${response.status} - ${errorText}`
756
+ );
757
+ }
758
+
759
+ return response.json();
760
+ }
761
+
762
+ /**
763
+ * Connect to a pod from a specific provider.
764
+ */
765
+ export async function connectToProviderPod(
766
+ providerName: string,
767
+ podId: string
768
+ ): Promise<PodConnectionResponse & { provider: string }> {
769
+ const response = await fetch(
770
+ `/api/gpu/providers/${providerName}/pods/${podId}/connect`,
771
+ {
772
+ method: "POST",
773
+ }
774
+ );
775
+
776
+ if (!response.ok) {
777
+ const errorText = await response.text();
778
+ throw new Error(
779
+ `Failed to connect to pod: ${response.status} - ${errorText}`
780
+ );
781
+ }
782
+
783
+ return response.json();
784
+ }
frontend/lib/settings.ts CHANGED
@@ -111,11 +111,13 @@ export type MetricsCollectionMode = 'persistent' | 'on-demand';
111
111
  export type NotebookSettings = {
112
112
  theme: keyof typeof THEMES;
113
113
  metricsCollectionMode: MetricsCollectionMode;
114
+ claudeAutoPreview: boolean; // Whether to auto-preview Claude's code edits in cells
114
115
  };
115
116
 
116
117
  export const DEFAULT_SETTINGS: NotebookSettings = {
117
118
  theme: 'light',
118
119
  metricsCollectionMode: 'on-demand', // Only collect metrics when popup is open to save memory
120
+ claudeAutoPreview: false, // OFF by default - users must explicitly enable auto-preview
119
121
  };
120
122
 
121
123
  const STORAGE_KEY = 'morecompute-settings';
@@ -134,6 +136,9 @@ export function loadSettings(): NotebookSettings {
134
136
  metricsCollectionMode: parsed.metricsCollectionMode === 'on-demand' || parsed.metricsCollectionMode === 'persistent'
135
137
  ? parsed.metricsCollectionMode
136
138
  : DEFAULT_SETTINGS.metricsCollectionMode,
139
+ claudeAutoPreview: typeof parsed.claudeAutoPreview === 'boolean'
140
+ ? parsed.claudeAutoPreview
141
+ : DEFAULT_SETTINGS.claudeAutoPreview,
137
142
  };
138
143
 
139
144
  // Save cleaned settings back to localStorage
@@ -14,7 +14,6 @@ export class WebSocketService {
14
14
  this.ws = new WebSocket(this.url);
15
15
 
16
16
  this.ws.onopen = () => {
17
- console.log('Connected to server');
18
17
  this.reconnectAttempts = 0;
19
18
  this.emit('connect', null);
20
19
  resolve();
@@ -26,8 +25,7 @@ export class WebSocketService {
26
25
  reject(error);
27
26
  };
28
27
 
29
- this.ws.onclose = (ev) => {
30
- console.log('Disconnected from server', { code: ev.code, reason: ev.reason, wasClean: ev.wasClean });
28
+ this.ws.onclose = () => {
31
29
  this.emit('disconnect', null);
32
30
  this.handleDisconnect();
33
31
  };
@@ -35,10 +33,9 @@ export class WebSocketService {
35
33
  this.ws.onmessage = (event) => {
36
34
  try {
37
35
  const data = JSON.parse(event.data);
38
- console.log('[WS RECV]', data);
39
36
  this.handleMessage(data);
40
- } catch (error) {
41
- console.error('Failed to parse message:', error);
37
+ } catch {
38
+ // Failed to parse message
42
39
  }
43
40
  };
44
41
  } catch (error) {
@@ -115,9 +112,8 @@ export class WebSocketService {
115
112
  // Attempt to reconnect
116
113
  if (this.reconnectAttempts < this.maxReconnectAttempts) {
117
114
  this.reconnectAttempts++;
118
- console.log(`Attempting to reconnect (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`);
119
115
  setTimeout(() => {
120
- this.connect(this.url).catch(console.error);
116
+ this.connect(this.url).catch(() => {});
121
117
  }, 1000 * this.reconnectAttempts);
122
118
  }
123
119
  }