mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-07-03 20:59:22 +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.
6.7 KiB
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
pythonandcodeExecution.enabledis true, a "Run" button is conditionally rendered inCodeToolbar. - Event Handling: The run button's
onClicktriggers thehandleRunScriptfunction. - Service Call:
handleRunScriptcallspyodideService.runScript(code), passing the Python code from the code block to the service. - State Management and Output Display: Uses
executionResultto 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>
<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
resolversMap, matching requests and responses via unique IDs. - API for UI: Expose the
runScript(script, context, timeout)method to UI. ReturnsPromise<{ text: string; image?: string }>to support multiple output types including images. - Output Processing: Receive
outputobjects 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 viaIpcChannel.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
stdoutandstderr. - 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
idand anoutputobject. Theoutputis a structured object withresult,text,error, and an optionalimagefield (for Base64 image data).
Data Flow
- UI Layer (CodeBlockView): User clicks the "Run" button.
- Service Layer (PyodideService):
- Receives the code execution request.
- Calls the Web Worker, passing user code.
- Worker Layer (pyodide.worker.ts):
- Loads the Pyodide runtime.
- Dynamically installs packages declared in
importstatements. - Injects Matplotlib shim: If code contains
matplotlib, prepends shim code that forces theAGGbackend. - Executes code and captures output: After execution, checks all
matplotlib.pyplotfigures; if images exist, saves them to an in-memoryBytesIOobject 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.
- Service Layer (PyodideService):
- Receives structured data from the Worker.
- Passes data as-is to the UI layer.
- UI Layer (CodeBlockView):
- Receives the object containing text and image data.
- Uses
useStateto manage execution results (executionResult). - Renders text output and image (if present) in the interface.