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
|
@@ -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/
|
|
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
|
|