diff --git a/gui/src/app/areas/ControlArea/DataGenerationArea/DataPyPanel.tsx b/gui/src/app/areas/ControlArea/DataGenerationArea/DataPyPanel.tsx index 4b17e0d8..38932b21 100644 --- a/gui/src/app/areas/ControlArea/DataGenerationArea/DataPyPanel.tsx +++ b/gui/src/app/areas/ControlArea/DataGenerationArea/DataPyPanel.tsx @@ -31,7 +31,7 @@ const DataPyPanel: FunctionComponent = () => { [consoleRef, onData, onStatus], ); - const { run, cancel } = usePyodideWorker(callbacks); + const { run } = usePyodideWorker(callbacks); const handleRun = useCallback( (code: string) => { @@ -62,7 +62,6 @@ const DataPyPanel: FunctionComponent = () => { language="python" status={status} onRun={handleRun} - onCancel={cancel} runnable={true} notRunnableReason="" onHelp={handleHelp} diff --git a/gui/src/app/areas/ControlArea/DataGenerationArea/DataRPanel.tsx b/gui/src/app/areas/ControlArea/DataGenerationArea/DataRPanel.tsx index 3f5ec994..784d9fff 100644 --- a/gui/src/app/areas/ControlArea/DataGenerationArea/DataRPanel.tsx +++ b/gui/src/app/areas/ControlArea/DataGenerationArea/DataRPanel.tsx @@ -18,8 +18,7 @@ const handleHelp = () => const DataRPanel: FunctionComponent = () => { const { consoleRef, status, onStatus, onData } = useDataGenState("r"); - const { run, cancel } = useWebR({ consoleRef, onStatus, onData }); - + const { run } = useWebR({ consoleRef, onStatus, onData }); const handleRun = useCallback( async (code: string) => { clearOutputDivs(consoleRef); @@ -41,7 +40,6 @@ const DataRPanel: FunctionComponent = () => { language="r" status={status} onRun={handleRun} - onCancel={cancel} runnable={true} notRunnableReason="" onHelp={handleHelp} diff --git a/gui/src/app/areas/ControlArea/SamplingArea/ResultsArea/AnalysisArea/AnalysisPyPanel.tsx b/gui/src/app/areas/ControlArea/SamplingArea/ResultsArea/AnalysisArea/AnalysisPyPanel.tsx index 0b28d2b5..1d03e980 100644 --- a/gui/src/app/areas/ControlArea/SamplingArea/ResultsArea/AnalysisArea/AnalysisPyPanel.tsx +++ b/gui/src/app/areas/ControlArea/SamplingArea/ResultsArea/AnalysisArea/AnalysisPyPanel.tsx @@ -36,7 +36,7 @@ const AnalysisPyPanel: FunctionComponent = ({ latestRun }) => { [consoleRef, imagesRef, onStatus], ); - const { run, cancel } = usePyodideWorker(callbacks); + const { run } = usePyodideWorker(callbacks); const handleRun = useCallback( (code: string) => { @@ -69,7 +69,6 @@ const AnalysisPyPanel: FunctionComponent = ({ latestRun }) => { language="python" status={status} onRun={handleRun} - onCancel={cancel} runnable={runnable} notRunnableReason={notRunnableReason} imagesRef={imagesRef} diff --git a/gui/src/app/areas/ControlArea/SamplingArea/ResultsArea/AnalysisArea/AnalysisRPanel.tsx b/gui/src/app/areas/ControlArea/SamplingArea/ResultsArea/AnalysisArea/AnalysisRPanel.tsx index e3835a12..a563c073 100644 --- a/gui/src/app/areas/ControlArea/SamplingArea/ResultsArea/AnalysisArea/AnalysisRPanel.tsx +++ b/gui/src/app/areas/ControlArea/SamplingArea/ResultsArea/AnalysisArea/AnalysisRPanel.tsx @@ -24,7 +24,7 @@ const AnalysisRPanel: FunctionComponent = ({ latestRun }) => { files, } = useAnalysisState(latestRun); - const { run, cancel } = useWebR({ consoleRef, imagesRef, onStatus }); + const { run } = useWebR({ consoleRef, imagesRef, onStatus }); const handleRun = useCallback( async (userCode: string) => { @@ -48,7 +48,6 @@ const AnalysisRPanel: FunctionComponent = ({ latestRun }) => { language="r" status={status} onRun={handleRun} - onCancel={cancel} runnable={runnable} notRunnableReason={notRunnableReason} imagesRef={imagesRef} diff --git a/gui/src/app/areas/ControlArea/SamplingArea/ResultsArea/SamplerOutputArea/DrawsTablePanel.tsx b/gui/src/app/areas/ControlArea/SamplingArea/ResultsArea/SamplerOutputArea/DrawsTablePanel.tsx index a68b877b..30e72e50 100644 --- a/gui/src/app/areas/ControlArea/SamplingArea/ResultsArea/SamplerOutputArea/DrawsTablePanel.tsx +++ b/gui/src/app/areas/ControlArea/SamplingArea/ResultsArea/SamplerOutputArea/DrawsTablePanel.tsx @@ -95,9 +95,7 @@ const DrawsTablePanel: FunctionComponent = ({ Chain Draw {paramNames.map((name, i) => ( - - {name} - + {name} ))} @@ -107,9 +105,7 @@ const DrawsTablePanel: FunctionComponent = ({ {drawChainIds[i]} {drawNumbers[i]} {formattedDraws.map((draw, j) => ( - - {draw[i]} - + {draw[i]} ))} ))} diff --git a/gui/src/app/components/FileEditor/ScriptEditor.tsx b/gui/src/app/components/FileEditor/ScriptEditor.tsx index 5fcb9dd0..5cd8497e 100644 --- a/gui/src/app/components/FileEditor/ScriptEditor.tsx +++ b/gui/src/app/components/FileEditor/ScriptEditor.tsx @@ -1,6 +1,6 @@ import { FunctionComponent, RefObject, useCallback, use, useMemo } from "react"; -import { Close, Help, PlayArrow } from "@mui/icons-material"; +import { Help, PlayArrow } from "@mui/icons-material"; import Box from "@mui/material/Box"; import { Split } from "@geoffcox/react-splitter"; import { useMonaco } from "@monaco-editor/react"; @@ -22,7 +22,6 @@ export type ScriptEditorProps = { filename: FileNames; dataKey: ProjectKnownFiles; onRun: (code: string) => void; - onCancel?: () => void; runnable: boolean; notRunnableReason?: string; onHelp?: () => void; @@ -36,7 +35,6 @@ const ScriptEditor: FunctionComponent = ({ filename, dataKey, onRun, - onCancel, runnable, notRunnableReason, onHelp, @@ -76,12 +74,11 @@ const ScriptEditor: FunctionComponent = ({ const monacoInstance = useMonaco(); - const scriptShortcuts: editor.IActionDescriptor[] = useMemo(() => { + const runCtrlEnter: editor.IActionDescriptor[] = useMemo(() => { if (!monacoInstance) { return []; } - const actions = [ - // Ctrl-Enter to run + return [ { id: "run-script", label: "Run Script", @@ -95,19 +92,7 @@ const ScriptEditor: FunctionComponent = ({ }, }, ]; - if (onCancel) { - // Ctrl-C to cancel - actions.push({ - id: "cancel-script", - label: "Cancel Script", - keybindings: [ - monacoInstance.KeyMod.CtrlCmd | monacoInstance.KeyCode.KeyC, - ], - run: onCancel, - }); - } - return actions; - }, [monacoInstance, onCancel, runCode, runnable, unsavedChanges]); + }, [monacoInstance, runCode, runnable, unsavedChanges]); const toolbarItems: ToolbarItem[] = useMemo(() => { return makeToolbar({ @@ -116,13 +101,11 @@ const ScriptEditor: FunctionComponent = ({ runnable: runnable && !unsavedChanges, notRunnableReason, onRun: runCode, - onCancel, onHelp, }); }, [ language, notRunnableReason, - onCancel, onHelp, runCode, runnable, @@ -141,7 +124,7 @@ const ScriptEditor: FunctionComponent = ({ onSaveText={onSaveText} toolbarItems={toolbarItems} contentOnEmpty={contentOnEmpty} - actions={scriptShortcuts} + actions={runCtrlEnter} /> @@ -155,9 +138,8 @@ const makeToolbar = (o: { notRunnableReason?: string; onRun: () => void; onHelp?: () => void; - onCancel?: () => void; }): ToolbarItem[] => { - const { status, onRun, runnable, onHelp, name, onCancel } = o; + const { status, onRun, runnable, onHelp, name } = o; const ret: ToolbarItem[] = []; if (onHelp !== undefined) { ret.push({ @@ -184,17 +166,6 @@ const makeToolbar = (o: { }); } - if (onCancel && status === "running") { - ret.push({ - type: "button", - tooltip: "Cancel", - label: "Cancel", - icon: , - onClick: onCancel, - color: "error", - }); - } - let label: string; let color: ColorOptions; if (status === "loading") { diff --git a/gui/src/app/core/Scripting/pyodide/pyodideWorker.ts b/gui/src/app/core/Scripting/pyodide/pyodideWorker.ts index 4326d195..2c706331 100644 --- a/gui/src/app/core/Scripting/pyodide/pyodideWorker.ts +++ b/gui/src/app/core/Scripting/pyodide/pyodideWorker.ts @@ -3,29 +3,33 @@ import { isMonacoWorkerNoise } from "@SpUtil/isMonacoWorkerNoise"; import { InterpreterStatus } from "@SpCore/Scripting/InterpreterTypes"; import { MessageFromPyodideWorker, - MessageToPyodideWorker, PyodideRunSettings, } from "./pyodideWorkerTypes"; import spDrawsScript from "./sp_load_draws.py?raw"; import spMPLScript from "./sp_patch_matplotlib.py?raw"; +let pyodide: PyodideInterface | null = null; const loadPyodideInstance = async () => { - const pyodide = await loadPyodide({ - indexURL: "https://cdn.jsdelivr.net/pyodide/v0.27.2/full", - stdout: (x: string) => { - sendStdout(x); - }, - stderr: (x: string) => { - sendStderr(x); - }, - packages: ["numpy", "micropip", "pandas"], - }); - console.log("pyodide loaded"); - - pyodide.FS.writeFile("sp_load_draws.py", spDrawsScript); - pyodide.FS.writeFile("sp_patch_matplotlib.py", spMPLScript); - - return pyodide; + if (pyodide === null) { + pyodide = await loadPyodide({ + indexURL: "https://cdn.jsdelivr.net/pyodide/v0.27.2/full", + stdout: (x: string) => { + sendStdout(x); + }, + stderr: (x: string) => { + sendStderr(x); + }, + packages: ["numpy", "micropip", "pandas"], + }); + setStatus("installing"); + + pyodide.FS.writeFile("sp_load_draws.py", spDrawsScript); + pyodide.FS.writeFile("sp_patch_matplotlib.py", spMPLScript); + + return pyodide; + } else { + return pyodide; + } }; const sendMessageToMain = (message: MessageFromPyodideWorker) => { @@ -52,38 +56,25 @@ const addImage = (image: any) => { sendMessageToMain({ type: "addImage", image }); }; -self.onmessage = async (e: MessageEvent) => { +console.log("pyodide worker loaded"); + +self.onmessage = async (e) => { if (isMonacoWorkerNoise(e.data)) { return; } const message = e.data; - await run( - message.code, - message.spData, - message.spRunSettings, - message.files, - message.interruptBuffer, - ); + await run(message.code, message.spData, message.spRunSettings, message.files); }; -console.log("pyodide worker initialized"); - -console.log("opportunistically loading pyodide"); -const pyodidePromise: Promise = loadPyodideInstance(); const run = async ( code: string, spData: Record | undefined, spPySettings: PyodideRunSettings, files: Record | undefined, - interruptBuffer: Uint8Array | undefined, ) => { setStatus("loading"); try { - const pyodide = await pyodidePromise; - if (interruptBuffer) { - pyodide.setInterruptBuffer(interruptBuffer); - } - setStatus("installing"); + const pyodide = await loadPyodideInstance(); const [scriptPreamble, scriptPostamble] = getScriptParts(spPySettings); @@ -104,7 +95,6 @@ const run = async ( let succeeded = false; try { const packageFutures = []; - let patch_http = false; const micropip = pyodide.pyimport("micropip"); if (spPySettings.showsPlots) { @@ -114,20 +104,10 @@ const run = async ( packageFutures.push(micropip.install("arviz")); } } - if (script.includes("requests")) { - patch_http = true; - packageFutures.push( - micropip.install(["requests", "lzma", "pyodide-http"]), - ); - } packageFutures.push(micropip.install("stanio")); packageFutures.push(pyodide.loadPackagesFromImports(script)); - await Promise.all(packageFutures); - if (patch_http) { - await pyodide.runPythonAsync(` - from pyodide_http import patch_all - patch_all() - `); + for (const f of packageFutures) { + await f; } if (files) { diff --git a/gui/src/app/core/Scripting/pyodide/pyodideWorkerTypes.ts b/gui/src/app/core/Scripting/pyodide/pyodideWorkerTypes.ts index eb60add8..d237b879 100644 --- a/gui/src/app/core/Scripting/pyodide/pyodideWorkerTypes.ts +++ b/gui/src/app/core/Scripting/pyodide/pyodideWorkerTypes.ts @@ -17,7 +17,6 @@ export type MessageToPyodideWorker = { spData: Record | undefined; spRunSettings: PyodideRunSettings; files: Record | undefined; - interruptBuffer: Uint8Array | undefined; }; export const isMessageToPyodideWorker = ( diff --git a/gui/src/app/core/Scripting/pyodide/usePyodideWorker.ts b/gui/src/app/core/Scripting/pyodide/usePyodideWorker.ts index 45f61fb7..322d8240 100644 --- a/gui/src/app/core/Scripting/pyodide/usePyodideWorker.ts +++ b/gui/src/app/core/Scripting/pyodide/usePyodideWorker.ts @@ -27,7 +27,6 @@ type RunPyProps = { class PyodideWorkerInterface { #worker: Worker | undefined; - #interruptBuffer: Uint8Array | undefined; private constructor(private callbacks: PyodideWorkerCallbacks) { // do not call this directly, use create() instead @@ -40,15 +39,6 @@ class PyodideWorkerInterface { type: "module", }); - if (window.crossOriginIsolated) { - this.#interruptBuffer = new Uint8Array(new SharedArrayBuffer(1)); - } else { - console.warn( - "SharedArrayBuffer is not available, interrupting the Pyodide worker will not work", - ); - this.#interruptBuffer = undefined; - } - this.#worker.onmessage = (e: MessageEvent) => { const msg = e.data; if (!isMessageFromPyodideWorker(msg)) { @@ -95,13 +85,8 @@ class PyodideWorkerInterface { spData, spRunSettings, files, - interruptBuffer: this.#interruptBuffer, }; if (this.#worker) { - if (this.#interruptBuffer) { - // clear in case previous run was interrupted - this.#interruptBuffer[0] = 0; - } this.#worker.postMessage(msg); } else { throw new Error("pyodide worker is not defined"); @@ -109,18 +94,8 @@ class PyodideWorkerInterface { } cancel() { - if (this.#interruptBuffer && this.#interruptBuffer[0] === 0) { - // SIGINT - this.#interruptBuffer[0] = 2; - } else { - // if the interrupt buffer doesn't exist, or has already been set - // (and the user is requesting cancellation still) - // we can just terminate the worker - this.#worker?.terminate(); - this.callbacks.onStatus("failed"); - this.callbacks.onStderr("Python execution cancelled by user"); - this.#initialize(); - } + this.#worker?.terminate(); + this.#initialize(); } } diff --git a/gui/src/app/core/Scripting/webR/useWebR.ts b/gui/src/app/core/Scripting/webR/useWebR.ts index 28800dd5..7e3a98ae 100644 --- a/gui/src/app/core/Scripting/webR/useWebR.ts +++ b/gui/src/app/core/Scripting/webR/useWebR.ts @@ -1,4 +1,4 @@ -import { RefObject, useCallback, useEffect, useMemo, useState } from "react"; +import { RefObject, useCallback, useEffect, useState } from "react"; import { WebR } from "webr"; import { InterpreterStatus } from "@SpCore/Scripting/InterpreterTypes"; import { writeConsoleOutToDiv } from "@SpCore/Scripting/OutputDivUtils"; @@ -121,16 +121,11 @@ const useWebR = ({ imagesRef, consoleRef, onStatus, onData }: useWebRProps) => { [consoleRef, loadWebRInstance, onData, onStatus], ); - const cancel = useMemo(() => { - // NOTE: only works if COORS is set to allow shared worker usage - if (window.crossOriginIsolated) { - return () => { - if (webR) { - webR.interrupt(); - } - }; + const cancel = useCallback(() => { + if (webR) { + // NOTE: only works if COORS is set to allow shared worker usage + webR.interrupt(); } - return undefined; }, [webR]); return { run, cancel }; @@ -190,14 +185,6 @@ const outputLoop = async ( canvas.getContext("2d")?.drawImage(output.data.image, 0, 0); } break; - case "prompt": - // in our case these are only generated by calls to interrupt() - writeConsoleOutToDiv( - consoleRef, - "R execution cancelled by user", - "stderr", - ); - break; default: console.log("unexpected webR message: ", output); } diff --git a/gui/vercel.json b/gui/vercel.json deleted file mode 100644 index 293ef17f..00000000 --- a/gui/vercel.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "headers": [ - { - "source": "/", - "headers": [ - { - "key": "Cross-Origin-Embedder-Policy", - "value": "require-corp" - }, - { - "key": "Cross-Origin-Opener-Policy", - "value": "same-origin" - } - ] - } - ] -} diff --git a/gui/vite.config.ts b/gui/vite.config.ts index 0a00a05d..d536b91c 100644 --- a/gui/vite.config.ts +++ b/gui/vite.config.ts @@ -45,10 +45,6 @@ export default defineConfig({ plugins: [react(), tsconfigPaths()], server: { host: "127.0.0.1", - headers: { - "Cross-Origin-Opener-Policy": "same-origin", - "Cross-Origin-Embedder-Policy": "require-corp", - }, }, worker: { format: "es",