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
@@ -0,0 +1,461 @@
1
+ "use client";
2
+
3
+ import React, { useState, useRef, useEffect } from "react";
4
+ import { useClaude } from "@/contexts/ClaudeContext";
5
+ import {
6
+ X,
7
+ Send,
8
+ Trash2,
9
+ Sparkles,
10
+ Check,
11
+ XCircle,
12
+ Key,
13
+ Copy,
14
+ CheckCheck,
15
+ } from "lucide-react";
16
+ import type { ClaudeMessage, ProposedEdit, ClaudeModel } from "@/types/claude";
17
+
18
+ // Simple markdown renderer for Claude messages
19
+ const renderMarkdown = (text: string): string => {
20
+ let html = text;
21
+
22
+ // Escape HTML first (but preserve our markdown)
23
+ const escapeHtml = (str: string) => {
24
+ return str
25
+ .replace(/&/g, "&")
26
+ .replace(/</g, "&lt;")
27
+ .replace(/>/g, "&gt;");
28
+ };
29
+
30
+ // Extract and preserve code blocks first
31
+ const codeBlocks: string[] = [];
32
+ // Pattern matches ```language\n or ```edit:N\n (any non-newline chars for identifier)
33
+ html = html.replace(/```([^\n]*)\n([\s\S]*?)```/g, (_, lang, code) => {
34
+ const index = codeBlocks.length;
35
+ const trimmedLang = lang.trim();
36
+ const langClass = trimmedLang
37
+ ? ` class="language-${trimmedLang.replace(/[^a-zA-Z0-9_-]/g, "-")}"`
38
+ : "";
39
+ // Display "edit" for edit blocks, otherwise show the language
40
+ const displayLang = trimmedLang.startsWith("edit:")
41
+ ? `edit (cell ${trimmedLang.split(":")[1]})`
42
+ : trimmedLang || "code";
43
+ codeBlocks.push(
44
+ `<div class="claude-code-block"><div class="claude-code-header"><span class="claude-code-lang">${escapeHtml(displayLang)}</span></div><pre><code${langClass}>${escapeHtml(code.trim())}</code></pre></div>`,
45
+ );
46
+ return `__CODE_BLOCK_${index}__`;
47
+ });
48
+
49
+ // Simple code blocks without language
50
+ html = html.replace(/```([\s\S]*?)```/g, (_, code) => {
51
+ const index = codeBlocks.length;
52
+ codeBlocks.push(
53
+ `<div class="claude-code-block"><pre><code>${escapeHtml(code.trim())}</code></pre></div>`,
54
+ );
55
+ return `__CODE_BLOCK_${index}__`;
56
+ });
57
+
58
+ // Headers
59
+ html = html.replace(/^### (.*$)/gim, "<h3>$1</h3>");
60
+ html = html.replace(/^## (.*$)/gim, "<h2>$1</h2>");
61
+ html = html.replace(/^# (.*$)/gim, "<h1>$1</h1>");
62
+
63
+ // Bold and Italic
64
+ html = html.replace(/\*\*\*([^*]+)\*\*\*/g, "<strong><em>$1</em></strong>");
65
+ html = html.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
66
+ html = html.replace(/\*([^*]+)\*/g, "<em>$1</em>");
67
+
68
+ // Inline code
69
+ html = html.replace(
70
+ /`([^`]+)`/g,
71
+ '<code class="claude-inline-code">$1</code>',
72
+ );
73
+
74
+ // Links
75
+ html = html.replace(
76
+ /\[([^\]]+)\]\(([^)]+)\)/g,
77
+ '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>',
78
+ );
79
+
80
+ // Lists - unordered
81
+ html = html.replace(/^[\s]*[-*] (.+)$/gim, "<li>$1</li>");
82
+
83
+ // Lists - ordered
84
+ html = html.replace(/^[\s]*\d+\. (.+)$/gim, "<li>$1</li>");
85
+
86
+ // Wrap consecutive li elements in ul/ol
87
+ html = html.replace(/(<li>[\s\S]*?<\/li>)+/g, (match) => `<ul>${match}</ul>`);
88
+
89
+ // Line breaks - double newline = paragraph, single = br
90
+ html = html.replace(/\n\n+/g, "</p><p>");
91
+ html = html.replace(/\n/g, "<br>");
92
+
93
+ // Wrap in paragraph if needed
94
+ if (!html.startsWith("<") && html.trim()) {
95
+ html = "<p>" + html + "</p>";
96
+ }
97
+
98
+ // Restore code blocks
99
+ codeBlocks.forEach((block, index) => {
100
+ html = html.replace(`__CODE_BLOCK_${index}__`, block);
101
+ });
102
+
103
+ // Clean up empty paragraphs
104
+ html = html.replace(/<p>\s*<\/p>/g, "");
105
+ html = html.replace(/<p><br><\/p>/g, "");
106
+
107
+ return html;
108
+ };
109
+
110
+ // Model selector component
111
+ interface ModelSelectorProps {
112
+ model: ClaudeModel;
113
+ onChange: (model: ClaudeModel) => void;
114
+ }
115
+
116
+ const MODEL_OPTIONS: { value: ClaudeModel; label: string }[] = [
117
+ { value: "haiku", label: "Haiku 4" },
118
+ { value: "sonnet", label: "Sonnet 4" },
119
+ { value: "opus", label: "Opus 4.5" },
120
+ ];
121
+
122
+ const ModelSelector: React.FC<ModelSelectorProps> = ({ model, onChange }) => {
123
+ return (
124
+ <div className="claude-model-selector">
125
+ {MODEL_OPTIONS.map((option) => (
126
+ <button
127
+ key={option.value}
128
+ className={`claude-model-option ${model === option.value ? "active" : ""}`}
129
+ onClick={() => onChange(option.value)}
130
+ title={
131
+ option.value === "haiku"
132
+ ? "Fast & cheap"
133
+ : option.value === "opus"
134
+ ? "Most capable"
135
+ : "Balanced"
136
+ }
137
+ >
138
+ {option.label}
139
+ </button>
140
+ ))}
141
+ </div>
142
+ );
143
+ };
144
+
145
+ const ClaudePanel: React.FC = () => {
146
+ const {
147
+ messages,
148
+ isPanelOpen,
149
+ isLoading,
150
+ isConfigured,
151
+ error,
152
+ model,
153
+ sendMessage,
154
+ applyEdit,
155
+ rejectEdit,
156
+ closePanel,
157
+ clearHistory,
158
+ setApiKey,
159
+ setModel,
160
+ } = useClaude();
161
+
162
+ const [inputValue, setInputValue] = useState("");
163
+ const [apiKeyInput, setApiKeyInput] = useState("");
164
+ const [isSettingKey, setIsSettingKey] = useState(false);
165
+ const messagesEndRef = useRef<HTMLDivElement>(null);
166
+ const inputRef = useRef<HTMLTextAreaElement>(null);
167
+
168
+ // Auto-scroll to bottom when new messages arrive
169
+ useEffect(() => {
170
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
171
+ }, [messages]);
172
+
173
+ // Focus input when panel opens
174
+ useEffect(() => {
175
+ if (isPanelOpen && inputRef.current) {
176
+ inputRef.current.focus();
177
+ }
178
+ }, [isPanelOpen]);
179
+
180
+ const handleSend = () => {
181
+ if (!inputValue.trim() || isLoading) return;
182
+ sendMessage(inputValue);
183
+ setInputValue("");
184
+ };
185
+
186
+ const handleKeyDown = (e: React.KeyboardEvent) => {
187
+ if (e.key === "Enter" && !e.shiftKey) {
188
+ e.preventDefault();
189
+ handleSend();
190
+ }
191
+ };
192
+
193
+ const handleSetApiKey = async () => {
194
+ if (!apiKeyInput.trim()) return;
195
+ setIsSettingKey(true);
196
+ const success = await setApiKey(apiKeyInput);
197
+ if (success) {
198
+ setApiKeyInput("");
199
+ }
200
+ setIsSettingKey(false);
201
+ };
202
+
203
+ if (!isPanelOpen) return null;
204
+
205
+ return (
206
+ <div className="claude-panel">
207
+ {/* Header */}
208
+ <div className="claude-panel-header">
209
+ <div className="claude-panel-title">
210
+ <Sparkles size={16} />
211
+ <span>Claude</span>
212
+ </div>
213
+ <div className="claude-panel-actions">
214
+ <button
215
+ className="claude-panel-btn"
216
+ onClick={clearHistory}
217
+ title="Clear history"
218
+ >
219
+ <Trash2 size={14} />
220
+ </button>
221
+ <button
222
+ className="claude-panel-btn"
223
+ onClick={closePanel}
224
+ title="Close"
225
+ >
226
+ <X size={14} />
227
+ </button>
228
+ </div>
229
+ </div>
230
+
231
+ {/* Error Banner */}
232
+ {error && (
233
+ <div className="claude-error-banner">
234
+ <XCircle size={14} />
235
+ <span>{error}</span>
236
+ </div>
237
+ )}
238
+
239
+ {/* Messages */}
240
+ <div className="claude-messages">
241
+ {messages.length === 0 ? (
242
+ <div className="claude-empty-state">
243
+ <Sparkles size={24} />
244
+ <p>Ask Claude to help with your code</p>
245
+ <p className="claude-empty-hint">this is a work in progress</p>
246
+ </div>
247
+ ) : (
248
+ messages.map((message) => (
249
+ <MessageBubble
250
+ key={message.id}
251
+ message={message}
252
+ onApplyEdit={applyEdit}
253
+ onRejectEdit={rejectEdit}
254
+ />
255
+ ))
256
+ )}
257
+ <div ref={messagesEndRef} />
258
+ </div>
259
+
260
+ {/* API Key Config */}
261
+ {!isConfigured && (
262
+ <div className="claude-config-banner">
263
+ <div className="claude-config-title">
264
+ <Key size={14} />
265
+ <span>Configure your Claude API key to get started</span>
266
+ </div>
267
+ <div className="claude-config-form">
268
+ <input
269
+ type="password"
270
+ value={apiKeyInput}
271
+ onChange={(e) => setApiKeyInput(e.target.value)}
272
+ placeholder="sk-ant-..."
273
+ className="claude-config-input"
274
+ onKeyDown={(e) => e.key === "Enter" && handleSetApiKey()}
275
+ />
276
+ <button
277
+ onClick={handleSetApiKey}
278
+ disabled={isSettingKey || !apiKeyInput.trim()}
279
+ className="claude-config-btn"
280
+ >
281
+ {isSettingKey ? "Saving..." : "Save"}
282
+ </button>
283
+ </div>
284
+ </div>
285
+ )}
286
+
287
+ {/* Input */}
288
+ <div className="claude-input-container">
289
+ <textarea
290
+ ref={inputRef}
291
+ value={inputValue}
292
+ onChange={(e) => setInputValue(e.target.value)}
293
+ onKeyDown={handleKeyDown}
294
+ placeholder={
295
+ isConfigured ? "Ask Claude..." : "Configure API key above"
296
+ }
297
+ disabled={!isConfigured || isLoading}
298
+ className="claude-input"
299
+ rows={1}
300
+ />
301
+ <button
302
+ onClick={handleSend}
303
+ disabled={!isConfigured || isLoading || !inputValue.trim()}
304
+ className="claude-send-btn"
305
+ >
306
+ {isLoading ? (
307
+ <div className="claude-loading-spinner" />
308
+ ) : (
309
+ <Send size={16} />
310
+ )}
311
+ </button>
312
+ </div>
313
+
314
+ {/* Model Selector */}
315
+ <div className="claude-model-row">
316
+ <span className="claude-model-label">Model:</span>
317
+ <ModelSelector model={model} onChange={setModel} />
318
+ </div>
319
+ </div>
320
+ );
321
+ };
322
+
323
+ interface MessageBubbleProps {
324
+ message: ClaudeMessage;
325
+ onApplyEdit: (editId: string) => void;
326
+ onRejectEdit: (editId: string) => void;
327
+ }
328
+
329
+ const MessageBubble: React.FC<MessageBubbleProps> = ({
330
+ message,
331
+ onApplyEdit,
332
+ onRejectEdit,
333
+ }) => {
334
+ const isUser = message.role === "user";
335
+
336
+ // Format the message content, removing edit blocks for display
337
+ const formatContent = (content: string) => {
338
+ // Remove edit blocks from display
339
+ return content.replace(/```edit:\d+\n[\s\S]*?```/g, "").trim();
340
+ };
341
+
342
+ const formattedContent = formatContent(message.content);
343
+
344
+ return (
345
+ <div className={`claude-message ${isUser ? "user" : "assistant"}`}>
346
+ <div className="claude-message-content">
347
+ {message.isStreaming ? (
348
+ <div className="claude-streaming">
349
+ <div
350
+ className="claude-message-text"
351
+ dangerouslySetInnerHTML={{
352
+ __html: renderMarkdown(formattedContent) || "Thinking...",
353
+ }}
354
+ />
355
+ <span className="claude-cursor">|</span>
356
+ </div>
357
+ ) : (
358
+ <>
359
+ {isUser ? (
360
+ <div className="claude-message-text">{formattedContent}</div>
361
+ ) : (
362
+ <div
363
+ className="claude-message-text claude-markdown"
364
+ dangerouslySetInnerHTML={{
365
+ __html: renderMarkdown(formattedContent),
366
+ }}
367
+ />
368
+ )}
369
+ {/* Proposed Edits */}
370
+ {message.proposedEdits && message.proposedEdits.length > 0 && (
371
+ <div className="claude-edits">
372
+ {message.proposedEdits.map((edit) => (
373
+ <EditCard
374
+ key={edit.id}
375
+ edit={edit}
376
+ onApply={() => onApplyEdit(edit.id)}
377
+ onReject={() => onRejectEdit(edit.id)}
378
+ />
379
+ ))}
380
+ </div>
381
+ )}
382
+ </>
383
+ )}
384
+ </div>
385
+ </div>
386
+ );
387
+ };
388
+
389
+ interface EditCardProps {
390
+ edit: ProposedEdit;
391
+ onApply: () => void;
392
+ onReject: () => void;
393
+ }
394
+
395
+ const EditCard: React.FC<EditCardProps> = ({ edit, onApply, onReject }) => {
396
+ const [copied, setCopied] = useState(false);
397
+ const isPending = edit.status === "pending";
398
+ const isApplied = edit.status === "applied";
399
+ const isRejected = edit.status === "rejected";
400
+
401
+ const handleCopy = async () => {
402
+ await navigator.clipboard.writeText(edit.newCode);
403
+ setCopied(true);
404
+ setTimeout(() => setCopied(false), 2000);
405
+ };
406
+
407
+ return (
408
+ <div
409
+ className={`claude-edit-card ${isApplied ? "applied" : ""} ${isRejected ? "rejected" : ""}`}
410
+ >
411
+ <div className="claude-edit-header">
412
+ <span className="claude-edit-cell">Cell {edit.cellIndex}</span>
413
+ <div className="claude-edit-actions">
414
+ <button
415
+ className="claude-edit-btn copy"
416
+ onClick={handleCopy}
417
+ title="Copy code"
418
+ >
419
+ {copied ? <CheckCheck size={12} /> : <Copy size={12} />}
420
+ </button>
421
+ {isPending && (
422
+ <>
423
+ <button
424
+ className="claude-edit-btn accept"
425
+ onClick={onApply}
426
+ title="Apply edit"
427
+ >
428
+ <Check size={12} />
429
+ Apply
430
+ </button>
431
+ <button
432
+ className="claude-edit-btn reject"
433
+ onClick={onReject}
434
+ title="Reject edit"
435
+ >
436
+ <X size={12} />
437
+ Reject
438
+ </button>
439
+ </>
440
+ )}
441
+ </div>
442
+ {isApplied && (
443
+ <span className="claude-edit-status applied">Applied</span>
444
+ )}
445
+ {isRejected && (
446
+ <span className="claude-edit-status rejected">Rejected</span>
447
+ )}
448
+ </div>
449
+ {edit.explanation && (
450
+ <div className="claude-edit-explanation">{edit.explanation}</div>
451
+ )}
452
+ <div className="claude-edit-preview">
453
+ <pre className="claude-edit-code">
454
+ <code>{edit.newCode}</code>
455
+ </pre>
456
+ </div>
457
+ </div>
458
+ );
459
+ };
460
+
461
+ export default ClaudePanel;