Files
CherryHQ-cherry-studio/docs/references/components/code-execution.md
fullex 53a3577389 refactor(renderer): flatten src/renderer/src to src/renderer
Move all renderer source from src/renderer/src/* up one level to
src/renderer/*, removing the redundant nested src directory.

- Update path aliases (@renderer, @types, @logger, @data) and TanStack
  Router paths in electron.vite.config.ts; update tsconfig.{json,web,node}
  path mappings and include globs.
- Fix Vite root-relative script paths in the 8 renderer HTML entries.
- Update cross-process relative imports in main/preload (language,
  apiServer models, preload index) to drop the /src segment.
- Switch renderer test imports of the logger mock to the @test-mocks alias.
- Update hardcoded renderer paths in scripts and their fixtures, lint
  configs (eslint/oxlint/biome), CODEOWNERS, docs, and the data-classify tool.
- Convert deep (../../+) relative imports within the renderer to the
  @renderer alias (69 files, 108 imports); keep single-level relatives.
- Fix doc links broken by the move and correct one pre-existing broken
  link in naming-conventions.md.
2026-05-28 21:40:20 -07:00

126 lines
6.7 KiB
Markdown

# Code Execution
This document describes the Python code execution feature for code blocks. The implementation uses [Pyodide][pyodide-link] 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
```mermaid
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][codeblock-view-link].
### 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][statusbar-link] component is rendered for unified display.
```typescript
// 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>
<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][pyodide-service-link].
### 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][pyodide-worker-link]. 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][codeblock-view-link])**: User clicks the "Run" button.
2. **Service Layer ([PyodideService][pyodide-service-link])**:
- Receives the code execution request.
- Calls the Web Worker, passing user code.
3. **Worker Layer ([pyodide.worker.ts][pyodide-worker-link])**:
- 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][pyodide-service-link])**:
- Receives structured data from the Worker.
- Passes data as-is to the UI layer.
5. **UI Layer ([CodeBlockView][codeblock-view-link])**:
- 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.
<!-- Link Definitions -->
[pyodide-link]: https://pyodide.org/
[codeblock-view-link]: /src/renderer/components/CodeBlockView/view.tsx
[pyodide-service-link]: /src/renderer/services/PyodideService.ts
[pyodide-worker-link]: /src/renderer/workers/pyodide.worker.ts
[statusbar-link]: /src/renderer/components/CodeBlockView/StatusBar.tsx