/** * 代码执行插件组件 * 使用 CodeMirror 编辑器 + 终端输出面板 + 交互式输入 */ import { useState, useRef, useEffect } from 'react'; import { Button, Space, Input, Typography } from 'antd'; import { PlayCircleOutlined, StopOutlined, ClearOutlined, CodeOutlined } from '@ant-design/icons'; import CodeMirror from '@uiw/react-codemirror'; import { python } from '@codemirror/lang-python'; import api from '../../services/api'; const { Text } = Typography; const CodeExecutor = ({ initialCode = '' }) => { const [code, setCode] = useState(initialCode); const [output, setOutput] = useState([]); const [isRunning, setIsRunning] = useState(false); const [needsInput, setNeedsInput] = useState(false); const [contextId, setContextId] = useState(null); const [userInput, setUserInput] = useState(''); const outputEndRef = useRef(null); useEffect(() => { if (initialCode) setCode(initialCode); }, [initialCode]); useEffect(() => { outputEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [output]); const appendOutput = (type, text) => { setOutput(prev => [...prev, { type, text }]); }; const handleRun = async () => { if (!code.trim()) return; setIsRunning(true); setNeedsInput(false); setOutput([{ type: 'info', text: '>>> 运行中...' }]); try { const res = await api.post('/code/execute', { code }); if (!res.success) { appendOutput('error', res.error || '执行失败'); if (res.traceback) appendOutput('error', res.traceback); setIsRunning(false); return; } // 显示初始输出 if (res.output) { appendOutput('stdout', res.output); } if (res.needsInput) { // 代码需要交互输入 setContextId(res.context_id); setNeedsInput(true); } else { // 代码直接执行完了 appendOutput('info', '>>> 执行完成'); setIsRunning(false); } } catch (error) { appendOutput('error', `错误: ${error.message}`); setIsRunning(false); } }; const handleInput = async () => { if (!contextId) return; const inputText = userInput; appendOutput('input', `<<< ${inputText}`); setUserInput(''); setNeedsInput(false); try { const res = await api.post('/code/input', { context_id: contextId, input: inputText }); if (!res.success) { appendOutput('error', res.error || '输入处理失败'); if (res.traceback) appendOutput('error', res.traceback); setIsRunning(false); setContextId(null); return; } if (res.output) { appendOutput('stdout', res.output); } if (res.needsInput) { // 还需要更多输入 setNeedsInput(true); } else { // 执行完成 appendOutput('info', '>>> 执行完成'); setIsRunning(false); setContextId(null); } } catch (error) { appendOutput('error', `错误: ${error.message}`); setIsRunning(false); setContextId(null); } }; const handleStop = async () => { if (contextId) { try { const res = await api.post('/code/stop', { context_id: contextId }); if (res.output) { appendOutput('stdout', res.output); } } catch (e) { /* ignore */ } } appendOutput('warning', '>>> 已终止'); setIsRunning(false); setNeedsInput(false); setContextId(null); }; const handleClear = () => { setOutput([]); }; const getOutputColor = (type) => { switch (type) { case 'error': return '#ef4444'; case 'warning': return '#f59e0b'; case 'input': return '#10b981'; case 'info': return '#6b7280'; default: return '#e5e7eb'; } }; return (