agent / templates /code_execution.html
samlax12's picture
Upload 25 files
20f7a0a verified
raw
history blame contribute delete
25.2 kB
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI代码助手 - Python执行环境</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/vs2015.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/python.min.js"></script>
<style>
/* Base styles */
:root {
--primary-color: #4361ee;
--secondary-color: #3f37c9;
--accent-color: #4cc9f0;
--success-color: #4caf50;
--warning-color: #ff9800;
--danger-color: #f44336;
--light-color: #f8f9fa;
--dark-color: #212529;
--border-color: #dee2e6;
--border-radius: 0.375rem;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 0;
height: 100vh;
background-color: #f5f7fa;
color: #333;
display: flex;
flex-direction: column;
}
/* Layout structure */
.workspace {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
flex: 1;
padding: 16px;
}
@media (max-width: 992px) {
.workspace {
grid-template-columns: 1fr;
}
}
.section {
background: #fff;
border-radius: var(--border-radius);
overflow: hidden;
display: flex;
flex-direction: column;
border: 1px solid var(--border-color);
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
}
.section-header {
background: #f8f9fa;
padding: 12px 16px;
border-bottom: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
align-items: center;
}
.section-title {
display: flex;
align-items: center;
gap: 8px;
font-size: 1rem;
font-weight: 500;
}
/* Code editor styles */
.editor-content {
flex: 1;
position: relative;
overflow: hidden;
}
.code-area {
position: absolute;
left: 40px;
right: 0;
top: 0;
bottom: 0;
padding: 12px 16px;
color: #333;
font-family: 'JetBrains Mono', 'Fira Code', monospace;
font-size: 14px;
line-height: 1.6;
overflow: auto;
}
.code-area pre {
margin: 0;
padding: 0;
background: none;
border: none;
}
.code-area code {
display: block;
padding: 0;
tab-size: 4;
font-family: 'JetBrains Mono', 'Fira Code', monospace;
outline: none;
position: relative;
min-height: 100%;
white-space: pre !important;
word-wrap: normal !important;
}
.line-numbers {
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 40px;
padding: 12px 0;
background: #f5f7fa;
border-right: 1px solid #e9ecef;
text-align: center;
color: #6c757d;
font-family: 'JetBrains Mono', 'Fira Code', monospace;
font-size: 14px;
line-height: 1.6;
user-select: none;
}
/* Terminal styles */
.output-content {
flex: 1;
background: #f8f9fa;
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
}
.terminal-window {
flex: 1;
overflow-y: auto;
background: #212529;
color: #f8f9fa;
}
#output {
color: #f8f9fa;
margin: 0;
padding: 12px 16px;
background: transparent;
border: none;
white-space: pre-wrap;
word-wrap: break-word;
line-height: 1.6;
font-size: 14px;
font-family: 'JetBrains Mono', 'Fira Code', monospace;
}
.console-input-container {
display: flex;
align-items: center;
background: #343a40;
border-top: 1px solid #495057;
padding: 8px 12px;
}
.console-prompt {
color: #4caf50;
font-family: 'JetBrains Mono', 'Fira Code', monospace;
margin-right: 8px;
font-size: 14px;
user-select: none;
}
.console-input {
flex: 1;
background: transparent;
border: none;
color: #f8f9fa;
font-family: 'JetBrains Mono', 'Fira Code', monospace;
font-size: 14px;
line-height: 1.5;
padding: 4px 0;
}
.console-input:focus {
outline: none;
}
/* Chat section */
.chat-section {
display: flex;
flex-direction: column;
height: 100%;
}
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 16px;
}
.message {
margin-bottom: 16px;
padding: 12px;
border-radius: var(--border-radius);
max-width: 85%;
position: relative;
}
.message.user {
background-color: #e3f2fd;
color: #0d47a1;
align-self: flex-end;
margin-left: auto;
}
.message.bot {
background-color: #f5f5f5;
color: #333;
align-self: flex-start;
border-left: 3px solid var(--primary-color);
}
.chat-input-container {
padding: 16px;
border-top: 1px solid var(--border-color);
background-color: #f9f9f9;
}
.input-row {
display: flex;
gap: 8px;
}
.chat-input {
flex: 1;
padding: 12px;
border: 1px solid var(--border-color);
border-radius: var(--border-radius);
resize: none;
font-size: 14px;
height: 100px;
}
.chat-input:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.1);
}
/* Button styles */
.btn {
padding: 8px 16px;
border: none;
border-radius: var(--border-radius);
cursor: pointer;
font-weight: 500;
font-size: 14px;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.2s ease;
}
.btn-primary {
background-color: var(--primary-color);
color: white;
}
.btn-primary:hover {
background-color: var(--secondary-color);
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.btn-secondary:hover {
background-color: #5a6268;
}
.btn-danger {
background-color: var(--danger-color);
color: white;
}
.btn-danger:hover {
background-color: #d32f2f;
}
/* Utilities */
.loading {
display: none;
align-items: center;
gap: 8px;
color: #6c757d;
font-size: 14px;
}
.loading.active {
display: flex;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.loading i {
animation: spin 1s linear infinite;
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
}
::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
/* Terminal text styles */
.term-input {
color: #4caf50;
}
.term-output {
color: #f8f9fa;
}
.term-error {
color: #f44336;
}
.term-warning {
color: #ff9800;
}
.term-system {
color: #2196f3;
}
/* Header */
.header {
background-color: #fff;
border-bottom: 1px solid var(--border-color);
padding: 1rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.header-content {
max-width: 1200px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 {
margin: 0;
font-size: 1.5rem;
color: var(--primary-color);
}
</style>
</head>
<body>
<header class="header">
<div class="header-content">
<h1>AI代码助手</h1>
<div>
<span class="badge bg-primary">Python编程环境</span>
</div>
</div>
</header>
<div class="workspace">
<div class="section">
<div class="section-header">
<div class="section-title">
<i class="bi bi-code-square"></i>
代码编辑器
</div>
<div style="display: flex; gap: 8px;">
<button class="btn btn-primary" id="runCode">
<i class="bi bi-play-fill"></i>
运行
</button>
<button class="btn btn-danger" id="stopCode" style="display: none;">
<i class="bi bi-stop-fill"></i>
停止
</button>
<button class="btn btn-secondary" id="clearCode">
<i class="bi bi-trash"></i>
清除
</button>
</div>
</div>
<div class="editor-content">
<div class="line-numbers" id="lineNumbers">1</div>
<div class="code-area" id="codeArea">
<pre><code class="language-python" contenteditable="true" spellcheck="false" autocorrect="off" autocapitalize="off"># 您的代码将在这里显示</code></pre>
</div>
</div>
</div>
<div class="section">
<div class="section-header">
<div class="section-title">
<i class="bi bi-terminal"></i>
终端输出
</div>
<div style="display: flex; gap: 8px;">
<button class="btn btn-secondary" id="clearTerminal">
<i class="bi bi-eraser"></i>
清除
</button>
</div>
</div>
<div class="output-content">
<div class="terminal-window">
<pre id="output"></pre>
</div>
<div class="console-input-container" id="consoleInputContainer" style="display: none;">
<div class="console-prompt">>></div>
<input type="text" id="consoleInput" class="console-input" autocomplete="off" spellcheck="false" />
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// Terminal elements
const terminalOutput = document.getElementById('output');
const consoleInputContainer = document.getElementById('consoleInputContainer');
const consoleInput = document.getElementById('consoleInput');
const clearTerminalBtn = document.getElementById('clearTerminal');
const stopCodeBtn = document.getElementById('stopCode');
// Editor elements
const codeArea = document.querySelector('#codeArea code');
const lineNumbers = document.getElementById('lineNumbers');
const runButton = document.getElementById('runCode');
const clearButton = document.getElementById('clearCode');
// State variables
let executionContext = null;
let isExecuting = false;
let executionStartTime = null;
// Initialize terminal
initializeTerminal();
// Initialize editor
updateLineNumbers();
// Function to update line numbers
function updateLineNumbers() {
const lines = codeArea.textContent.split('\n').length;
lineNumbers.innerHTML = Array.from({length: lines}, (_, i) => i + 1).join('<br>');
}
// Initialize terminal with welcome message
function initializeTerminal() {
clearTerminalOutput();
appendToTerminal("欢迎使用Python交互式终端", "term-system");
appendToTerminal("使用'运行'按钮执行您的代码", "term-system");
appendToTerminal("", "term-system"); // 空行
appendToTerminal(">>> 准备执行代码...", "term-system");
}
// Append text to terminal
function appendToTerminal(text, type = null) {
const lines = text.split('\n');
let html = '';
for (const line of lines) {
if (type) {
html += `<span class="${type}">${escapeHtml(line)}</span>\n`;
} else {
html += escapeHtml(line) + '\n';
}
}
terminalOutput.innerHTML += html;
terminalOutput.scrollTop = terminalOutput.scrollHeight;
}
// Clear terminal output
function clearTerminalOutput() {
terminalOutput.innerHTML = '';
}
// Escape HTML to prevent XSS
function escapeHtml(text) {
return text
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
// Run code
runButton.addEventListener('click', async () => {
if (isExecuting) return; // Prevent multiple executions
const code = codeArea.textContent;
// Clear terminal and show execution start
clearTerminalOutput();
appendToTerminal("开始执行Python代码...", "term-system");
// Update UI state
isExecuting = true;
executionStartTime = performance.now();
runButton.style.display = 'none';
stopCodeBtn.style.display = 'flex';
try {
const response = await fetch('/api/code/execute', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code })
});
const data = await response.json();
if (data.success) {
// Show output (if any)
if (data.output && data.output.trim()) {
appendToTerminal(data.output, "term-output");
}
if (data.needsInput) {
// Code is waiting for input
executionContext = data.context_id;
consoleInputContainer.style.display = 'flex';
consoleInput.focus();
} else {
// Code has completed, no input needed
appendToTerminal("程序执行完成", "term-system");
finishExecution();
}
} else {
// Handle error
appendToTerminal(`错误: ${data.error}`, "term-error");
if (data.traceback) {
appendToTerminal(data.traceback, "term-error");
}
appendToTerminal("执行失败", "term-system");
finishExecution();
}
} catch (error) {
appendToTerminal(`系统错误: ${error.message}`, "term-error");
appendToTerminal("执行失败", "term-system");
finishExecution();
}
});
// Submit console input
consoleInput.addEventListener('keydown', async (e) => {
if (e.key === 'Enter' && executionContext) {
const input = consoleInput.value;
consoleInput.value = '';
// Add input to terminal
appendToTerminal(`>> ${input}`, "term-input");
try {
// Send input to backend
const response = await fetch('/api/code/input', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
input: input,
context_id: executionContext
})
});
const data = await response.json();
if (data.success) {
// Show new output
if (data.output && data.output.trim()) {
appendToTerminal(data.output, "term-output");
}
if (data.needsInput) {
// Still waiting for more input
consoleInput.focus();
} else {
// Execution completed
appendToTerminal(">>> 程序执行完成", "term-system");
finishExecution();
}
} else {
// Handle error
appendToTerminal(`错误: ${data.error}`, "term-error");
if (data.traceback) {
appendToTerminal(data.traceback, "term-error");
}
finishExecution();
}
} catch (error) {
appendToTerminal(`系统错误: ${error.message}`, "term-error");
finishExecution();
}
}
});
// Stop code execution
stopCodeBtn.addEventListener('click', async () => {
if (!executionContext) return;
try {
// Send stop request to server
const response = await fetch('/api/code/stop', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ context_id: executionContext })
});
// Clean up UI regardless of response
appendToTerminal("用户终止了执行", "term-warning");
finishExecution();
} catch (error) {
console.error('停止执行时出错:', error);
finishExecution();
}
});
// Clear code
clearButton.addEventListener('click', () => {
codeArea.textContent = '# 您的代码将在这里显示';
updateLineNumbers();
clearTerminalOutput();
initializeTerminal();
consoleInputContainer.style.display = 'none';
executionContext = null;
isExecuting = false;
runButton.style.display = 'flex';
stopCodeBtn.style.display = 'none';
});
// Clear terminal
clearTerminalBtn.addEventListener('click', () => {
if (!isExecuting) {
clearTerminalOutput();
initializeTerminal();
} else {
// If execution is in progress, just add a separator
appendToTerminal("\n--- 已清除终端 ---\n", "term-system");
}
});
// Update line numbers on code changes
codeArea.addEventListener('input', updateLineNumbers);
// Handle tab key
codeArea.addEventListener('keydown', (e) => {
if (e.key === 'Tab') {
e.preventDefault();
document.execCommand('insertText', false, ' ');
}
});
// Function to clean up after execution completes
function finishExecution() {
consoleInputContainer.style.display = 'none';
executionContext = null;
isExecuting = false;
runButton.style.display = 'flex';
stopCodeBtn.style.display = 'none';
}
// Check if code was provided via URL parameters
const urlParams = new URLSearchParams(window.location.search);
const initialCode = urlParams.get('code');
if (initialCode) {
try {
codeArea.textContent = decodeURIComponent(initialCode);
updateLineNumbers();
} catch (e) {
console.error('Failed to decode initial code:', e);
}
}
// Check for messages from parent frame
window.addEventListener('message', (event) => {
if (event.data && event.data.type === 'setCode') {
codeArea.textContent = event.data.code;
updateLineNumbers();
}
});
});
</script>
</body>
</html>