/** * Code Execution 工具服务 * 混合架构:Pyodide (浏览器端 Python) + Piston API (服务端多语言) * * - Python + 图形绘制 → Pyodide (支持 matplotlib 渲染) * - 其他语言/普通代码 → Piston API * * Piston API 文档: https://github.com/engineer-man/piston * Pyodide 文档: https://pyodide.org/ */ import { shouldUsePyodide, executePythonInPyodide, type LoadingCallback, type PyodideExecutionResult, } from './pyodideRunner'; export interface CodeExecutionInput { code: string; language: string; stdin?: string; /** Pyodide 加载进度回调(可选) */ onProgress?: LoadingCallback; } export interface CodeExecutionResponse { success: boolean; output?: string; error?: string; language?: string; version?: string; /** Base64 编码的图片数组(matplotlib 输出) */ images?: string[]; /** 执行引擎: 'pyodide' | 'piston' */ engine?: 'pyodide' | 'piston'; /** 执行时间 (ms) */ executionTime?: number; } // Piston API 支持的语言映射 const LANGUAGE_MAP: Record = { python: { language: 'python', version: '3.10.0' }, python3: { language: 'python', version: '3.10.0' }, javascript: { language: 'javascript', version: '18.15.0' }, js: { language: 'javascript', version: '18.15.0' }, typescript: { language: 'typescript', version: '5.0.3' }, ts: { language: 'typescript', version: '5.0.3' }, java: { language: 'java', version: '15.0.2' }, c: { language: 'c', version: '10.2.0' }, cpp: { language: 'c++', version: '10.2.0' }, 'c++': { language: 'c++', version: '10.2.0' }, go: { language: 'go', version: '1.16.2' }, rust: { language: 'rust', version: '1.68.2' }, ruby: { language: 'ruby', version: '3.0.1' }, php: { language: 'php', version: '8.2.3' }, swift: { language: 'swift', version: '5.3.3' }, kotlin: { language: 'kotlin', version: '1.8.20' }, scala: { language: 'scala', version: '3.2.2' }, r: { language: 'r', version: '4.1.1' }, bash: { language: 'bash', version: '5.2.0' }, shell: { language: 'bash', version: '5.2.0' }, sql: { language: 'sqlite3', version: '3.36.0' }, lua: { language: 'lua', version: '5.4.4' }, perl: { language: 'perl', version: '5.36.0' }, haskell: { language: 'haskell', version: '9.0.1' }, elixir: { language: 'elixir', version: '1.14.3' }, clojure: { language: 'clojure', version: '1.10.3' }, }; // Piston API 端点 const PISTON_API_URL = 'https://emkc.org/api/v2/piston/execute'; /** * 执行代码 * 智能选择执行引擎: * - Python + 图形代码 → Pyodide (浏览器端) * - 其他情况 → Piston API (服务端) */ export async function executeCode(input: CodeExecutionInput): Promise { const { code, language, stdin, onProgress } = input; const startTime = Date.now(); // 检查是否应该使用 Pyodide(Python + 图形) if (shouldUsePyodide(code, language)) { return executePythonWithPyodide(code, onProgress, startTime); } // 使用 Piston API 执行 return executePythonWithPiston(code, language, stdin, startTime); } /** * 使用 Pyodide 执行 Python 代码(支持 matplotlib 图形) */ async function executePythonWithPyodide( code: string, onProgress?: LoadingCallback, startTime?: number ): Promise { const start = startTime || Date.now(); try { const result: PyodideExecutionResult = await executePythonInPyodide(code, onProgress); return { success: result.success, output: result.output, error: result.error, language: 'python', version: 'Pyodide (WebAssembly)', images: result.images, engine: 'pyodide', executionTime: Date.now() - start, }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : '执行错误', language: 'python', engine: 'pyodide', executionTime: Date.now() - start, }; } } /** * 使用 Piston API 执行代码 */ async function executePythonWithPiston( code: string, language: string, stdin?: string, startTime?: number ): Promise { const start = startTime || Date.now(); // 获取语言映射 const langConfig = LANGUAGE_MAP[language.toLowerCase()]; if (!langConfig) { return { success: false, error: `不支持的编程语言: ${language}。支持的语言: ${Object.keys(LANGUAGE_MAP).join(', ')}`, engine: 'piston', executionTime: Date.now() - start, }; } try { const response = await fetch(PISTON_API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ language: langConfig.language, version: langConfig.version, files: [ { name: getFileName(langConfig.language), content: code, }, ], stdin: stdin || '', args: [], compile_timeout: 10000, run_timeout: 5000, compile_memory_limit: -1, run_memory_limit: -1, }), }); if (!response.ok) { const errorText = await response.text(); console.error('Piston API error:', errorText); return { success: false, error: `代码执行 API 错误: ${response.status}`, engine: 'piston', executionTime: Date.now() - start, }; } const data = await response.json(); // 检查编译错误 if (data.compile && data.compile.code !== 0) { return { success: false, error: data.compile.stderr || data.compile.output || '编译错误', language: langConfig.language, version: langConfig.version, engine: 'piston', executionTime: Date.now() - start, }; } // 检查运行错误 if (data.run && data.run.code !== 0) { return { success: true, // 代码执行了,但有运行时错误 output: data.run.stdout || '', error: data.run.stderr || '', language: langConfig.language, version: langConfig.version, engine: 'piston', executionTime: Date.now() - start, }; } return { success: true, output: data.run?.stdout || data.run?.output || '', error: data.run?.stderr || '', language: langConfig.language, version: langConfig.version, engine: 'piston', executionTime: Date.now() - start, }; } catch (error) { console.error('Code execution error:', error); return { success: false, error: error instanceof Error ? error.message : '未知错误', engine: 'piston', executionTime: Date.now() - start, }; } } /** * 根据语言获取文件名 */ function getFileName(language: string): string { const fileNameMap: Record = { python: 'main.py', javascript: 'main.js', typescript: 'main.ts', java: 'Main.java', c: 'main.c', 'c++': 'main.cpp', go: 'main.go', rust: 'main.rs', ruby: 'main.rb', php: 'main.php', swift: 'main.swift', kotlin: 'Main.kt', scala: 'Main.scala', r: 'main.r', bash: 'main.sh', sqlite3: 'main.sql', lua: 'main.lua', perl: 'main.pl', haskell: 'Main.hs', elixir: 'main.exs', clojure: 'main.clj', }; return fileNameMap[language] || 'main.txt'; } /** * 格式化代码执行结果为文本(完整版 - 发送给 AI) */ export function formatExecutionResult(response: CodeExecutionResponse): string { if (!response.success && !response.output && !response.images?.length) { return `❌ 执行失败: ${response.error}`; } let result = ''; // 执行引擎信息 if (response.engine) { const engineName = response.engine === 'pyodide' ? 'Pyodide (浏览器)' : 'Piston (服务器)'; result += `**执行引擎**: ${engineName}\n`; } if (response.language && response.version) { result += `**语言**: ${response.language} (${response.version})\n`; } if (response.executionTime) { result += `**执行时间**: ${response.executionTime}ms\n`; } result += '\n'; if (response.output) { result += `## 输出结果\n\`\`\`\n${response.output}\n\`\`\`\n`; } // 图片信息(仅标记有图片,实际渲染由前端处理) if (response.images && response.images.length > 0) { result += `\n## 图形输出\n已生成 ${response.images.length} 张图表\n`; } if (response.error) { result += `\n## 错误信息\n\`\`\`\n${response.error}\n\`\`\`\n`; } return result || '执行完成,无输出'; } /** * 格式化代码执行结果为简短文本(显示给用户) */ export function formatExecutionResultShort(response: CodeExecutionResponse, language: string): string { if (!response.success && !response.output && !response.images?.length) { return `❌ 代码执行失败: ${response.error}`; } const langDisplay = response.language || language; const engineDisplay = response.engine === 'pyodide' ? ' [Pyodide]' : ''; const timeDisplay = response.executionTime ? ` (${response.executionTime}ms)` : ''; if (response.error && !response.output && !response.images?.length) { return `⚠️ ${langDisplay}${engineDisplay} 代码执行出错`; } // 计算输出行数 const outputLines = response.output?.split('\n').filter(l => l.trim()).length || 0; const imageCount = response.images?.length || 0; // 构建结果描述 const parts: string[] = []; if (outputLines > 0) { parts.push(`输出 ${outputLines} 行`); } if (imageCount > 0) { parts.push(`生成 ${imageCount} 张图表`); } const resultDesc = parts.length > 0 ? parts.join(',') : '无输出'; return `✅ ${langDisplay}${engineDisplay} 代码执行完成${timeDisplay},${resultDesc}`; } /** * 获取支持的语言列表 */ export function getSupportedLanguages(): string[] { return Object.keys(LANGUAGE_MAP); }