more-compute 0.4.2__tar.gz → 0.4.4__tar.gz
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.
- {more_compute-0.4.2/more_compute.egg-info → more_compute-0.4.4}/PKG-INFO +1 -1
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/popups/ComputePopup.tsx +72 -42
- {more_compute-0.4.2 → more_compute-0.4.4/more_compute.egg-info}/PKG-INFO +1 -1
- more_compute-0.4.4/morecompute/__version__.py +1 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/notebook.py +21 -2
- more_compute-0.4.4/morecompute/utils/notebook_converter.py +297 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/utils/py_percent_parser.py +13 -3
- more_compute-0.4.2/morecompute/__version__.py +0 -1
- more_compute-0.4.2/morecompute/utils/notebook_converter.py +0 -129
- {more_compute-0.4.2 → more_compute-0.4.4}/LICENSE +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/MANIFEST.in +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/README.md +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/.gitignore +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/README.md +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/__init__.py +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/app/favicon.ico +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/app/globals.css +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/app/layout.tsx +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/app/page.tsx +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/Notebook.tsx +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/cell/AddCellButton.tsx +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/cell/CellButton.tsx +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/cell/MonacoCell.tsx +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/layout/ConnectionBanner.tsx +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/layout/Sidebar.tsx +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/modals/ConfirmModal.tsx +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/modals/ErrorModal.tsx +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/modals/SuccessModal.tsx +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/output/CellOutput.tsx +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/output/ErrorDisplay.tsx +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/output/MarkdownRenderer.tsx +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/popups/FilterPopup.tsx +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/popups/FolderPopup.tsx +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/popups/MetricsPopup.tsx +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/popups/PackagesPopup.tsx +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/popups/SettingsPopup.tsx +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/contexts/PodWebSocketContext.tsx +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/eslint.config.mjs +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/lib/api.ts +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/lib/monaco-themes.ts +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/lib/settings.ts +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/lib/themes.json +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/lib/websocket-native.ts +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/lib/websocket.ts +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/next-env.d.ts +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/next.config.mjs +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/next.config.ts +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/package-lock.json +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/package.json +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/postcss.config.mjs +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/assets/icons/add.svg +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/assets/icons/check.svg +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/assets/icons/copy.svg +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/assets/icons/folder.svg +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/assets/icons/metric.svg +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/assets/icons/packages.svg +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/assets/icons/play.svg +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/assets/icons/python.svg +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/assets/icons/setting.svg +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/assets/icons/stop.svg +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/assets/icons/trash.svg +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/assets/icons/up-down.svg +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/assets/icons/x.svg +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/file.svg +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/fonts/Fira.ttf +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/fonts/Tiempos.woff2 +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/fonts/VeraMono.ttf +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/globe.svg +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/next.svg +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/vercel.svg +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/window.svg +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/styling_README.md +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/tailwind.config.ts +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/tsconfig.json +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/frontend/types/notebook.ts +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/kernel_run.py +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/more_compute.egg-info/SOURCES.txt +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/more_compute.egg-info/dependency_links.txt +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/more_compute.egg-info/entry_points.txt +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/more_compute.egg-info/requires.txt +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/more_compute.egg-info/top_level.txt +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/__init__.py +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/cli.py +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/execution/__init__.py +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/execution/__main__.py +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/execution/executor.py +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/execution/worker.py +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/models/__init__.py +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/models/api_models.py +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/process_worker.py +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/server.py +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/services/data_manager.py +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/services/lsp_service.py +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/services/pod_manager.py +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/services/pod_monitor.py +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/services/prime_intellect.py +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/static/styles.css +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/utils/__init__.py +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/utils/cache_util.py +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/utils/cell_magics.py +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/utils/config_util.py +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/utils/error_utils.py +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/utils/line_magics.py +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/utils/notebook_util.py +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/utils/python_environment_util.py +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/utils/shell_utils.py +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/utils/special_commands.py +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/utils/system_environment_util.py +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/utils/zmq_util.py +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/pyproject.toml +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/setup.cfg +0 -0
- {more_compute-0.4.2 → more_compute-0.4.4}/setup.py +0 -0
|
@@ -74,6 +74,7 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
74
74
|
const [connectionHealth, setConnectionHealth] = useState<"healthy" | "unhealthy" | "unknown">("unknown");
|
|
75
75
|
const [searchQuery, setSearchQuery] = useState("");
|
|
76
76
|
const [discoveredPod, setDiscoveredPod] = useState<GPUPod | null>(null);
|
|
77
|
+
const [showApiKeyInput, setShowApiKeyInput] = useState(false);
|
|
77
78
|
|
|
78
79
|
// Filter popup state
|
|
79
80
|
const [showFilterPopup, setShowFilterPopup] = useState(false);
|
|
@@ -218,6 +219,16 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
218
219
|
setPods(pods);
|
|
219
220
|
} catch (err) {
|
|
220
221
|
console.error("Failed to load GPU pods:", err);
|
|
222
|
+
// If 401 error, API key is invalid - allow user to reset it
|
|
223
|
+
if (err instanceof Error && err.message.includes("401")) {
|
|
224
|
+
setApiConfigured(false);
|
|
225
|
+
setShowApiKeyInput(true);
|
|
226
|
+
setErrorModal({
|
|
227
|
+
isOpen: true,
|
|
228
|
+
title: "Invalid API Key",
|
|
229
|
+
message: "Your API key is invalid or has expired. Please enter a new API key to continue.",
|
|
230
|
+
});
|
|
231
|
+
}
|
|
221
232
|
} finally {
|
|
222
233
|
setLoading(false);
|
|
223
234
|
}
|
|
@@ -561,8 +572,10 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
561
572
|
try {
|
|
562
573
|
await setGpuApiKey(apiKey);
|
|
563
574
|
setApiConfigured(true);
|
|
575
|
+
setShowApiKeyInput(false);
|
|
564
576
|
setApiKey("");
|
|
565
577
|
await loadGPUPods();
|
|
578
|
+
await loadAvailableGPUs();
|
|
566
579
|
} catch (err) {
|
|
567
580
|
setSaveError(
|
|
568
581
|
err instanceof Error ? err.message : "Failed to save API key",
|
|
@@ -681,26 +694,43 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
681
694
|
<h3 className="runtime-section-title" style={{ fontSize: "12px", fontWeight: 500 }}>
|
|
682
695
|
Remote GPU Pods
|
|
683
696
|
</h3>
|
|
684
|
-
{apiConfigured && (
|
|
685
|
-
<
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
697
|
+
{apiConfigured && !showApiKeyInput && (
|
|
698
|
+
<div style={{ display: "flex", gap: "6px" }}>
|
|
699
|
+
<button
|
|
700
|
+
className="runtime-btn runtime-btn-secondary"
|
|
701
|
+
onClick={() => setShowApiKeyInput(true)}
|
|
702
|
+
style={{
|
|
703
|
+
fontSize: "11px",
|
|
704
|
+
padding: "6px 12px",
|
|
705
|
+
backgroundColor: "var(--text-secondary)",
|
|
706
|
+
color: "white",
|
|
707
|
+
border: "none",
|
|
708
|
+
borderRadius: "8px",
|
|
709
|
+
cursor: "pointer"
|
|
710
|
+
}}
|
|
711
|
+
>
|
|
712
|
+
Reset API Key
|
|
713
|
+
</button>
|
|
714
|
+
<button
|
|
715
|
+
className="runtime-btn runtime-btn-secondary"
|
|
716
|
+
onClick={handleConnectToPrimeIntellect}
|
|
717
|
+
style={{
|
|
718
|
+
fontSize: "11px",
|
|
719
|
+
padding: "6px 12px",
|
|
720
|
+
backgroundColor: "var(--mc-text-color)",
|
|
721
|
+
color: "var(--mc-cell-background)",
|
|
722
|
+
border: "none",
|
|
723
|
+
borderRadius: "8px",
|
|
724
|
+
cursor: "pointer"
|
|
725
|
+
}}
|
|
726
|
+
>
|
|
727
|
+
Manage
|
|
728
|
+
</button>
|
|
729
|
+
</div>
|
|
700
730
|
)}
|
|
701
731
|
</div>
|
|
702
732
|
|
|
703
|
-
{apiConfigured === false ? (
|
|
733
|
+
{(apiConfigured === false || showApiKeyInput) ? (
|
|
704
734
|
<div className="runtime-empty-state" style={{ padding: "6px" }}>
|
|
705
735
|
<p
|
|
706
736
|
style={{
|
|
@@ -750,8 +780,8 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
750
780
|
flex: 1,
|
|
751
781
|
fontSize: "11px",
|
|
752
782
|
padding: "6px 12px",
|
|
753
|
-
backgroundColor: "
|
|
754
|
-
color: "
|
|
783
|
+
backgroundColor: "var(--mc-text-color)",
|
|
784
|
+
color: "var(--mc-cell-background)",
|
|
755
785
|
border: "none",
|
|
756
786
|
borderRadius: "8px",
|
|
757
787
|
cursor: "pointer"
|
|
@@ -765,8 +795,8 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
765
795
|
style={{
|
|
766
796
|
fontSize: "11px",
|
|
767
797
|
padding: "6px 12px",
|
|
768
|
-
backgroundColor: "
|
|
769
|
-
color: "
|
|
798
|
+
backgroundColor: "var(--mc-text-color)",
|
|
799
|
+
color: "var(--mc-cell-background)",
|
|
770
800
|
border: "none",
|
|
771
801
|
borderRadius: "8px",
|
|
772
802
|
cursor: "pointer"
|
|
@@ -788,19 +818,19 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
788
818
|
) : !connectedPodId && discoveredPod ? (
|
|
789
819
|
<div style={{ padding: "8px 0" }}>
|
|
790
820
|
<div style={{
|
|
791
|
-
backgroundColor: "
|
|
792
|
-
border: "1px solid
|
|
821
|
+
backgroundColor: "rgba(251, 191, 36, 0.1)",
|
|
822
|
+
border: "1px solid rgba(251, 191, 36, 0.3)",
|
|
793
823
|
borderRadius: "8px",
|
|
794
824
|
padding: "12px",
|
|
795
825
|
marginBottom: "8px"
|
|
796
826
|
}}>
|
|
797
|
-
<div style={{ fontSize: "11px", fontWeight: 600, color: "
|
|
827
|
+
<div style={{ fontSize: "11px", fontWeight: 600, color: "var(--mc-text-color)", marginBottom: "4px" }}>
|
|
798
828
|
⚠️ Running Pod Detected
|
|
799
829
|
</div>
|
|
800
|
-
<div style={{ fontSize: "10px", color: "
|
|
830
|
+
<div style={{ fontSize: "10px", color: "var(--text-secondary)", marginBottom: "8px" }}>
|
|
801
831
|
Found a running pod but not connected (backend may have restarted). This pod is still costing money!
|
|
802
832
|
</div>
|
|
803
|
-
<div style={{ fontSize: "10px", marginBottom: "8px" }}>
|
|
833
|
+
<div style={{ fontSize: "10px", marginBottom: "8px", color: "var(--mc-text-color)" }}>
|
|
804
834
|
<span style={{ fontWeight: 500 }}>{discoveredPod.name}</span> • {discoveredPod.gpuType} • ${discoveredPod.costPerHour.toFixed(2)}/hour
|
|
805
835
|
</div>
|
|
806
836
|
<div style={{ display: "flex", gap: "6px" }}>
|
|
@@ -811,8 +841,8 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
811
841
|
flex: 1,
|
|
812
842
|
fontSize: "10px",
|
|
813
843
|
padding: "6px 12px",
|
|
814
|
-
backgroundColor: "
|
|
815
|
-
color: "
|
|
844
|
+
backgroundColor: "rgba(251, 191, 36, 0.8)",
|
|
845
|
+
color: "var(--mc-text-color)",
|
|
816
846
|
border: "none",
|
|
817
847
|
borderRadius: "8px",
|
|
818
848
|
cursor: "pointer",
|
|
@@ -828,7 +858,7 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
828
858
|
style={{
|
|
829
859
|
fontSize: "10px",
|
|
830
860
|
padding: "6px 12px",
|
|
831
|
-
backgroundColor: "
|
|
861
|
+
backgroundColor: "rgba(220, 38, 38, 0.9)",
|
|
832
862
|
color: "white",
|
|
833
863
|
border: "none",
|
|
834
864
|
borderRadius: "8px",
|
|
@@ -855,7 +885,7 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
855
885
|
<span style={{
|
|
856
886
|
marginLeft: "6px",
|
|
857
887
|
fontSize: "9px",
|
|
858
|
-
backgroundColor: "
|
|
888
|
+
backgroundColor: "rgba(16, 185, 129, 0.9)",
|
|
859
889
|
color: "white",
|
|
860
890
|
padding: "2px 6px",
|
|
861
891
|
borderRadius: "4px"
|
|
@@ -877,8 +907,8 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
877
907
|
style={{
|
|
878
908
|
fontSize: "10px",
|
|
879
909
|
padding: "6px 12px",
|
|
880
|
-
backgroundColor: "
|
|
881
|
-
color: "
|
|
910
|
+
backgroundColor: "var(--mc-text-color)",
|
|
911
|
+
color: "var(--mc-cell-background)",
|
|
882
912
|
border: "none",
|
|
883
913
|
borderRadius: "8px",
|
|
884
914
|
cursor: "pointer"
|
|
@@ -893,7 +923,7 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
893
923
|
style={{
|
|
894
924
|
fontSize: "10px",
|
|
895
925
|
padding: "6px 12px",
|
|
896
|
-
backgroundColor: "
|
|
926
|
+
backgroundColor: "rgba(220, 38, 38, 0.9)",
|
|
897
927
|
color: "white",
|
|
898
928
|
border: "none",
|
|
899
929
|
borderRadius: "8px",
|
|
@@ -912,7 +942,7 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
912
942
|
style={{
|
|
913
943
|
fontSize: "10px",
|
|
914
944
|
padding: "6px 12px",
|
|
915
|
-
backgroundColor: "
|
|
945
|
+
backgroundColor: "rgba(16, 185, 129, 0.9)",
|
|
916
946
|
color: "white",
|
|
917
947
|
border: "none",
|
|
918
948
|
borderRadius: "8px",
|
|
@@ -928,7 +958,7 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
928
958
|
style={{
|
|
929
959
|
fontSize: "10px",
|
|
930
960
|
padding: "6px 12px",
|
|
931
|
-
backgroundColor: "
|
|
961
|
+
backgroundColor: "rgba(220, 38, 38, 0.9)",
|
|
932
962
|
color: "white",
|
|
933
963
|
border: "none",
|
|
934
964
|
borderRadius: "8px",
|
|
@@ -975,10 +1005,10 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
975
1005
|
flex: 1,
|
|
976
1006
|
padding: "6px 12px",
|
|
977
1007
|
fontSize: "11px",
|
|
978
|
-
border: "1px solid
|
|
1008
|
+
border: "1px solid var(--mc-border)",
|
|
979
1009
|
borderRadius: "8px",
|
|
980
|
-
backgroundColor: "var(--background)",
|
|
981
|
-
color: "var(--text)",
|
|
1010
|
+
backgroundColor: "var(--mc-background)",
|
|
1011
|
+
color: "var(--mc-text-color)",
|
|
982
1012
|
outline: "none",
|
|
983
1013
|
}}
|
|
984
1014
|
/>
|
|
@@ -989,8 +1019,8 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
989
1019
|
padding: "6px 12px",
|
|
990
1020
|
fontSize: "11px",
|
|
991
1021
|
position: "relative",
|
|
992
|
-
backgroundColor: "
|
|
993
|
-
color: "
|
|
1022
|
+
backgroundColor: "var(--mc-text-color)",
|
|
1023
|
+
color: "var(--mc-cell-background)",
|
|
994
1024
|
border: "none",
|
|
995
1025
|
borderRadius: "8px",
|
|
996
1026
|
cursor: "pointer"
|
|
@@ -1119,8 +1149,8 @@ const ComputePopup: React.FC<ComputePopupProps> = ({ onClose }) => {
|
|
|
1119
1149
|
fontSize: "10px",
|
|
1120
1150
|
padding: "6px 16px",
|
|
1121
1151
|
whiteSpace: "nowrap",
|
|
1122
|
-
backgroundColor: "
|
|
1123
|
-
color: "
|
|
1152
|
+
backgroundColor: "var(--mc-text-color)",
|
|
1153
|
+
color: "var(--mc-cell-background)",
|
|
1124
1154
|
border: "none",
|
|
1125
1155
|
borderRadius: "8px",
|
|
1126
1156
|
cursor: "pointer"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.4"
|
|
@@ -3,6 +3,7 @@ from pathlib import Path
|
|
|
3
3
|
from typing import List, Dict, Any
|
|
4
4
|
from uuid import uuid4
|
|
5
5
|
from .utils.py_percent_parser import parse_py_percent, generate_py_percent
|
|
6
|
+
from .utils.notebook_converter import detect_colab_format, parse_colab_py
|
|
6
7
|
|
|
7
8
|
class Notebook:
|
|
8
9
|
"""Manages the state of a notebook's cells."""
|
|
@@ -92,11 +93,29 @@ class Notebook:
|
|
|
92
93
|
|
|
93
94
|
# Check file extension
|
|
94
95
|
if path.suffix == '.py':
|
|
95
|
-
# Load .py file
|
|
96
|
+
# Load .py file - auto-detect format
|
|
96
97
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
97
98
|
content = f.read()
|
|
98
99
|
|
|
99
|
-
|
|
100
|
+
# Detect if it's Colab format (docstrings) or py:percent format (# %%)
|
|
101
|
+
if detect_colab_format(content):
|
|
102
|
+
print(f" Detected Colab format - auto-converting to py:percent")
|
|
103
|
+
# Parse Colab format (docstrings as markdown)
|
|
104
|
+
loaded_cells = parse_colab_py(content)
|
|
105
|
+
data = {
|
|
106
|
+
'cells': loaded_cells,
|
|
107
|
+
'metadata': {
|
|
108
|
+
'kernelspec': {
|
|
109
|
+
'display_name': 'Python 3',
|
|
110
|
+
'language': 'python',
|
|
111
|
+
'name': 'python3'
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else:
|
|
116
|
+
# Parse py:percent format (# %% markers)
|
|
117
|
+
data = parse_py_percent(content)
|
|
118
|
+
|
|
100
119
|
loaded_cells = data.get('cells', [])
|
|
101
120
|
|
|
102
121
|
# Ensure stable IDs for all cells
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"""Converter utilities for notebook formats."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import List, Set, Dict
|
|
7
|
+
from .py_percent_parser import generate_py_percent, parse_py_percent
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def extract_pip_dependencies(notebook_data: dict) -> Set[str]:
|
|
11
|
+
"""
|
|
12
|
+
Extract package names from !pip install and %pip install commands.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
notebook_data: Parsed notebook JSON
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
Set of package names
|
|
19
|
+
"""
|
|
20
|
+
packages = set()
|
|
21
|
+
|
|
22
|
+
for cell in notebook_data.get('cells', []):
|
|
23
|
+
if cell.get('cell_type') != 'code':
|
|
24
|
+
continue
|
|
25
|
+
|
|
26
|
+
source = cell.get('source', [])
|
|
27
|
+
if isinstance(source, list):
|
|
28
|
+
source = ''.join(source)
|
|
29
|
+
|
|
30
|
+
# Match: !pip install package1 package2
|
|
31
|
+
# Match: %pip install package1 package2
|
|
32
|
+
pip_pattern = r'[!%]pip\s+install\s+([^\n]+)'
|
|
33
|
+
matches = re.finditer(pip_pattern, source)
|
|
34
|
+
|
|
35
|
+
for match in matches:
|
|
36
|
+
install_line = match.group(1)
|
|
37
|
+
# Remove common flags
|
|
38
|
+
install_line = re.sub(r'--[^\s]+\s*', '', install_line)
|
|
39
|
+
install_line = re.sub(r'-[qU]\s*', '', install_line)
|
|
40
|
+
|
|
41
|
+
# Extract package names (handle package==version format)
|
|
42
|
+
parts = install_line.split()
|
|
43
|
+
for part in parts:
|
|
44
|
+
part = part.strip()
|
|
45
|
+
if part and not part.startswith('-'):
|
|
46
|
+
packages.add(part)
|
|
47
|
+
|
|
48
|
+
return packages
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def convert_ipynb_to_py(ipynb_path: Path, output_path: Path, include_uv_deps: bool = True) -> None:
|
|
52
|
+
"""
|
|
53
|
+
Convert .ipynb notebook to .py format with py:percent cell markers.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
ipynb_path: Path to input .ipynb file
|
|
57
|
+
output_path: Path to output .py file
|
|
58
|
+
include_uv_deps: Whether to add UV inline script dependencies
|
|
59
|
+
"""
|
|
60
|
+
# Read notebook
|
|
61
|
+
with open(ipynb_path, 'r', encoding='utf-8') as f:
|
|
62
|
+
notebook_data = json.load(f)
|
|
63
|
+
|
|
64
|
+
cells = notebook_data.get('cells', [])
|
|
65
|
+
|
|
66
|
+
# Generate UV dependencies header if requested
|
|
67
|
+
header_lines = []
|
|
68
|
+
if include_uv_deps:
|
|
69
|
+
dependencies = extract_pip_dependencies(notebook_data)
|
|
70
|
+
if dependencies:
|
|
71
|
+
header_lines.append('# /// script')
|
|
72
|
+
header_lines.append('# dependencies = [')
|
|
73
|
+
for dep in sorted(dependencies):
|
|
74
|
+
header_lines.append(f'# "{dep}",')
|
|
75
|
+
header_lines.append('# ]')
|
|
76
|
+
header_lines.append('# ///')
|
|
77
|
+
header_lines.append('')
|
|
78
|
+
|
|
79
|
+
# Generate py:percent format
|
|
80
|
+
py_content = generate_py_percent(cells)
|
|
81
|
+
|
|
82
|
+
# Combine header and content
|
|
83
|
+
if header_lines:
|
|
84
|
+
final_content = '\n'.join(header_lines) + '\n' + py_content
|
|
85
|
+
else:
|
|
86
|
+
final_content = py_content
|
|
87
|
+
|
|
88
|
+
# Write output
|
|
89
|
+
with open(output_path, 'w', encoding='utf-8') as f:
|
|
90
|
+
f.write(final_content)
|
|
91
|
+
|
|
92
|
+
print(f"✓ Converted {ipynb_path.name} → {output_path.name}")
|
|
93
|
+
|
|
94
|
+
# Show dependencies if found
|
|
95
|
+
if include_uv_deps and dependencies:
|
|
96
|
+
print(f" Found dependencies: {', '.join(sorted(dependencies))}")
|
|
97
|
+
print(f" Run with: more-compute {output_path.name}")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def detect_colab_format(content: str) -> bool:
|
|
101
|
+
"""
|
|
102
|
+
Detect if a .py file is in Colab export format (docstrings as markdown).
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
content: Raw .py file content
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
True if appears to be Colab format, False otherwise
|
|
109
|
+
"""
|
|
110
|
+
# Check for actual cell markers (# %% at start of line)
|
|
111
|
+
# Must be ONLY # %% optionally followed by [markdown] or whitespace
|
|
112
|
+
# NOT # %%capture, # %%time, etc. (IPython magics)
|
|
113
|
+
has_cell_markers = bool(re.search(r'^\s*# %%\s*(?:\[markdown\])?\s*$', content, re.MULTILINE))
|
|
114
|
+
|
|
115
|
+
# If it has # %% markers, it's NOT Colab format (it's py:percent)
|
|
116
|
+
# Even if it has a Colab header comment!
|
|
117
|
+
if has_cell_markers:
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
# Check for multi-line docstrings (Colab's markdown format)
|
|
121
|
+
has_docstrings = '"""' in content
|
|
122
|
+
|
|
123
|
+
# Colab format: has docstrings and no cell markers
|
|
124
|
+
return has_docstrings
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def parse_colab_py(content: str) -> List[Dict]:
|
|
128
|
+
"""
|
|
129
|
+
Parse Colab-exported .py file into cell structure.
|
|
130
|
+
|
|
131
|
+
Colab format uses:
|
|
132
|
+
- Multi-line docstrings ('''..''' or \"\"\"...\"\"\") for markdown cells
|
|
133
|
+
- Regular Python code for code cells
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
content: Raw .py file content from Colab export
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
List of cell dicts with 'cell_type' and 'source'
|
|
140
|
+
"""
|
|
141
|
+
cells = []
|
|
142
|
+
|
|
143
|
+
# Split on docstring boundaries
|
|
144
|
+
# Pattern matches both ''' and """ with optional content
|
|
145
|
+
pattern = r'("""[\s\S]*?"""|\'\'\'[\s\S]*?\'\'\')'
|
|
146
|
+
parts = re.split(pattern, content)
|
|
147
|
+
|
|
148
|
+
for part in parts:
|
|
149
|
+
part = part.strip()
|
|
150
|
+
if not part:
|
|
151
|
+
continue
|
|
152
|
+
|
|
153
|
+
# Check if this is a docstring (markdown)
|
|
154
|
+
if (part.startswith('"""') and part.endswith('"""')) or \
|
|
155
|
+
(part.startswith("'''") and part.endswith("'''")):
|
|
156
|
+
# It's a markdown cell
|
|
157
|
+
# Remove the triple quotes
|
|
158
|
+
markdown_content = part[3:-3].strip()
|
|
159
|
+
|
|
160
|
+
# Skip empty markdown cells
|
|
161
|
+
if markdown_content:
|
|
162
|
+
cells.append({
|
|
163
|
+
'cell_type': 'markdown',
|
|
164
|
+
'source': markdown_content,
|
|
165
|
+
'metadata': {}
|
|
166
|
+
})
|
|
167
|
+
else:
|
|
168
|
+
# It's a code cell
|
|
169
|
+
# Skip commented out code and special markers
|
|
170
|
+
if part and not part.startswith('# Commented out IPython magic'):
|
|
171
|
+
cells.append({
|
|
172
|
+
'cell_type': 'code',
|
|
173
|
+
'source': part,
|
|
174
|
+
'metadata': {},
|
|
175
|
+
'execution_count': None,
|
|
176
|
+
'outputs': []
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
return cells
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def convert_colab_py_to_py_percent(input_path: Path, output_path: Path, include_uv_deps: bool = True) -> None:
|
|
183
|
+
"""
|
|
184
|
+
Convert Colab-exported .py file to py:percent format (# %% markers).
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
input_path: Path to Colab .py file
|
|
188
|
+
output_path: Path to output .py file with # %% markers
|
|
189
|
+
include_uv_deps: Whether to extract and add UV inline script dependencies
|
|
190
|
+
"""
|
|
191
|
+
# Read Colab .py file
|
|
192
|
+
with open(input_path, 'r', encoding='utf-8') as f:
|
|
193
|
+
content = f.read()
|
|
194
|
+
|
|
195
|
+
# Detect format
|
|
196
|
+
if not detect_colab_format(content):
|
|
197
|
+
print(f"Warning: {input_path.name} doesn't appear to be in Colab format")
|
|
198
|
+
print(f" (Already has # %% markers or missing docstrings)")
|
|
199
|
+
return
|
|
200
|
+
|
|
201
|
+
# Parse Colab format into cells
|
|
202
|
+
cells = parse_colab_py(content)
|
|
203
|
+
|
|
204
|
+
if not cells:
|
|
205
|
+
print(f"Error: No cells found in {input_path.name}")
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
# Extract dependencies if requested
|
|
209
|
+
header_lines = []
|
|
210
|
+
if include_uv_deps:
|
|
211
|
+
# Create a temporary notebook structure to extract dependencies
|
|
212
|
+
temp_notebook = {'cells': cells}
|
|
213
|
+
dependencies = extract_pip_dependencies(temp_notebook)
|
|
214
|
+
|
|
215
|
+
if dependencies:
|
|
216
|
+
header_lines.append('# /// script')
|
|
217
|
+
header_lines.append('# dependencies = [')
|
|
218
|
+
for dep in sorted(dependencies):
|
|
219
|
+
header_lines.append(f'# "{dep}",')
|
|
220
|
+
header_lines.append('# ]')
|
|
221
|
+
header_lines.append('# ///')
|
|
222
|
+
header_lines.append('')
|
|
223
|
+
|
|
224
|
+
# Generate py:percent format
|
|
225
|
+
py_content = generate_py_percent(cells)
|
|
226
|
+
|
|
227
|
+
# Combine header and content
|
|
228
|
+
if header_lines:
|
|
229
|
+
final_content = '\n'.join(header_lines) + '\n' + py_content
|
|
230
|
+
else:
|
|
231
|
+
final_content = py_content
|
|
232
|
+
|
|
233
|
+
# Write output
|
|
234
|
+
with open(output_path, 'w', encoding='utf-8') as f:
|
|
235
|
+
f.write(final_content)
|
|
236
|
+
|
|
237
|
+
print(f"✓ Converted Colab format {input_path.name} → {output_path.name}")
|
|
238
|
+
print(f" Format: Colab docstrings → py:percent (# %%) markers")
|
|
239
|
+
|
|
240
|
+
if include_uv_deps and dependencies:
|
|
241
|
+
print(f" Found dependencies: {', '.join(sorted(dependencies))}")
|
|
242
|
+
print(f" Run with: more-compute {output_path.name}")
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def convert_py_to_ipynb(py_path: Path, output_path: Path) -> None:
|
|
246
|
+
"""
|
|
247
|
+
Convert .py notebook to .ipynb format.
|
|
248
|
+
|
|
249
|
+
Automatically detects format (Colab, VSCode, JupyterLab).
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
py_path: Path to input .py file
|
|
253
|
+
output_path: Path to output .ipynb file
|
|
254
|
+
"""
|
|
255
|
+
# Read .py file
|
|
256
|
+
with open(py_path, 'r', encoding='utf-8') as f:
|
|
257
|
+
py_content = f.read()
|
|
258
|
+
|
|
259
|
+
# Detect format and parse accordingly
|
|
260
|
+
if detect_colab_format(py_content):
|
|
261
|
+
# Parse Colab format (docstrings as markdown)
|
|
262
|
+
cells = parse_colab_py(py_content)
|
|
263
|
+
notebook_data = {
|
|
264
|
+
'cells': cells,
|
|
265
|
+
'metadata': {
|
|
266
|
+
'kernelspec': {
|
|
267
|
+
'display_name': 'Python 3',
|
|
268
|
+
'language': 'python',
|
|
269
|
+
'name': 'python3'
|
|
270
|
+
},
|
|
271
|
+
'language_info': {
|
|
272
|
+
'name': 'python',
|
|
273
|
+
'version': '3.8.0'
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
'nbformat': 4,
|
|
277
|
+
'nbformat_minor': 4
|
|
278
|
+
}
|
|
279
|
+
else:
|
|
280
|
+
# Parse py:percent format (# %% or # In[N]:)
|
|
281
|
+
notebook_data = parse_py_percent(py_content)
|
|
282
|
+
|
|
283
|
+
# Ensure source is in list format (Jupyter notebook standard)
|
|
284
|
+
for cell in notebook_data.get('cells', []):
|
|
285
|
+
source = cell.get('source', '')
|
|
286
|
+
if isinstance(source, str):
|
|
287
|
+
# Split into lines and keep newlines (Jupyter format)
|
|
288
|
+
lines = source.split('\n')
|
|
289
|
+
# Add \n to each line except the last
|
|
290
|
+
cell['source'] = [line + '\n' for line in lines[:-1]] + ([lines[-1]] if lines[-1] else [])
|
|
291
|
+
|
|
292
|
+
# Write .ipynb file
|
|
293
|
+
with open(output_path, 'w', encoding='utf-8') as f:
|
|
294
|
+
json.dump(notebook_data, f, indent=1, ensure_ascii=False)
|
|
295
|
+
|
|
296
|
+
print(f"Converted {py_path.name} -> {output_path.name}")
|
|
297
|
+
print(f" Upload to Google Colab or open in Jupyter")
|
|
@@ -8,6 +8,10 @@ def parse_py_percent(content: str) -> Dict:
|
|
|
8
8
|
"""
|
|
9
9
|
Parse py:percent format Python file into notebook structure.
|
|
10
10
|
|
|
11
|
+
Supports multiple formats:
|
|
12
|
+
# %% - VSCode/PyCharm format
|
|
13
|
+
# In[N]: - JupyterLab export format
|
|
14
|
+
|
|
11
15
|
Format:
|
|
12
16
|
# %%
|
|
13
17
|
code cell content
|
|
@@ -23,9 +27,15 @@ def parse_py_percent(content: str) -> Dict:
|
|
|
23
27
|
"""
|
|
24
28
|
cells = []
|
|
25
29
|
|
|
26
|
-
#
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
# Check if using JupyterLab In[] format
|
|
31
|
+
has_in_markers = bool(re.search(r'# In\[\d+\]:', content))
|
|
32
|
+
|
|
33
|
+
if has_in_markers:
|
|
34
|
+
# Parse JupyterLab # In[N]: format
|
|
35
|
+
parts = re.split(r'(# In\[\d+\]:.*?\n)', content)
|
|
36
|
+
else:
|
|
37
|
+
# Parse VSCode # %% format
|
|
38
|
+
parts = re.split(r'(# %%.*?\n)', content)
|
|
29
39
|
|
|
30
40
|
# First part before any cell marker (usually imports/metadata)
|
|
31
41
|
if parts[0].strip():
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.4.2"
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
"""Converter utilities for notebook formats."""
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
import re
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
from typing import List, Set
|
|
7
|
-
from .py_percent_parser import generate_py_percent, parse_py_percent
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def extract_pip_dependencies(notebook_data: dict) -> Set[str]:
|
|
11
|
-
"""
|
|
12
|
-
Extract package names from !pip install and %pip install commands.
|
|
13
|
-
|
|
14
|
-
Args:
|
|
15
|
-
notebook_data: Parsed notebook JSON
|
|
16
|
-
|
|
17
|
-
Returns:
|
|
18
|
-
Set of package names
|
|
19
|
-
"""
|
|
20
|
-
packages = set()
|
|
21
|
-
|
|
22
|
-
for cell in notebook_data.get('cells', []):
|
|
23
|
-
if cell.get('cell_type') != 'code':
|
|
24
|
-
continue
|
|
25
|
-
|
|
26
|
-
source = cell.get('source', [])
|
|
27
|
-
if isinstance(source, list):
|
|
28
|
-
source = ''.join(source)
|
|
29
|
-
|
|
30
|
-
# Match: !pip install package1 package2
|
|
31
|
-
# Match: %pip install package1 package2
|
|
32
|
-
pip_pattern = r'[!%]pip\s+install\s+([^\n]+)'
|
|
33
|
-
matches = re.finditer(pip_pattern, source)
|
|
34
|
-
|
|
35
|
-
for match in matches:
|
|
36
|
-
install_line = match.group(1)
|
|
37
|
-
# Remove common flags
|
|
38
|
-
install_line = re.sub(r'--[^\s]+\s*', '', install_line)
|
|
39
|
-
install_line = re.sub(r'-[qU]\s*', '', install_line)
|
|
40
|
-
|
|
41
|
-
# Extract package names (handle package==version format)
|
|
42
|
-
parts = install_line.split()
|
|
43
|
-
for part in parts:
|
|
44
|
-
part = part.strip()
|
|
45
|
-
if part and not part.startswith('-'):
|
|
46
|
-
packages.add(part)
|
|
47
|
-
|
|
48
|
-
return packages
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def convert_ipynb_to_py(ipynb_path: Path, output_path: Path, include_uv_deps: bool = True) -> None:
|
|
52
|
-
"""
|
|
53
|
-
Convert .ipynb notebook to .py format with py:percent cell markers.
|
|
54
|
-
|
|
55
|
-
Args:
|
|
56
|
-
ipynb_path: Path to input .ipynb file
|
|
57
|
-
output_path: Path to output .py file
|
|
58
|
-
include_uv_deps: Whether to add UV inline script dependencies
|
|
59
|
-
"""
|
|
60
|
-
# Read notebook
|
|
61
|
-
with open(ipynb_path, 'r', encoding='utf-8') as f:
|
|
62
|
-
notebook_data = json.load(f)
|
|
63
|
-
|
|
64
|
-
cells = notebook_data.get('cells', [])
|
|
65
|
-
|
|
66
|
-
# Generate UV dependencies header if requested
|
|
67
|
-
header_lines = []
|
|
68
|
-
if include_uv_deps:
|
|
69
|
-
dependencies = extract_pip_dependencies(notebook_data)
|
|
70
|
-
if dependencies:
|
|
71
|
-
header_lines.append('# /// script')
|
|
72
|
-
header_lines.append('# dependencies = [')
|
|
73
|
-
for dep in sorted(dependencies):
|
|
74
|
-
header_lines.append(f'# "{dep}",')
|
|
75
|
-
header_lines.append('# ]')
|
|
76
|
-
header_lines.append('# ///')
|
|
77
|
-
header_lines.append('')
|
|
78
|
-
|
|
79
|
-
# Generate py:percent format
|
|
80
|
-
py_content = generate_py_percent(cells)
|
|
81
|
-
|
|
82
|
-
# Combine header and content
|
|
83
|
-
if header_lines:
|
|
84
|
-
final_content = '\n'.join(header_lines) + '\n' + py_content
|
|
85
|
-
else:
|
|
86
|
-
final_content = py_content
|
|
87
|
-
|
|
88
|
-
# Write output
|
|
89
|
-
with open(output_path, 'w', encoding='utf-8') as f:
|
|
90
|
-
f.write(final_content)
|
|
91
|
-
|
|
92
|
-
print(f"✓ Converted {ipynb_path.name} → {output_path.name}")
|
|
93
|
-
|
|
94
|
-
# Show dependencies if found
|
|
95
|
-
if include_uv_deps and dependencies:
|
|
96
|
-
print(f" Found dependencies: {', '.join(sorted(dependencies))}")
|
|
97
|
-
print(f" Run with: more-compute {output_path.name}")
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def convert_py_to_ipynb(py_path: Path, output_path: Path) -> None:
|
|
101
|
-
"""
|
|
102
|
-
Convert .py notebook to .ipynb format.
|
|
103
|
-
|
|
104
|
-
Args:
|
|
105
|
-
py_path: Path to input .py file
|
|
106
|
-
output_path: Path to output .ipynb file
|
|
107
|
-
"""
|
|
108
|
-
# Read .py file
|
|
109
|
-
with open(py_path, 'r', encoding='utf-8') as f:
|
|
110
|
-
py_content = f.read()
|
|
111
|
-
|
|
112
|
-
# Parse py:percent format to notebook structure
|
|
113
|
-
notebook_data = parse_py_percent(py_content)
|
|
114
|
-
|
|
115
|
-
# Ensure source is in list format (Jupyter notebook standard)
|
|
116
|
-
for cell in notebook_data.get('cells', []):
|
|
117
|
-
source = cell.get('source', '')
|
|
118
|
-
if isinstance(source, str):
|
|
119
|
-
# Split into lines and keep newlines (Jupyter format)
|
|
120
|
-
lines = source.split('\n')
|
|
121
|
-
# Add \n to each line except the last
|
|
122
|
-
cell['source'] = [line + '\n' for line in lines[:-1]] + ([lines[-1]] if lines[-1] else [])
|
|
123
|
-
|
|
124
|
-
# Write .ipynb file
|
|
125
|
-
with open(output_path, 'w', encoding='utf-8') as f:
|
|
126
|
-
json.dump(notebook_data, f, indent=1, ensure_ascii=False)
|
|
127
|
-
|
|
128
|
-
print(f"Converted {py_path.name} -> {output_path.name}")
|
|
129
|
-
print(f" Upload to Google Colab or open in Jupyter")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|