more-compute 0.2.6__py3-none-any.whl → 0.3.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 +38 -133
- frontend/app/layout.tsx +54 -5
- frontend/components/Notebook.tsx +9 -1
- frontend/components/cell/CellButton.tsx +2 -2
- frontend/components/cell/MonacoCell.tsx +1 -15
- frontend/components/output/CellOutput.tsx +77 -17
- frontend/components/output/ErrorDisplay.tsx +3 -28
- frontend/components/popups/MetricsPopup.tsx +42 -7
- frontend/components/popups/PackagesPopup.tsx +2 -1
- frontend/lib/api.ts +6 -2
- frontend/lib/settings.ts +7 -0
- frontend/lib/websocket-native.ts +3 -0
- frontend/styling_README.md +15 -2
- {more_compute-0.2.6.dist-info → more_compute-0.3.0.dist-info}/METADATA +1 -1
- {more_compute-0.2.6.dist-info → more_compute-0.3.0.dist-info}/RECORD +27 -25
- morecompute/__version__.py +1 -1
- morecompute/execution/executor.py +12 -5
- morecompute/execution/worker.py +93 -1
- morecompute/server.py +4 -0
- morecompute/utils/cell_magics.py +713 -0
- morecompute/utils/line_magics.py +949 -0
- morecompute/utils/shell_utils.py +68 -0
- morecompute/utils/special_commands.py +106 -173
- frontend/components/Cell.tsx +0 -383
- {more_compute-0.2.6.dist-info → more_compute-0.3.0.dist-info}/WHEEL +0 -0
- {more_compute-0.2.6.dist-info → more_compute-0.3.0.dist-info}/entry_points.txt +0 -0
- {more_compute-0.2.6.dist-info → more_compute-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {more_compute-0.2.6.dist-info → more_compute-0.3.0.dist-info}/top_level.txt +0 -0
frontend/app/globals.css
CHANGED
|
@@ -210,8 +210,8 @@ body {
|
|
|
210
210
|
.kernel-status-indicator {
|
|
211
211
|
display: flex;
|
|
212
212
|
align-items: center;
|
|
213
|
-
background:
|
|
214
|
-
border: 1px solid
|
|
213
|
+
background: var(--mc-cell-background);
|
|
214
|
+
border: 1px solid var(--mc-border);
|
|
215
215
|
border-radius: 20px;
|
|
216
216
|
padding: 8px 12px;
|
|
217
217
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
@@ -252,7 +252,7 @@ body {
|
|
|
252
252
|
}
|
|
253
253
|
|
|
254
254
|
.status-text {
|
|
255
|
-
color:
|
|
255
|
+
color: var(--mc-text-color);
|
|
256
256
|
}
|
|
257
257
|
|
|
258
258
|
/* Notebook Wrapper */
|
|
@@ -575,131 +575,7 @@ body {
|
|
|
575
575
|
padding: 0;
|
|
576
576
|
}
|
|
577
577
|
|
|
578
|
-
|
|
579
|
-
position: relative;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
.cell-editor {
|
|
583
|
-
width: 100%;
|
|
584
|
-
border: none;
|
|
585
|
-
outline: none;
|
|
586
|
-
resize: none;
|
|
587
|
-
font-family: 'Fira', 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
|
|
588
|
-
font-size: 14px;
|
|
589
|
-
line-height: 1.6;
|
|
590
|
-
padding: 16px 20px;
|
|
591
|
-
background: transparent;
|
|
592
|
-
color: var(--mc-text-color);
|
|
593
|
-
min-height: 100px;
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
.cell-editor:focus {
|
|
597
|
-
outline: none;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
/* Markdown editor styling - uses Fira font for better readability */
|
|
601
|
-
.markdown-editor-container {
|
|
602
|
-
/* Inherits from cell-editor-container */
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
.markdown-editor {
|
|
606
|
-
font-family: 'Fira', 'CustomFont', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
|
|
607
|
-
/* Keep other properties from .cell-editor */
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
/* Code editor styling - keeps monospace font */
|
|
611
|
-
.code-editor-container {
|
|
612
|
-
/* Inherits from cell-editor-container */
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
.code-editor {
|
|
616
|
-
/* Inherits monospace font from .cell-editor */
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
/* CodeMirror Styling */
|
|
620
|
-
.CodeMirror {
|
|
621
|
-
height: auto;
|
|
622
|
-
font-family: 'Fira', 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
|
|
623
|
-
font-size: 14px;
|
|
624
|
-
line-height: 1.6;
|
|
625
|
-
border: none;
|
|
626
|
-
background: transparent;
|
|
627
|
-
color: var(--mc-text-color);
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
.CodeMirror-focused {
|
|
631
|
-
outline: none;
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
.CodeMirror-scroll {
|
|
635
|
-
/* Content-based sizing - start small and grow with content */
|
|
636
|
-
min-height: 32px; /* Single line minimum */
|
|
637
|
-
padding: 8px 16px;
|
|
638
|
-
transition: min-height 0.2s ease;
|
|
639
|
-
background: var(--mc-cell-background);
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
/* Auto-sizing based on content */
|
|
643
|
-
.CodeMirror {
|
|
644
|
-
/* Let CodeMirror size itself based on content */
|
|
645
|
-
height: auto !important;
|
|
646
|
-
min-height: 32px;
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
.CodeMirror-sizer {
|
|
650
|
-
min-height: auto !important;
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
/* Smart sizing classes applied dynamically */
|
|
654
|
-
.cell.cell-empty .CodeMirror-scroll {
|
|
655
|
-
min-height: 32px; /* Very compact for empty cells */
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
.cell.cell-single-line .CodeMirror-scroll {
|
|
659
|
-
min-height: 40px; /* Slightly more space for single line */
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
.cell.cell-focused .CodeMirror-scroll {
|
|
663
|
-
min-height: 48px; /* More space when focused for editing */
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
.CodeMirror-cursor {
|
|
667
|
-
border-left: 2px solid #3b82f6;
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
.CodeMirror-selected {
|
|
671
|
-
background: #dbeafe;
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
.CodeMirror-line {
|
|
675
|
-
padding: 0;
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
.CodeMirror-placeholder {
|
|
679
|
-
color: #9ca3af !important;
|
|
680
|
-
font-style: italic;
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
/* Line numbers styling */
|
|
684
|
-
.CodeMirror-linenumbers {
|
|
685
|
-
background: var(--mc-cell-background);
|
|
686
|
-
border-right: 1px solid #e5e7eb;
|
|
687
|
-
padding-right: 8px;
|
|
688
|
-
min-width: 30px;
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
.CodeMirror-linenumber {
|
|
692
|
-
color: var(--mc-line-number-color);
|
|
693
|
-
font-size: 11px;
|
|
694
|
-
text-align: right;
|
|
695
|
-
padding: 0 5px 0 0;
|
|
696
|
-
min-width: 20px;
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
.CodeMirror-gutters {
|
|
700
|
-
background: var(--mc-cell-background);
|
|
701
|
-
border-right: 1px solid #e5e7eb;
|
|
702
|
-
}
|
|
578
|
+
/* Legacy textarea editor styles removed - now using Monaco Editor exclusively */
|
|
703
579
|
|
|
704
580
|
/* Markdown Rendered Content - Auto-sizing */
|
|
705
581
|
.markdown-rendered {
|
|
@@ -1434,6 +1310,7 @@ body {
|
|
|
1434
1310
|
border: 1px solid var(--mc-border);
|
|
1435
1311
|
border-radius: 8px;
|
|
1436
1312
|
background: var(--mc-cell-background);
|
|
1313
|
+
overflow: hidden;
|
|
1437
1314
|
}
|
|
1438
1315
|
|
|
1439
1316
|
.metric-panel-header {
|
|
@@ -1450,6 +1327,7 @@ body {
|
|
|
1450
1327
|
|
|
1451
1328
|
.metric-panel-body {
|
|
1452
1329
|
padding: 10px 12px;
|
|
1330
|
+
overflow: hidden;
|
|
1453
1331
|
}
|
|
1454
1332
|
|
|
1455
1333
|
.metric-big-value .value {
|
|
@@ -1466,6 +1344,8 @@ body {
|
|
|
1466
1344
|
|
|
1467
1345
|
.mini-chart {
|
|
1468
1346
|
margin-top: 6px;
|
|
1347
|
+
max-width: 100%;
|
|
1348
|
+
height: auto;
|
|
1469
1349
|
}
|
|
1470
1350
|
|
|
1471
1351
|
.metrics-footer {
|
|
@@ -1577,7 +1457,7 @@ body {
|
|
|
1577
1457
|
.packages-table {
|
|
1578
1458
|
display: flex;
|
|
1579
1459
|
flex-direction: column;
|
|
1580
|
-
border: 1px solid
|
|
1460
|
+
border: 1px solid var(--mc-border);
|
|
1581
1461
|
border-radius: 8px;
|
|
1582
1462
|
overflow: hidden;
|
|
1583
1463
|
background: var(--mc-cell-background);
|
|
@@ -1590,14 +1470,38 @@ body {
|
|
|
1590
1470
|
padding: 10px 12px;
|
|
1591
1471
|
font-size: 12px;
|
|
1592
1472
|
font-weight: 600;
|
|
1593
|
-
color:
|
|
1594
|
-
border-bottom: 1px solid
|
|
1595
|
-
background:
|
|
1473
|
+
color: var(--mc-text-color);
|
|
1474
|
+
border-bottom: 1px solid var(--mc-border);
|
|
1475
|
+
background: var(--mc-secondary);
|
|
1476
|
+
opacity: 0.8;
|
|
1596
1477
|
}
|
|
1597
1478
|
|
|
1598
1479
|
.packages-list {
|
|
1599
1480
|
overflow-y: auto;
|
|
1600
1481
|
max-height: calc(100vh - 260px);
|
|
1482
|
+
/* Firefox scrollbar styling */
|
|
1483
|
+
scrollbar-width: thin;
|
|
1484
|
+
scrollbar-color: var(--mc-border) var(--mc-cell-background);
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
/* Packages list scrollbar styling (Webkit browsers) */
|
|
1488
|
+
.packages-list::-webkit-scrollbar {
|
|
1489
|
+
width: 8px;
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
.packages-list::-webkit-scrollbar-track {
|
|
1493
|
+
background: var(--mc-cell-background);
|
|
1494
|
+
border-radius: 4px;
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
.packages-list::-webkit-scrollbar-thumb {
|
|
1498
|
+
background: var(--mc-border);
|
|
1499
|
+
border-radius: 4px;
|
|
1500
|
+
transition: background 0.2s ease;
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
.packages-list::-webkit-scrollbar-thumb:hover {
|
|
1504
|
+
background: var(--mc-secondary);
|
|
1601
1505
|
}
|
|
1602
1506
|
|
|
1603
1507
|
.package-row {
|
|
@@ -1605,7 +1509,7 @@ body {
|
|
|
1605
1509
|
grid-template-columns: 1fr 140px;
|
|
1606
1510
|
gap: 8px;
|
|
1607
1511
|
padding: 10px 12px;
|
|
1608
|
-
border-bottom: 1px solid
|
|
1512
|
+
border-bottom: 1px solid var(--mc-border);
|
|
1609
1513
|
}
|
|
1610
1514
|
|
|
1611
1515
|
.package-row:last-child {
|
|
@@ -1694,6 +1598,7 @@ body {
|
|
|
1694
1598
|
color: var(--mc-line-number-color) !important;
|
|
1695
1599
|
font-size: 11px !important;
|
|
1696
1600
|
font-family: 'Fira', 'SF Mono', Monaco, monospace;
|
|
1601
|
+
padding-right: 12px !important;
|
|
1697
1602
|
}
|
|
1698
1603
|
|
|
1699
1604
|
.monaco-editor .margin-view-overlays {
|
frontend/app/layout.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useState, useEffect } from "react";
|
|
3
|
+
import { useState, useEffect, useRef } from "react";
|
|
4
4
|
import Script from "next/script";
|
|
5
5
|
import Sidebar from "@/components/layout/Sidebar";
|
|
6
6
|
import FolderPopup from "@/components/popups/FolderPopup";
|
|
@@ -13,21 +13,65 @@ import {
|
|
|
13
13
|
PodWebSocketProvider,
|
|
14
14
|
usePodWebSocket,
|
|
15
15
|
} from "@/contexts/PodWebSocketContext";
|
|
16
|
-
import { loadSettings, applyTheme } from "@/lib/settings";
|
|
16
|
+
import { loadSettings, applyTheme, type NotebookSettings } from "@/lib/settings";
|
|
17
|
+
import { fetchMetrics, type MetricsSnapshot } from "@/lib/api";
|
|
17
18
|
import "./globals.css";
|
|
18
19
|
|
|
20
|
+
const POLL_MS = 3000;
|
|
21
|
+
|
|
19
22
|
function AppContent({ children }: { children: React.ReactNode }) {
|
|
20
|
-
const [appSettings, setAppSettings] = useState(
|
|
23
|
+
const [appSettings, setAppSettings] = useState<NotebookSettings>(() => loadSettings());
|
|
21
24
|
const [activePopup, setActivePopup] = useState<string | null>(null);
|
|
22
25
|
const { connectionState, gpuPods, connectingPodId } = usePodWebSocket();
|
|
23
26
|
|
|
27
|
+
// Persistent metrics collection
|
|
28
|
+
const [metricsHistory, setMetricsHistory] = useState<MetricsSnapshot[]>([]);
|
|
29
|
+
const intervalRef = useRef<number | null>(null);
|
|
30
|
+
|
|
24
31
|
// Apply theme on initial mount
|
|
25
32
|
useEffect(() => {
|
|
26
33
|
const settings = loadSettings();
|
|
27
34
|
applyTheme(settings.theme);
|
|
28
35
|
}, []);
|
|
29
36
|
|
|
30
|
-
|
|
37
|
+
// Persistent metrics collection (runs when mode is 'persistent')
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (appSettings.metricsCollectionMode !== 'persistent') {
|
|
40
|
+
// Stop collection if mode changes to on-demand
|
|
41
|
+
if (intervalRef.current) {
|
|
42
|
+
window.clearInterval(intervalRef.current);
|
|
43
|
+
intervalRef.current = null;
|
|
44
|
+
}
|
|
45
|
+
// Clear history when switching to on-demand mode
|
|
46
|
+
setMetricsHistory([]);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Start persistent collection
|
|
51
|
+
const load = async () => {
|
|
52
|
+
try {
|
|
53
|
+
const snap = await fetchMetrics();
|
|
54
|
+
setMetricsHistory((prev) => {
|
|
55
|
+
const arr = [...prev, snap];
|
|
56
|
+
return arr.slice(-100); // Keep last 100 snapshots
|
|
57
|
+
});
|
|
58
|
+
} catch {
|
|
59
|
+
// Silently fail if metrics API is unavailable
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
load(); // Initial load
|
|
64
|
+
intervalRef.current = window.setInterval(load, POLL_MS);
|
|
65
|
+
|
|
66
|
+
return () => {
|
|
67
|
+
if (intervalRef.current) {
|
|
68
|
+
window.clearInterval(intervalRef.current);
|
|
69
|
+
intervalRef.current = null;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}, [appSettings.metricsCollectionMode]);
|
|
73
|
+
|
|
74
|
+
const handleSettingsChange = (settings: NotebookSettings) => {
|
|
31
75
|
console.log("Settings updated:", settings);
|
|
32
76
|
setAppSettings(settings);
|
|
33
77
|
};
|
|
@@ -52,7 +96,12 @@ function AppContent({ children }: { children: React.ReactNode }) {
|
|
|
52
96
|
case "compute":
|
|
53
97
|
return <ComputePopup {...props} />;
|
|
54
98
|
case "metrics":
|
|
55
|
-
return
|
|
99
|
+
return (
|
|
100
|
+
<MetricsPopup
|
|
101
|
+
{...props}
|
|
102
|
+
sharedHistory={appSettings.metricsCollectionMode === 'persistent' ? metricsHistory : undefined}
|
|
103
|
+
/>
|
|
104
|
+
);
|
|
56
105
|
case "settings":
|
|
57
106
|
return (
|
|
58
107
|
<SettingsPopup {...props} onSettingsChange={handleSettingsChange} />
|
frontend/components/Notebook.tsx
CHANGED
|
@@ -435,6 +435,12 @@ export const Notebook: React.FC<NotebookProps> = ({
|
|
|
435
435
|
dispatch({ type: "NOTEBOOK_UPDATED", payload: data });
|
|
436
436
|
}, []);
|
|
437
437
|
|
|
438
|
+
const handleHeartbeat = useCallback((data: any) => {
|
|
439
|
+
// Heartbeat received - execution still in progress
|
|
440
|
+
// Cell spinner already showing via executingCells set
|
|
441
|
+
console.log("[Heartbeat]", data?.message || "Execution in progress");
|
|
442
|
+
}, []);
|
|
443
|
+
|
|
438
444
|
const handleKernelStatusUpdate = useCallback(
|
|
439
445
|
(status: "connecting" | "connected" | "disconnected") => {
|
|
440
446
|
setKernelStatus(status);
|
|
@@ -485,6 +491,7 @@ export const Notebook: React.FC<NotebookProps> = ({
|
|
|
485
491
|
ws.on("execution_complete", handleExecutionComplete);
|
|
486
492
|
ws.on("execution_result", handleExecuteResult);
|
|
487
493
|
ws.on("execution_error", handleExecutionError);
|
|
494
|
+
ws.on("heartbeat", handleHeartbeat);
|
|
488
495
|
|
|
489
496
|
return () => ws.disconnect();
|
|
490
497
|
}, [
|
|
@@ -497,6 +504,7 @@ export const Notebook: React.FC<NotebookProps> = ({
|
|
|
497
504
|
handleExecuteResult,
|
|
498
505
|
handleExecutionComplete,
|
|
499
506
|
handleExecutionError,
|
|
507
|
+
handleHeartbeat,
|
|
500
508
|
]);
|
|
501
509
|
|
|
502
510
|
// Simplified save management - only save on Ctrl+S or Run
|
|
@@ -528,7 +536,7 @@ export const Notebook: React.FC<NotebookProps> = ({
|
|
|
528
536
|
if (cell.cell_type === "markdown") {
|
|
529
537
|
// Save before rendering markdown
|
|
530
538
|
saveNotebook();
|
|
531
|
-
// Markdown rendering is handled locally in
|
|
539
|
+
// Markdown rendering is handled locally in MonacoCell.tsx now
|
|
532
540
|
return;
|
|
533
541
|
}
|
|
534
542
|
|
|
@@ -39,9 +39,9 @@ export const CellButton: React.FC<CellButtonProps> = ({
|
|
|
39
39
|
style={{
|
|
40
40
|
width: '28px',
|
|
41
41
|
height: '28px',
|
|
42
|
-
border: '1px solid
|
|
42
|
+
border: '1px solid var(--mc-border)',
|
|
43
43
|
borderRadius: '4px',
|
|
44
|
-
backgroundColor: isHovered ? '
|
|
44
|
+
backgroundColor: isHovered ? 'var(--mc-secondary)' : 'var(--mc-cell-background)',
|
|
45
45
|
display: 'flex',
|
|
46
46
|
alignItems: 'center',
|
|
47
47
|
justifyContent: 'center',
|
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
ChevronDownIcon,
|
|
17
17
|
} from "@radix-ui/react-icons";
|
|
18
18
|
import { Check, X } from "lucide-react";
|
|
19
|
-
import { fixIndentation } from "@/lib/api";
|
|
20
19
|
import { loadMonacoThemes } from "@/lib/monaco-themes";
|
|
21
20
|
import { loadSettings } from "@/lib/settings";
|
|
22
21
|
|
|
@@ -285,18 +284,6 @@ export const MonacoCell: React.FC<CellProps> = ({
|
|
|
285
284
|
}
|
|
286
285
|
};
|
|
287
286
|
|
|
288
|
-
const handleFixIndentation = async () => {
|
|
289
|
-
try {
|
|
290
|
-
const fixedCode = await fixIndentation(cell.source);
|
|
291
|
-
onUpdate(indexRef.current, fixedCode);
|
|
292
|
-
if (editorRef.current) {
|
|
293
|
-
editorRef.current.setValue(fixedCode);
|
|
294
|
-
}
|
|
295
|
-
} catch (err) {
|
|
296
|
-
console.error("Failed to fix indentation:", err);
|
|
297
|
-
}
|
|
298
|
-
};
|
|
299
|
-
|
|
300
287
|
// ============================================================================
|
|
301
288
|
// MONACO SETUP
|
|
302
289
|
// ============================================================================
|
|
@@ -690,7 +677,7 @@ export const MonacoCell: React.FC<CellProps> = ({
|
|
|
690
677
|
overviewRulerBorder: false,
|
|
691
678
|
overviewRulerLanes: 0,
|
|
692
679
|
// NOTE: fixedOverflowWidgets has known positioning bugs in 2024 - disabled
|
|
693
|
-
padding: { top: 8, bottom: 8 }, // All padding managed by Monaco
|
|
680
|
+
padding: { top: 8, bottom: 8, left: 8 }, // All padding managed by Monaco
|
|
694
681
|
scrollbar: {
|
|
695
682
|
vertical: "auto",
|
|
696
683
|
horizontal: "auto",
|
|
@@ -710,7 +697,6 @@ export const MonacoCell: React.FC<CellProps> = ({
|
|
|
710
697
|
<CellOutput
|
|
711
698
|
outputs={cell.outputs}
|
|
712
699
|
error={cell.error}
|
|
713
|
-
onFixIndentation={handleFixIndentation}
|
|
714
700
|
/>
|
|
715
701
|
</div>
|
|
716
702
|
</div>
|
|
@@ -1,18 +1,70 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { FC } from 'react';
|
|
3
|
+
import { FC, useState } from 'react';
|
|
4
4
|
import { Output } from '@/types/notebook';
|
|
5
5
|
import ErrorDisplay from './ErrorDisplay';
|
|
6
|
+
import { Copy, Check } from 'lucide-react';
|
|
6
7
|
|
|
7
8
|
interface CellOutputProps {
|
|
8
9
|
outputs: Output[];
|
|
9
10
|
error: any;
|
|
10
|
-
onFixIndentation?: () => void;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
interface OutputWithCopyProps {
|
|
14
|
+
content: string;
|
|
15
|
+
className: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const OutputWithCopy: FC<OutputWithCopyProps> = ({ content, className }) => {
|
|
19
|
+
const [isCopied, setIsCopied] = useState(false);
|
|
20
|
+
|
|
21
|
+
const copyToClipboard = () => {
|
|
22
|
+
navigator.clipboard.writeText(content).then(() => {
|
|
23
|
+
setIsCopied(true);
|
|
24
|
+
setTimeout(() => setIsCopied(false), 2000);
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div style={{ position: 'relative' }}>
|
|
30
|
+
{/* Copy Button */}
|
|
31
|
+
<button
|
|
32
|
+
onClick={copyToClipboard}
|
|
33
|
+
style={{
|
|
34
|
+
position: 'absolute',
|
|
35
|
+
top: '8px',
|
|
36
|
+
right: '8px',
|
|
37
|
+
zIndex: 10,
|
|
38
|
+
background: 'var(--mc-cell-background)',
|
|
39
|
+
border: '1px solid var(--mc-border)',
|
|
40
|
+
borderRadius: '4px',
|
|
41
|
+
padding: '6px',
|
|
42
|
+
cursor: 'pointer',
|
|
43
|
+
display: 'flex',
|
|
44
|
+
alignItems: 'center',
|
|
45
|
+
justifyContent: 'center',
|
|
46
|
+
transition: 'all 0.2s ease'
|
|
47
|
+
}}
|
|
48
|
+
onMouseEnter={(e) => {
|
|
49
|
+
e.currentTarget.style.background = 'var(--mc-secondary)';
|
|
50
|
+
}}
|
|
51
|
+
onMouseLeave={(e) => {
|
|
52
|
+
e.currentTarget.style.background = 'var(--mc-cell-background)';
|
|
53
|
+
}}
|
|
54
|
+
title="Copy output to clipboard"
|
|
55
|
+
>
|
|
56
|
+
{isCopied ? <Check size={14} style={{ color: 'var(--mc-primary)' }} /> : <Copy size={14} />}
|
|
57
|
+
</button>
|
|
58
|
+
|
|
59
|
+
{/* Output Content */}
|
|
60
|
+
<pre className={className}>{content}</pre>
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const CellOutput: FC<CellOutputProps> = ({ outputs, error }) => {
|
|
14
66
|
if (error) {
|
|
15
|
-
return <ErrorDisplay error={error}
|
|
67
|
+
return <ErrorDisplay error={error} />;
|
|
16
68
|
}
|
|
17
69
|
|
|
18
70
|
if (!outputs || outputs.length === 0) {
|
|
@@ -26,15 +78,19 @@ const CellOutput: FC<CellOutputProps> = ({ outputs, error, onFixIndentation }) =
|
|
|
26
78
|
switch (output.output_type) {
|
|
27
79
|
case 'stream':
|
|
28
80
|
return (
|
|
29
|
-
<
|
|
30
|
-
{
|
|
31
|
-
|
|
81
|
+
<OutputWithCopy
|
|
82
|
+
key={index}
|
|
83
|
+
content={output.text}
|
|
84
|
+
className={`output-stream ${output.name}`}
|
|
85
|
+
/>
|
|
32
86
|
);
|
|
33
87
|
case 'execute_result':
|
|
34
88
|
return (
|
|
35
|
-
<
|
|
36
|
-
{
|
|
37
|
-
|
|
89
|
+
<OutputWithCopy
|
|
90
|
+
key={index}
|
|
91
|
+
content={output.data?.['text/plain'] || ''}
|
|
92
|
+
className="output-result"
|
|
93
|
+
/>
|
|
38
94
|
);
|
|
39
95
|
case 'display_data': {
|
|
40
96
|
const img = (output as any).data?.['image/png'];
|
|
@@ -47,18 +103,22 @@ const CellOutput: FC<CellOutputProps> = ({ outputs, error, onFixIndentation }) =
|
|
|
47
103
|
);
|
|
48
104
|
}
|
|
49
105
|
return (
|
|
50
|
-
<
|
|
51
|
-
{
|
|
52
|
-
|
|
106
|
+
<OutputWithCopy
|
|
107
|
+
key={index}
|
|
108
|
+
content={(output as any).data?.['text/plain'] || ''}
|
|
109
|
+
className="output-result"
|
|
110
|
+
/>
|
|
53
111
|
);
|
|
54
112
|
}
|
|
55
113
|
case 'error':
|
|
56
|
-
return <ErrorDisplay key={index} error={output}
|
|
114
|
+
return <ErrorDisplay key={index} error={output} />;
|
|
57
115
|
default:
|
|
58
116
|
return (
|
|
59
|
-
<
|
|
60
|
-
{
|
|
61
|
-
|
|
117
|
+
<OutputWithCopy
|
|
118
|
+
key={index}
|
|
119
|
+
content={JSON.stringify(output, null, 2)}
|
|
120
|
+
className="output-unknown"
|
|
121
|
+
/>
|
|
62
122
|
);
|
|
63
123
|
}
|
|
64
124
|
})}
|
|
@@ -16,12 +16,10 @@ import { Output, ErrorOutput } from '@/types/notebook';
|
|
|
16
16
|
interface ErrorDisplayProps {
|
|
17
17
|
error: Output;
|
|
18
18
|
maxLines?: number;
|
|
19
|
-
onFixIndentation?: () => void;
|
|
20
19
|
}
|
|
21
20
|
|
|
22
|
-
const TypedErrorDisplay: FC<{ error: ErrorOutput
|
|
21
|
+
const TypedErrorDisplay: FC<{ error: ErrorOutput }> = ({ error }) => {
|
|
23
22
|
const [isCopied, setIsCopied] = useState(false);
|
|
24
|
-
const isIndentationError = error.ename === 'IndentationError';
|
|
25
23
|
|
|
26
24
|
const copyToClipboard = () => {
|
|
27
25
|
const errorDetails = `Error: ${error.ename}: ${error.evalue}\n\nTraceback:\n${error.traceback.join('\n')}`;
|
|
@@ -112,29 +110,6 @@ const TypedErrorDisplay: FC<{ error: ErrorOutput; onFixIndentation?: () => void
|
|
|
112
110
|
display: 'flex',
|
|
113
111
|
gap: '4px'
|
|
114
112
|
}}>
|
|
115
|
-
{/* Fix Indentation Button */}
|
|
116
|
-
{isIndentationError && onFixIndentation && (
|
|
117
|
-
<button
|
|
118
|
-
onClick={onFixIndentation}
|
|
119
|
-
style={{
|
|
120
|
-
background: 'rgba(59, 130, 246, 0.1)',
|
|
121
|
-
border: '1px solid #3b82f6',
|
|
122
|
-
borderRadius: '4px',
|
|
123
|
-
padding: '6px 10px',
|
|
124
|
-
cursor: 'pointer',
|
|
125
|
-
display: 'flex',
|
|
126
|
-
alignItems: 'center',
|
|
127
|
-
justifyContent: 'center',
|
|
128
|
-
transition: 'all 0.2s ease',
|
|
129
|
-
fontSize: '11px',
|
|
130
|
-
fontWeight: 500,
|
|
131
|
-
color: '#3b82f6'
|
|
132
|
-
}}
|
|
133
|
-
title="Auto-fix indentation"
|
|
134
|
-
>
|
|
135
|
-
Fix Indent
|
|
136
|
-
</button>
|
|
137
|
-
)}
|
|
138
113
|
{/* Copy Button */}
|
|
139
114
|
<button
|
|
140
115
|
onClick={copyToClipboard}
|
|
@@ -197,12 +172,12 @@ const TypedErrorDisplay: FC<{ error: ErrorOutput; onFixIndentation?: () => void
|
|
|
197
172
|
);
|
|
198
173
|
};
|
|
199
174
|
|
|
200
|
-
const ErrorDisplay: FC<ErrorDisplayProps> = ({ error
|
|
175
|
+
const ErrorDisplay: FC<ErrorDisplayProps> = ({ error }) => {
|
|
201
176
|
// Type guard to ensure we have an ErrorOutput
|
|
202
177
|
if (error.output_type !== 'error') {
|
|
203
178
|
return null;
|
|
204
179
|
}
|
|
205
|
-
return <TypedErrorDisplay error={error}
|
|
180
|
+
return <TypedErrorDisplay error={error} />;
|
|
206
181
|
};
|
|
207
182
|
|
|
208
183
|
export default ErrorDisplay;
|