Files
CherryHQ-cherry-studio/src/renderer/workers/pyodide.worker.ts
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

218 lines
5.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// <reference lib="webworker" />
interface WorkerResponse {
type: 'initialized' | 'init-error' | 'system-error'
id?: string
output?: PyodideOutput
error?: string
}
// 定义输出结构类型
interface PyodideOutput {
result: any
text: string | null
error: string | null
image?: string
}
const PYODIDE_INDEX_URL = 'https://cdn.jsdelivr.net/pyodide/v0.28.0/full/'
const PYODIDE_MODULE_URL = PYODIDE_INDEX_URL + 'pyodide.mjs'
const MATPLOTLIB_SHIM_CODE = `
def __cherry_studio_matplotlib_setup():
import os
# Set backend before importing pyplot
os.environ["MPLBACKEND"] = "AGG"
import io
import base64
import matplotlib.pyplot as plt
# Save original show function
_original_show = plt.show
# Define and replace with new show function
def _new_show(*args, **kwargs):
global pyodide_matplotlib_image
fig = plt.gcf()
if not fig.canvas.get_renderer()._renderer:
return
buf = io.BytesIO()
fig.savefig(buf, format='png')
buf.seek(0)
img_str = base64.b64encode(buf.read()).decode('utf-8')
# Pass data via global variable
pyodide_matplotlib_image = f"data:image/png;base64,{img_str}"
plt.clf()
plt.close(fig)
# Replace global show function
plt.show = _new_show
__cherry_studio_matplotlib_setup()
del __cherry_studio_matplotlib_setup
`
// 声明全局变量用于输出
let output: PyodideOutput = {
result: null,
text: null,
error: null
}
const pyodidePromise = (async () => {
// 重置输出变量
output = {
result: null,
text: null,
error: null
}
try {
// 动态加载 Pyodide 脚本
const pyodideModule = await import(/* @vite-ignore */ PYODIDE_MODULE_URL)
// 加载 Pyodide 并捕获标准输出/错误
return await pyodideModule.loadPyodide({
indexURL: PYODIDE_INDEX_URL,
stdout: (text: string) => {
if (output.text) {
output.text += `${text}\n`
} else {
output.text = `${text}\n`
}
},
stderr: (text: string) => {
if (output.error) {
output.error += `${text}\n`
} else {
output.error = `${text}\n`
}
}
})
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error)
// 通知主线程初始化错误
self.postMessage({
type: 'init-error',
error: errorMessage
} as WorkerResponse)
throw error
}
})()
// 处理结果,确保所有类型都能安全序列化
function processResult(result: any): any {
try {
if (result && typeof result.toJs === 'function') {
return processResult(result.toJs())
}
if (Array.isArray(result)) {
return result.map((item) => processResult(item))
}
if (typeof result === 'object' && result !== null) {
return Object.fromEntries(Object.entries(result).map(([key, value]) => [key, processResult(value)]))
}
return result
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error)
return { __error__: 'Result processing failed', details: errorMessage }
}
}
// 通知主线程已加载
pyodidePromise
.then(() => {
self.postMessage({ type: 'initialized' } as WorkerResponse)
})
.catch((error: unknown) => {
const errorMessage = error instanceof Error ? error.message : String(error)
self.postMessage({
type: 'init-error',
error: errorMessage
} as WorkerResponse)
})
// 处理消息
self.onmessage = async (event) => {
const { id, python } = event.data
// 重置输出变量
output = {
result: null,
text: null,
error: null
}
let globals
try {
const pyodide = await pyodidePromise
// 创建一个新的全局作用域
globals = pyodide.globals.get('dict')()
// 载入需要的包
try {
await pyodide.loadPackagesFromImports(python)
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error)
throw new Error(`Failed to load required packages: ${errorMessage}`)
}
// 执行代码
try {
// 注入 Matplotlib 垫片代码
if (python.includes('matplotlib')) {
await pyodide.runPythonAsync(MATPLOTLIB_SHIM_CODE, { globals })
}
output.result = await pyodide.runPythonAsync(python, { globals })
// 处理结果,确保安全序列化
output.result = processResult(output.result)
// 检查是否有 Matplotlib 图像输出
const image = globals.get('pyodide_matplotlib_image')
if (image) {
output.image = image
}
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error)
// 不设置 output.result但设置错误信息
if (output.error) {
output.error += `\nExecution error:\n${errorMessage}`
} else {
output.error = `Execution error:\n${errorMessage}`
}
}
} catch (error: unknown) {
// 处理所有其他错误
const errorMessage = error instanceof Error ? error.message : String(error)
if (output.error) {
output.error += `\nSystem error:\n${errorMessage}`
} else {
output.error = `System error:\n${errorMessage}`
}
// 发送错误信息
self.postMessage({
type: 'system-error',
id,
error: errorMessage
} as WorkerResponse)
} finally {
globals?.destroy()
self.postMessage({ id, output } as WorkerResponse)
}
}