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.
Files changed (112) hide show
  1. {more_compute-0.4.2/more_compute.egg-info → more_compute-0.4.4}/PKG-INFO +1 -1
  2. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/popups/ComputePopup.tsx +72 -42
  3. {more_compute-0.4.2 → more_compute-0.4.4/more_compute.egg-info}/PKG-INFO +1 -1
  4. more_compute-0.4.4/morecompute/__version__.py +1 -0
  5. {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/notebook.py +21 -2
  6. more_compute-0.4.4/morecompute/utils/notebook_converter.py +297 -0
  7. {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/utils/py_percent_parser.py +13 -3
  8. more_compute-0.4.2/morecompute/__version__.py +0 -1
  9. more_compute-0.4.2/morecompute/utils/notebook_converter.py +0 -129
  10. {more_compute-0.4.2 → more_compute-0.4.4}/LICENSE +0 -0
  11. {more_compute-0.4.2 → more_compute-0.4.4}/MANIFEST.in +0 -0
  12. {more_compute-0.4.2 → more_compute-0.4.4}/README.md +0 -0
  13. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/.gitignore +0 -0
  14. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/README.md +0 -0
  15. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/__init__.py +0 -0
  16. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/app/favicon.ico +0 -0
  17. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/app/globals.css +0 -0
  18. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/app/layout.tsx +0 -0
  19. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/app/page.tsx +0 -0
  20. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/Notebook.tsx +0 -0
  21. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/cell/AddCellButton.tsx +0 -0
  22. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/cell/CellButton.tsx +0 -0
  23. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/cell/MonacoCell.tsx +0 -0
  24. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/layout/ConnectionBanner.tsx +0 -0
  25. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/layout/Sidebar.tsx +0 -0
  26. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/modals/ConfirmModal.tsx +0 -0
  27. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/modals/ErrorModal.tsx +0 -0
  28. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/modals/SuccessModal.tsx +0 -0
  29. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/output/CellOutput.tsx +0 -0
  30. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/output/ErrorDisplay.tsx +0 -0
  31. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/output/MarkdownRenderer.tsx +0 -0
  32. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/popups/FilterPopup.tsx +0 -0
  33. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/popups/FolderPopup.tsx +0 -0
  34. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/popups/MetricsPopup.tsx +0 -0
  35. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/popups/PackagesPopup.tsx +0 -0
  36. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/components/popups/SettingsPopup.tsx +0 -0
  37. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/contexts/PodWebSocketContext.tsx +0 -0
  38. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/eslint.config.mjs +0 -0
  39. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/lib/api.ts +0 -0
  40. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/lib/monaco-themes.ts +0 -0
  41. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/lib/settings.ts +0 -0
  42. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/lib/themes.json +0 -0
  43. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/lib/websocket-native.ts +0 -0
  44. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/lib/websocket.ts +0 -0
  45. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/next-env.d.ts +0 -0
  46. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/next.config.mjs +0 -0
  47. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/next.config.ts +0 -0
  48. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/package-lock.json +0 -0
  49. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/package.json +0 -0
  50. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/postcss.config.mjs +0 -0
  51. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/assets/icons/add.svg +0 -0
  52. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/assets/icons/check.svg +0 -0
  53. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/assets/icons/copy.svg +0 -0
  54. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/assets/icons/folder.svg +0 -0
  55. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/assets/icons/metric.svg +0 -0
  56. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/assets/icons/packages.svg +0 -0
  57. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/assets/icons/play.svg +0 -0
  58. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/assets/icons/python.svg +0 -0
  59. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/assets/icons/setting.svg +0 -0
  60. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/assets/icons/stop.svg +0 -0
  61. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/assets/icons/trash.svg +0 -0
  62. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/assets/icons/up-down.svg +0 -0
  63. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/assets/icons/x.svg +0 -0
  64. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/file.svg +0 -0
  65. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/fonts/Fira.ttf +0 -0
  66. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/fonts/Tiempos.woff2 +0 -0
  67. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/fonts/VeraMono.ttf +0 -0
  68. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/globe.svg +0 -0
  69. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/next.svg +0 -0
  70. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/vercel.svg +0 -0
  71. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/public/window.svg +0 -0
  72. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/styling_README.md +0 -0
  73. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/tailwind.config.ts +0 -0
  74. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/tsconfig.json +0 -0
  75. {more_compute-0.4.2 → more_compute-0.4.4}/frontend/types/notebook.ts +0 -0
  76. {more_compute-0.4.2 → more_compute-0.4.4}/kernel_run.py +0 -0
  77. {more_compute-0.4.2 → more_compute-0.4.4}/more_compute.egg-info/SOURCES.txt +0 -0
  78. {more_compute-0.4.2 → more_compute-0.4.4}/more_compute.egg-info/dependency_links.txt +0 -0
  79. {more_compute-0.4.2 → more_compute-0.4.4}/more_compute.egg-info/entry_points.txt +0 -0
  80. {more_compute-0.4.2 → more_compute-0.4.4}/more_compute.egg-info/requires.txt +0 -0
  81. {more_compute-0.4.2 → more_compute-0.4.4}/more_compute.egg-info/top_level.txt +0 -0
  82. {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/__init__.py +0 -0
  83. {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/cli.py +0 -0
  84. {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/execution/__init__.py +0 -0
  85. {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/execution/__main__.py +0 -0
  86. {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/execution/executor.py +0 -0
  87. {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/execution/worker.py +0 -0
  88. {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/models/__init__.py +0 -0
  89. {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/models/api_models.py +0 -0
  90. {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/process_worker.py +0 -0
  91. {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/server.py +0 -0
  92. {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/services/data_manager.py +0 -0
  93. {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/services/lsp_service.py +0 -0
  94. {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/services/pod_manager.py +0 -0
  95. {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/services/pod_monitor.py +0 -0
  96. {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/services/prime_intellect.py +0 -0
  97. {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/static/styles.css +0 -0
  98. {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/utils/__init__.py +0 -0
  99. {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/utils/cache_util.py +0 -0
  100. {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/utils/cell_magics.py +0 -0
  101. {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/utils/config_util.py +0 -0
  102. {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/utils/error_utils.py +0 -0
  103. {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/utils/line_magics.py +0 -0
  104. {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/utils/notebook_util.py +0 -0
  105. {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/utils/python_environment_util.py +0 -0
  106. {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/utils/shell_utils.py +0 -0
  107. {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/utils/special_commands.py +0 -0
  108. {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/utils/system_environment_util.py +0 -0
  109. {more_compute-0.4.2 → more_compute-0.4.4}/morecompute/utils/zmq_util.py +0 -0
  110. {more_compute-0.4.2 → more_compute-0.4.4}/pyproject.toml +0 -0
  111. {more_compute-0.4.2 → more_compute-0.4.4}/setup.cfg +0 -0
  112. {more_compute-0.4.2 → more_compute-0.4.4}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: more-compute
3
- Version: 0.4.2
3
+ Version: 0.4.4
4
4
  Summary: An interactive notebook environment for local and GPU computing
5
5
  Home-page: https://github.com/DannyMang/MORECOMPUTE
6
6
  Author: MoreCompute Team
@@ -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
- <button
686
- className="runtime-btn runtime-btn-secondary"
687
- onClick={handleConnectToPrimeIntellect}
688
- style={{
689
- fontSize: "11px",
690
- padding: "6px 12px",
691
- backgroundColor: "#000",
692
- color: "white",
693
- border: "none",
694
- borderRadius: "8px",
695
- cursor: "pointer"
696
- }}
697
- >
698
- Manage
699
- </button>
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: "#000",
754
- color: "white",
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: "#000",
769
- color: "white",
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: "#fff3cd",
792
- border: "1px solid #ffc107",
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: "#856404", marginBottom: "4px" }}>
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: "#856404", marginBottom: "8px" }}>
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: "#ffc107",
815
- color: "#000",
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: "#dc2626",
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: "#10b981",
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: "#000",
881
- color: "white",
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: "#dc2626",
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: "#10b981",
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: "#dc2626",
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 #d1d5db",
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: "#000",
993
- color: "white",
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: "#000",
1123
- color: "white",
1152
+ backgroundColor: "var(--mc-text-color)",
1153
+ color: "var(--mc-cell-background)",
1124
1154
  border: "none",
1125
1155
  borderRadius: "8px",
1126
1156
  cursor: "pointer"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: more-compute
3
- Version: 0.4.2
3
+ Version: 0.4.4
4
4
  Summary: An interactive notebook environment for local and GPU computing
5
5
  Home-page: https://github.com/DannyMang/MORECOMPUTE
6
6
  Author: MoreCompute Team
@@ -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 with py:percent format
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
- data = parse_py_percent(content)
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
- # Split by cell markers (# %%)
27
- # Keep the marker in the split to determine cell type
28
- parts = re.split(r'(# %%.*?\n)', content)
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