Files
cherry-studio/docs/zh/references/code-execution.md
fullex bb41709ce8 docs: update docs directory structure
- 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.
2025-11-26 13:17:01 +08:00

6.6 KiB
Raw Permalink Blame History

代码执行功能

本文档说明了代码块的 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

关键机制:

  • 运行按钮:当代码块语言为 pythoncodeExecution.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 通信。
  • 请求处理:使用 resolvers Map 管理并发请求,通过唯一 ID 匹配请求和响应。
  • 为 UI 提供 API:向 UI 提供 runScript(script, context, timeout) 方法。此方法的返回值已修改为 Promise<{ text: string; image?: string }>,以支持包括图像在内的多种输出类型。
  • 输出处理:从 Worker 接收包含文本、错误和可选图像数据的 output 对象。它将文本和错误格式化为对用户友好的单个字符串,然后连同图像数据一起包装成对象返回给 UI 层。
  • IPC 端点:该服务还提供了一个 python-execution-request IPC 端点,允许主进程请求执行 Python 代码,展示了其灵活的架构。

3. Worker 层

核心的 Python 执行发生在 pyodide.worker.ts 中定义的 Web Worker 内部。这确保了计算密集的 Python 代码不会冻结用户界面。

Worker 逻辑:

  • Pyodide 加载Worker 从 CDN 加载 Pyodide 引擎,并设置处理器以捕获 Python 的 stdoutstderr
  • 动态包安装:使用 pyodide.loadPackagesFromImports() 自动分析并安装代码中导入的依赖包。
  • 按需执行垫片代码Worker 会检查传入的代码中是否包含 "matplotlib" 字符串。如果是,它会先执行一段 Python“垫片”代码确保图像输出到全局命名空间。
  • 结果序列化:执行结果通过 .toJs() 等方法被递归转换为可序列化的标准 JavaScript 对象。
  • 返回结构化输出执行后Worker 将一个包含 idoutput 对象的-消息发回服务层。output 对象是一个结构化对象,包含 resulttexterror 以及一个可选的 image 字段(用于 Base64 图像数据)。

数据流

最终的数据流如下:

  1. UI 层 (CodeBlockView): 用户点击“运行”按钮。
  2. 服务层 (PyodideService):
    • 接收到代码执行请求。
    • 调用 Web Worker (pyodide.worker.ts),传递用户代码。
  3. Worker 层 (pyodide.worker.ts):
    • 加载 Pyodide 运行时。
    • 动态安装代码中 import 语句声明的依赖包。
    • 注入 Matplotlib 垫片: 如果代码中包含 matplotlib,则在用户代码前拼接垫片代码,强制使用 AGG 后端。
    • 执行代码并捕获输出: 在代码执行后,检查 matplotlib.pyplot 的所有 figure如果存在图像则将其保存到内存中的 BytesIO 对象,并编码为 Base64 字符串。
    • 结构化返回: 将捕获的文本输出和 Base64 图像数据封装在一个 JSON 对象中 ({ "text": "...", "image": "data:image/png;base64,..." }) 返回给主线程。
  4. 服务层 (PyodideService):
    • 接收来自 Worker 的结构化数据。
    • 将数据原样传递给 UI 层。
  5. UI 层 (CodeBlockView):
    • 接收包含文本和图像数据的对象。
    • 使用一个 useState 来管理执行结果 (executionResult)。
    • 在界面上分别渲染文本输出和图像(如果存在)。