From f652821c4e9966601ad902928c379d544ffae42a Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Tue, 5 Aug 2025 15:20:02 -0400 Subject: [PATCH 01/13] added code editor --- package-lock.json | 262 +++++++++++++++++++++++++++++++---- package.json | 11 +- src/EventsTab.jsx | 40 ++++++ src/PythonCodeEditor.css | 122 ++++++++++++++++ src/PythonCodeEditor.jsx | 138 ++++++++++++++++++ src/backend.py | 138 +++++++++++++++++- src/custom_pathsim_blocks.py | 5 + src/pathsim_utils.py | 13 +- 8 files changed, 693 insertions(+), 36 deletions(-) create mode 100644 src/PythonCodeEditor.css create mode 100644 src/PythonCodeEditor.jsx diff --git a/package-lock.json b/package-lock.json index 80e67500..d6dafee1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,16 +8,17 @@ "name": "fuel-cycle-sim", "version": "0.0.0", "dependencies": { + "@codeium/react-code-editor": "^1.0.0", "@xyflow/react": "^12.8.1", "plotly.js": "^3.0.3", - "react": "^19.1.0", - "react-dom": "^19.1.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", "react-plotly.js": "^2.6.0" }, "devDependencies": { "@eslint/js": "^9.25.0", - "@types/react": "^19.1.2", - "@types/react-dom": "^19.1.2", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", "@vitejs/plugin-react": "^4.4.1", "concurrently": "^9.2.0", "eslint": "^9.25.0", @@ -323,6 +324,123 @@ "node": ">=6.9.0" } }, + "node_modules/@bufbuild/buf": { + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/@bufbuild/buf/-/buf-1.28.1.tgz", + "integrity": "sha512-WRDagrf0uBjfV9s5eyrSPJDcdI4A5Q7JMCA4aMrHRR8fo/TTjniDBjJprszhaguqsDkn/LS4QIu92HVFZCrl9A==", + "hasInstallScript": true, + "bin": { + "buf": "bin/buf", + "protoc-gen-buf-breaking": "bin/protoc-gen-buf-breaking", + "protoc-gen-buf-lint": "bin/protoc-gen-buf-lint" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@bufbuild/buf-darwin-arm64": "1.28.1", + "@bufbuild/buf-darwin-x64": "1.28.1", + "@bufbuild/buf-linux-aarch64": "1.28.1", + "@bufbuild/buf-linux-x64": "1.28.1", + "@bufbuild/buf-win32-arm64": "1.28.1", + "@bufbuild/buf-win32-x64": "1.28.1" + } + }, + "node_modules/@bufbuild/buf-darwin-arm64": { + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-arm64/-/buf-darwin-arm64-1.28.1.tgz", + "integrity": "sha512-nAyvwKkcd8qQTExCZo5MtSRhXLK7e3vzKFKHjXfkveRakSUST2HFlFZAHfErZimN4wBrPTN0V0hNRU8PPjkMpQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@bufbuild/buf-darwin-x64": { + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-x64/-/buf-darwin-x64-1.28.1.tgz", + "integrity": "sha512-b0eT3xd3vX5a5lWAbo5h7FPuf9MsOJI4I39qs4TZnrlZ8BOuPfqzwzijiFf9UCwaX2vR1NQXexIoQ80Ci+fCHw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@bufbuild/buf-linux-aarch64": { + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-aarch64/-/buf-linux-aarch64-1.28.1.tgz", + "integrity": "sha512-p5h9bZCVLMh8No9/7k7ulXzsFx5P7Lu6DiUMjSJ6aBXPMYo6Xl7r/6L2cQkpsZ53HMtIxCgMYS9a7zoS4K8wIw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@bufbuild/buf-linux-x64": { + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-x64/-/buf-linux-x64-1.28.1.tgz", + "integrity": "sha512-fVJ3DiRigIso06jgEl+JNp59Y5t2pxDHd10d3SA4r+14sXbZ2J7Gy/wBqVXPry4x/jW567KKlvmhg7M5ZBgCQQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@bufbuild/buf-win32-arm64": { + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-arm64/-/buf-win32-arm64-1.28.1.tgz", + "integrity": "sha512-KJiRJpugQRK/jXC46Xjlb68UydWhCZj2jHdWLIwNtgXd1WTJ3LngChZV7Y6pPK08pwBAVz0JYeVbD5IlTCD4TQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@bufbuild/buf-win32-x64": { + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-x64/-/buf-win32-x64-1.28.1.tgz", + "integrity": "sha512-vMnc+7OVCkmlRWQsgYHgUqiBPRIjD8XeoRyApJ07YZzGs7DkRH4LhvmacJbLd3wORylbn6gLz3pQa8J/M61mzg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@bufbuild/protobuf": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.4.2.tgz", + "integrity": "sha512-JyEH8Z+OD5Sc2opSg86qMHn1EM1Sa+zj/Tc0ovxdwk56ByVNONJSabuCUbLQp+eKN3rWNfrho0X+3SEqEPXIow==" + }, "node_modules/@choojs/findup": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/@choojs/findup/-/findup-0.2.1.tgz", @@ -334,6 +452,44 @@ "findup": "bin/findup.js" } }, + "node_modules/@codeium/react-code-editor": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@codeium/react-code-editor/-/react-code-editor-1.0.12.tgz", + "integrity": "sha512-ps0J00jGBs/mhr1R9u+UTW+uK/6ba0WhCr2qYr7M9/w4ZQQXYmiZ3zniZwJGAhle69y5AE7SiUTVWZK7BU/4UA==", + "dependencies": { + "@bufbuild/buf": "1.28.1", + "@bufbuild/protobuf": "1.4.2", + "@connectrpc/connect": "1.1.3", + "@connectrpc/connect-web": "1.1.3", + "@monaco-editor/react": "^4.6.0", + "dotenv": "^16.3.1" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "monaco-editor": "^0.45.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, + "node_modules/@connectrpc/connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@connectrpc/connect/-/connect-1.1.3.tgz", + "integrity": "sha512-AXkbsLQe2Nm7VuoN5nqp05GEb9mPa/f5oFzDqTbHME4i8TghTrlY03uefbhuAq4wjsnfDnmuxHZvn6ndlgXmbg==", + "peerDependencies": { + "@bufbuild/protobuf": "^1.3.3" + } + }, + "node_modules/@connectrpc/connect-web": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@connectrpc/connect-web/-/connect-web-1.1.3.tgz", + "integrity": "sha512-WfShOZt91duJngqivYF4wJFRbeRa4bF/fPMfDVN0MAYSX3VuaTMn8o9qgKN7tsg2H2ZClyOVQwMkZx6IdcP7Zw==", + "peerDependencies": { + "@bufbuild/protobuf": "^1.3.3", + "@connectrpc/connect": "1.1.3" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", @@ -1138,6 +1294,27 @@ "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz", "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==" }, + "node_modules/@monaco-editor/loader": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.5.0.tgz", + "integrity": "sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==", + "dependencies": { + "state-local": "^1.0.6" + } + }, + "node_modules/@monaco-editor/react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz", + "integrity": "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==", + "dependencies": { + "@monaco-editor/loader": "^1.5.0" + }, + "peerDependencies": { + "monaco-editor": ">= 0.25.0 < 1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/@plotly/d3": { "version": "3.8.2", "resolved": "https://registry.npmjs.org/@plotly/d3/-/d3-3.8.2.tgz", @@ -1706,24 +1883,29 @@ "resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.5.tgz", "integrity": "sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==" }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "devOptional": true + }, "node_modules/@types/react": { - "version": "19.1.8", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", - "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", + "version": "18.3.23", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz", + "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==", "devOptional": true, - "license": "MIT", "dependencies": { + "@types/prop-types": "*", "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "19.1.6", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz", - "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "dev": true, - "license": "MIT", "peerDependencies": { - "@types/react": "^19.0.0" + "@types/react": "^18.0.0" } }, "node_modules/@types/supercluster": { @@ -2557,6 +2739,17 @@ "resolved": "https://registry.npmjs.org/detect-kerning/-/detect-kerning-2.1.2.tgz", "integrity": "sha512-I3JIbrnKPAntNLl1I6TpSQQdQ4AutYzv/sKMFKbepawV/hlH0GmYKhUoOEMd4xqaUHT+Bm0f4127lh5qs1m1tw==" }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/draw-svg-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/draw-svg-path/-/draw-svg-path-1.0.0.tgz", @@ -4062,6 +4255,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/monaco-editor": { + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.45.0.tgz", + "integrity": "sha512-mjv1G1ZzfEE3k9HZN0dQ2olMdwIfaeAAjFiwNprLfYNRSz7ctv9XuCT7gPtBGrMUeV1/iZzYKj17Khu1hxoHOA==", + "peer": true + }, "node_modules/mouse-change": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/mouse-change/-/mouse-change-1.4.0.tgz", @@ -4521,24 +4720,26 @@ } }, "node_modules/react": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", - "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", - "license": "MIT", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", - "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", - "license": "MIT", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "dependencies": { - "scheduler": "^0.26.0" + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^19.1.0" + "react": "^18.3.1" } }, "node_modules/react-is": { @@ -4820,10 +5021,12 @@ "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==" }, "node_modules/scheduler": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", - "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", - "license": "MIT" + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dependencies": { + "loose-envify": "^1.1.0" + } }, "node_modules/semver": { "version": "6.3.1", @@ -4908,6 +5111,11 @@ "node": "*" } }, + "node_modules/state-local": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz", + "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==" + }, "node_modules/static-eval": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.1.1.tgz", diff --git a/package.json b/package.json index b16423e9..b4132213 100644 --- a/package.json +++ b/package.json @@ -14,14 +14,15 @@ "dependencies": { "@xyflow/react": "^12.8.1", "plotly.js": "^3.0.3", - "react": "^19.1.0", - "react-dom": "^19.1.0", - "react-plotly.js": "^2.6.0" + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-plotly.js": "^2.6.0", + "@codeium/react-code-editor": "^1.0.0" }, "devDependencies": { "@eslint/js": "^9.25.0", - "@types/react": "^19.1.2", - "@types/react-dom": "^19.1.2", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", "@vitejs/plugin-react": "^4.4.1", "concurrently": "^9.2.0", "eslint": "^9.25.0", diff --git a/src/EventsTab.jsx b/src/EventsTab.jsx index 247a66c8..904f84e3 100644 --- a/src/EventsTab.jsx +++ b/src/EventsTab.jsx @@ -1,5 +1,27 @@ import { useState } from 'react'; +import { CodeiumEditor } from "@codeium/react-code-editor"; +import PythonCodeEditor from './PythonCodeEditor'; +import './PythonCodeEditor.css'; +export const IdeWithAutocomplete = () => { + const handleCodeExecution = (result) => { + if (result.success) { + console.log('Code executed successfully:', result); + // You can add notifications here if needed + } else { + console.error('Code execution failed:', result.error); + } + }; + + return ( +
+ +
+ ); +}; // Define default parameters for each event type const eventDefaults = { 'Schedule': { @@ -478,6 +500,24 @@ const EventsTab = ({ events, setEvents }) => { )} + + {/* Python Code Editor Section */} +
+

+ Python Code Editor +

+
+

+ Define Python variables and functions here. They will be available in your event functions and throughout the simulation. +

+ +
+
); diff --git a/src/PythonCodeEditor.css b/src/PythonCodeEditor.css new file mode 100644 index 00000000..00dbce9f --- /dev/null +++ b/src/PythonCodeEditor.css @@ -0,0 +1,122 @@ +/* Python Code Editor Styles */ +.python-code-editor { + border: 1px solid #333; + border-radius: 8px; + background: #1e1e1e; + color: #d4d4d4; + margin: 10px 0; +} + +.editor-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 15px; + background: #2d2d30; + border-bottom: 1px solid #333; + border-radius: 8px 8px 0 0; +} + +.editor-header h3 { + margin: 0; + color: #d4d4d4; + font-size: 16px; + font-weight: 500; +} + +.execute-btn { + background: #007acc; + color: white; + border: none; + padding: 8px 16px; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + transition: background-color 0.2s; +} + +.execute-btn:hover:not(:disabled) { + background: #005a9e; +} + +.execute-btn:disabled { + background: #555; + cursor: not-allowed; +} + +.editor-container { + border-radius: 0 0 8px 8px; + overflow: hidden; +} + +.execution-result { + margin-top: 10px; + padding: 15px; + border-radius: 4px; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; +} + +.execution-result.success { + background: #1e3a1e; + border: 1px solid #4caf50; +} + +.execution-result.error { + background: #3a1e1e; + border: 1px solid #f44336; +} + +.result-header { + margin-bottom: 10px; + font-weight: bold; +} + +.output pre { + background: #2d2d30; + padding: 10px; + border-radius: 4px; + margin: 5px 0; + white-space: pre-wrap; + word-wrap: break-word; +} + +.variables ul, +.functions ul { + list-style: none; + padding: 0; + margin: 5px 0; +} + +.variables li, +.functions li { + background: #2d2d30; + padding: 5px 10px; + margin: 2px 0; + border-radius: 3px; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; +} + +.variables code, +.functions code { + color: #9cdcfe; + background: transparent; +} + +.error-message pre { + background: #3a1e1e; + padding: 10px; + border-radius: 4px; + margin: 5px 0; + white-space: pre-wrap; + word-wrap: break-word; + color: #ff9999; +} + +/* Dark theme adjustments */ +.python-code-editor .monaco-editor { + background: #1e1e1e !important; +} + +.python-code-editor .monaco-editor .margin { + background: #1e1e1e !important; +} diff --git a/src/PythonCodeEditor.jsx b/src/PythonCodeEditor.jsx new file mode 100644 index 00000000..05020a65 --- /dev/null +++ b/src/PythonCodeEditor.jsx @@ -0,0 +1,138 @@ +import React, { useState, useCallback } from 'react'; +import { CodeiumEditor } from "@codeium/react-code-editor"; +import { getApiEndpoint } from './config.js'; + +const PythonCodeEditor = ({ + initialCode = "# Define your Python variables and functions here\n# Example:\n# my_variable = 42\n# def my_function(x):\n# return x * 2\n", + onCodeChange, + onExecute, + height = "400px" +}) => { + const [code, setCode] = useState(initialCode); + const [isExecuting, setIsExecuting] = useState(false); + const [executionResult, setExecutionResult] = useState(null); + + const handleCodeChange = useCallback((newCode) => { + setCode(newCode); + if (onCodeChange) { + onCodeChange(newCode); + } + }, [onCodeChange]); + + const executeCode = async () => { + setIsExecuting(true); + setExecutionResult(null); + + try { + const response = await fetch(getApiEndpoint('/execute-python'), { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ code }) + }); + + const result = await response.json(); + setExecutionResult(result); + + if (onExecute) { + onExecute(result); + } + } catch (error) { + setExecutionResult({ + success: false, + error: `Network error: ${error.message}` + }); + } finally { + setIsExecuting(false); + } + }; + + return ( +
+
+

Python Code Editor

+ +
+ +
+ +
+ + {executionResult && ( +
+
+ {executionResult.success ? 'Execution Result:' : 'Error:'} +
+ + {executionResult.success ? ( +
+ {executionResult.output && ( +
+ Output: +
{executionResult.output}
+
+ )} + + {executionResult.variables && Object.keys(executionResult.variables).length > 0 && ( +
+ Variables added to namespace: +
    + {Object.entries(executionResult.variables).map(([name, value]) => ( +
  • + {name} = {JSON.stringify(value)} +
  • + ))} +
+
+ )} + + {executionResult.functions && executionResult.functions.length > 0 && ( +
+ Functions added to namespace: +
    + {executionResult.functions.map((funcName) => ( +
  • + {funcName}() +
  • + ))} +
+
+ )} +
+ ) : ( +
+
{executionResult.error}
+
+ )} +
+ )} +
+ ); +}; + +export default PythonCodeEditor; diff --git a/src/backend.py b/src/backend.py index 047e1781..fc0b170e 100644 --- a/src/backend.py +++ b/src/backend.py @@ -8,12 +8,16 @@ import plotly import json as plotly_json import inspect -import numpy as np +import io +from contextlib import redirect_stdout, redirect_stderr from .convert_to_python import convert_graph_to_python from .pathsim_utils import make_pathsim_model, map_str_to_object from pathsim.blocks import Scope, Spectrum +# Import pathsim_utils to share eval_namespace +from . import pathsim_utils + # Sphinx imports for docstring processing from docutils.core import publish_parts @@ -320,6 +324,138 @@ def run_pathsim(): return jsonify({"success": False, "error": f"Server error: {str(e)}"}), 500 +# Global namespace for user-defined variables and functions +eval_namespace = {} + + +@app.route("/execute-python", methods=["POST"]) +def execute_python(): + """Execute Python code and update the global eval_namespace with any new variables/functions.""" + global eval_namespace + + try: + data = request.json + code = data.get("code", "") + + if not code.strip(): + return jsonify({"success": False, "error": "No code provided"}), 400 + + # Create a temporary namespace that includes current eval_namespace + temp_namespace = eval_namespace.copy() + temp_namespace.update(globals()) + + # Capture stdout and stderr + stdout_capture = io.StringIO() + stderr_capture = io.StringIO() + + # Track variables before execution + vars_before = set(temp_namespace.keys()) + + try: + with redirect_stdout(stdout_capture), redirect_stderr(stderr_capture): + exec(code, temp_namespace) + + # Capture any output + output = stdout_capture.getvalue() + error_output = stderr_capture.getvalue() + + if error_output: + return jsonify({"success": False, "error": error_output}), 400 + + # Find new variables and functions + vars_after = set(temp_namespace.keys()) + new_vars = vars_after - vars_before + + # Filter out built-ins and modules, keep user-defined items + user_variables = {} + user_functions = [] + + for var_name in new_vars: + if not var_name.startswith("__"): + value = temp_namespace[var_name] + if callable(value) and hasattr(value, "__name__"): + user_functions.append(var_name) + # Add function to eval_namespace + eval_namespace[var_name] = value + else: + # Try to serialize the value for display + try: + if isinstance(value, (int, float, str, bool, list, dict)): + user_variables[var_name] = value + else: + user_variables[var_name] = str(value) + # Add variable to eval_namespace + eval_namespace[var_name] = value + except Exception: + user_variables[var_name] = ( + f"<{type(value).__name__} object>" + ) + eval_namespace[var_name] = value + + # Sync with pathsim_utils shared_eval_namespace + pathsim_utils.shared_eval_namespace.update(eval_namespace) + + return jsonify( + { + "success": True, + "output": output if output else None, + "variables": user_variables, + "functions": user_functions, + "message": f"Executed successfully. Added {len(user_variables)} variables and {len(user_functions)} functions to namespace.", + } + ) + + except SyntaxError as e: + return jsonify({"success": False, "error": f"Syntax Error: {str(e)}"}), 400 + except Exception as e: + return jsonify({"success": False, "error": f"Runtime Error: {str(e)}"}), 400 + + except Exception as e: + return jsonify({"success": False, "error": f"Server error: {str(e)}"}), 500 + + +@app.route("/get-eval-namespace", methods=["GET"]) +def get_eval_namespace(): + """Get the current eval_namespace for debugging/inspection.""" + try: + # Create a serializable version of the namespace + serializable_namespace = {} + + for key, value in eval_namespace.items(): + if callable(value): + serializable_namespace[key] = f"" + else: + try: + if isinstance(value, (int, float, str, bool, list, dict)): + serializable_namespace[key] = value + else: + serializable_namespace[key] = str(value) + except Exception: + serializable_namespace[key] = f"<{type(value).__name__} object>" + + return jsonify({"success": True, "namespace": serializable_namespace}) + except Exception as e: + return jsonify( + {"success": False, "error": f"Error retrieving namespace: {str(e)}"} + ), 500 + + +@app.route("/clear-eval-namespace", methods=["POST"]) +def clear_eval_namespace(): + """Clear the eval_namespace.""" + global eval_namespace + try: + eval_namespace.clear() + pathsim_utils.shared_eval_namespace.clear() + return jsonify( + {"success": True, "message": "Eval namespace cleared successfully."} + ) + except Exception as e: + return jsonify( + {"success": False, "error": f"Error clearing namespace: {str(e)}"} + ), 500 + + # Catch-all route for React Router (SPA routing) @app.route("/") def catch_all(path): diff --git a/src/custom_pathsim_blocks.py b/src/custom_pathsim_blocks.py index b01e9971..c2b172e7 100644 --- a/src/custom_pathsim_blocks.py +++ b/src/custom_pathsim_blocks.py @@ -255,6 +255,7 @@ def create_reset_events(self) -> list[pathsim.blocks.Schedule]: # FESTIM wall +from pathsim.utils.register import Register class FestimWall(Block): @@ -270,6 +271,9 @@ def __init__( except ImportError: raise ImportError("festim is needed for FestimWall node.") + self.inputs = Register(size=2) + self.outputs = Register(size=2) + self.thickness = thickness self.temperature = temperature self.surface_area = surface_area @@ -344,6 +348,7 @@ def update(self, t): inputs = self.inputs.to_array() c_0 = inputs[self.name_to_input_port["c_0"]] c_L = inputs[self.name_to_input_port["c_L"]] + # print(c_0, c_L) if t == 0.0: flux_0, flux_L = 0, 0 diff --git a/src/pathsim_utils.py b/src/pathsim_utils.py index 6bb583f7..23f44068 100644 --- a/src/pathsim_utils.py +++ b/src/pathsim_utils.py @@ -36,6 +36,9 @@ from flask import jsonify import inspect +# Shared eval_namespace - will be set by backend +shared_eval_namespace = {} + NAME_TO_SOLVER = { "SSPRK22": pathsim.solvers.SSPRK22, "SSPRK33": pathsim.solvers.SSPRK33, @@ -107,7 +110,10 @@ def find_block_by_id(block_id: str, blocks: list[Block]) -> Block: def make_global_variables(global_vars): # Validate and exec global variables so that they are usable later in this script. # Return a namespace dictionary containing the global variables - global_namespace = globals() + global_namespace = globals().copy() + + # Include shared variables from the code editor + global_namespace.update(shared_eval_namespace) for var in global_vars: var_name = var.get("name", "").strip() @@ -136,7 +142,7 @@ def make_global_variables(global_vars): # Execute in global namespace for backwards compatibility exec(f"{var_name} = {var_value}", global_namespace) # Also store in local namespace for eval calls - global_namespace[var_name] = eval(var_value) + global_namespace[var_name] = eval(var_value, global_namespace) except Exception as e: raise ValueError(f"Error setting global variable '{var_name}': {str(e)}") @@ -200,7 +206,8 @@ def auto_block_construction(node: dict, eval_namespace: dict = None) -> Block: The constructed block object. """ if eval_namespace is None: - eval_namespace = globals() + eval_namespace = globals().copy() + eval_namespace.update(shared_eval_namespace) if node["type"] not in map_str_to_object: raise ValueError(f"Unknown block type: {node['type']}") From b5136819a358c76f6c684e209c6ac4c691450f8b Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Tue, 5 Aug 2025 15:23:28 -0400 Subject: [PATCH 02/13] moved global var tabs to its own file --- src/App.jsx | 249 +---------------------------------- src/GlobalVariablesTab.jsx | 257 +++++++++++++++++++++++++++++++++++++ 2 files changed, 263 insertions(+), 243 deletions(-) create mode 100644 src/GlobalVariablesTab.jsx diff --git a/src/App.jsx b/src/App.jsx index 0bbba41d..9def1935 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -19,6 +19,7 @@ import NodeSidebar from './NodeSidebar'; import { DnDProvider, useDnD } from './DnDContext.jsx'; import ContextMenu from './ContextMenu.jsx'; import EventsTab from './EventsTab.jsx'; +import GlobalVariablesTab from './GlobalVariablesTab.jsx'; import { isValidPythonIdentifier } from './utils.js'; import { makeEdge } from './CustomEdge'; import { nodeTypes } from './nodeConfig.js'; @@ -568,36 +569,6 @@ const DnDFlow = () => { } }; - const addGlobalVariable = () => { - const newVariable = { - id: Date.now().toString(), - name: '', - value: '', - nameError: false - }; - setGlobalVariables([...globalVariables, newVariable]); - }; - - const removeGlobalVariable = (id) => { - setGlobalVariables(globalVariables.filter(variable => variable.id !== id)); - }; - - const updateGlobalVariable = (id, field, value) => { - setGlobalVariables(globalVariables.map(variable => { - if (variable.id === id) { - const updatedVariable = { ...variable, [field]: value }; - - // Validate name field - if (field === 'name') { - updatedVariable.nameError = value !== '' && !isValidPythonIdentifier(value); - } - - return updatedVariable; - } - return variable; - })); - }; - //When user connects two nodes by dragging, creates an edge according to the styles in our makeEdge function const onConnect = useCallback( (params) => { @@ -1645,219 +1616,11 @@ const DnDFlow = () => { {/* Global Variables Tab */} {activeTab === 'globals' && ( -
-
-

- Global Variables -

-
-

- Define global variables that can be used in node definitions throughout your model. -

- - {globalVariables.length === 0 ? ( -
- No global variables defined. Click "Add Variable" to create one. -
- ) : ( -
- {globalVariables.map((variable) => ( -
-
- - updateGlobalVariable(variable.id, 'name', e.target.value)} - placeholder="variable_name" - style={{ - width: '95%', - padding: '8px', - borderRadius: '4px', - border: variable.nameError ? '2px solid #e74c3c' : '1px solid #666', - backgroundColor: '#2c2c54', - color: '#ffffff', - fontSize: '14px' - }} - /> - {variable.nameError && ( -
- Invalid Python variable name -
- )} -
-
- - updateGlobalVariable(variable.id, 'value', e.target.value)} - placeholder="0.5" - style={{ - width: '95%', - padding: '8px', - borderRadius: '4px', - border: '1px solid #666', - backgroundColor: '#2c2c54', - color: '#ffffff', - fontSize: '14px' - }} - /> - {/* Placeholder div to maintain alignment */} -
- {/* Empty space to match error message height */} -
-
-
- -
-
- ))} -
- )} - -
- - -
-
- -
-

Usage Instructions:

-
    -
  • Variable names must be valid Python identifiers (start with letter/underscore, contain only letters/digits/underscores)
  • -
  • Cannot use Python keywords like "if", "for", "class", "def", etc.
  • -
  • Use meaningful names (e.g., "flow_rate", "temperature", "my_constant")
  • -
  • Use numeric values, expressions, or references to other variables
  • -
  • Variables can be referenced in node parameters using their exact names
  • -
  • Variables are saved and loaded with your graph files
  • -
-
-
-
+ )} {/* Results Tab */} diff --git a/src/GlobalVariablesTab.jsx b/src/GlobalVariablesTab.jsx new file mode 100644 index 00000000..661e7d3e --- /dev/null +++ b/src/GlobalVariablesTab.jsx @@ -0,0 +1,257 @@ +import React from 'react'; +import { isValidPythonIdentifier } from './utils.js'; + +const GlobalVariablesTab = ({ + globalVariables, + setGlobalVariables, + setActiveTab +}) => { + + const addGlobalVariable = () => { + const newVariable = { + id: Date.now().toString(), + name: '', + value: '', + nameError: false + }; + setGlobalVariables([...globalVariables, newVariable]); + }; + + const removeGlobalVariable = (id) => { + setGlobalVariables(globalVariables.filter(variable => variable.id !== id)); + }; + + const updateGlobalVariable = (id, field, value) => { + setGlobalVariables(globalVariables.map(variable => { + if (variable.id === id) { + const updatedVariable = { ...variable, [field]: value }; + + // Validate name field + if (field === 'name') { + updatedVariable.nameError = value !== '' && !isValidPythonIdentifier(value); + } + + return updatedVariable; + } + return variable; + })); + }; + + return ( +
+
+

+ Global Variables +

+
+

+ Define global variables that can be used in node definitions throughout your model. +

+ + {globalVariables.length === 0 ? ( +
+ No global variables defined. Click "Add Variable" to create one. +
+ ) : ( +
+ {globalVariables.map((variable) => ( +
+
+ + updateGlobalVariable(variable.id, 'name', e.target.value)} + placeholder="variable_name" + style={{ + width: '95%', + padding: '8px', + borderRadius: '4px', + border: variable.nameError ? '2px solid #e74c3c' : '1px solid #666', + backgroundColor: '#2c2c54', + color: '#ffffff', + fontSize: '14px' + }} + /> + {variable.nameError && ( +
+ Invalid Python variable name +
+ )} +
+
+ + updateGlobalVariable(variable.id, 'value', e.target.value)} + placeholder="0.5" + style={{ + width: '95%', + padding: '8px', + borderRadius: '4px', + border: '1px solid #666', + backgroundColor: '#2c2c54', + color: '#ffffff', + fontSize: '14px' + }} + /> + {/* Placeholder div to maintain alignment */} +
+ {/* Empty space to match error message height */} +
+
+
+ +
+
+ ))} +
+ )} + +
+ + +
+
+ +
+

Usage Instructions:

+
    +
  • Variable names must be valid Python identifiers (start with letter/underscore, contain only letters/digits/underscores)
  • +
  • Cannot use Python keywords like "if", "for", "class", "def", etc.
  • +
  • Use meaningful names (e.g., "flow_rate", "temperature", "my_constant")
  • +
  • Use numeric values, expressions, or references to other variables
  • +
  • Variables can be referenced in node parameters using their exact names
  • +
  • Variables are saved and loaded with your graph files
  • +
+
+
+
+ ); +}; + +export default GlobalVariablesTab; From 706536f8bbcbea3a52f4b047fa19d7a23713a8a7 Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Tue, 5 Aug 2025 15:28:11 -0400 Subject: [PATCH 03/13] moved to global vars tab --- src/EventsTab.jsx | 40 ------------------------------------ src/GlobalVariablesTab.jsx | 42 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 40 deletions(-) diff --git a/src/EventsTab.jsx b/src/EventsTab.jsx index 904f84e3..247a66c8 100644 --- a/src/EventsTab.jsx +++ b/src/EventsTab.jsx @@ -1,27 +1,5 @@ import { useState } from 'react'; -import { CodeiumEditor } from "@codeium/react-code-editor"; -import PythonCodeEditor from './PythonCodeEditor'; -import './PythonCodeEditor.css'; -export const IdeWithAutocomplete = () => { - const handleCodeExecution = (result) => { - if (result.success) { - console.log('Code executed successfully:', result); - // You can add notifications here if needed - } else { - console.error('Code execution failed:', result.error); - } - }; - - return ( -
- -
- ); -}; // Define default parameters for each event type const eventDefaults = { 'Schedule': { @@ -500,24 +478,6 @@ const EventsTab = ({ events, setEvents }) => { )} - - {/* Python Code Editor Section */} -
-

- Python Code Editor -

-
-

- Define Python variables and functions here. They will be available in your event functions and throughout the simulation. -

- -
-
); diff --git a/src/GlobalVariablesTab.jsx b/src/GlobalVariablesTab.jsx index 661e7d3e..9d075566 100644 --- a/src/GlobalVariablesTab.jsx +++ b/src/GlobalVariablesTab.jsx @@ -1,6 +1,31 @@ import React from 'react'; import { isValidPythonIdentifier } from './utils.js'; + +import { CodeiumEditor } from "@codeium/react-code-editor"; +import PythonCodeEditor from './PythonCodeEditor'; +import './PythonCodeEditor.css'; + +export const IdeWithAutocomplete = () => { + const handleCodeExecution = (result) => { + if (result.success) { + console.log('Code executed successfully:', result); + // You can add notifications here if needed + } else { + console.error('Code execution failed:', result.error); + } + }; + + return ( +
+ +
+ ); +}; + const GlobalVariablesTab = ({ globalVariables, setGlobalVariables, @@ -249,6 +274,23 @@ const GlobalVariablesTab = ({
  • Variables are saved and loaded with your graph files
  • + {/* Python Code Editor Section */} +
    +

    + Python Code Editor +

    +
    +

    + Define Python variables and functions here. They will be available in your event functions and throughout the simulation. +

    + +
    +
    ); From 5a67db4b642d6b3dd6135988489f951b99689390 Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Tue, 5 Aug 2025 15:47:17 -0400 Subject: [PATCH 04/13] save state of python editor --- src/App.jsx | 31 ++++++++++++++++++++++++++++--- src/GlobalVariablesTab.jsx | 15 ++++++++++----- src/PythonCodeEditor.jsx | 4 +--- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 9def1935..71c347ea 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -81,6 +81,10 @@ const DnDFlow = () => { // Global variables state const [globalVariables, setGlobalVariables] = useState([]); const [events, setEvents] = useState([]); + + // Python code editor state + const [pythonCode, setPythonCode] = useState("# Define your Python variables and functions here\n# Example:\n# my_variable = 42\n# def my_function(x):\n# return x * 2\n"); + const [defaultValues, setDefaultValues] = useState({}); const [isEditingLabel, setIsEditingLabel] = useState(false); const [tempLabel, setTempLabel] = useState(''); @@ -223,7 +227,8 @@ const DnDFlow = () => { nodeCounter, solverParams, globalVariables, - events + events, + pythonCode }; // Check if File System Access API is supported @@ -303,7 +308,15 @@ const DnDFlow = () => { } // Load the graph data - const { nodes: loadedNodes, edges: loadedEdges, nodeCounter: loadedNodeCounter, solverParams: loadedSolverParams, globalVariables: loadedGlobalVariables, events: loadedEvents } = graphData; + const { + nodes: loadedNodes, + edges: loadedEdges, + nodeCounter: loadedNodeCounter, + solverParams: loadedSolverParams, + globalVariables: loadedGlobalVariables, + events: loadedEvents, + pythonCode: loadedPythonCode + } = graphData; setNodes(loadedNodes || []); setEdges(loadedEdges || []); setSelectedNode(null); @@ -321,6 +334,7 @@ const DnDFlow = () => { }); setGlobalVariables(loadedGlobalVariables ?? []); setEvents(loadedEvents ?? []); + setPythonCode(loadedPythonCode ?? "# Define your Python variables and functions here\n# Example:\n# my_variable = 42\n# def my_function(x):\n# return x * 2\n"); alert('Graph loaded successfully!'); } catch (error) { @@ -355,7 +369,15 @@ const DnDFlow = () => { return; } - const { nodes: loadedNodes, edges: loadedEdges, nodeCounter: loadedNodeCounter, solverParams: loadedSolverParams, globalVariables: loadedGlobalVariables, events: loadedEvents } = graphData; + const { + nodes: loadedNodes, + edges: loadedEdges, + nodeCounter: loadedNodeCounter, + solverParams: loadedSolverParams, + globalVariables: loadedGlobalVariables, + events: loadedEvents, + pythonCode: loadedPythonCode + } = graphData; setNodes(loadedNodes || []); setEdges(loadedEdges || []); setSelectedNode(null); @@ -373,6 +395,7 @@ const DnDFlow = () => { }); setGlobalVariables(loadedGlobalVariables ?? []); setEvents(loadedEvents ?? []); + setPythonCode(loadedPythonCode ?? "# Define your Python variables and functions here\n# Example:\n# my_variable = 42\n# def my_function(x):\n# return x * 2\n"); alert('Graph loaded successfully!'); } catch (error) { @@ -1620,6 +1643,8 @@ const DnDFlow = () => { globalVariables={globalVariables} setGlobalVariables={setGlobalVariables} setActiveTab={setActiveTab} + pythonCode={pythonCode} + setPythonCode={setPythonCode} /> )} diff --git a/src/GlobalVariablesTab.jsx b/src/GlobalVariablesTab.jsx index 9d075566..c01348da 100644 --- a/src/GlobalVariablesTab.jsx +++ b/src/GlobalVariablesTab.jsx @@ -1,12 +1,10 @@ -import React from 'react'; import { isValidPythonIdentifier } from './utils.js'; -import { CodeiumEditor } from "@codeium/react-code-editor"; import PythonCodeEditor from './PythonCodeEditor'; import './PythonCodeEditor.css'; -export const IdeWithAutocomplete = () => { +export const IdeWithAutocomplete = ({ pythonCode, setPythonCode }) => { const handleCodeExecution = (result) => { if (result.success) { console.log('Code executed successfully:', result); @@ -19,6 +17,8 @@ export const IdeWithAutocomplete = () => { return (
    @@ -29,7 +29,9 @@ export const IdeWithAutocomplete = () => { const GlobalVariablesTab = ({ globalVariables, setGlobalVariables, - setActiveTab + setActiveTab, + pythonCode, + setPythonCode }) => { const addGlobalVariable = () => { @@ -288,7 +290,10 @@ const GlobalVariablesTab = ({

    Define Python variables and functions here. They will be available in your event functions and throughout the simulation.

    - +
    diff --git a/src/PythonCodeEditor.jsx b/src/PythonCodeEditor.jsx index 05020a65..43f9f3d8 100644 --- a/src/PythonCodeEditor.jsx +++ b/src/PythonCodeEditor.jsx @@ -3,17 +3,15 @@ import { CodeiumEditor } from "@codeium/react-code-editor"; import { getApiEndpoint } from './config.js'; const PythonCodeEditor = ({ - initialCode = "# Define your Python variables and functions here\n# Example:\n# my_variable = 42\n# def my_function(x):\n# return x * 2\n", + code = "# Define your Python variables and functions here\n# Example:\n# my_variable = 42\n# def my_function(x):\n# return x * 2\n", onCodeChange, onExecute, height = "400px" }) => { - const [code, setCode] = useState(initialCode); const [isExecuting, setIsExecuting] = useState(false); const [executionResult, setExecutionResult] = useState(null); const handleCodeChange = useCallback((newCode) => { - setCode(newCode); if (onCodeChange) { onCodeChange(newCode); } From 35640b265b016cbd39e3e1fc166718c52368817c Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Tue, 5 Aug 2025 15:55:30 -0400 Subject: [PATCH 05/13] simplified eval_namespace --- src/pathsim_utils.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/pathsim_utils.py b/src/pathsim_utils.py index 23f44068..d9f51b2a 100644 --- a/src/pathsim_utils.py +++ b/src/pathsim_utils.py @@ -205,9 +205,6 @@ def auto_block_construction(node: dict, eval_namespace: dict = None) -> Block: Returns: The constructed block object. """ - if eval_namespace is None: - eval_namespace = globals().copy() - eval_namespace.update(shared_eval_namespace) if node["type"] not in map_str_to_object: raise ValueError(f"Unknown block type: {node['type']}") From 4d7296d21ccc18b4e42db177601bb7a440fb29ea Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Tue, 5 Aug 2025 16:26:21 -0400 Subject: [PATCH 06/13] replaced with code mirror --- package-lock.json | 407 +++++++++++++++++++++------------------ package.json | 3 +- src/PythonCodeEditor.jsx | 28 +-- 3 files changed, 227 insertions(+), 211 deletions(-) diff --git a/package-lock.json b/package-lock.json index d6dafee1..d3650495 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,8 @@ "name": "fuel-cycle-sim", "version": "0.0.0", "dependencies": { - "@codeium/react-code-editor": "^1.0.0", + "@uiw/codemirror-theme-vscode": "^4.24.2", + "@uiw/react-codemirror": "^4.24.2", "@xyflow/react": "^12.8.1", "plotly.js": "^3.0.3", "react": "^18.2.0", @@ -266,6 +267,14 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz", + "integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", @@ -324,170 +333,100 @@ "node": ">=6.9.0" } }, - "node_modules/@bufbuild/buf": { - "version": "1.28.1", - "resolved": "https://registry.npmjs.org/@bufbuild/buf/-/buf-1.28.1.tgz", - "integrity": "sha512-WRDagrf0uBjfV9s5eyrSPJDcdI4A5Q7JMCA4aMrHRR8fo/TTjniDBjJprszhaguqsDkn/LS4QIu92HVFZCrl9A==", - "hasInstallScript": true, - "bin": { - "buf": "bin/buf", - "protoc-gen-buf-breaking": "bin/protoc-gen-buf-breaking", - "protoc-gen-buf-lint": "bin/protoc-gen-buf-lint" - }, - "engines": { - "node": ">=12" + "node_modules/@choojs/findup": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@choojs/findup/-/findup-0.2.1.tgz", + "integrity": "sha512-YstAqNb0MCN8PjdLCDfRsBcGVRN41f3vgLvaI0IrIcBp4AqILRSS0DeWNGkicC+f/zRIPJLc+9RURVSepwvfBw==", + "dependencies": { + "commander": "^2.15.1" }, - "optionalDependencies": { - "@bufbuild/buf-darwin-arm64": "1.28.1", - "@bufbuild/buf-darwin-x64": "1.28.1", - "@bufbuild/buf-linux-aarch64": "1.28.1", - "@bufbuild/buf-linux-x64": "1.28.1", - "@bufbuild/buf-win32-arm64": "1.28.1", - "@bufbuild/buf-win32-x64": "1.28.1" - } - }, - "node_modules/@bufbuild/buf-darwin-arm64": { - "version": "1.28.1", - "resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-arm64/-/buf-darwin-arm64-1.28.1.tgz", - "integrity": "sha512-nAyvwKkcd8qQTExCZo5MtSRhXLK7e3vzKFKHjXfkveRakSUST2HFlFZAHfErZimN4wBrPTN0V0hNRU8PPjkMpQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@bufbuild/buf-darwin-x64": { - "version": "1.28.1", - "resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-x64/-/buf-darwin-x64-1.28.1.tgz", - "integrity": "sha512-b0eT3xd3vX5a5lWAbo5h7FPuf9MsOJI4I39qs4TZnrlZ8BOuPfqzwzijiFf9UCwaX2vR1NQXexIoQ80Ci+fCHw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" + "bin": { + "findup": "bin/findup.js" } }, - "node_modules/@bufbuild/buf-linux-aarch64": { - "version": "1.28.1", - "resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-aarch64/-/buf-linux-aarch64-1.28.1.tgz", - "integrity": "sha512-p5h9bZCVLMh8No9/7k7ulXzsFx5P7Lu6DiUMjSJ6aBXPMYo6Xl7r/6L2cQkpsZ53HMtIxCgMYS9a7zoS4K8wIw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "node_modules/@codemirror/autocomplete": { + "version": "6.18.6", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz", + "integrity": "sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" } }, - "node_modules/@bufbuild/buf-linux-x64": { - "version": "1.28.1", - "resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-x64/-/buf-linux-x64-1.28.1.tgz", - "integrity": "sha512-fVJ3DiRigIso06jgEl+JNp59Y5t2pxDHd10d3SA4r+14sXbZ2J7Gy/wBqVXPry4x/jW567KKlvmhg7M5ZBgCQQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "node_modules/@codemirror/commands": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.1.tgz", + "integrity": "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" } }, - "node_modules/@bufbuild/buf-win32-arm64": { - "version": "1.28.1", - "resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-arm64/-/buf-win32-arm64-1.28.1.tgz", - "integrity": "sha512-KJiRJpugQRK/jXC46Xjlb68UydWhCZj2jHdWLIwNtgXd1WTJ3LngChZV7Y6pPK08pwBAVz0JYeVbD5IlTCD4TQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" + "node_modules/@codemirror/language": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.2.tgz", + "integrity": "sha512-p44TsNArL4IVXDTbapUmEkAlvWs2CFQbcfc0ymDsis1kH2wh0gcY96AS29c/vp2d0y2Tquk1EDSaawpzilUiAw==", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.1.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" } }, - "node_modules/@bufbuild/buf-win32-x64": { - "version": "1.28.1", - "resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-x64/-/buf-win32-x64-1.28.1.tgz", - "integrity": "sha512-vMnc+7OVCkmlRWQsgYHgUqiBPRIjD8XeoRyApJ07YZzGs7DkRH4LhvmacJbLd3wORylbn6gLz3pQa8J/M61mzg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" + "node_modules/@codemirror/lint": { + "version": "6.8.5", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz", + "integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.35.0", + "crelt": "^1.0.5" } }, - "node_modules/@bufbuild/protobuf": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.4.2.tgz", - "integrity": "sha512-JyEH8Z+OD5Sc2opSg86qMHn1EM1Sa+zj/Tc0ovxdwk56ByVNONJSabuCUbLQp+eKN3rWNfrho0X+3SEqEPXIow==" - }, - "node_modules/@choojs/findup": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@choojs/findup/-/findup-0.2.1.tgz", - "integrity": "sha512-YstAqNb0MCN8PjdLCDfRsBcGVRN41f3vgLvaI0IrIcBp4AqILRSS0DeWNGkicC+f/zRIPJLc+9RURVSepwvfBw==", + "node_modules/@codemirror/search": { + "version": "6.5.11", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz", + "integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==", "dependencies": { - "commander": "^2.15.1" - }, - "bin": { - "findup": "bin/findup.js" + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "crelt": "^1.0.5" } }, - "node_modules/@codeium/react-code-editor": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@codeium/react-code-editor/-/react-code-editor-1.0.12.tgz", - "integrity": "sha512-ps0J00jGBs/mhr1R9u+UTW+uK/6ba0WhCr2qYr7M9/w4ZQQXYmiZ3zniZwJGAhle69y5AE7SiUTVWZK7BU/4UA==", + "node_modules/@codemirror/state": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz", + "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==", "dependencies": { - "@bufbuild/buf": "1.28.1", - "@bufbuild/protobuf": "1.4.2", - "@connectrpc/connect": "1.1.3", - "@connectrpc/connect-web": "1.1.3", - "@monaco-editor/react": "^4.6.0", - "dotenv": "^16.3.1" - }, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "monaco-editor": "^0.45.0", - "react": "^18.2.0", - "react-dom": "^18.2.0" + "@marijn/find-cluster-break": "^1.0.0" } }, - "node_modules/@connectrpc/connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@connectrpc/connect/-/connect-1.1.3.tgz", - "integrity": "sha512-AXkbsLQe2Nm7VuoN5nqp05GEb9mPa/f5oFzDqTbHME4i8TghTrlY03uefbhuAq4wjsnfDnmuxHZvn6ndlgXmbg==", - "peerDependencies": { - "@bufbuild/protobuf": "^1.3.3" + "node_modules/@codemirror/theme-one-dark": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz", + "integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/highlight": "^1.0.0" } }, - "node_modules/@connectrpc/connect-web": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@connectrpc/connect-web/-/connect-web-1.1.3.tgz", - "integrity": "sha512-WfShOZt91duJngqivYF4wJFRbeRa4bF/fPMfDVN0MAYSX3VuaTMn8o9qgKN7tsg2H2ZClyOVQwMkZx6IdcP7Zw==", - "peerDependencies": { - "@bufbuild/protobuf": "^1.3.3", - "@connectrpc/connect": "1.1.3" + "node_modules/@codemirror/view": { + "version": "6.38.1", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.1.tgz", + "integrity": "sha512-RmTOkE7hRU3OVREqFVITWHz6ocgBjv08GoePscAakgVQfciA3SGCEk7mb9IzwW61cKKmlTpHXG6DUE5Ubx+MGQ==", + "dependencies": { + "@codemirror/state": "^6.5.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" } }, "node_modules/@esbuild/aix-ppc64": { @@ -1201,6 +1140,27 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@lezer/common": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", + "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz", + "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz", + "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, "node_modules/@mapbox/geojson-rewind": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz", @@ -1294,26 +1254,10 @@ "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz", "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==" }, - "node_modules/@monaco-editor/loader": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.5.0.tgz", - "integrity": "sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==", - "dependencies": { - "state-local": "^1.0.6" - } - }, - "node_modules/@monaco-editor/react": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz", - "integrity": "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==", - "dependencies": { - "@monaco-editor/loader": "^1.5.0" - }, - "peerDependencies": { - "monaco-editor": ">= 0.25.0 < 1", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==" }, "node_modules/@plotly/d3": { "version": "3.8.2", @@ -1916,6 +1860,86 @@ "@types/geojson": "*" } }, + "node_modules/@uiw/codemirror-extensions-basic-setup": { + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.24.2.tgz", + "integrity": "sha512-wW/gjLRvVUeYyhdh2TApn25cvdcR+Rhg6R/j3eTOvXQzU1HNzNYCVH4YKVIfgtfdM/Xs+N8fkk+rbr1YvBppCg==", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@codemirror/autocomplete": ">=6.0.0", + "@codemirror/commands": ">=6.0.0", + "@codemirror/language": ">=6.0.0", + "@codemirror/lint": ">=6.0.0", + "@codemirror/search": ">=6.0.0", + "@codemirror/state": ">=6.0.0", + "@codemirror/view": ">=6.0.0" + } + }, + "node_modules/@uiw/codemirror-theme-vscode": { + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-vscode/-/codemirror-theme-vscode-4.24.2.tgz", + "integrity": "sha512-5rLvAMKNjqtHSy3BZnI1Q4ZLqUDDJC5fKOp8Mv5VjPqEmAxEAIGrIXBnhAFlRg5VjHbHEMtM1O5ugL3IeHoBdQ==", + "dependencies": { + "@uiw/codemirror-themes": "4.24.2" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/codemirror-themes": { + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-themes/-/codemirror-themes-4.24.2.tgz", + "integrity": "sha512-0fQusJE08APL+1WM0xYqvampVM2RpABHjvZbcJWHdRc+teZbrP8907VW1ZX1tO/7/xgRRt4Fc3RHPCcTs7b0NQ==", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@codemirror/language": ">=6.0.0", + "@codemirror/state": ">=6.0.0", + "@codemirror/view": ">=6.0.0" + } + }, + "node_modules/@uiw/react-codemirror": { + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/@uiw/react-codemirror/-/react-codemirror-4.24.2.tgz", + "integrity": "sha512-kp7DhTq4RR+M2zJBQBrHn1dIkBrtOskcwJX4vVsKGByReOvfMrhqRkGTxYMRDTX6x75EG2mvBJPDKYcUQcHWBw==", + "dependencies": { + "@babel/runtime": "^7.18.6", + "@codemirror/commands": "^6.1.0", + "@codemirror/state": "^6.1.1", + "@codemirror/theme-one-dark": "^6.0.0", + "@uiw/codemirror-extensions-basic-setup": "4.24.2", + "codemirror": "^6.0.0" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@babel/runtime": ">=7.11.0", + "@codemirror/state": ">=6.0.0", + "@codemirror/theme-one-dark": ">=6.0.0", + "@codemirror/view": ">=6.0.0", + "codemirror": ">=6.0.0", + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, "node_modules/@vitejs/plugin-react": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.5.2.tgz", @@ -2245,6 +2269,20 @@ "node": ">=12" } }, + "node_modules/codemirror": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", + "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, "node_modules/color-alpha": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/color-alpha/-/color-alpha-1.0.4.tgz", @@ -2422,6 +2460,11 @@ "resolved": "https://registry.npmjs.org/country-regex/-/country-regex-1.1.0.tgz", "integrity": "sha512-iSPlClZP8vX7MC3/u6s3lrDuoQyhQukh5LyABJ3hvfzbQ3Yyayd4fp04zjLnfi267B/B2FkumcWWgrbban7sSA==" }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2739,17 +2782,6 @@ "resolved": "https://registry.npmjs.org/detect-kerning/-/detect-kerning-2.1.2.tgz", "integrity": "sha512-I3JIbrnKPAntNLl1I6TpSQQdQ4AutYzv/sKMFKbepawV/hlH0GmYKhUoOEMd4xqaUHT+Bm0f4127lh5qs1m1tw==" }, - "node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, "node_modules/draw-svg-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/draw-svg-path/-/draw-svg-path-1.0.0.tgz", @@ -4255,12 +4287,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/monaco-editor": { - "version": "0.45.0", - "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.45.0.tgz", - "integrity": "sha512-mjv1G1ZzfEE3k9HZN0dQ2olMdwIfaeAAjFiwNprLfYNRSz7ctv9XuCT7gPtBGrMUeV1/iZzYKj17Khu1hxoHOA==", - "peer": true - }, "node_modules/mouse-change": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/mouse-change/-/mouse-change-1.4.0.tgz", @@ -5111,11 +5137,6 @@ "node": "*" } }, - "node_modules/state-local": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz", - "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==" - }, "node_modules/static-eval": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.1.1.tgz", @@ -5217,6 +5238,11 @@ "resolved": "https://registry.npmjs.org/strongly-connected-components/-/strongly-connected-components-1.0.1.tgz", "integrity": "sha512-i0TFx4wPcO0FwX+4RkLJi1MxmcTv90jNZgxMu9XRnMXMeFUY1VJlIoXpZunPUvUUqbCT1pg5PEkFqqpcaElNaA==" }, + "node_modules/style-mod": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", + "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==" + }, "node_modules/supercluster": { "version": "7.1.5", "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-7.1.5.tgz", @@ -5554,6 +5580,11 @@ "pbf": "^3.2.1" } }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==" + }, "node_modules/weak-map": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/weak-map/-/weak-map-1.0.8.tgz", diff --git a/package.json b/package.json index b4132213..2686dcc5 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-plotly.js": "^2.6.0", - "@codeium/react-code-editor": "^1.0.0" + "@uiw/react-codemirror": "^4.24.2", + "@uiw/codemirror-theme-vscode": "^4.24.2" }, "devDependencies": { "@eslint/js": "^9.25.0", diff --git a/src/PythonCodeEditor.jsx b/src/PythonCodeEditor.jsx index 43f9f3d8..52686d0e 100644 --- a/src/PythonCodeEditor.jsx +++ b/src/PythonCodeEditor.jsx @@ -1,7 +1,9 @@ -import React, { useState, useCallback } from 'react'; -import { CodeiumEditor } from "@codeium/react-code-editor"; +import { useState, useCallback } from 'react'; +import CodeMirror from '@uiw/react-codemirror'; +import { vscodeDark } from '@uiw/codemirror-theme-vscode'; import { getApiEndpoint } from './config.js'; + const PythonCodeEditor = ({ code = "# Define your Python variables and functions here\n# Example:\n# my_variable = 42\n# def my_function(x):\n# return x * 2\n", onCodeChange, @@ -49,7 +51,7 @@ const PythonCodeEditor = ({ return (
    -

    Python Code Editor

    + {/*

    Python Code Editor

    */}
    - +
    {executionResult && ( From 69776f7e9278c9e3689fbf1e9cfdd712b3a74253 Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Tue, 5 Aug 2025 19:58:15 -0400 Subject: [PATCH 11/13] removed shared namespace --- src/PythonCodeEditor.jsx | 2 +- src/backend.py | 4 ---- src/pathsim_utils.py | 5 ----- 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/PythonCodeEditor.jsx b/src/PythonCodeEditor.jsx index cadebdd3..36c00d13 100644 --- a/src/PythonCodeEditor.jsx +++ b/src/PythonCodeEditor.jsx @@ -58,7 +58,7 @@ const PythonCodeEditor = ({ disabled={isExecuting} className="execute-btn" > - {isExecuting ? 'Executing...' : 'Execute Code'} + {isExecuting ? 'Executing...' : 'Test Code'}
    diff --git a/src/backend.py b/src/backend.py index fc0b170e..9e6177d0 100644 --- a/src/backend.py +++ b/src/backend.py @@ -392,9 +392,6 @@ def execute_python(): ) eval_namespace[var_name] = value - # Sync with pathsim_utils shared_eval_namespace - pathsim_utils.shared_eval_namespace.update(eval_namespace) - return jsonify( { "success": True, @@ -446,7 +443,6 @@ def clear_eval_namespace(): global eval_namespace try: eval_namespace.clear() - pathsim_utils.shared_eval_namespace.clear() return jsonify( {"success": True, "message": "Eval namespace cleared successfully."} ) diff --git a/src/pathsim_utils.py b/src/pathsim_utils.py index d9f51b2a..501034e9 100644 --- a/src/pathsim_utils.py +++ b/src/pathsim_utils.py @@ -36,8 +36,6 @@ from flask import jsonify import inspect -# Shared eval_namespace - will be set by backend -shared_eval_namespace = {} NAME_TO_SOLVER = { "SSPRK22": pathsim.solvers.SSPRK22, @@ -112,9 +110,6 @@ def make_global_variables(global_vars): # Return a namespace dictionary containing the global variables global_namespace = globals().copy() - # Include shared variables from the code editor - global_namespace.update(shared_eval_namespace) - for var in global_vars: var_name = var.get("name", "").strip() var_value = var.get("value", "") From fd6bc15ea40d3b3514cd8db8947b456cd072cc6c Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Tue, 5 Aug 2025 20:07:57 -0400 Subject: [PATCH 12/13] removed unused API functions + always display all variables --- src/backend.py | 69 +++++--------------------------------------------- 1 file changed, 6 insertions(+), 63 deletions(-) diff --git a/src/backend.py b/src/backend.py index 9e6177d0..040ee77d 100644 --- a/src/backend.py +++ b/src/backend.py @@ -15,9 +15,6 @@ from .pathsim_utils import make_pathsim_model, map_str_to_object from pathsim.blocks import Scope, Spectrum -# Import pathsim_utils to share eval_namespace -from . import pathsim_utils - # Sphinx imports for docstring processing from docutils.core import publish_parts @@ -324,14 +321,9 @@ def run_pathsim(): return jsonify({"success": False, "error": f"Server error: {str(e)}"}), 500 -# Global namespace for user-defined variables and functions -eval_namespace = {} - - @app.route("/execute-python", methods=["POST"]) def execute_python(): - """Execute Python code and update the global eval_namespace with any new variables/functions.""" - global eval_namespace + """Execute Python code and returns variables/functions.""" try: data = request.json @@ -341,16 +333,13 @@ def execute_python(): return jsonify({"success": False, "error": "No code provided"}), 400 # Create a temporary namespace that includes current eval_namespace - temp_namespace = eval_namespace.copy() - temp_namespace.update(globals()) + temp_namespace = {} + # temp_namespace.update(globals()) # Capture stdout and stderr stdout_capture = io.StringIO() stderr_capture = io.StringIO() - # Track variables before execution - vars_before = set(temp_namespace.keys()) - try: with redirect_stdout(stdout_capture), redirect_stderr(stderr_capture): exec(code, temp_namespace) @@ -363,20 +352,18 @@ def execute_python(): return jsonify({"success": False, "error": error_output}), 400 # Find new variables and functions - vars_after = set(temp_namespace.keys()) - new_vars = vars_after - vars_before + vars = set(temp_namespace.keys()) + # new_vars = vars_after - vars_before # Filter out built-ins and modules, keep user-defined items user_variables = {} user_functions = [] - for var_name in new_vars: + for var_name in vars: if not var_name.startswith("__"): value = temp_namespace[var_name] if callable(value) and hasattr(value, "__name__"): user_functions.append(var_name) - # Add function to eval_namespace - eval_namespace[var_name] = value else: # Try to serialize the value for display try: @@ -384,13 +371,10 @@ def execute_python(): user_variables[var_name] = value else: user_variables[var_name] = str(value) - # Add variable to eval_namespace - eval_namespace[var_name] = value except Exception: user_variables[var_name] = ( f"<{type(value).__name__} object>" ) - eval_namespace[var_name] = value return jsonify( { @@ -411,47 +395,6 @@ def execute_python(): return jsonify({"success": False, "error": f"Server error: {str(e)}"}), 500 -@app.route("/get-eval-namespace", methods=["GET"]) -def get_eval_namespace(): - """Get the current eval_namespace for debugging/inspection.""" - try: - # Create a serializable version of the namespace - serializable_namespace = {} - - for key, value in eval_namespace.items(): - if callable(value): - serializable_namespace[key] = f"" - else: - try: - if isinstance(value, (int, float, str, bool, list, dict)): - serializable_namespace[key] = value - else: - serializable_namespace[key] = str(value) - except Exception: - serializable_namespace[key] = f"<{type(value).__name__} object>" - - return jsonify({"success": True, "namespace": serializable_namespace}) - except Exception as e: - return jsonify( - {"success": False, "error": f"Error retrieving namespace: {str(e)}"} - ), 500 - - -@app.route("/clear-eval-namespace", methods=["POST"]) -def clear_eval_namespace(): - """Clear the eval_namespace.""" - global eval_namespace - try: - eval_namespace.clear() - return jsonify( - {"success": True, "message": "Eval namespace cleared successfully."} - ) - except Exception as e: - return jsonify( - {"success": False, "error": f"Error clearing namespace: {str(e)}"} - ), 500 - - # Catch-all route for React Router (SPA routing) @app.route("/") def catch_all(path): From 2678f45f5b67396d8d23657db3f9e95cf33b9c8d Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Tue, 5 Aug 2025 20:55:16 -0400 Subject: [PATCH 13/13] added example --- example_graphs/events_advanced.json | 365 ++++++++++++++++++++++++++++ 1 file changed, 365 insertions(+) create mode 100644 example_graphs/events_advanced.json diff --git a/example_graphs/events_advanced.json b/example_graphs/events_advanced.json new file mode 100644 index 00000000..e642150a --- /dev/null +++ b/example_graphs/events_advanced.json @@ -0,0 +1,365 @@ +{ + "nodes": [ + { + "id": "2", + "type": "process", + "position": { + "x": 868.2944372219196, + "y": 463.7056320088598 + }, + "data": { + "label": "process 2", + "initial_value": "", + "residence_time": "30", + "source_term": "" + }, + "measured": { + "width": 200, + "height": 120 + }, + "selected": false, + "dragging": false + }, + { + "id": "5", + "type": "amplifier", + "position": { + "x": 324.22187866193707, + "y": 500.1415773669528 + }, + "data": { + "label": "amplifier 5", + "gain": "-1" + }, + "measured": { + "width": 90, + "height": 80 + }, + "selected": false, + "dragging": false + }, + { + "id": "6", + "type": "scope", + "position": { + "x": 849.5352656218994, + "y": 616.3744300730544 + }, + "data": { + "label": "scope 6", + "labels": "", + "sampling_rate": "", + "t_wait": "" + }, + "measured": { + "width": 120, + "height": 140 + }, + "selected": false, + "dragging": false + }, + { + "id": "7", + "type": "scope", + "position": { + "x": 884.3339278689236, + "y": 272.62551973484125 + }, + "data": { + "label": "scope 7", + "labels": "", + "sampling_rate": "", + "t_wait": "" + }, + "measured": { + "width": 120, + "height": 140 + }, + "selected": false, + "dragging": false + }, + { + "id": "8", + "type": "adder", + "position": { + "x": 470.494411363933, + "y": 507.51391609780273 + }, + "data": { + "label": "adder 8", + "operations": "" + }, + "measured": { + "width": 64, + "height": 64 + }, + "selected": false, + "dragging": false + }, + { + "id": "9", + "type": "integrator", + "position": { + "x": 600.7345026088365, + "y": 514.5712609435857 + }, + "data": { + "label": "storage", + "initial_value": "startup_inv", + "reset_times": "" + }, + "measured": { + "width": 200, + "height": 48 + }, + "selected": false, + "dragging": false + }, + { + "id": "10", + "type": "pulsesource", + "position": { + "x": 533.6515111182074, + "y": 340.6779400691841 + }, + "data": { + "label": "fusion rate", + "T": "", + "amplitude": "", + "duty": "", + "t_fall": "", + "t_rise": "", + "tau": "" + }, + "measured": { + "width": 205, + "height": 53 + }, + "selected": true, + "dragging": false + }, + { + "id": "12", + "type": "amplifier", + "position": { + "x": 734.4442527405984, + "y": 407.0512209610127 + }, + "data": { + "label": "amplifier 12", + "gain": "1.1" + }, + "measured": { + "width": 90, + "height": 80 + }, + "selected": false, + "dragging": false + } + ], + "edges": [ + { + "id": "e2-6-from_inv", + "source": "2", + "target": "6", + "sourceHandle": "inv", + "targetHandle": null, + "type": "smoothstep", + "data": {}, + "style": { + "strokeWidth": 2, + "stroke": "#ECDFCC" + }, + "markerEnd": { + "type": "arrowclosed", + "width": 20, + "height": 20, + "color": "#ECDFCC" + } + }, + { + "id": "e5-8", + "source": "5", + "target": "8", + "sourceHandle": null, + "targetHandle": null, + "type": "smoothstep", + "data": {}, + "style": { + "strokeWidth": 2, + "stroke": "#ECDFCC" + }, + "markerEnd": { + "type": "arrowclosed", + "width": 20, + "height": 20, + "color": "#ECDFCC" + } + }, + { + "id": "e2-8-from_mass_flow_rate", + "source": "2", + "target": "8", + "sourceHandle": "mass_flow_rate", + "targetHandle": null, + "type": "smoothstep", + "data": {}, + "style": { + "strokeWidth": 2, + "stroke": "#ECDFCC" + }, + "markerEnd": { + "type": "arrowclosed", + "width": 20, + "height": 20, + "color": "#ECDFCC" + } + }, + { + "id": "e8-9", + "source": "8", + "target": "9", + "sourceHandle": null, + "targetHandle": null, + "type": "smoothstep", + "data": {}, + "style": { + "strokeWidth": 2, + "stroke": "#ECDFCC" + }, + "markerEnd": { + "type": "arrowclosed", + "width": 20, + "height": 20, + "color": "#ECDFCC" + } + }, + { + "id": "e9-6", + "source": "9", + "target": "6", + "sourceHandle": null, + "targetHandle": null, + "type": "smoothstep", + "data": {}, + "style": { + "strokeWidth": 2, + "stroke": "#ECDFCC" + }, + "markerEnd": { + "type": "arrowclosed", + "width": 20, + "height": 20, + "color": "#ECDFCC" + } + }, + { + "id": "e10-5", + "source": "10", + "target": "5", + "sourceHandle": null, + "targetHandle": null, + "type": "smoothstep", + "data": {}, + "style": { + "strokeWidth": 2, + "stroke": "#ECDFCC" + }, + "markerEnd": { + "type": "arrowclosed", + "width": 20, + "height": 20, + "color": "#ECDFCC" + } + }, + { + "id": "e10-7", + "source": "10", + "target": "7", + "sourceHandle": null, + "targetHandle": null, + "type": "smoothstep", + "data": {}, + "style": { + "strokeWidth": 2, + "stroke": "#ECDFCC" + }, + "markerEnd": { + "type": "arrowclosed", + "width": 20, + "height": 20, + "color": "#ECDFCC" + } + }, + { + "id": "e10-12", + "source": "10", + "target": "12", + "sourceHandle": null, + "targetHandle": null, + "type": "smoothstep", + "data": {}, + "style": { + "strokeWidth": 2, + "stroke": "#ECDFCC" + }, + "markerEnd": { + "type": "arrowclosed", + "width": 20, + "height": 20, + "color": "#ECDFCC" + } + }, + { + "id": "e12-2", + "source": "12", + "target": "2", + "sourceHandle": null, + "targetHandle": null, + "type": "smoothstep", + "data": {}, + "style": { + "strokeWidth": 2, + "stroke": "#ECDFCC" + }, + "markerEnd": { + "type": "arrowclosed", + "width": 20, + "height": 20, + "color": "#ECDFCC" + } + } + ], + "nodeCounter": 13, + "solverParams": { + "dt": "0.01", + "dt_min": "1e-6", + "dt_max": "1.0", + "Solver": "SSPRK22", + "tolerance_fpi": "1e-6", + "iterations_max": "100", + "log": "true", + "simulation_duration": "300", + "extra_params": "{}" + }, + "globalVariables": [], + "events": [ + { + "name": "storage_empty", + "type": "ZeroCrossingDown", + "func_evt": "fun_stor_evt", + "func_act": "fun_stor_act", + "tolerance": "1e-8", + "id": 1754440495739 + }, + { + "name": "restart", + "type": "ZeroCrossingUp", + "func_evt": "func_evt_start", + "func_act": "func_act_start", + "tolerance": "1e-8", + "id": 1754440898819 + } + ], + "pythonCode": "def fun_stor_evt(t):\n *_, I = storage_9()\n return I\n\ndef fun_stor_act(t):\n fusion_rate_10.off()\n\nstartup_inv = 10\n\ndef func_evt_start(t):\n *_, I = storage_9()\n return I - startup_inv\n\ndef func_act_start(t):\n fusion_rate_10.on()\n " +} \ No newline at end of file