- Updated links in CONTRIBUTING.md and README.md to point to the correct Chinese documentation paths. - Removed outdated files including the English and Chinese versions of the branching strategy, contributing guide, and test plan documents. - Cleaned up references to non-existent documentation in the project structure to streamline the contributor experience.
6.6 KiB
6.6 KiB
代码执行功能
本文档说明了代码块的 Python 代码执行功能。该实现利用 Pyodide 在浏览器环境中直接运行 Python 代码,并将其置于 Web Worker 中,以避免阻塞主 UI 线程。
整个实现分为三个主要部分:UI 层、服务层和 Worker 层。
执行流程图
sequenceDiagram
participant 用户
participant CodeBlockView (UI)
participant PyodideService (服务)
participant PyodideWorker (Worker)
用户->>CodeBlockView (UI): 点击“运行”按钮
CodeBlockView (UI)->>PyodideService (服务): 调用 runScript(code)
PyodideService (服务)->>PyodideWorker (Worker): 发送 postMessage({ id, python: code })
PyodideWorker (Worker)->>PyodideWorker (Worker): 加载 Pyodide 和相关包
PyodideWorker (Worker)->>PyodideWorker (Worker): (按需)注入垫片并合并代码
PyodideWorker (Worker)->>PyodideWorker (Worker): 执行合并后的 Python 代码
PyodideWorker (Worker)-->>PyodideService (服务): 返回 postMessage({ id, output })
PyodideService (服务)-->>CodeBlockView (UI): 返回 { text, image } 对象
CodeBlockView (UI)->>用户: 在状态栏中显示文本和/或图像输出
1. UI 层
面向用户的代码执行组件是 CodeBlockView。
关键机制:
- 运行按钮:当代码块语言为
python且codeExecution.enabled设置为 true 时,CodeToolbar中会条件性地渲染一个“运行”按钮。 - 事件处理:运行按钮的
onClick事件会触发handleRunScript函数。 - 服务调用:
handleRunScript调用pyodideService.runScript(code),将代码块中的 Python 代码传递给服务。 - 状态管理与输出显示:使用
executionResult来管理所有执行输出,只要有任何结果(文本或图像),StatusBar 组件就会被渲染以统一显示。
// 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]);
// ... 在 JSX 中
{isExecutable && executionResult && (
<StatusBar>
{executionResult.text}
{executionResult.image && (
<ImageOutput>
<img src={executionResult.image} alt="Matplotlib plot" />
</ImageOutput>
)}
</StatusBar>
)}
2. 服务层
服务层充当 UI 组件和运行 Pyodide 的 Web Worker 之间的桥梁。其逻辑封装在位于单例类 PyodideService。
主要职责:
- Worker 管理:初始化、管理并与 Pyodide Web Worker 通信。
- 请求处理:使用
resolversMap 管理并发请求,通过唯一 ID 匹配请求和响应。 - 为 UI 提供 API:向 UI 提供
runScript(script, context, timeout)方法。此方法的返回值已修改为Promise<{ text: string; image?: string }>,以支持包括图像在内的多种输出类型。 - 输出处理:从 Worker 接收包含文本、错误和可选图像数据的
output对象。它将文本和错误格式化为对用户友好的单个字符串,然后连同图像数据一起包装成对象返回给 UI 层。 - IPC 端点:该服务还提供了一个
python-execution-requestIPC 端点,允许主进程请求执行 Python 代码,展示了其灵活的架构。
3. Worker 层
核心的 Python 执行发生在 pyodide.worker.ts 中定义的 Web Worker 内部。这确保了计算密集的 Python 代码不会冻结用户界面。
Worker 逻辑:
- Pyodide 加载:Worker 从 CDN 加载 Pyodide 引擎,并设置处理器以捕获 Python 的
stdout和stderr。 - 动态包安装:使用
pyodide.loadPackagesFromImports()自动分析并安装代码中导入的依赖包。 - 按需执行垫片代码:Worker 会检查传入的代码中是否包含 "matplotlib" 字符串。如果是,它会先执行一段 Python“垫片”代码确保图像输出到全局命名空间。
- 结果序列化:执行结果通过
.toJs()等方法被递归转换为可序列化的标准 JavaScript 对象。 - 返回结构化输出:执行后,Worker 将一个包含
id和output对象的-消息发回服务层。output对象是一个结构化对象,包含result、text、error以及一个可选的image字段(用于 Base64 图像数据)。
数据流
最终的数据流如下:
- UI 层 (CodeBlockView): 用户点击“运行”按钮。
- 服务层 (PyodideService):
- 接收到代码执行请求。
- 调用 Web Worker (pyodide.worker.ts),传递用户代码。
- Worker 层 (pyodide.worker.ts):
- 加载 Pyodide 运行时。
- 动态安装代码中
import语句声明的依赖包。 - 注入 Matplotlib 垫片: 如果代码中包含
matplotlib,则在用户代码前拼接垫片代码,强制使用AGG后端。 - 执行代码并捕获输出: 在代码执行后,检查
matplotlib.pyplot的所有 figure,如果存在图像,则将其保存到内存中的BytesIO对象,并编码为 Base64 字符串。 - 结构化返回: 将捕获的文本输出和 Base64 图像数据封装在一个 JSON 对象中 (
{ "text": "...", "image": "data:image/png;base64,..." }) 返回给主线程。
- 服务层 (PyodideService):
- 接收来自 Worker 的结构化数据。
- 将数据原样传递给 UI 层。
- UI 层 (CodeBlockView):
- 接收包含文本和图像数据的对象。
- 使用一个
useState来管理执行结果 (executionResult)。 - 在界面上分别渲染文本输出和图像(如果存在)。