Files
CherryHQ-cherry-studio/docs/references/components/code-execution.md
fullex a4007f30bb refactor(python-service): formalize python execution IPC channels
Add Python_ExecutionRequest and Python_ExecutionResponse to the
IpcChannel enum and replace the bare string literals used by
PythonService (main) and PyodideService (renderer) so the push-style
request/response channels are governed the same way as Python_Execute.

Wire values are normalized to the repository's 'namespace:action-dashed'
convention (python:execution-request / python:execution-response) since
both ends are updated in the same change.
2026-04-19 07:34:22 -07:00

6.7 KiB

Code Execution

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.

Execution Flow

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

1. UI Layer

The user-facing code execution component is CodeBlockView.

Key Mechanisms:

  • Run Button: When the code block language is python and codeExecution.enabled is true, a "Run" button is conditionally rendered in CodeToolbar.
  • Event Handling: The run button's onClick triggers the handleRunScript function.
  • Service Call: handleRunScript calls pyodideService.runScript(code), passing the Python code from the code block to the service.
  • State Management and Output Display: Uses executionResult to manage all execution output; whenever there's any result (text or image), the StatusBar component is rendered for unified display.
// src/renderer/src/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>
        <img src={executionResult.image} alt="Matplotlib plot" />
      </ImageOutput>
    )}
  </StatusBar>
)}

2. Service Layer

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.

Main Responsibilities:

  • Worker Management: Initialize, manage, and communicate with the Pyodide Web Worker.
  • Request Handling: Manage concurrent requests using a resolvers Map, matching requests and responses via unique IDs.
  • API for UI: Expose the runScript(script, context, timeout) method to UI. Returns Promise<{ text: string; image?: string }> to support multiple output types including images.
  • Output Processing: Receive 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.
  • IPC Endpoint: The service also listens on IpcChannel.Python_ExecutionRequest (python:execution-request) and replies via IpcChannel.Python_ExecutionResponse (python:execution-response), allowing the main process to request Python code execution.

3. Worker Layer

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.

Worker Logic:

  • Pyodide Loading: The Worker loads the Pyodide engine from CDN and sets up handlers to capture Python's stdout and stderr.
  • Dynamic Package Installation: Uses pyodide.loadPackagesFromImports() to automatically analyze and install packages imported in the code.
  • On-demand Shim Execution: The Worker checks if the incoming code contains "matplotlib". If so, it executes a Python "shim" first to ensure image output goes to the global namespace.
  • Result Serialization: Execution results are recursively converted to serializable standard JavaScript objects via .toJs() and similar methods.
  • Structured Output: After execution, the Worker sends a message back to the service layer containing id and an output object. The output is a structured object with result, text, error, and an optional image field (for Base64 image data).

Data Flow

  1. UI Layer (CodeBlockView): User clicks the "Run" button.
  2. Service Layer (PyodideService):
    • Receives the code execution request.
    • Calls the Web Worker, passing user code.
  3. Worker Layer (pyodide.worker.ts):
    • Loads the Pyodide runtime.
    • Dynamically installs packages declared in import statements.
    • Injects Matplotlib shim: If code contains matplotlib, prepends shim code that forces the AGG backend.
    • Executes code and captures output: After execution, checks all matplotlib.pyplot figures; if images exist, saves them to an in-memory BytesIO object and encodes as Base64 strings.
    • Structured return: Wraps captured text output and Base64 image data in a JSON object ({ "text": "...", "image": "data:image/png;base64,..." }) and returns it to the main thread.
  4. Service Layer (PyodideService):
    • Receives structured data from the Worker.
    • Passes data as-is to the UI layer.
  5. UI Layer (CodeBlockView):
    • Receives the object containing text and image data.
    • Uses useState to manage execution results (executionResult).
    • Renders text output and image (if present) in the interface.