docs/references/components/code-execution.md
This document describes the Python code execution feature for code blocks. The implementation uses Pyodide to run Python code directly in the browser environment, placed inside a Web Worker to avoid blocking the main UI thread.
The entire implementation is divided into three main parts: UI Layer, Service Layer, and Worker Layer.
sequenceDiagram
participant User
participant CodeBlockView (UI)
participant PyodideService (Service)
participant PyodideWorker (Worker)
User->>CodeBlockView (UI): Click "Run" button
CodeBlockView (UI)->>PyodideService (Service): Call runScript(code)
PyodideService (Service)->>PyodideWorker (Worker): Send postMessage({ id, python: code })
PyodideWorker (Worker)->>PyodideWorker (Worker): Load Pyodide and related packages
PyodideWorker (Worker)->>PyodideWorker (Worker): (On demand) Inject shims and merge code
PyodideWorker (Worker)->>PyodideWorker (Worker): Execute merged Python code
PyodideWorker (Worker)-->>PyodideService (Service): Return postMessage({ id, output })
PyodideService (Service)-->>CodeBlockView (UI): Return { text, image } object
CodeBlockView (UI)->>User: Display text and/or image output in status bar
The user-facing code execution component is CodeBlockView.
python and codeExecution.enabled is true, a "Run" button is conditionally rendered in CodeToolbar.onClick triggers the handleRunScript function.handleRunScript calls pyodideService.runScript(code), passing the Python code from the code block to the service.executionResult to manage all execution output; whenever there's any result (text or image), the StatusBar component is rendered for unified display.// src/renderer/components/CodeBlockView/view.tsx
const [executionResult, setExecutionResult] = useState<{ text: string; image?: string } | null>(null)
const handleRunScript = useCallback(() => {
setIsRunning(true)
setExecutionResult(null)
pyodideService
.runScript(children, {}, codeExecution.timeoutMinutes * 60000)
.then((result) => {
setExecutionResult(result)
})
.catch((error) => {
console.error('Unexpected error:', error)
setExecutionResult({
text: `Unexpected error: ${error.message || 'Unknown error'}`
})
})
.finally(() => {
setIsRunning(false)
})
}, [children, codeExecution.timeoutMinutes]);
// ... in JSX
{isExecutable && executionResult && (
<StatusBar>
{executionResult.text}
{executionResult.image && (
<ImageOutput>
</ImageOutput>
)}
</StatusBar>
)}
The service layer acts as a bridge between UI components and the Web Worker running Pyodide. Its logic is encapsulated in the singleton class PyodideService.
resolvers Map, matching requests and responses via unique IDs.runScript(script, context, timeout) method to UI. Returns Promise<{ text: string; image?: string }> to support multiple output types including images.output objects containing text, errors, and optional image data from the Worker. Format text and errors into a user-friendly string and return it along with image data to the UI layer.IpcChannel.Python_ExecutionRequest (python:execution-request) and replies via IpcChannel.Python_ExecutionResponse (python:execution-response), allowing the main process to request Python code execution.The core Python execution happens inside the Web Worker defined in pyodide.worker.ts. This ensures computationally intensive Python code doesn't freeze the user interface.
stdout and stderr.pyodide.loadPackagesFromImports() to automatically analyze and install packages imported in the code..toJs() and similar methods.id and an output object. The output is a structured object with result, text, error, and an optional image field (for Base64 image data).import statements.matplotlib, prepends shim code that forces the AGG backend.matplotlib.pyplot figures; if images exist, saves them to an in-memory BytesIO object and encodes as Base64 strings.{ "text": "...", "image": "data:image/png;base64,..." }) and returns it to the main thread.useState to manage execution results (executionResult).