From 73cf7b7b91e27d62d309f260b61353b38f7735d3 Mon Sep 17 00:00:00 2001 From: Jeremy Magland Date: Wed, 31 Jul 2024 19:23:26 -0400 Subject: [PATCH 1/9] CompileContextProvider and Compile Model button --- gui/src/app/App.tsx | 5 +- gui/src/app/CompileContext/CompileContext.ts | 25 ++++ .../CompileContext/CompileContextProvider.tsx | 86 +++++++++++++ gui/src/app/FileEditor/StanFileEditor.tsx | 67 ++-------- gui/src/app/RunPanel/RunPanel.tsx | 120 +++++++++++------- gui/src/app/pages/HomePage/HomePage.tsx | 33 ++--- .../SamplingWindow/SamplingWindow.tsx | 9 +- gui/tsconfig.json | 3 +- 8 files changed, 224 insertions(+), 124 deletions(-) create mode 100644 gui/src/app/CompileContext/CompileContext.ts create mode 100644 gui/src/app/CompileContext/CompileContextProvider.tsx diff --git a/gui/src/app/App.tsx b/gui/src/app/App.tsx index 67a3efa9..a068ad44 100644 --- a/gui/src/app/App.tsx +++ b/gui/src/app/App.tsx @@ -4,6 +4,7 @@ import ProjectContextProvider from "@SpCore/ProjectContextProvider"; import HomePage from "@SpPages/HomePage"; import { Analytics } from "@vercel/analytics/react"; import { BrowserRouter } from "react-router-dom"; +import { CompileContextProvider } from "./CompileContext/CompileContextProvider"; const theme = createTheme(); @@ -13,7 +14,9 @@ function App() {
- + + +
diff --git a/gui/src/app/CompileContext/CompileContext.ts b/gui/src/app/CompileContext/CompileContext.ts new file mode 100644 index 00000000..b629cf7e --- /dev/null +++ b/gui/src/app/CompileContext/CompileContext.ts @@ -0,0 +1,25 @@ +import { createContext } from "react"; + +export type CompileStatus = + | "preparing" + | "compiling" + | "compiled" + | "failed" + | ""; + +type CompileContextType = { + compileStatus: CompileStatus; + compileMessage: string; + compiledMainJsUrl?: string; + validSyntax: boolean; + compile: () => void; + setValidSyntax: (valid: boolean) => void; +}; + +export const CompileContext = createContext({ + compileStatus: "", + compileMessage: "", + validSyntax: false, + compile: () => {}, + setValidSyntax: () => {}, +}); diff --git a/gui/src/app/CompileContext/CompileContextProvider.tsx b/gui/src/app/CompileContext/CompileContextProvider.tsx new file mode 100644 index 00000000..cd0391d3 --- /dev/null +++ b/gui/src/app/CompileContext/CompileContextProvider.tsx @@ -0,0 +1,86 @@ +import { ProjectContext } from "@SpCore/ProjectContextProvider"; +import compileStanProgram from "@SpStanc/compileStanProgram"; +import { + FunctionComponent, + PropsWithChildren, + useCallback, + useContext, + useEffect, + useState, +} from "react"; +import { CompileContext, CompileStatus } from "./CompileContext"; + +type CompileContextProviderProps = { + // none +}; + +export const CompileContextProvider: FunctionComponent< + PropsWithChildren +> = ({ children }) => { + const { data } = useContext(ProjectContext); + const [compileStatus, setCompileStatus] = useState(""); + const [ + theStanFileContentThasHasBeenCompiled, + setTheStanFileContentThasHasBeenCompiled, + ] = useState(""); + const [compileMessage, setCompileMessage] = useState(""); + const [compiledMainJsUrl, setCompiledMainJsUrl] = useState< + string | undefined + >(undefined); + const [validSyntax, setValidSyntax] = useState(false); + + useEffect(() => { + // if the compiled content is not the same as the current content, + // then the state should not be compiled or failed + if (data.stanFileContent !== theStanFileContentThasHasBeenCompiled) { + if (compileStatus === "compiled" || compileStatus === "failed") { + setCompileStatus(""); + setCompiledMainJsUrl(""); + } + } + }, [ + data.stanFileContent, + theStanFileContentThasHasBeenCompiled, + compileStatus, + setCompiledMainJsUrl, + ]); + + const handleCompile = useCallback(async () => { + setCompileStatus("compiling"); + await new Promise((resolve) => setTimeout(resolve, 500)); // for effect + const onStatus = (msg: string) => { + setCompileMessage(msg); + }; + const stanWasmServerUrl = + localStorage.getItem("stanWasmServerUrl") || + "https://trom-stan-wasm-server.magland.org"; + const { mainJsUrl } = await compileStanProgram( + stanWasmServerUrl, + data.stanFileContent, + onStatus, + ); + + if (!mainJsUrl) { + setCompileStatus("failed"); + return; + } + setCompiledMainJsUrl(mainJsUrl); + setCompileStatus("compiled"); + setTheStanFileContentThasHasBeenCompiled(data.stanFileContent); + }, [data.stanFileContent, setCompiledMainJsUrl]); + + return ( + + {children} + + ); +}; diff --git a/gui/src/app/FileEditor/StanFileEditor.tsx b/gui/src/app/FileEditor/StanFileEditor.tsx index 84dc8452..9ade1205 100644 --- a/gui/src/app/FileEditor/StanFileEditor.tsx +++ b/gui/src/app/FileEditor/StanFileEditor.tsx @@ -3,12 +3,12 @@ import { AutoFixHigh, Cancel, Settings } from "@mui/icons-material"; import StanCompileResultWindow from "@SpComponents/StanCompileResultWindow"; import TextEditor from "@SpComponents/TextEditor"; import { ToolbarItem } from "@SpComponents/ToolBar"; -import compileStanProgram from "@SpStanc/compileStanProgram"; import { stancErrorsToCodeMarkers } from "@SpStanc/Linting"; import useStanc from "@SpStanc/useStanc"; +import { CompileContext } from "@SpCompileContext/CompileContext"; import { FunctionComponent, - useCallback, + useContext, useEffect, useMemo, useState, @@ -22,11 +22,8 @@ type Props = { setEditedFileContent: (text: string) => void; onDeleteFile?: () => void; readOnly: boolean; - setCompiledUrl: (s: string) => void; }; -type CompileStatus = "preparing" | "compiling" | "compiled" | "failed" | ""; - const StanFileEditor: FunctionComponent = ({ fileName, fileContent, @@ -34,7 +31,6 @@ const StanFileEditor: FunctionComponent = ({ editedFileContent, setEditedFileContent, readOnly, - setCompiledUrl, }) => { const { stancErrors, requestFormat } = useStanc( "main.stan", @@ -42,61 +38,22 @@ const StanFileEditor: FunctionComponent = ({ setEditedFileContent, ); + const { setValidSyntax } = useContext(CompileContext); + + const { compileStatus, compileMessage, compile } = useContext(CompileContext); + const validSyntax = useMemo(() => { return stancErrors.errors === undefined; }, [stancErrors]); + useEffect(() => { + setValidSyntax(validSyntax); + }, [validSyntax, setValidSyntax]); + const hasWarnings = useMemo(() => { return stancErrors.warnings && stancErrors.warnings.length > 0; }, [stancErrors]); - const [compileStatus, setCompileStatus] = useState(""); - const [ - theStanFileContentThasHasBeenCompiled, - setTheStanFileContentThasHasBeenCompiled, - ] = useState(""); - const [compileMessage, setCompileMessage] = useState(""); - - const handleCompile = useCallback(async () => { - setCompileStatus("compiling"); - await new Promise((resolve) => setTimeout(resolve, 500)); // for effect - const onStatus = (msg: string) => { - setCompileMessage(msg); - }; - const stanWasmServerUrl = - localStorage.getItem("stanWasmServerUrl") || - "https://trom-stan-wasm-server.magland.org"; - const { mainJsUrl } = await compileStanProgram( - stanWasmServerUrl, - fileContent, - onStatus, - ); - - if (!mainJsUrl) { - setCompileStatus("failed"); - return; - } - setCompiledUrl(mainJsUrl); - setCompileStatus("compiled"); - setTheStanFileContentThasHasBeenCompiled(fileContent); - }, [fileContent, setCompiledUrl]); - - useEffect(() => { - // if the compiled content is not the same as the current content, - // then the state should not be compiled or failed - if (fileContent !== theStanFileContentThasHasBeenCompiled) { - if (compileStatus === "compiled" || compileStatus === "failed") { - setCompileStatus(""); - setCompiledUrl(""); - } - } - }, [ - fileContent, - theStanFileContentThasHasBeenCompiled, - compileStatus, - setCompiledUrl, - ]); - const [syntaxWindowVisible, setSyntaxWindowVisible] = useState(false); const toolbarItems: ToolbarItem[] = useMemo(() => { @@ -146,7 +103,7 @@ const StanFileEditor: FunctionComponent = ({ tooltip: "Compile Stan model", label: "Compile", icon: , - onClick: handleCompile, + onClick: compile, color: "darkblue", }); } @@ -170,7 +127,7 @@ const StanFileEditor: FunctionComponent = ({ }, [ editedFileContent, fileContent, - handleCompile, + compile, requestFormat, validSyntax, compileStatus, diff --git a/gui/src/app/RunPanel/RunPanel.tsx b/gui/src/app/RunPanel/RunPanel.tsx index 8b2f18a5..eac63b96 100644 --- a/gui/src/app/RunPanel/RunPanel.tsx +++ b/gui/src/app/RunPanel/RunPanel.tsx @@ -4,13 +4,19 @@ import LinearProgress, { LinearProgressProps, } from "@mui/material/LinearProgress"; import Typography from "@mui/material/Typography"; -import { FunctionComponent, useCallback } from "react"; +import { FunctionComponent, useCallback, useContext } from "react"; -import { SamplingOpts } from "@SpCore/ProjectDataModel"; +import { + ProjectDataModel, + SamplingOpts, + modelHasUnsavedChanges, +} from "@SpCore/ProjectDataModel"; import { Progress } from "@SpStanSampler/StanModelWorker"; import StanSampler from "@SpStanSampler/StanSampler"; import { StanRun } from "@SpStanSampler/useStanSampler"; import Button from "@mui/material/Button"; +import { CompileContext } from "@SpCompileContext/CompileContext"; +import { ProjectContext } from "@SpCore/ProjectContextProvider"; type RunPanelProps = { sampler?: StanSampler; @@ -39,8 +45,10 @@ const RunPanel: FunctionComponent = ({ sampler.cancel(); }, [sampler]); - if (!sampler) - return
Stan model not compiled
; + const { compile, compileMessage, compileStatus, validSyntax } = + useContext(CompileContext); + + const { data: projectData } = useContext(ProjectContext); if (!dataIsSaved) { return
Data not saved
; @@ -48,53 +56,79 @@ const RunPanel: FunctionComponent = ({ return (
-
- -   - {runStatus === "sampling" && ( + {compileStatus === "compiled" ? ( +
+ +   + {runStatus === "sampling" && ( + + )} +
+ {runStatus === "loading" && ( +
Loading compiled Stan model...
+ )} + {runStatus === "sampling" && ( +
+ Sampling + +
+ )} + {runStatus === "completed" &&
done sampling
} + {runStatus === "failed" && ( +
+ Sampling failed! +
{errorMessage}
+ + (see browser console for more details) + +
+ )} +
+ ) : ["preparing", "compiling"].includes(compileStatus) ? ( +
{compileMessage}
+ ) : ( +
- )} -
- {runStatus === "loading" &&
Loading compiled Stan model...
} - {runStatus === "sampling" && ( -
- Sampling - -
- )} - {runStatus === "completed" &&
done sampling
} - {runStatus === "failed" && ( -
- Sampling failed! -
{errorMessage}
- - (see browser console for more details) - -
- )} -
+
+ )}
); }; +const isCompileModelDisabled = ( + projectData: ProjectDataModel, + validSyntax: boolean, +) => { + if (!projectData.stanFileContent.trim()) return true; + if (modelHasUnsavedChanges(projectData)) return true; + if (!validSyntax) return true; + return false; +}; + type SamplingProgressComponentProps = { report: Progress | undefined; numChains: number; diff --git a/gui/src/app/pages/HomePage/HomePage.tsx b/gui/src/app/pages/HomePage/HomePage.tsx index 75042619..6e1ffa52 100644 --- a/gui/src/app/pages/HomePage/HomePage.tsx +++ b/gui/src/app/pages/HomePage/HomePage.tsx @@ -1,7 +1,11 @@ +import { Split } from "@geoffcox/react-splitter"; import Box from "@mui/material/Box"; import styled from "@mui/material/styles/styled"; import useMediaQuery from "@mui/material/useMediaQuery"; import StanFileEditor from "@SpComponents/StanFileEditor"; +import TabWidget from "@SpComponents/TabWidget"; +import TextEditor from "@SpComponents/TextEditor"; +import { FileNames } from "@SpCore/FileMapping"; import { ProjectContext } from "@SpCore/ProjectContextProvider"; import { modelHasUnsavedChanges, @@ -9,6 +13,8 @@ import { } from "@SpCore/ProjectDataModel"; import Sidebar, { drawerWidth } from "@SpPages/Sidebar"; import TopBar from "@SpPages/TopBar"; +import DataPyWindow from "@SpScripting/DataGeneration/DataPyWindow"; +import DataRWindow from "@SpScripting/DataGeneration/DataRWindow"; import { FunctionComponent, useContext, @@ -16,13 +22,7 @@ import { useRef, useState, } from "react"; -import TabWidget from "@SpComponents/TabWidget"; import SamplingWindow from "./SamplingWindow/SamplingWindow"; -import { FileNames } from "@SpCore/FileMapping"; -import DataRWindow from "@SpScripting/DataGeneration/DataRWindow"; -import DataPyWindow from "@SpScripting/DataGeneration/DataPyWindow"; -import TextEditor from "@SpComponents/TextEditor"; -import { Split } from "@geoffcox/react-splitter"; type Props = { // @@ -31,8 +31,6 @@ type Props = { const HomePage: FunctionComponent = () => { const { data } = useContext(ProjectContext); - const [compiledMainJsUrl, setCompiledMainJsUrl] = useState(""); - const smallScreen = useMediaQuery("(max-width:600px)"); const [leftPanelCollapsed, setLeftPanelCollapsed] = useState(smallScreen); @@ -64,8 +62,8 @@ const HomePage: FunctionComponent = () => { - - + + @@ -73,15 +71,13 @@ const HomePage: FunctionComponent = () => { }; type RightViewProps = { - compiledMainJsUrl: string; + // none }; -const RightView: FunctionComponent = ({ - compiledMainJsUrl, -}) => { +const RightView: FunctionComponent = () => { return ( - + @@ -91,12 +87,10 @@ const RightView: FunctionComponent = ({ }; type LeftViewProps = { - setCompiledMainJsUrl: (url: string) => void; + // none }; -const LeftView: FunctionComponent = ({ - setCompiledMainJsUrl, -}) => { +const LeftView: FunctionComponent = () => { const { data, update } = useContext(ProjectContext); return ( @@ -119,7 +113,6 @@ const LeftView: FunctionComponent = ({ }) } readOnly={false} - setCompiledUrl={setCompiledMainJsUrl} /> = ({ - compiledMainJsUrl, -}) => { +const SamplingWindow: FunctionComponent = () => { const { data, update } = useContext(ProjectContext); const parsedData = useMemo(() => { try { @@ -39,6 +38,8 @@ const SamplingWindow: FunctionComponent = ({ [update], ); + const { compiledMainJsUrl } = useContext(CompileContext); + const { sampler, latestRun } = useStanSampler(compiledMainJsUrl); const isSampling = latestRun.status === "sampling"; return ( diff --git a/gui/tsconfig.json b/gui/tsconfig.json index 765fbda1..2fba5cc3 100644 --- a/gui/tsconfig.json +++ b/gui/tsconfig.json @@ -45,7 +45,8 @@ "@SpStanSampler/*": ["app/StanSampler/*"], "@SpUtil/*": ["app/util/*"], "@SpStanStats/*": ["app/util/stan_stats/*"], - "@SpScripting/*": ["app/Scripting/*"] + "@SpScripting/*": ["app/Scripting/*"], + "@SpCompileContext/*": ["app/CompileContext/*"] } }, "include": ["src", "test"], From 675b37f07b9d7fe344b20796e4dba9dc8c7deb70 Mon Sep 17 00:00:00 2001 From: Jeremy Magland Date: Thu, 1 Aug 2024 11:08:40 -0400 Subject: [PATCH 2/9] Move stanWasmServerUrl to CompileContext and fix compilation server dlg --- .../CompilationServerConnectionControl.tsx | 23 +++++++----- .../ConfigureCompilationServerDialog.tsx | 37 +++++++++++++------ gui/src/app/CompileContext/CompileContext.ts | 4 ++ .../CompileContext/CompileContextProvider.tsx | 24 ++++++++++-- 4 files changed, 64 insertions(+), 24 deletions(-) diff --git a/gui/src/app/CompilationServerConnectionControl/CompilationServerConnectionControl.tsx b/gui/src/app/CompilationServerConnectionControl/CompilationServerConnectionControl.tsx index 91b0987e..1cd9c482 100644 --- a/gui/src/app/CompilationServerConnectionControl/CompilationServerConnectionControl.tsx +++ b/gui/src/app/CompilationServerConnectionControl/CompilationServerConnectionControl.tsx @@ -3,10 +3,17 @@ import { Cancel, Check } from "@mui/icons-material"; import CloseableDialog, { useDialogControls, } from "@SpComponents/CloseableDialog"; -import { FunctionComponent, useCallback, useEffect, useState } from "react"; +import { + FunctionComponent, + useCallback, + useContext, + useEffect, + useState, +} from "react"; import ConfigureCompilationServerDialog from "./ConfigureCompilationServerDialog"; import IconButton from "@mui/material/IconButton"; import Typography from "@mui/material/Typography"; +import { CompileContext } from "@SpCompileContext/CompileContext"; export const publicUrl = "https://trom-stan-wasm-server.magland.org"; export const localUrl = "http://localhost:8083"; @@ -18,13 +25,8 @@ type CompilationServerConnectionControlProps = { const CompilationServerConnectionControl: FunctionComponent< CompilationServerConnectionControlProps > = () => { - const [stanWasmServerUrl, setStanWasmServerUrl] = useState( - localStorage.getItem("stanWasmServerUrl") || publicUrl, - ); + const { stanWasmServerUrl } = useContext(CompileContext); const { isConnected, retryConnection } = useIsConnected(stanWasmServerUrl); - useEffect(() => { - localStorage.setItem("stanWasmServerUrl", stanWasmServerUrl); - }, [stanWasmServerUrl]); const { handleOpen: openDialog, @@ -63,8 +65,6 @@ const CompilationServerConnectionControl: FunctionComponent< handleClose={closeDialog} > @@ -82,6 +82,11 @@ const useIsConnected = (stanWasmServerUrl: string) => { }, []); useEffect(() => { setIsConnected(false); + if (!probeUrl.startsWith("http") && !probeUrl.startsWith("https")) { + // important to do this check because otherwise fetch may succeed because + // the server of this web app may respond with success + return; + } (async () => { try { const response = await fetch(probeUrl); diff --git a/gui/src/app/CompilationServerConnectionControl/ConfigureCompilationServerDialog.tsx b/gui/src/app/CompilationServerConnectionControl/ConfigureCompilationServerDialog.tsx index 61e8cf80..82b31a85 100644 --- a/gui/src/app/CompilationServerConnectionControl/ConfigureCompilationServerDialog.tsx +++ b/gui/src/app/CompilationServerConnectionControl/ConfigureCompilationServerDialog.tsx @@ -1,4 +1,10 @@ -import { FunctionComponent, useCallback, useState } from "react"; +import { + FunctionComponent, + useCallback, + useContext, + useMemo, + useState, +} from "react"; import { localUrl, publicUrl } from "./CompilationServerConnectionControl"; import FormControl from "@mui/material/FormControl"; import Divider from "@mui/material/Divider"; @@ -9,20 +15,30 @@ import Radio from "@mui/material/Radio"; import TextField from "@mui/material/TextField"; import IconButton from "@mui/material/IconButton"; import { Refresh } from "@mui/icons-material"; +import { CompileContext } from "@SpCompileContext/CompileContext"; type ServerType = "public" | "local" | "custom"; type ConfigureCompilationServerDialogProps = { - stanWasmServerUrl: string; - setStanWasmServerUrl: (url: string) => void; isConnected: boolean; onRetry: () => void; }; const ConfigureCompilationServerDialog: FunctionComponent< ConfigureCompilationServerDialogProps -> = ({ stanWasmServerUrl, setStanWasmServerUrl, isConnected, onRetry }) => { - const [choice, setChoice] = useState("public"); +> = ({ isConnected, onRetry }) => { + const { stanWasmServerUrl, setStanWasmServerUrl } = + useContext(CompileContext); + + const serverType: ServerType = useMemo(() => { + if (stanWasmServerUrl === publicUrl) { + return "public"; + } else if (stanWasmServerUrl === localUrl) { + return "local"; + } else { + return "custom"; + } + }, [stanWasmServerUrl]); const makeChoice = useCallback( (_: unknown, choice: string) => { @@ -35,7 +51,6 @@ const ConfigureCompilationServerDialog: FunctionComponent< } else { return; } - setChoice(choice); }, [setStanWasmServerUrl], ); @@ -64,7 +79,7 @@ const ConfigureCompilationServerDialog: FunctionComponent< Compilation server - + } @@ -82,13 +97,13 @@ const ConfigureCompilationServerDialog: FunctionComponent< /> - {choice === "custom" && ( + {serverType === "custom" && (

setStanWasmServerUrl(e.target.value)} /> @@ -97,7 +112,7 @@ const ConfigureCompilationServerDialog: FunctionComponent< )} - {choice === "local" && ( + {serverType === "local" && (

To start a local compilation server{" "} @@ -110,7 +125,7 @@ const ConfigureCompilationServerDialog: FunctionComponent<

)} - {choice === "public" && ( + {serverType === "public" && (

The public server ({publicUrl}) is diff --git a/gui/src/app/CompileContext/CompileContext.ts b/gui/src/app/CompileContext/CompileContext.ts index b629cf7e..cfd9d01e 100644 --- a/gui/src/app/CompileContext/CompileContext.ts +++ b/gui/src/app/CompileContext/CompileContext.ts @@ -14,6 +14,8 @@ type CompileContextType = { validSyntax: boolean; compile: () => void; setValidSyntax: (valid: boolean) => void; + stanWasmServerUrl: string; + setStanWasmServerUrl: (url: string) => void; }; export const CompileContext = createContext({ @@ -22,4 +24,6 @@ export const CompileContext = createContext({ validSyntax: false, compile: () => {}, setValidSyntax: () => {}, + stanWasmServerUrl: "", + setStanWasmServerUrl: () => {}, }); diff --git a/gui/src/app/CompileContext/CompileContextProvider.tsx b/gui/src/app/CompileContext/CompileContextProvider.tsx index cd0391d3..97bf3929 100644 --- a/gui/src/app/CompileContext/CompileContextProvider.tsx +++ b/gui/src/app/CompileContext/CompileContextProvider.tsx @@ -14,6 +14,10 @@ type CompileContextProviderProps = { // none }; +const initialStanWasmServerUrl = + localStorage.getItem("stanWasmServerUrl") || + "https://trom-stan-wasm-server.magland.org"; + export const CompileContextProvider: FunctionComponent< PropsWithChildren > = ({ children }) => { @@ -45,15 +49,20 @@ export const CompileContextProvider: FunctionComponent< setCompiledMainJsUrl, ]); + const [stanWasmServerUrl, setStanWasmServerUrl] = useState( + initialStanWasmServerUrl, + ); + useEffect(() => { + // persist to local storage + localStorage.setItem("stanWasmServerUrl", stanWasmServerUrl); + }, [stanWasmServerUrl]); + const handleCompile = useCallback(async () => { setCompileStatus("compiling"); await new Promise((resolve) => setTimeout(resolve, 500)); // for effect const onStatus = (msg: string) => { setCompileMessage(msg); }; - const stanWasmServerUrl = - localStorage.getItem("stanWasmServerUrl") || - "https://trom-stan-wasm-server.magland.org"; const { mainJsUrl } = await compileStanProgram( stanWasmServerUrl, data.stanFileContent, @@ -67,7 +76,12 @@ export const CompileContextProvider: FunctionComponent< setCompiledMainJsUrl(mainJsUrl); setCompileStatus("compiled"); setTheStanFileContentThasHasBeenCompiled(data.stanFileContent); - }, [data.stanFileContent, setCompiledMainJsUrl]); + }, [ + data.stanFileContent, + setCompiledMainJsUrl, + setCompileStatus, + stanWasmServerUrl, + ]); return ( {children} From 58f8bea80c015f6524022dba901b63e973acd815 Mon Sep 17 00:00:00 2001 From: Jeremy Magland Date: Thu, 1 Aug 2024 11:10:53 -0400 Subject: [PATCH 3/9] move compileStanProgram.ts --- gui/src/app/CompileContext/CompileContextProvider.tsx | 2 +- .../compileStanProgram.ts | 0 .../{compileStanProgram => CompileContext}/mainJsUrlCache.ts | 0 gui/tsconfig.json | 3 +-- 4 files changed, 2 insertions(+), 3 deletions(-) rename gui/src/app/{compileStanProgram => CompileContext}/compileStanProgram.ts (100%) rename gui/src/app/{compileStanProgram => CompileContext}/mainJsUrlCache.ts (100%) diff --git a/gui/src/app/CompileContext/CompileContextProvider.tsx b/gui/src/app/CompileContext/CompileContextProvider.tsx index 97bf3929..5f2c5d04 100644 --- a/gui/src/app/CompileContext/CompileContextProvider.tsx +++ b/gui/src/app/CompileContext/CompileContextProvider.tsx @@ -1,5 +1,5 @@ import { ProjectContext } from "@SpCore/ProjectContextProvider"; -import compileStanProgram from "@SpStanc/compileStanProgram"; +import compileStanProgram from "@SpCompileContext/compileStanProgram"; import { FunctionComponent, PropsWithChildren, diff --git a/gui/src/app/compileStanProgram/compileStanProgram.ts b/gui/src/app/CompileContext/compileStanProgram.ts similarity index 100% rename from gui/src/app/compileStanProgram/compileStanProgram.ts rename to gui/src/app/CompileContext/compileStanProgram.ts diff --git a/gui/src/app/compileStanProgram/mainJsUrlCache.ts b/gui/src/app/CompileContext/mainJsUrlCache.ts similarity index 100% rename from gui/src/app/compileStanProgram/mainJsUrlCache.ts rename to gui/src/app/CompileContext/mainJsUrlCache.ts diff --git a/gui/tsconfig.json b/gui/tsconfig.json index 2fba5cc3..32cd84e7 100644 --- a/gui/tsconfig.json +++ b/gui/tsconfig.json @@ -39,8 +39,7 @@ "@SpPages/*": ["app/pages/HomePage/*"], "@SpStanc/*": [ "app/Stanc/*", - "app/CompilationServerConnectionControl/*", - "app/compileStanProgram/*" + "app/CompilationServerConnectionControl/*" ], "@SpStanSampler/*": ["app/StanSampler/*"], "@SpUtil/*": ["app/util/*"], From 14e7691f0f32cac4081b22fc291b783dbf2b3a0f Mon Sep 17 00:00:00 2001 From: Jeremy Magland Date: Thu, 1 Aug 2024 11:32:03 -0400 Subject: [PATCH 4/9] code cleanup for #191 response --- .../ConfigureCompilationServerDialog.tsx | 16 +-- gui/src/app/FileEditor/StanFileEditor.tsx | 5 +- gui/src/app/RunPanel/RunPanel.tsx | 116 +++++++++--------- 3 files changed, 70 insertions(+), 67 deletions(-) diff --git a/gui/src/app/CompilationServerConnectionControl/ConfigureCompilationServerDialog.tsx b/gui/src/app/CompilationServerConnectionControl/ConfigureCompilationServerDialog.tsx index f67e3e21..db956905 100644 --- a/gui/src/app/CompilationServerConnectionControl/ConfigureCompilationServerDialog.tsx +++ b/gui/src/app/CompilationServerConnectionControl/ConfigureCompilationServerDialog.tsx @@ -1,15 +1,15 @@ -import { FunctionComponent, useCallback, useContext, useState } from "react"; -import { localUrl, publicUrl } from "./CompilationServerConnectionControl"; -import FormControl from "@mui/material/FormControl"; +import { CompileContext } from "@SpCompileContext/CompileContext"; +import { Refresh } from "@mui/icons-material"; import Divider from "@mui/material/Divider"; -import FormLabel from "@mui/material/FormLabel"; -import RadioGroup from "@mui/material/RadioGroup"; +import FormControl from "@mui/material/FormControl"; import FormControlLabel from "@mui/material/FormControlLabel"; +import FormLabel from "@mui/material/FormLabel"; +import IconButton from "@mui/material/IconButton"; import Radio from "@mui/material/Radio"; +import RadioGroup from "@mui/material/RadioGroup"; import TextField from "@mui/material/TextField"; -import IconButton from "@mui/material/IconButton"; -import { Refresh } from "@mui/icons-material"; -import { CompileContext } from "@SpCompileContext/CompileContext"; +import { FunctionComponent, useCallback, useContext } from "react"; +import { localUrl, publicUrl } from "./CompilationServerConnectionControl"; type ServerType = "public" | "local" | "custom"; diff --git a/gui/src/app/FileEditor/StanFileEditor.tsx b/gui/src/app/FileEditor/StanFileEditor.tsx index 9ade1205..1ead7abd 100644 --- a/gui/src/app/FileEditor/StanFileEditor.tsx +++ b/gui/src/app/FileEditor/StanFileEditor.tsx @@ -38,9 +38,8 @@ const StanFileEditor: FunctionComponent = ({ setEditedFileContent, ); - const { setValidSyntax } = useContext(CompileContext); - - const { compileStatus, compileMessage, compile } = useContext(CompileContext); + const { compileStatus, compileMessage, compile, setValidSyntax } = + useContext(CompileContext); const validSyntax = useMemo(() => { return stancErrors.errors === undefined; diff --git a/gui/src/app/RunPanel/RunPanel.tsx b/gui/src/app/RunPanel/RunPanel.tsx index eac63b96..433ab241 100644 --- a/gui/src/app/RunPanel/RunPanel.tsx +++ b/gui/src/app/RunPanel/RunPanel.tsx @@ -53,68 +53,72 @@ const RunPanel: FunctionComponent = ({ if (!dataIsSaved) { return

Data not saved
; } - return ( -
-
- {compileStatus === "compiled" ? ( + + let content; + if (compileStatus === "compiled") { + content = ( +
+ +   + {runStatus === "sampling" && ( + + )} +
+ {runStatus === "loading" &&
Loading compiled Stan model...
} + {runStatus === "sampling" && (
- -   - {runStatus === "sampling" && ( - - )} -
- {runStatus === "loading" && ( -
Loading compiled Stan model...
- )} - {runStatus === "sampling" && ( -
- Sampling - -
- )} - {runStatus === "completed" &&
done sampling
} - {runStatus === "failed" && ( -
- Sampling failed! -
{errorMessage}
- - (see browser console for more details) - -
- )} + Sampling +
- ) : ["preparing", "compiling"].includes(compileStatus) ? ( -
{compileMessage}
- ) : ( + )} + {runStatus === "completed" &&
done sampling
} + {runStatus === "failed" && (
- + Sampling failed! +
{errorMessage}
+ + (see browser console for more details) +
)}
+ ); + } else if (["preparing", "compiling"].includes(compileStatus)) { + content =
{compileMessage}
; + } else { + content = ( +
+ +
+ ); + } + + return ( +
+
{content}
); }; From d8c473b2e193ea3ef9ef39746dcd70e435e813a0 Mon Sep 17 00:00:00 2001 From: Jeremy Magland Date: Thu, 1 Aug 2024 11:40:46 -0400 Subject: [PATCH 5/9] Fix probeUrl check for http and https protocols --- .../CompilationServerConnectionControl.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/src/app/CompilationServerConnectionControl/CompilationServerConnectionControl.tsx b/gui/src/app/CompilationServerConnectionControl/CompilationServerConnectionControl.tsx index 0c3c17b0..d4e05466 100644 --- a/gui/src/app/CompilationServerConnectionControl/CompilationServerConnectionControl.tsx +++ b/gui/src/app/CompilationServerConnectionControl/CompilationServerConnectionControl.tsx @@ -85,7 +85,7 @@ const useIsConnected = (stanWasmServerUrl: string) => { }, []); useEffect(() => { setIsConnected(false); - if (!probeUrl.startsWith("http") && !probeUrl.startsWith("https")) { + if (!probeUrl.startsWith("http://") && !probeUrl.startsWith("https://")) { // important to do this check because otherwise fetch may succeed because // the server of this web app may respond with success return; From 29fa90eed21f373f43df07f0872a342a957741e4 Mon Sep 17 00:00:00 2001 From: Jeff Soules Date: Thu, 1 Aug 2024 12:16:02 -0400 Subject: [PATCH 6/9] Refactor compiled branch of runPanel to separate file. --- gui/src/app/RunPanel/CompiledRunPanel.tsx | 141 ++++++++++++++++++++++ gui/src/app/RunPanel/RunPanel.tsx | 116 ++---------------- 2 files changed, 153 insertions(+), 104 deletions(-) create mode 100644 gui/src/app/RunPanel/CompiledRunPanel.tsx diff --git a/gui/src/app/RunPanel/CompiledRunPanel.tsx b/gui/src/app/RunPanel/CompiledRunPanel.tsx new file mode 100644 index 00000000..167ed641 --- /dev/null +++ b/gui/src/app/RunPanel/CompiledRunPanel.tsx @@ -0,0 +1,141 @@ +import Box from "@mui/material/Box"; +import LinearProgress, { + LinearProgressProps, +} from "@mui/material/LinearProgress"; +import Typography from "@mui/material/Typography"; +import { FunctionComponent } from "react"; + +import Button from "@mui/material/Button"; +import { SamplingOpts } from "@SpCore/ProjectDataModel"; +import { Progress } from "@SpStanSampler/StanModelWorker"; +import { StanSamplerStatus } from "@SpStanSampler/StanSampler"; + +type CompiledRunPanelProps = { + handleRun: () => Promise; + cancelRun: () => void; + runStatus: StanSamplerStatus; + progress: Progress | undefined; + samplingOpts: SamplingOpts; + errorMessage: string; +}; + +const loadingDiv =
Loading compiled Stan model...
; +const completedDiv =
done sampling
; + +const CompiledRunPanel: FunctionComponent = ({ + handleRun, + runStatus, + cancelRun, + progress, + samplingOpts, + errorMessage, +}) => { + const samplingDiv = ( + <> + +
+ Sampling + +
+ + ); + + const failedDiv = ( +
+ Sampling failed! +
{errorMessage}
+ (see browser console for more details) +
+ ); + + return ( +
+ +   +
+ {runStatus === "loading" ? ( + loadingDiv + ) : runStatus === "completed" ? ( + completedDiv + ) : runStatus === "failed" ? ( + failedDiv + ) : runStatus === "sampling" ? ( + samplingDiv + ) : ( + <> + )} +
+ ); +}; + +type SamplingProgressComponentProps = { + report: Progress | undefined; + numChains: number; +}; + +const SamplingProgressComponent: FunctionComponent< + SamplingProgressComponentProps +> = ({ report, numChains }) => { + if (!report) return ; + const progress = + ((report.iteration + (report.chain - 1) * report.totalIterations) / + (report.totalIterations * numChains)) * + 100; + return ( + <> +
+ +
+
+ Chain {report.chain} Iteration: {report.iteration} /{" "} + {report.totalIterations} ({report.warmup ? "Warmup" : "Sampling"}) +
+ + ); +}; + +// from https://mui.com/material-ui/react-progress/#linear-with-label +const LinearProgressWithLabel = ( + props: LinearProgressProps & { value: number }, +) => { + return ( + + + + + + {`${Math.round( + props.value, + )}%`} + + + ); +}; + +export default CompiledRunPanel; diff --git a/gui/src/app/RunPanel/RunPanel.tsx b/gui/src/app/RunPanel/RunPanel.tsx index 433ab241..d9c9a96c 100644 --- a/gui/src/app/RunPanel/RunPanel.tsx +++ b/gui/src/app/RunPanel/RunPanel.tsx @@ -1,22 +1,17 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import Box from "@mui/material/Box"; -import LinearProgress, { - LinearProgressProps, -} from "@mui/material/LinearProgress"; -import Typography from "@mui/material/Typography"; import { FunctionComponent, useCallback, useContext } from "react"; +import Button from "@mui/material/Button"; +import { CompileContext } from "@SpCompileContext/CompileContext"; +import { ProjectContext } from "@SpCore/ProjectContextProvider"; import { ProjectDataModel, SamplingOpts, modelHasUnsavedChanges, } from "@SpCore/ProjectDataModel"; -import { Progress } from "@SpStanSampler/StanModelWorker"; import StanSampler from "@SpStanSampler/StanSampler"; import { StanRun } from "@SpStanSampler/useStanSampler"; -import Button from "@mui/material/Button"; -import { CompileContext } from "@SpCompileContext/CompileContext"; -import { ProjectContext } from "@SpCore/ProjectContextProvider"; +import CompiledRunPanel from "./CompiledRunPanel"; type RunPanelProps = { sampler?: StanSampler; @@ -57,48 +52,14 @@ const RunPanel: FunctionComponent = ({ let content; if (compileStatus === "compiled") { content = ( -
- -   - {runStatus === "sampling" && ( - - )} -
- {runStatus === "loading" &&
Loading compiled Stan model...
} - {runStatus === "sampling" && ( -
- Sampling - -
- )} - {runStatus === "completed" &&
done sampling
} - {runStatus === "failed" && ( -
- Sampling failed! -
{errorMessage}
- - (see browser console for more details) - -
- )} -
+ ); } else if (["preparing", "compiling"].includes(compileStatus)) { content =
{compileMessage}
; @@ -133,57 +94,4 @@ const isCompileModelDisabled = ( return false; }; -type SamplingProgressComponentProps = { - report: Progress | undefined; - numChains: number; -}; - -const SamplingProgressComponent: FunctionComponent< - SamplingProgressComponentProps -> = ({ report, numChains }) => { - if (!report) return ; - const progress = - ((report.iteration + (report.chain - 1) * report.totalIterations) / - (report.totalIterations * numChains)) * - 100; - return ( - <> -
- -
-
- Chain {report.chain} Iteration: {report.iteration} /{" "} - {report.totalIterations} ({report.warmup ? "Warmup" : "Sampling"}) -
- - ); -}; - -// from https://mui.com/material-ui/react-progress/#linear-with-label -const LinearProgressWithLabel = ( - props: LinearProgressProps & { value: number }, -) => { - return ( - - - - - - {`${Math.round( - props.value, - )}%`} - - - ); -}; - export default RunPanel; From a9cd2d4e3bbf1b860dadf39b1c480cb21e8f34ea Mon Sep 17 00:00:00 2001 From: Jeff Soules Date: Thu, 1 Aug 2024 12:20:36 -0400 Subject: [PATCH 7/9] Simplify logic in RunPanel --- gui/src/app/RunPanel/RunPanel.tsx | 57 ++++++++++++++++--------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/gui/src/app/RunPanel/RunPanel.tsx b/gui/src/app/RunPanel/RunPanel.tsx index d9c9a96c..83d8a394 100644 --- a/gui/src/app/RunPanel/RunPanel.tsx +++ b/gui/src/app/RunPanel/RunPanel.tsx @@ -49,37 +49,38 @@ const RunPanel: FunctionComponent = ({ return
Data not saved
; } - let content; - if (compileStatus === "compiled") { - content = ( - - ); - } else if (["preparing", "compiling"].includes(compileStatus)) { - content =
{compileMessage}
; - } else { - content = ( -
- -
- ); - } + const compileDiv = ( +
+ +
+ ); + + const compilingDiv =
{compileMessage}
; return (
-
{content}
+
+ {compileStatus === "compiled" ? ( + + ) : ["preparing", "compiling"].includes(compileStatus) ? ( + compilingDiv + ) : ( + compileDiv + )} +
); }; From e4726b607e6a2eebd1712c8c0860c4d36b5b7fb6 Mon Sep 17 00:00:00 2001 From: Jeremy Magland Date: Thu, 1 Aug 2024 12:32:57 -0400 Subject: [PATCH 8/9] Improve isCompileModelDisabled logic --- gui/src/app/RunPanel/RunPanel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/src/app/RunPanel/RunPanel.tsx b/gui/src/app/RunPanel/RunPanel.tsx index 83d8a394..dc2c6c56 100644 --- a/gui/src/app/RunPanel/RunPanel.tsx +++ b/gui/src/app/RunPanel/RunPanel.tsx @@ -89,9 +89,9 @@ const isCompileModelDisabled = ( projectData: ProjectDataModel, validSyntax: boolean, ) => { + if (!validSyntax) return true; if (!projectData.stanFileContent.trim()) return true; if (modelHasUnsavedChanges(projectData)) return true; - if (!validSyntax) return true; return false; }; From 25e4902641e9364c5c941be3364d613fca7e2f61 Mon Sep 17 00:00:00 2001 From: Jeremy Magland Date: Thu, 1 Aug 2024 12:49:06 -0400 Subject: [PATCH 9/9] serverTypeFromUrl --- .../CompilationServerConnectionControl.tsx | 15 +++++++-------- .../ConfigureCompilationServerDialog.tsx | 15 ++++++--------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/gui/src/app/CompilationServerConnectionControl/CompilationServerConnectionControl.tsx b/gui/src/app/CompilationServerConnectionControl/CompilationServerConnectionControl.tsx index d4e05466..3ff84156 100644 --- a/gui/src/app/CompilationServerConnectionControl/CompilationServerConnectionControl.tsx +++ b/gui/src/app/CompilationServerConnectionControl/CompilationServerConnectionControl.tsx @@ -18,7 +18,7 @@ import { CompileContext } from "@SpCompileContext/CompileContext"; export const publicUrl = "https://trom-stan-wasm-server.magland.org"; export const localUrl = "http://localhost:8083"; -export type ServerType = "public" | "local" | "custom"; +type ServerType = "public" | "local" | "custom"; type CompilationServerConnectionControlProps = { // none @@ -40,12 +40,7 @@ const CompilationServerConnectionControl: FunctionComponent< retryConnection(); }, [retryConnection]); - const serverLabel = - stanWasmServerUrl === publicUrl - ? "public" - : stanWasmServerUrl === localUrl - ? "local" - : "custom"; + const serverType = serverTypeForUrl(stanWasmServerUrl); return ( <> @@ -58,7 +53,7 @@ const CompilationServerConnectionControl: FunctionComponent<   {isConnected ? "connected to " : "not connected to "} - {serverLabel} + {serverType} { + return url === publicUrl ? "public" : url === localUrl ? "local" : "custom"; +}; + const useIsConnected = (stanWasmServerUrl: string) => { const probeUrl = `${stanWasmServerUrl}/probe`; const [isConnected, setIsConnected] = useState(false); diff --git a/gui/src/app/CompilationServerConnectionControl/ConfigureCompilationServerDialog.tsx b/gui/src/app/CompilationServerConnectionControl/ConfigureCompilationServerDialog.tsx index db956905..095cbafa 100644 --- a/gui/src/app/CompilationServerConnectionControl/ConfigureCompilationServerDialog.tsx +++ b/gui/src/app/CompilationServerConnectionControl/ConfigureCompilationServerDialog.tsx @@ -9,9 +9,11 @@ import Radio from "@mui/material/Radio"; import RadioGroup from "@mui/material/RadioGroup"; import TextField from "@mui/material/TextField"; import { FunctionComponent, useCallback, useContext } from "react"; -import { localUrl, publicUrl } from "./CompilationServerConnectionControl"; - -type ServerType = "public" | "local" | "custom"; +import { + localUrl, + publicUrl, + serverTypeForUrl, +} from "./CompilationServerConnectionControl"; type ConfigureCompilationServerDialogProps = { isConnected: boolean; @@ -24,12 +26,7 @@ const ConfigureCompilationServerDialog: FunctionComponent< const { stanWasmServerUrl, setStanWasmServerUrl } = useContext(CompileContext); - const serverType: ServerType = - stanWasmServerUrl === publicUrl - ? "public" - : stanWasmServerUrl === localUrl - ? "local" - : "custom"; + const serverType = serverTypeForUrl(stanWasmServerUrl); const makeChoice = useCallback( (_: unknown, choice: string) => {