more-compute 0.4.3__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 +739 -418
  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.3.dist-info → more_compute-0.5.0.dist-info}/METADATA +53 -11
  31. {more_compute-0.4.3.dist-info → more_compute-0.5.0.dist-info}/RECORD +56 -37
  32. {more_compute-0.4.3.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.3.dist-info/licenses → more_compute-0.5.0.dist-info}/LICENSE +0 -0
  56. {more_compute-0.4.3.dist-info → more_compute-0.5.0.dist-info}/entry_points.txt +0 -0
  57. {more_compute-0.4.3.dist-info → more_compute-0.5.0.dist-info}/top_level.txt +0 -0
@@ -47,7 +47,7 @@ const SettingsPopup: React.FC<{ onSettingsChange?: (settings: NotebookSettings)
47
47
  return (
48
48
  <div className="settings-container">
49
49
  <div style={{ fontSize: '12px', marginBottom: '8px', color: 'var(--mc-text-secondary, #6b7280)' }}>
50
- See all themes at <a href="https://github.com/DannyMang/more-compute/blob/main/frontend/styling_README.md" target="_blank" rel="noopener noreferrer" style={{ color: 'var(--mc-link-color, #3b82f6)', textDecoration: 'underline' }}>styling_README.md</a>
50
+ See all settings + possible themes at <a href="https://github.com/DannyMang/more-compute/blob/main/frontend/settings.md" target="_blank" rel="noopener noreferrer" style={{ color: 'var(--mc-link-color, #3b82f6)', textDecoration: 'underline' }}>settings.md</a>
51
51
  </div>
52
52
  <textarea
53
53
  className="settings-editor"
@@ -0,0 +1,392 @@
1
+ "use client";
2
+
3
+ import React, {
4
+ createContext,
5
+ useContext,
6
+ useState,
7
+ useCallback,
8
+ useRef,
9
+ useEffect,
10
+ } from "react";
11
+ import type {
12
+ ClaudeMessage,
13
+ ProposedEdit,
14
+ ClaudeState,
15
+ ClaudeActions,
16
+ ClaudeModel,
17
+ } from "@/types/claude";
18
+
19
+ interface ClaudeContextType extends ClaudeState, ClaudeActions {}
20
+
21
+ const ClaudeContext = createContext<ClaudeContextType | undefined>(undefined);
22
+
23
+ export const useClaude = () => {
24
+ const context = useContext(ClaudeContext);
25
+ if (!context) {
26
+ throw new Error("useClaude must be used within a ClaudeProvider");
27
+ }
28
+ return context;
29
+ };
30
+
31
+ interface ClaudeProviderProps {
32
+ children: React.ReactNode;
33
+ }
34
+
35
+ export const ClaudeProvider: React.FC<ClaudeProviderProps> = ({ children }) => {
36
+ const [messages, setMessages] = useState<ClaudeMessage[]>([]);
37
+ const [pendingEdits, setPendingEdits] = useState<Map<number, ProposedEdit>>(
38
+ new Map()
39
+ );
40
+ const [isPanelOpen, setIsPanelOpen] = useState(false);
41
+ const [isLoading, setIsLoading] = useState(false);
42
+ const [isConfigured, setIsConfigured] = useState(false);
43
+ const [error, setError] = useState<string | null>(null);
44
+ const [model, setModelState] = useState<ClaudeModel>("sonnet");
45
+
46
+ const wsRef = useRef<WebSocket | null>(null);
47
+ const currentMessageRef = useRef<string>("");
48
+ const currentMessageIdRef = useRef<string | null>(null);
49
+
50
+ // Check if Claude is configured on mount
51
+ useEffect(() => {
52
+ const checkConfig = async () => {
53
+ try {
54
+ const response = await fetch("/api/claude/config");
55
+ if (response.ok) {
56
+ const data = await response.json();
57
+ setIsConfigured(data.configured);
58
+ }
59
+ } catch (e) {
60
+ console.error("[Claude] Failed to check config:", e);
61
+ }
62
+ };
63
+ checkConfig();
64
+ }, []);
65
+
66
+ // Connect to WebSocket for Claude messages
67
+ useEffect(() => {
68
+ const connectWebSocket = () => {
69
+ const ws = new WebSocket("ws://127.0.0.1:3141/ws");
70
+
71
+ ws.onopen = () => {
72
+ // WebSocket connected
73
+ };
74
+
75
+ ws.onmessage = (event) => {
76
+ try {
77
+ const message = JSON.parse(event.data);
78
+ handleWebSocketMessage(message);
79
+ } catch (err) {
80
+ console.error("[Claude] Failed to parse message:", err);
81
+ }
82
+ };
83
+
84
+ ws.onerror = (error) => {
85
+ console.error("[Claude] WebSocket error:", error);
86
+ };
87
+
88
+ ws.onclose = () => {
89
+ wsRef.current = null;
90
+ // Reconnect after a delay
91
+ setTimeout(connectWebSocket, 2000);
92
+ };
93
+
94
+ wsRef.current = ws;
95
+ };
96
+
97
+ connectWebSocket();
98
+
99
+ return () => {
100
+ if (wsRef.current) {
101
+ wsRef.current.close();
102
+ }
103
+ };
104
+ }, []);
105
+
106
+ const handleWebSocketMessage = useCallback((message: any) => {
107
+ switch (message.type) {
108
+ case "claude_stream_start":
109
+ currentMessageIdRef.current = message.data.messageId;
110
+ currentMessageRef.current = "";
111
+ setIsLoading(true);
112
+ setError(null);
113
+
114
+ // Add a new assistant message that will be updated
115
+ setMessages((prev) => [
116
+ ...prev,
117
+ {
118
+ id: message.data.messageId,
119
+ role: "assistant",
120
+ content: "",
121
+ timestamp: Date.now(),
122
+ isStreaming: true,
123
+ },
124
+ ]);
125
+ break;
126
+
127
+ case "claude_stream_chunk":
128
+ if (message.data.messageId === currentMessageIdRef.current) {
129
+ currentMessageRef.current += message.data.chunk;
130
+
131
+ // Update the message with new content
132
+ setMessages((prev) =>
133
+ prev.map((msg) =>
134
+ msg.id === currentMessageIdRef.current
135
+ ? { ...msg, content: currentMessageRef.current }
136
+ : msg
137
+ )
138
+ );
139
+ }
140
+ break;
141
+
142
+ case "claude_stream_end":
143
+ if (message.data.messageId === currentMessageIdRef.current) {
144
+ const proposedEdits = message.data.proposedEdits || [];
145
+
146
+ // Update the message with final content and edits
147
+ setMessages((prev) =>
148
+ prev.map((msg) =>
149
+ msg.id === currentMessageIdRef.current
150
+ ? {
151
+ ...msg,
152
+ content: message.data.fullResponse,
153
+ isStreaming: false,
154
+ proposedEdits: proposedEdits.map((e: any) => ({
155
+ id: e.id,
156
+ cellIndex: e.cellIndex,
157
+ originalCode: e.originalCode,
158
+ newCode: e.newCode,
159
+ explanation: e.explanation,
160
+ status: "pending" as const,
161
+ })),
162
+ }
163
+ : msg
164
+ )
165
+ );
166
+
167
+ // Add pending edits to the map
168
+ const newPendingEdits = new Map<number, ProposedEdit>();
169
+ for (const edit of proposedEdits) {
170
+ newPendingEdits.set(edit.cellIndex, {
171
+ id: edit.id,
172
+ cellIndex: edit.cellIndex,
173
+ originalCode: edit.originalCode,
174
+ newCode: edit.newCode,
175
+ explanation: edit.explanation,
176
+ status: "pending",
177
+ });
178
+ }
179
+ setPendingEdits(newPendingEdits);
180
+
181
+ setIsLoading(false);
182
+ currentMessageIdRef.current = null;
183
+ }
184
+ break;
185
+
186
+ case "claude_error":
187
+ setError(message.data.error);
188
+ setIsLoading(false);
189
+ currentMessageIdRef.current = null;
190
+ break;
191
+
192
+ case "claude_edit_applied":
193
+ // Update the edit status
194
+ setPendingEdits((prev) => {
195
+ const next = new Map(prev);
196
+ const edit = Array.from(prev.values()).find(
197
+ (e) => e.id === message.data.editId
198
+ );
199
+ if (edit) {
200
+ next.delete(edit.cellIndex);
201
+ }
202
+ return next;
203
+ });
204
+
205
+ // Update message edit status
206
+ setMessages((prev) =>
207
+ prev.map((msg) => ({
208
+ ...msg,
209
+ proposedEdits: msg.proposedEdits?.map((e) =>
210
+ e.id === message.data.editId ? { ...e, status: "applied" as const } : e
211
+ ),
212
+ }))
213
+ );
214
+ break;
215
+
216
+ case "claude_edit_rejected":
217
+ // Update the edit status
218
+ setPendingEdits((prev) => {
219
+ const next = new Map(prev);
220
+ const edit = Array.from(prev.values()).find(
221
+ (e) => e.id === message.data.editId
222
+ );
223
+ if (edit) {
224
+ next.delete(edit.cellIndex);
225
+ }
226
+ return next;
227
+ });
228
+
229
+ // Update message edit status
230
+ setMessages((prev) =>
231
+ prev.map((msg) => ({
232
+ ...msg,
233
+ proposedEdits: msg.proposedEdits?.map((e) =>
234
+ e.id === message.data.editId ? { ...e, status: "rejected" as const } : e
235
+ ),
236
+ }))
237
+ );
238
+ break;
239
+ }
240
+ }, []);
241
+
242
+ const sendMessage = useCallback(
243
+ (message: string) => {
244
+ if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
245
+ setError("Not connected to server");
246
+ return;
247
+ }
248
+
249
+ if (!message.trim()) {
250
+ return;
251
+ }
252
+
253
+ // Add user message to history
254
+ const userMessage: ClaudeMessage = {
255
+ id: `user-${Date.now()}`,
256
+ role: "user",
257
+ content: message,
258
+ timestamp: Date.now(),
259
+ };
260
+ setMessages((prev) => [...prev, userMessage]);
261
+
262
+ // Build history for context
263
+ const history = messages.map((msg) => ({
264
+ role: msg.role,
265
+ content: msg.content,
266
+ }));
267
+
268
+ // Send to backend
269
+ wsRef.current.send(
270
+ JSON.stringify({
271
+ type: "claude_message",
272
+ data: {
273
+ message,
274
+ history,
275
+ model,
276
+ },
277
+ })
278
+ );
279
+ },
280
+ [messages, model]
281
+ );
282
+
283
+ const applyEdit = useCallback((editId: string) => {
284
+ if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
285
+ setError("Not connected to server");
286
+ return;
287
+ }
288
+
289
+ // Find the edit
290
+ const edit = Array.from(pendingEdits.values()).find((e) => e.id === editId);
291
+ if (!edit) {
292
+ return;
293
+ }
294
+
295
+ wsRef.current.send(
296
+ JSON.stringify({
297
+ type: "claude_apply_edit",
298
+ data: {
299
+ editId,
300
+ cellIndex: edit.cellIndex,
301
+ newCode: edit.newCode,
302
+ },
303
+ })
304
+ );
305
+ }, [pendingEdits]);
306
+
307
+ const rejectEdit = useCallback((editId: string) => {
308
+ if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
309
+ setError("Not connected to server");
310
+ return;
311
+ }
312
+
313
+ wsRef.current.send(
314
+ JSON.stringify({
315
+ type: "claude_reject_edit",
316
+ data: {
317
+ editId,
318
+ },
319
+ })
320
+ );
321
+ }, []);
322
+
323
+ const togglePanel = useCallback(() => {
324
+ setIsPanelOpen((prev) => !prev);
325
+ }, []);
326
+
327
+ const openPanel = useCallback(() => {
328
+ setIsPanelOpen(true);
329
+ }, []);
330
+
331
+ const closePanel = useCallback(() => {
332
+ setIsPanelOpen(false);
333
+ }, []);
334
+
335
+ const clearHistory = useCallback(() => {
336
+ setMessages([]);
337
+ setPendingEdits(new Map());
338
+ setError(null);
339
+ }, []);
340
+
341
+ const setModel = useCallback((newModel: ClaudeModel) => {
342
+ setModelState(newModel);
343
+ }, []);
344
+
345
+ const setApiKey = useCallback(async (apiKey: string): Promise<boolean> => {
346
+ try {
347
+ const response = await fetch("/api/claude/config", {
348
+ method: "POST",
349
+ headers: {
350
+ "Content-Type": "application/json",
351
+ },
352
+ body: JSON.stringify({ api_key: apiKey }),
353
+ });
354
+
355
+ if (response.ok) {
356
+ setIsConfigured(true);
357
+ setError(null);
358
+ return true;
359
+ } else {
360
+ const data = await response.json();
361
+ setError(data.detail || "Failed to save API key");
362
+ return false;
363
+ }
364
+ } catch (e) {
365
+ setError("Failed to connect to server");
366
+ return false;
367
+ }
368
+ }, []);
369
+
370
+ const value: ClaudeContextType = {
371
+ messages,
372
+ pendingEdits,
373
+ isPanelOpen,
374
+ isLoading,
375
+ isConfigured,
376
+ error,
377
+ model,
378
+ sendMessage,
379
+ applyEdit,
380
+ rejectEdit,
381
+ togglePanel,
382
+ openPanel,
383
+ closePanel,
384
+ clearHistory,
385
+ setApiKey,
386
+ setModel,
387
+ };
388
+
389
+ return (
390
+ <ClaudeContext.Provider value={value}>{children}</ClaudeContext.Provider>
391
+ );
392
+ };
@@ -11,6 +11,8 @@ export interface GPUPod {
11
11
  region: string;
12
12
  costPerHour: number;
13
13
  sshConnection: string | null;
14
+ provider?: string; // Provider name (e.g., "runpod", "lambda_labs", "prime_intellect")
15
+ gpuCount?: number; // Number of GPUs
14
16
  }
15
17
 
16
18
  export type ConnectionState = 'provisioning' | 'deploying' | 'connected' | null;
@@ -99,7 +101,6 @@ export const PodWebSocketProvider: React.FC<PodWebSocketProviderProps> = ({ chil
99
101
  const ws = new WebSocket(wsUrl);
100
102
 
101
103
  ws.onopen = () => {
102
- console.log('[PodWebSocket] Connected');
103
104
  setIsConnected(true);
104
105
  reconnectAttemptsRef.current = 0;
105
106
  };
@@ -116,10 +117,19 @@ export const PodWebSocketProvider: React.FC<PodWebSocketProviderProps> = ({ chil
116
117
 
117
118
  if (isFullyReady) {
118
119
  uiStatus = "running";
119
- } else if (podData.status === "ACTIVE" || podData.status === "PROVISIONING" || podData.status === "PENDING") {
120
+ } else if (podData.status === "ACTIVE" || podData.status === "PROVISIONING" || podData.status === "PENDING" || podData.status === "STARTING") {
120
121
  uiStatus = "starting";
121
122
  }
122
123
 
124
+ // Check for auto-connect callback - do this OUTSIDE of setGpuPods to avoid closure issues
125
+ const callback = autoConnectCallbacksRef.current.get(podData.pod_id);
126
+ if (callback && isFullyReady) {
127
+ autoConnectCallbacksRef.current.delete(podData.pod_id);
128
+ setTimeout(() => {
129
+ callback(podData.pod_id);
130
+ }, 5000);
131
+ }
132
+
123
133
  // Update pod in the list
124
134
  setGpuPods((prevPods) => {
125
135
  const existingPodIndex = prevPods.findIndex((p) => p.id === podData.pod_id);
@@ -127,7 +137,6 @@ export const PodWebSocketProvider: React.FC<PodWebSocketProviderProps> = ({ chil
127
137
  if (existingPodIndex >= 0) {
128
138
  // Update existing pod
129
139
  const updatedPods = [...prevPods];
130
- const wasStarting = prevPods[existingPodIndex].status === "starting";
131
140
 
132
141
  updatedPods[existingPodIndex] = {
133
142
  id: podData.pod_id,
@@ -137,22 +146,10 @@ export const PodWebSocketProvider: React.FC<PodWebSocketProviderProps> = ({ chil
137
146
  region: prevPods[existingPodIndex].region,
138
147
  costPerHour: podData.price_hr,
139
148
  sshConnection: podData.ssh_connection || null,
149
+ provider: podData.provider || prevPods[existingPodIndex].provider,
150
+ gpuCount: podData.gpu_count || prevPods[existingPodIndex].gpuCount,
140
151
  };
141
152
 
142
- // Trigger auto-connect callback if pod just became ready
143
- // Small delay to ensure SSH is accepting connections
144
- if (wasStarting && isFullyReady) {
145
- console.log(`[PodWebSocket] Pod ${podData.pod_id} is ready, scheduling auto-connect in 5s`);
146
- const callback = autoConnectCallbacksRef.current.get(podData.pod_id);
147
- if (callback) {
148
- setTimeout(() => {
149
- console.log(`[PodWebSocket] Triggering auto-connect for pod ${podData.pod_id}`);
150
- callback(podData.pod_id);
151
- }, 5000); // Short delay, backend now handles retries
152
- autoConnectCallbacksRef.current.delete(podData.pod_id);
153
- }
154
- }
155
-
156
153
  return updatedPods;
157
154
  } else {
158
155
  // Add new pod if not in list
@@ -166,6 +163,8 @@ export const PodWebSocketProvider: React.FC<PodWebSocketProviderProps> = ({ chil
166
163
  region: "Unknown",
167
164
  costPerHour: podData.price_hr,
168
165
  sshConnection: podData.ssh_connection || null,
166
+ provider: podData.provider,
167
+ gpuCount: podData.gpu_count,
169
168
  },
170
169
  ];
171
170
  }
@@ -182,21 +181,17 @@ export const PodWebSocketProvider: React.FC<PodWebSocketProviderProps> = ({ chil
182
181
  };
183
182
 
184
183
  ws.onclose = () => {
185
- console.log('[PodWebSocket] Disconnected');
186
184
  setIsConnected(false);
187
185
  wsRef.current = null;
188
186
 
189
187
  // Attempt to reconnect with exponential backoff
190
188
  if (reconnectAttemptsRef.current < maxReconnectAttempts) {
191
189
  const delay = Math.min(1000 * Math.pow(2, reconnectAttemptsRef.current), 30000);
192
- console.log(`[PodWebSocket] Reconnecting in ${delay}ms (attempt ${reconnectAttemptsRef.current + 1}/${maxReconnectAttempts})`);
193
190
 
194
191
  reconnectTimeoutRef.current = setTimeout(() => {
195
192
  reconnectAttemptsRef.current++;
196
193
  connectWebSocket();
197
194
  }, delay);
198
- } else {
199
- console.log('[PodWebSocket] Max reconnect attempts reached');
200
195
  }
201
196
  };
202
197