more-compute 0.1.2__py3-none-any.whl → 0.1.4__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 (68) hide show
  1. frontend/.DS_Store +0 -0
  2. frontend/.gitignore +41 -0
  3. frontend/README.md +36 -0
  4. frontend/__init__.py +1 -0
  5. frontend/app/favicon.ico +0 -0
  6. frontend/app/globals.css +1537 -0
  7. frontend/app/layout.tsx +173 -0
  8. frontend/app/page.tsx +11 -0
  9. frontend/components/AddCellButton.tsx +42 -0
  10. frontend/components/Cell.tsx +244 -0
  11. frontend/components/CellButton.tsx +58 -0
  12. frontend/components/CellOutput.tsx +70 -0
  13. frontend/components/ErrorDisplay.tsx +208 -0
  14. frontend/components/ErrorModal.tsx +154 -0
  15. frontend/components/MarkdownRenderer.tsx +84 -0
  16. frontend/components/Notebook.tsx +520 -0
  17. frontend/components/Sidebar.tsx +46 -0
  18. frontend/components/popups/ComputePopup.tsx +879 -0
  19. frontend/components/popups/FilterPopup.tsx +427 -0
  20. frontend/components/popups/FolderPopup.tsx +171 -0
  21. frontend/components/popups/MetricsPopup.tsx +168 -0
  22. frontend/components/popups/PackagesPopup.tsx +112 -0
  23. frontend/components/popups/PythonPopup.tsx +292 -0
  24. frontend/components/popups/SettingsPopup.tsx +68 -0
  25. frontend/eslint.config.mjs +25 -0
  26. frontend/lib/api.ts +469 -0
  27. frontend/lib/settings.ts +87 -0
  28. frontend/lib/websocket-native.ts +202 -0
  29. frontend/lib/websocket.ts +134 -0
  30. frontend/next-env.d.ts +6 -0
  31. frontend/next.config.mjs +17 -0
  32. frontend/next.config.ts +7 -0
  33. frontend/package-lock.json +5676 -0
  34. frontend/package.json +41 -0
  35. frontend/postcss.config.mjs +5 -0
  36. frontend/public/assets/icons/add.svg +1 -0
  37. frontend/public/assets/icons/check.svg +1 -0
  38. frontend/public/assets/icons/copy.svg +1 -0
  39. frontend/public/assets/icons/folder.svg +1 -0
  40. frontend/public/assets/icons/metric.svg +1 -0
  41. frontend/public/assets/icons/packages.svg +1 -0
  42. frontend/public/assets/icons/play.svg +1 -0
  43. frontend/public/assets/icons/python.svg +265 -0
  44. frontend/public/assets/icons/setting.svg +1 -0
  45. frontend/public/assets/icons/stop.svg +1 -0
  46. frontend/public/assets/icons/trash.svg +1 -0
  47. frontend/public/assets/icons/up-down.svg +1 -0
  48. frontend/public/assets/icons/x.svg +1 -0
  49. frontend/public/file.svg +1 -0
  50. frontend/public/fonts/Fira.ttf +0 -0
  51. frontend/public/fonts/Tiempos.woff2 +0 -0
  52. frontend/public/fonts/VeraMono.ttf +0 -0
  53. frontend/public/globe.svg +1 -0
  54. frontend/public/next.svg +1 -0
  55. frontend/public/vercel.svg +1 -0
  56. frontend/public/window.svg +1 -0
  57. frontend/tailwind.config.ts +29 -0
  58. frontend/tsconfig.json +27 -0
  59. frontend/types/notebook.ts +58 -0
  60. kernel_run.py +7 -0
  61. {more_compute-0.1.2.dist-info → more_compute-0.1.4.dist-info}/METADATA +1 -1
  62. more_compute-0.1.4.dist-info/RECORD +86 -0
  63. {more_compute-0.1.2.dist-info → more_compute-0.1.4.dist-info}/top_level.txt +1 -0
  64. morecompute/__version__.py +1 -0
  65. more_compute-0.1.2.dist-info/RECORD +0 -26
  66. {more_compute-0.1.2.dist-info → more_compute-0.1.4.dist-info}/WHEEL +0 -0
  67. {more_compute-0.1.2.dist-info → more_compute-0.1.4.dist-info}/entry_points.txt +0 -0
  68. {more_compute-0.1.2.dist-info → more_compute-0.1.4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,173 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import Script from "next/script";
5
+ import Sidebar from "@/components/Sidebar";
6
+ import FolderPopup from "@/components/popups/FolderPopup";
7
+ import PackagesPopup from "@/components/popups/PackagesPopup";
8
+ import PythonPopup from "@/components/popups/PythonPopup";
9
+ import ComputePopup from "@/components/popups/ComputePopup";
10
+ import MetricsPopup from "@/components/popups/MetricsPopup";
11
+ import SettingsPopup from "@/components/popups/SettingsPopup";
12
+ import "./globals.css";
13
+
14
+ export default function RootLayout({
15
+ children,
16
+ }: Readonly<{
17
+ children: React.ReactNode;
18
+ }>) {
19
+ const [appSettings, setAppSettings] = useState({});
20
+ const [pythonEnvironment, setPythonEnvironment] = useState(null);
21
+ const [activePopup, setActivePopup] = useState<string | null>(null);
22
+
23
+ const handleSettingsChange = (settings: any) => {
24
+ console.log("Settings updated:", settings);
25
+ setAppSettings(settings);
26
+ };
27
+
28
+ const handleEnvironmentSwitch = (env: any) => {
29
+ console.log("Switching to environment:", env);
30
+ setPythonEnvironment(env);
31
+ };
32
+
33
+ const togglePopup = (popupType: string) => {
34
+ setActivePopup((prev) => (prev === popupType ? null : popupType));
35
+ };
36
+
37
+ const closePopup = () => {
38
+ setActivePopup(null);
39
+ };
40
+
41
+ const renderPopup = () => {
42
+ if (!activePopup) return null;
43
+
44
+ const props = { onClose: closePopup };
45
+ switch (activePopup) {
46
+ case "folder":
47
+ return <FolderPopup {...props} />;
48
+ case "packages":
49
+ return <PackagesPopup {...props} />;
50
+ case "python":
51
+ return (
52
+ <PythonPopup
53
+ {...props}
54
+ onEnvironmentSwitch={handleEnvironmentSwitch}
55
+ />
56
+ );
57
+ case "compute":
58
+ return <ComputePopup {...props} />;
59
+ case "metrics":
60
+ return <MetricsPopup {...props} />;
61
+ case "settings":
62
+ return (
63
+ <SettingsPopup {...props} onSettingsChange={handleSettingsChange} />
64
+ );
65
+ default:
66
+ return null;
67
+ }
68
+ };
69
+
70
+ const getPopupTitle = () => {
71
+ switch (activePopup) {
72
+ case "folder":
73
+ return "Files";
74
+ case "packages":
75
+ return "Packages";
76
+ case "python":
77
+ return "Python Environment";
78
+ case "compute":
79
+ return "Compute Resources";
80
+ case "metrics":
81
+ return "System Metrics";
82
+ case "settings":
83
+ return "Settings";
84
+ default:
85
+ return "";
86
+ }
87
+ };
88
+
89
+ const notebookPath = process.env.NEXT_PUBLIC_NOTEBOOK_PATH || "";
90
+ const notebookRoot = process.env.NEXT_PUBLIC_NOTEBOOK_ROOT || "";
91
+
92
+ return (
93
+ <html lang="en">
94
+ <head>
95
+ <title>MoreCompute</title>
96
+ <meta name="description" content="Python notebook interface" />
97
+ <link
98
+ rel="stylesheet"
99
+ href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.css"
100
+ />
101
+ <link
102
+ rel="stylesheet"
103
+ href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/theme/default.min.css"
104
+ />
105
+ </head>
106
+ <body data-notebook-path={notebookPath} data-notebook-root={notebookRoot}>
107
+ <div id="app">
108
+ <Sidebar onTogglePopup={togglePopup} activePopup={activePopup} />
109
+ <div
110
+ id="popup-overlay"
111
+ className="popup-overlay"
112
+ style={{ display: activePopup ? "flex" : "none" }}
113
+ >
114
+ {activePopup && (
115
+ <div className="popup-content">
116
+ <div className="popup-header">
117
+ <h2 className="popup-title">{getPopupTitle()}</h2>
118
+ <button className="popup-close" onClick={closePopup}>
119
+ ×
120
+ </button>
121
+ </div>
122
+ <div className="popup-body">{renderPopup()}</div>
123
+ </div>
124
+ )}
125
+ </div>
126
+ <div
127
+ id="kernel-banner"
128
+ className="kernel-banner"
129
+ style={{ display: "none" }}
130
+ >
131
+ <div className="kernel-message">
132
+ <span className="kernel-status-text">🔴 Kernel Disconnected</span>
133
+ <span className="kernel-subtitle">
134
+ The notebook kernel has stopped running. Restart to continue.
135
+ </span>
136
+ </div>
137
+ </div>
138
+ <div className="kernel-status-bar">
139
+ <div className="kernel-status-indicator">
140
+ <span
141
+ id="kernel-status-dot"
142
+ className="status-dot connecting"
143
+ ></span>
144
+ <span id="kernel-status-text" className="status-text">
145
+ Connecting...
146
+ </span>
147
+ </div>
148
+ </div>
149
+ <div className="main-content">{children}</div>
150
+ <div style={{ display: "none" }}>
151
+ <span id="connection-status">Connected</span>
152
+ <span id="kernel-status">Ready</span>
153
+ <img
154
+ id="copy-icon-template"
155
+ src="/assets/icons/copy.svg"
156
+ alt="Copy"
157
+ />
158
+ <img
159
+ id="check-icon-template"
160
+ src="/assets/icons/check.svg"
161
+ alt="Copied"
162
+ />
163
+ </div>
164
+ </div>
165
+ <Script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.js" />
166
+ <Script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/python/python.min.js" />
167
+ <Script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/addon/edit/closebrackets.min.js" />
168
+ <Script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/addon/edit/matchbrackets.min.js" />
169
+ <Script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js" />
170
+ </body>
171
+ </html>
172
+ );
173
+ }
frontend/app/page.tsx ADDED
@@ -0,0 +1,11 @@
1
+ import { Notebook } from '@/components/Notebook';
2
+
3
+ export default function Home() {
4
+ return (
5
+ <div className="notebook-wrapper">
6
+ <div id="notebook" className="notebook">
7
+ <Notebook notebookName="default" />
8
+ </div>
9
+ </div>
10
+ );
11
+ }
@@ -0,0 +1,42 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+ import { CodeIcon, PlusIcon, TextIcon } from '@radix-ui/react-icons';
5
+
6
+ interface AddCellButtonProps {
7
+ onAddCell: (type: 'code' | 'markdown') => void;
8
+ }
9
+
10
+ export const AddCellButton: React.FC<AddCellButtonProps> = ({ onAddCell }) => {
11
+ const handleAdd = (type: 'code' | 'markdown', e: React.MouseEvent) => {
12
+ e.stopPropagation();
13
+ onAddCell(type);
14
+ };
15
+
16
+ return (
17
+ <div className="add-cell-button">
18
+ <div className="cell-type-menu">
19
+ <button
20
+ type="button"
21
+ className="cell-type-option"
22
+ data-type="code"
23
+ onClick={(e) => handleAdd('code', e)}
24
+ >
25
+ <CodeIcon className="w-4 h-4" />
26
+ <span>Code</span>
27
+ </button>
28
+ <button
29
+ type="button"
30
+ className="cell-type-option"
31
+ data-type="markdown"
32
+ onClick={(e) => handleAdd('markdown', e)}
33
+ >
34
+ <TextIcon className="w-4 h-4" />
35
+ <span>Text</span>
36
+ </button>
37
+ </div>
38
+ </div>
39
+ );
40
+ };
41
+
42
+ export default AddCellButton;
@@ -0,0 +1,244 @@
1
+ 'use client';
2
+
3
+ import React, { useRef, useEffect, useState } from 'react';
4
+ import { Cell as CellType } from '@/types/notebook';
5
+ import CellOutput from './CellOutput';
6
+ import AddCellButton from './AddCellButton';
7
+ import MarkdownRenderer from './MarkdownRenderer';
8
+ import CellButton from './CellButton';
9
+ import { UpdateIcon, LinkBreak2Icon, PlayIcon, RowSpacingIcon } from '@radix-ui/react-icons';
10
+ import { Check, X } from 'lucide-react';
11
+ import { fixIndentation } from '@/lib/api';
12
+
13
+ declare const CodeMirror: any;
14
+
15
+ interface CellProps {
16
+ cell: CellType;
17
+ index: number;
18
+ isActive: boolean;
19
+ isExecuting: boolean;
20
+ onExecute: (index: number) => void;
21
+ onInterrupt: (index: number) => void;
22
+ onDelete: (index: number) => void;
23
+ onUpdate: (index: number, source: string) => void;
24
+ onSetActive: (index: number) => void;
25
+ onAddCell: (type: 'code' | 'markdown', index: number) => void;
26
+ }
27
+
28
+ export const Cell: React.FC<CellProps> = ({
29
+ cell,
30
+ index,
31
+ isActive,
32
+ isExecuting,
33
+ onExecute,
34
+ onDelete,
35
+ onInterrupt,
36
+ onUpdate,
37
+ onSetActive,
38
+ onAddCell,
39
+ }) => {
40
+ const editorRef = useRef<HTMLTextAreaElement>(null);
41
+ const codeMirrorInstance = useRef<any>(null);
42
+ // Keep a ref to the latest index to avoid stale closures in event handlers
43
+ const indexRef = useRef<number>(index);
44
+ useEffect(() => { indexRef.current = index; }, [index]);
45
+
46
+ // Execution timer (shows while running and persists final duration afterwards)
47
+ const intervalRef = useRef<any>(null);
48
+ const [elapsedLabel, setElapsedLabel] = useState<string | null>(cell.execution_time ?? null);
49
+
50
+ const formatMs = (ms: number): string => {
51
+ if (ms < 1000) return `${ms.toFixed(0)}ms`;
52
+ if (ms < 60_000) return `${(ms / 1000).toFixed(1)}s`;
53
+ const totalSeconds = Math.floor(ms / 1000);
54
+ const minutes = Math.floor(totalSeconds / 60);
55
+ const seconds = totalSeconds % 60;
56
+ return `${minutes}:${seconds.toString().padStart(2, '0')}s`;
57
+ };
58
+
59
+ const parseExecTime = (s?: string | null): number | null => {
60
+ if (!s) return null;
61
+ // Accept "123.4ms" or "1.2s"
62
+ if (s.endsWith('ms')) return parseFloat(s.replace('ms', ''));
63
+ if (s.endsWith('s')) return parseFloat(s.replace('s', '')) * 1000;
64
+ return null;
65
+ };
66
+
67
+ useEffect(() => {
68
+ if (isExecuting) {
69
+ const start = Date.now();
70
+ setElapsedLabel('0ms');
71
+ if (intervalRef.current) clearInterval(intervalRef.current);
72
+ intervalRef.current = setInterval(() => {
73
+ setElapsedLabel(formatMs(Date.now() - start));
74
+ }, 100);
75
+ } else {
76
+ if (intervalRef.current) {
77
+ clearInterval(intervalRef.current);
78
+ intervalRef.current = null;
79
+ }
80
+ // Persist final time from cell.execution_time if available
81
+ const ms = parseExecTime(cell.execution_time as any);
82
+ if (ms != null) setElapsedLabel(formatMs(ms));
83
+ }
84
+ return () => {
85
+ if (intervalRef.current) {
86
+ clearInterval(intervalRef.current);
87
+ intervalRef.current = null;
88
+ }
89
+ };
90
+ }, [isExecuting, cell.execution_time]);
91
+ const [isEditing, setIsEditing] = useState(() => cell.cell_type === 'code' || !cell.source?.trim());
92
+
93
+ // Determine if this is a markdown cell with content in display mode
94
+ const isMarkdownWithContent = cell.cell_type === 'markdown' && !isEditing && cell.source?.trim();
95
+
96
+ useEffect(() => {
97
+ if (isEditing) {
98
+ if (!codeMirrorInstance.current && editorRef.current && typeof CodeMirror !== 'undefined') {
99
+ const editor = CodeMirror.fromTextArea(editorRef.current, {
100
+ mode: cell.cell_type === 'code' ? 'python' : 'text/plain',
101
+ lineNumbers: cell.cell_type === 'code',
102
+ theme: 'default',
103
+ lineWrapping: true,
104
+ placeholder: cell.cell_type === 'code' ? 'Enter code...' : 'Enter markdown...',
105
+ });
106
+ codeMirrorInstance.current = editor;
107
+
108
+ editor.on('change', (instance: any) => onUpdate(indexRef.current, instance.getValue()));
109
+ editor.on('focus', () => onSetActive(indexRef.current));
110
+ editor.on('blur', () => {
111
+ if (cell.cell_type === 'markdown') setIsEditing(false);
112
+ });
113
+ editor.on('keydown', (instance: any, event: KeyboardEvent) => {
114
+ if (event.shiftKey && event.key === 'Enter') {
115
+ event.preventDefault();
116
+ handleExecute();
117
+ }
118
+ });
119
+
120
+ if (editor.getValue() !== cell.source) {
121
+ editor.setValue(cell.source);
122
+ }
123
+ }
124
+ } else {
125
+ if (codeMirrorInstance.current) {
126
+ codeMirrorInstance.current.toTextArea();
127
+ codeMirrorInstance.current = null;
128
+ }
129
+ }
130
+ }, [isEditing, cell.source]);
131
+
132
+ const handleExecute = () => {
133
+ if (cell.cell_type === 'markdown') {
134
+ onExecute(indexRef.current); // Call onExecute for save logic
135
+ setIsEditing(false);
136
+ } else {
137
+ if (isExecuting) {
138
+ onInterrupt(indexRef.current);
139
+ } else {
140
+ onExecute(indexRef.current);
141
+ }
142
+ }
143
+ };
144
+
145
+ const handleCellClick = () => {
146
+ onSetActive(indexRef.current);
147
+ if (cell.cell_type === 'markdown') {
148
+ setIsEditing(true);
149
+ }
150
+ };
151
+
152
+ const handleFixIndentation = async () => {
153
+ try {
154
+ const fixedCode = await fixIndentation(cell.source);
155
+ onUpdate(indexRef.current, fixedCode);
156
+
157
+ // Update CodeMirror if it's initialized
158
+ if (codeMirrorInstance.current) {
159
+ codeMirrorInstance.current.setValue(fixedCode);
160
+ }
161
+ } catch (err) {
162
+ console.error('Failed to fix indentation:', err);
163
+ }
164
+ };
165
+
166
+ return (
167
+ <div className="cell-wrapper">
168
+ {!isMarkdownWithContent && (
169
+ <div className="cell-status-indicator">
170
+ <span className="status-indicator">
171
+ <span className="status-bracket">[</span>
172
+ {isExecuting ? (
173
+ <UpdateIcon className="w-1 h-1" />
174
+ ) : cell.error ? (
175
+ <X size={14} color="#dc2626" />
176
+ ) : cell.execution_count != null ? (
177
+ <Check size={14} color="#16a34a" />
178
+ ) : (
179
+ <span style={{ width: '14px', height: '14px', display: 'inline-block' }}></span>
180
+ )}
181
+ <span className="status-bracket">]</span>
182
+ </span>
183
+ {elapsedLabel && (
184
+ <span className="status-timer" title="Execution time">{elapsedLabel}</span>
185
+ )}
186
+ </div>
187
+ )}
188
+ <div className="add-cell-line add-line-above">
189
+ <AddCellButton onAddCell={(type) => onAddCell(type, indexRef.current)} />
190
+ </div>
191
+
192
+ <div
193
+ className={`cell ${isActive ? 'active' : ''} ${isExecuting ? 'executing' : ''} ${isMarkdownWithContent ? 'markdown-display-mode' : ''}`}
194
+ data-cell-index={index}
195
+ >
196
+ {/* Only show hover controls for non-markdown-display cells */}
197
+ {!isMarkdownWithContent && (
198
+ <div className="cell-hover-controls">
199
+ <div className="cell-actions-right">
200
+ <CellButton
201
+ icon={
202
+ <PlayIcon className="w-6 h-6" />
203
+ }
204
+ onClick={(e) => { e.stopPropagation(); handleExecute(); }}
205
+ title={isExecuting ? "Stop execution" : "Run cell"}
206
+ isLoading={isExecuting}
207
+ />
208
+ <CellButton
209
+ icon={
210
+ <RowSpacingIcon className="w-6 h-6" />
211
+ }
212
+ title="Drag to reorder"
213
+ />
214
+ <CellButton
215
+ icon={
216
+ <LinkBreak2Icon className="w-5 h-5" />
217
+ }
218
+ onClick={(e) => { e.stopPropagation(); onDelete(indexRef.current); }}
219
+ title="Delete cell"
220
+ />
221
+ </div>
222
+ </div>
223
+ )}
224
+
225
+ <div className={`cell-content ${isMarkdownWithContent ? 'cursor-pointer' : ''}`} onClick={handleCellClick}>
226
+ <div className="cell-input">
227
+ {isEditing || cell.cell_type === 'code' ? (
228
+ <div className={`cell-editor-container ${cell.cell_type === 'markdown' ? 'markdown-editor-container' : 'code-editor-container'}`}>
229
+ <textarea ref={editorRef} defaultValue={cell.source} className={`cell-editor ${cell.cell_type === 'markdown' ? 'markdown-editor' : 'code-editor'}`} />
230
+ </div>
231
+ ) : (
232
+ <MarkdownRenderer source={cell.source} onClick={() => setIsEditing(true)} />
233
+ )}
234
+ </div>
235
+ <CellOutput outputs={cell.outputs} error={cell.error} onFixIndentation={handleFixIndentation} />
236
+ </div>
237
+ </div>
238
+
239
+ <div className="add-cell-line add-line-below">
240
+ <AddCellButton onAddCell={(type) => onAddCell(type, indexRef.current + 1)} />
241
+ </div>
242
+ </div>
243
+ );
244
+ };
@@ -0,0 +1,58 @@
1
+ 'use client';
2
+
3
+ import React, { useState } from 'react';
4
+
5
+ interface CellButtonProps {
6
+ icon: React.ReactNode;
7
+ onClick?: (e: React.MouseEvent) => void;
8
+ title?: string;
9
+ disabled?: boolean;
10
+ isLoading?: boolean;
11
+ className?: string;
12
+ }
13
+
14
+ export const CellButton: React.FC<CellButtonProps> = ({
15
+ icon,
16
+ onClick,
17
+ title,
18
+ disabled = false,
19
+ isLoading = false,
20
+ className = ''
21
+ }) => {
22
+ const [isHovered, setIsHovered] = useState(false);
23
+
24
+ const handleClick = (e: React.MouseEvent) => {
25
+ if (onClick && !disabled && !isLoading) {
26
+ onClick(e);
27
+ }
28
+ };
29
+
30
+ return (
31
+ <button
32
+ type="button"
33
+ className={`cell-button ${className}`}
34
+ onClick={handleClick}
35
+ onMouseEnter={() => setIsHovered(true)}
36
+ onMouseLeave={() => setIsHovered(false)}
37
+ disabled={disabled || isLoading}
38
+ title={title}
39
+ style={{
40
+ width: '28px',
41
+ height: '28px',
42
+ border: '1px solid #D9DEE5',
43
+ borderRadius: '4px',
44
+ backgroundColor: isHovered ? '#E6E8EC' : 'rgba(243, 244, 246, 0.5)',
45
+ display: 'flex',
46
+ alignItems: 'center',
47
+ justifyContent: 'center',
48
+ cursor: disabled || isLoading ? 'not-allowed' : 'pointer',
49
+ transition: 'background-color 0.15s ease',
50
+ opacity: disabled ? 0.5 : 1
51
+ }}
52
+ >
53
+ {icon}
54
+ </button>
55
+ );
56
+ };
57
+
58
+ export default CellButton;
@@ -0,0 +1,70 @@
1
+ 'use client';
2
+
3
+ import { FC } from 'react';
4
+ import { Output } from '@/types/notebook';
5
+ import ErrorDisplay from './ErrorDisplay';
6
+
7
+ interface CellOutputProps {
8
+ outputs: Output[];
9
+ error: any;
10
+ onFixIndentation?: () => void;
11
+ }
12
+
13
+ const CellOutput: FC<CellOutputProps> = ({ outputs, error, onFixIndentation }) => {
14
+ if (error) {
15
+ return <ErrorDisplay error={error} onFixIndentation={onFixIndentation} />;
16
+ }
17
+
18
+ if (!outputs || outputs.length === 0) {
19
+ return null;
20
+ }
21
+
22
+ return (
23
+ <div className="cell-output">
24
+ <div className="output-content">
25
+ {outputs.map((output, index) => {
26
+ switch (output.output_type) {
27
+ case 'stream':
28
+ return (
29
+ <pre key={index} className={`output-stream ${output.name}`}>
30
+ {output.text}
31
+ </pre>
32
+ );
33
+ case 'execute_result':
34
+ return (
35
+ <pre key={index} className="output-result">
36
+ {output.data?.['text/plain']}
37
+ </pre>
38
+ );
39
+ case 'display_data': {
40
+ const img = (output as any).data?.['image/png'];
41
+ const alt = (output as any).data?.['text/plain'] || 'image/png';
42
+ if (img) {
43
+ return (
44
+ <div key={index} className="output-result">
45
+ <img src={`data:image/png;base64,${img}`} alt={alt} />
46
+ </div>
47
+ );
48
+ }
49
+ return (
50
+ <pre key={index} className="output-result">
51
+ {(output as any).data?.['text/plain']}
52
+ </pre>
53
+ );
54
+ }
55
+ case 'error':
56
+ return <ErrorDisplay key={index} error={output} onFixIndentation={onFixIndentation} />;
57
+ default:
58
+ return (
59
+ <pre key={index} className="output-unknown">
60
+ {JSON.stringify(output, null, 2)}
61
+ </pre>
62
+ );
63
+ }
64
+ })}
65
+ </div>
66
+ </div>
67
+ );
68
+ };
69
+
70
+ export default CellOutput;