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.
- frontend/app/globals.css +734 -27
- frontend/app/layout.tsx +13 -3
- frontend/components/Notebook.tsx +2 -14
- frontend/components/cell/MonacoCell.tsx +99 -5
- frontend/components/layout/Sidebar.tsx +39 -4
- frontend/components/panels/ClaudePanel.tsx +461 -0
- frontend/components/popups/ComputePopup.tsx +738 -447
- frontend/components/popups/FilterPopup.tsx +305 -189
- frontend/components/popups/MetricsPopup.tsx +20 -1
- frontend/components/popups/ProviderConfigModal.tsx +322 -0
- frontend/components/popups/ProviderDropdown.tsx +398 -0
- frontend/components/popups/SettingsPopup.tsx +1 -1
- frontend/contexts/ClaudeContext.tsx +392 -0
- frontend/contexts/PodWebSocketContext.tsx +16 -21
- frontend/hooks/useInlineDiff.ts +269 -0
- frontend/lib/api.ts +323 -12
- frontend/lib/settings.ts +5 -0
- frontend/lib/websocket-native.ts +4 -8
- frontend/lib/websocket.ts +1 -2
- frontend/package-lock.json +733 -36
- frontend/package.json +2 -0
- frontend/public/assets/icons/providers/lambda_labs.svg +22 -0
- frontend/public/assets/icons/providers/prime_intellect.svg +18 -0
- frontend/public/assets/icons/providers/runpod.svg +9 -0
- frontend/public/assets/icons/providers/vastai.svg +1 -0
- frontend/settings.md +54 -0
- frontend/tsconfig.tsbuildinfo +1 -0
- frontend/types/claude.ts +194 -0
- kernel_run.py +13 -0
- {more_compute-0.4.4.dist-info → more_compute-0.5.0.dist-info}/METADATA +53 -11
- {more_compute-0.4.4.dist-info → more_compute-0.5.0.dist-info}/RECORD +56 -37
- {more_compute-0.4.4.dist-info → more_compute-0.5.0.dist-info}/WHEEL +1 -1
- morecompute/__init__.py +1 -1
- morecompute/__version__.py +1 -1
- morecompute/execution/executor.py +24 -67
- morecompute/execution/worker.py +6 -72
- morecompute/models/api_models.py +62 -0
- morecompute/notebook.py +11 -0
- morecompute/server.py +641 -133
- morecompute/services/claude_service.py +392 -0
- morecompute/services/pod_manager.py +168 -67
- morecompute/services/pod_monitor.py +67 -39
- morecompute/services/prime_intellect.py +0 -4
- morecompute/services/providers/__init__.py +92 -0
- morecompute/services/providers/base_provider.py +336 -0
- morecompute/services/providers/lambda_labs_provider.py +394 -0
- morecompute/services/providers/provider_factory.py +194 -0
- morecompute/services/providers/runpod_provider.py +504 -0
- morecompute/services/providers/vastai_provider.py +407 -0
- morecompute/utils/cell_magics.py +0 -3
- morecompute/utils/config_util.py +93 -3
- morecompute/utils/special_commands.py +5 -32
- morecompute/utils/version_check.py +117 -0
- frontend/styling_README.md +0 -23
- {more_compute-0.4.4.dist-info/licenses → more_compute-0.5.0.dist-info}/LICENSE +0 -0
- {more_compute-0.4.4.dist-info → more_compute-0.5.0.dist-info}/entry_points.txt +0 -0
- {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
|
-
|
|
175
|
-
|
|
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
|
|
191
|
-
security
|
|
192
|
-
|
|
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
|
-
|
|
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
|
|
201
|
-
country
|
|
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
|
-
|
|
303
|
-
|
|
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?.
|
|
306
|
-
queryParams.set("
|
|
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
|
frontend/lib/websocket-native.ts
CHANGED
|
@@ -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 = (
|
|
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
|
|
41
|
-
|
|
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(
|
|
116
|
+
this.connect(this.url).catch(() => {});
|
|
121
117
|
}, 1000 * this.reconnectAttempts);
|
|
122
118
|
}
|
|
123
119
|
}
|