mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-07-04 05:00:00 +08:00
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.
126 lines
6.7 KiB
Markdown
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
|