samlax12 commited on
Commit
75c7479
·
verified ·
1 Parent(s): a07ea87

Update templates/code_execution.html

Browse files
Files changed (1) hide show
  1. templates/code_execution.html +717 -717
templates/code_execution.html CHANGED
@@ -1,718 +1,718 @@
1
- <!DOCTYPE html>
2
- <html lang="zh-CN">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>AI代码助手 - Python执行环境</title>
7
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
8
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css">
9
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/vs2015.min.css">
10
- <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
11
- <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/python.min.js"></script>
12
- <style>
13
- /* Base styles */
14
- :root {
15
- --primary-color: #4361ee;
16
- --secondary-color: #3f37c9;
17
- --accent-color: #4cc9f0;
18
- --success-color: #4caf50;
19
- --warning-color: #ff9800;
20
- --danger-color: #f44336;
21
- --light-color: #f8f9fa;
22
- --dark-color: #212529;
23
- --border-color: #dee2e6;
24
- --border-radius: 0.375rem;
25
- }
26
-
27
- body {
28
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
29
- margin: 0;
30
- padding: 0;
31
- height: 100vh;
32
- background-color: #f5f7fa;
33
- color: #333;
34
- display: flex;
35
- flex-direction: column;
36
- }
37
-
38
- /* Layout structure */
39
- .workspace {
40
- display: grid;
41
- grid-template-columns: 1fr 1fr;
42
- gap: 16px;
43
- flex: 1;
44
- padding: 16px;
45
- }
46
-
47
- @media (max-width: 992px) {
48
- .workspace {
49
- grid-template-columns: 1fr;
50
- }
51
- }
52
-
53
- .section {
54
- background: #fff;
55
- border-radius: var(--border-radius);
56
- overflow: hidden;
57
- display: flex;
58
- flex-direction: column;
59
- border: 1px solid var(--border-color);
60
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
61
- }
62
-
63
- .section-header {
64
- background: #f8f9fa;
65
- padding: 12px 16px;
66
- border-bottom: 1px solid var(--border-color);
67
- display: flex;
68
- justify-content: space-between;
69
- align-items: center;
70
- }
71
-
72
- .section-title {
73
- display: flex;
74
- align-items: center;
75
- gap: 8px;
76
- font-size: 1rem;
77
- font-weight: 500;
78
- }
79
-
80
- /* Code editor styles */
81
- .editor-content {
82
- flex: 1;
83
- position: relative;
84
- overflow: hidden;
85
- }
86
-
87
- .code-area {
88
- position: absolute;
89
- left: 40px;
90
- right: 0;
91
- top: 0;
92
- bottom: 0;
93
- padding: 12px 16px;
94
- color: #333;
95
- font-family: 'JetBrains Mono', 'Fira Code', monospace;
96
- font-size: 14px;
97
- line-height: 1.6;
98
- overflow: auto;
99
- }
100
-
101
- .code-area pre {
102
- margin: 0;
103
- padding: 0;
104
- background: none;
105
- border: none;
106
- }
107
-
108
- .code-area code {
109
- display: block;
110
- padding: 0;
111
- tab-size: 4;
112
- font-family: 'JetBrains Mono', 'Fira Code', monospace;
113
- outline: none;
114
- position: relative;
115
- min-height: 100%;
116
- white-space: pre !important;
117
- word-wrap: normal !important;
118
- }
119
-
120
- .line-numbers {
121
- position: absolute;
122
- left: 0;
123
- top: 0;
124
- bottom: 0;
125
- width: 40px;
126
- padding: 12px 0;
127
- background: #f5f7fa;
128
- border-right: 1px solid #e9ecef;
129
- text-align: center;
130
- color: #6c757d;
131
- font-family: 'JetBrains Mono', 'Fira Code', monospace;
132
- font-size: 14px;
133
- line-height: 1.6;
134
- user-select: none;
135
- }
136
-
137
- /* Terminal styles */
138
- .output-content {
139
- flex: 1;
140
- background: #f8f9fa;
141
- position: relative;
142
- overflow: hidden;
143
- display: flex;
144
- flex-direction: column;
145
- }
146
-
147
- .terminal-window {
148
- flex: 1;
149
- overflow-y: auto;
150
- background: #212529;
151
- color: #f8f9fa;
152
- }
153
-
154
- #output {
155
- color: #f8f9fa;
156
- margin: 0;
157
- padding: 12px 16px;
158
- background: transparent;
159
- border: none;
160
- white-space: pre-wrap;
161
- word-wrap: break-word;
162
- line-height: 1.6;
163
- font-size: 14px;
164
- font-family: 'JetBrains Mono', 'Fira Code', monospace;
165
- }
166
-
167
- .console-input-container {
168
- display: flex;
169
- align-items: center;
170
- background: #343a40;
171
- border-top: 1px solid #495057;
172
- padding: 8px 12px;
173
- }
174
-
175
- .console-prompt {
176
- color: #4caf50;
177
- font-family: 'JetBrains Mono', 'Fira Code', monospace;
178
- margin-right: 8px;
179
- font-size: 14px;
180
- user-select: none;
181
- }
182
-
183
- .console-input {
184
- flex: 1;
185
- background: transparent;
186
- border: none;
187
- color: #f8f9fa;
188
- font-family: 'JetBrains Mono', 'Fira Code', monospace;
189
- font-size: 14px;
190
- line-height: 1.5;
191
- padding: 4px 0;
192
- }
193
-
194
- .console-input:focus {
195
- outline: none;
196
- }
197
-
198
- /* Chat section */
199
- .chat-section {
200
- display: flex;
201
- flex-direction: column;
202
- height: 100%;
203
- }
204
-
205
- .chat-messages {
206
- flex: 1;
207
- overflow-y: auto;
208
- padding: 16px;
209
- }
210
-
211
- .message {
212
- margin-bottom: 16px;
213
- padding: 12px;
214
- border-radius: var(--border-radius);
215
- max-width: 85%;
216
- position: relative;
217
- }
218
-
219
- .message.user {
220
- background-color: #e3f2fd;
221
- color: #0d47a1;
222
- align-self: flex-end;
223
- margin-left: auto;
224
- }
225
-
226
- .message.bot {
227
- background-color: #f5f5f5;
228
- color: #333;
229
- align-self: flex-start;
230
- border-left: 3px solid var(--primary-color);
231
- }
232
-
233
- .chat-input-container {
234
- padding: 16px;
235
- border-top: 1px solid var(--border-color);
236
- background-color: #f9f9f9;
237
- }
238
-
239
- .input-row {
240
- display: flex;
241
- gap: 8px;
242
- }
243
-
244
- .chat-input {
245
- flex: 1;
246
- padding: 12px;
247
- border: 1px solid var(--border-color);
248
- border-radius: var(--border-radius);
249
- resize: none;
250
- font-size: 14px;
251
- height: 100px;
252
- }
253
-
254
- .chat-input:focus {
255
- outline: none;
256
- border-color: var(--primary-color);
257
- box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.1);
258
- }
259
-
260
- /* Button styles */
261
- .btn {
262
- padding: 8px 16px;
263
- border: none;
264
- border-radius: var(--border-radius);
265
- cursor: pointer;
266
- font-weight: 500;
267
- font-size: 14px;
268
- display: flex;
269
- align-items: center;
270
- gap: 8px;
271
- transition: all 0.2s ease;
272
- }
273
-
274
- .btn-primary {
275
- background-color: var(--primary-color);
276
- color: white;
277
- }
278
-
279
- .btn-primary:hover {
280
- background-color: var(--secondary-color);
281
- }
282
-
283
- .btn-secondary {
284
- background-color: #6c757d;
285
- color: white;
286
- }
287
-
288
- .btn-secondary:hover {
289
- background-color: #5a6268;
290
- }
291
-
292
- .btn-danger {
293
- background-color: var(--danger-color);
294
- color: white;
295
- }
296
-
297
- .btn-danger:hover {
298
- background-color: #d32f2f;
299
- }
300
-
301
- /* Utilities */
302
- .loading {
303
- display: none;
304
- align-items: center;
305
- gap: 8px;
306
- color: #6c757d;
307
- font-size: 14px;
308
- }
309
-
310
- .loading.active {
311
- display: flex;
312
- }
313
-
314
- @keyframes spin {
315
- to { transform: rotate(360deg); }
316
- }
317
-
318
- .loading i {
319
- animation: spin 1s linear infinite;
320
- }
321
-
322
- /* Custom scrollbar */
323
- ::-webkit-scrollbar {
324
- width: 8px;
325
- height: 8px;
326
- }
327
-
328
- ::-webkit-scrollbar-track {
329
- background: #f1f1f1;
330
- }
331
-
332
- ::-webkit-scrollbar-thumb {
333
- background: #c1c1c1;
334
- border-radius: 4px;
335
- }
336
-
337
- ::-webkit-scrollbar-thumb:hover {
338
- background: #a8a8a8;
339
- }
340
-
341
- /* Terminal text styles */
342
- .term-input {
343
- color: #4caf50;
344
- }
345
-
346
- .term-output {
347
- color: #f8f9fa;
348
- }
349
-
350
- .term-error {
351
- color: #f44336;
352
- }
353
-
354
- .term-warning {
355
- color: #ff9800;
356
- }
357
-
358
- .term-system {
359
- color: #2196f3;
360
- }
361
-
362
- /* Header */
363
- .header {
364
- background-color: #fff;
365
- border-bottom: 1px solid var(--border-color);
366
- padding: 1rem;
367
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
368
- }
369
-
370
- .header-content {
371
- max-width: 1200px;
372
- margin: 0 auto;
373
- display: flex;
374
- justify-content: space-between;
375
- align-items: center;
376
- }
377
-
378
- .header h1 {
379
- margin: 0;
380
- font-size: 1.5rem;
381
- color: var(--primary-color);
382
- }
383
- </style>
384
- </head>
385
- <body>
386
- <header class="header">
387
- <div class="header-content">
388
- <h1>AI代码助手</h1>
389
- <div>
390
- <span class="badge bg-primary">Python编程环境</span>
391
- </div>
392
- </div>
393
- </header>
394
-
395
- <div class="workspace">
396
- <div class="section">
397
- <div class="section-header">
398
- <div class="section-title">
399
- <i class="bi bi-code-square"></i>
400
- 代码编辑器
401
- </div>
402
- <div style="display: flex; gap: 8px;">
403
- <button class="btn btn-primary" id="runCode">
404
- <i class="bi bi-play-fill"></i>
405
- 运行
406
- </button>
407
- <button class="btn btn-danger" id="stopCode" style="display: none;">
408
- <i class="bi bi-stop-fill"></i>
409
- 停止
410
- </button>
411
- <button class="btn btn-secondary" id="clearCode">
412
- <i class="bi bi-trash"></i>
413
- 清除
414
- </button>
415
- </div>
416
- </div>
417
- <div class="editor-content">
418
- <div class="line-numbers" id="lineNumbers">1</div>
419
- <div class="code-area" id="codeArea">
420
- <pre><code class="language-python" contenteditable="true" spellcheck="false" autocorrect="off" autocapitalize="off"># 您的代码将在这里显示</code></pre>
421
- </div>
422
- </div>
423
- </div>
424
-
425
- <div class="section">
426
- <div class="section-header">
427
- <div class="section-title">
428
- <i class="bi bi-terminal"></i>
429
- 终端输出
430
- </div>
431
- <div style="display: flex; gap: 8px;">
432
- <button class="btn btn-secondary" id="clearTerminal">
433
- <i class="bi bi-eraser"></i>
434
- 清除
435
- </button>
436
- </div>
437
- </div>
438
- <div class="output-content">
439
- <div class="terminal-window">
440
- <pre id="output"></pre>
441
- </div>
442
- <div class="console-input-container" id="consoleInputContainer" style="display: none;">
443
- <div class="console-prompt">>></div>
444
- <input type="text" id="consoleInput" class="console-input" autocomplete="off" spellcheck="false" />
445
- </div>
446
- </div>
447
- </div>
448
- </div>
449
-
450
- <script>
451
- document.addEventListener('DOMContentLoaded', () => {
452
- // Terminal elements
453
- const terminalOutput = document.getElementById('output');
454
- const consoleInputContainer = document.getElementById('consoleInputContainer');
455
- const consoleInput = document.getElementById('consoleInput');
456
- const clearTerminalBtn = document.getElementById('clearTerminal');
457
- const stopCodeBtn = document.getElementById('stopCode');
458
-
459
- // Editor elements
460
- const codeArea = document.querySelector('#codeArea code');
461
- const lineNumbers = document.getElementById('lineNumbers');
462
- const runButton = document.getElementById('runCode');
463
- const clearButton = document.getElementById('clearCode');
464
-
465
- // State variables
466
- let executionContext = null;
467
- let isExecuting = false;
468
- let executionStartTime = null;
469
-
470
- // Initialize terminal
471
- initializeTerminal();
472
-
473
- // Initialize editor
474
- updateLineNumbers();
475
-
476
- // Function to update line numbers
477
- function updateLineNumbers() {
478
- const lines = codeArea.textContent.split('\n').length;
479
- lineNumbers.innerHTML = Array.from({length: lines}, (_, i) => i + 1).join('<br>');
480
- }
481
-
482
- // Initialize terminal with welcome message
483
- function initializeTerminal() {
484
- clearTerminalOutput();
485
- appendToTerminal("欢迎使用Python交互式终端", "term-system");
486
- appendToTerminal("使用'运行'按钮执行您的代码", "term-system");
487
- appendToTerminal("", "term-system"); // 空行
488
- appendToTerminal(">>> 准备执行代码...", "term-system");
489
- }
490
-
491
- // Append text to terminal
492
- function appendToTerminal(text, type = null) {
493
- const lines = text.split('\n');
494
- let html = '';
495
-
496
- for (const line of lines) {
497
- if (type) {
498
- html += `<span class="${type}">${escapeHtml(line)}</span>\n`;
499
- } else {
500
- html += escapeHtml(line) + '\n';
501
- }
502
- }
503
-
504
- terminalOutput.innerHTML += html;
505
- terminalOutput.scrollTop = terminalOutput.scrollHeight;
506
- }
507
-
508
- // Clear terminal output
509
- function clearTerminalOutput() {
510
- terminalOutput.innerHTML = '';
511
- }
512
-
513
- // Escape HTML to prevent XSS
514
- function escapeHtml(text) {
515
- return text
516
- .replace(/&/g, "&amp;")
517
- .replace(/</g, "&lt;")
518
- .replace(/>/g, "&gt;")
519
- .replace(/"/g, "&quot;")
520
- .replace(/'/g, "&#039;");
521
- }
522
-
523
- // Run code
524
- runButton.addEventListener('click', async () => {
525
- if (isExecuting) return; // Prevent multiple executions
526
-
527
- const code = codeArea.textContent;
528
-
529
- // Clear terminal and show execution start
530
- clearTerminalOutput();
531
- appendToTerminal("开始执行Python代码...", "term-system");
532
-
533
- // Update UI state
534
- isExecuting = true;
535
- executionStartTime = performance.now();
536
- runButton.style.display = 'none';
537
- stopCodeBtn.style.display = 'flex';
538
-
539
- try {
540
- const response = await fetch('/api/code/execute', {
541
- method: 'POST',
542
- headers: { 'Content-Type': 'application/json' },
543
- body: JSON.stringify({ code })
544
- });
545
-
546
- const data = await response.json();
547
-
548
- if (data.success) {
549
- // Show output (if any)
550
- if (data.output && data.output.trim()) {
551
- appendToTerminal(data.output, "term-output");
552
- }
553
-
554
- if (data.needsInput) {
555
- // Code is waiting for input
556
- executionContext = data.context_id;
557
- consoleInputContainer.style.display = 'flex';
558
- consoleInput.focus();
559
- } else {
560
- // Code has completed, no input needed
561
- appendToTerminal("程序执行完成", "term-system");
562
- finishExecution();
563
- }
564
- } else {
565
- // Handle error
566
- appendToTerminal(`错误: ${data.error}`, "term-error");
567
- if (data.traceback) {
568
- appendToTerminal(data.traceback, "term-error");
569
- }
570
- appendToTerminal("执行失败", "term-system");
571
- finishExecution();
572
- }
573
- } catch (error) {
574
- appendToTerminal(`系统错误: ${error.message}`, "term-error");
575
- appendToTerminal("执行失败", "term-system");
576
- finishExecution();
577
- }
578
- });
579
-
580
- // Submit console input
581
- consoleInput.addEventListener('keydown', async (e) => {
582
- if (e.key === 'Enter' && executionContext) {
583
- const input = consoleInput.value;
584
- consoleInput.value = '';
585
-
586
- // Add input to terminal
587
- appendToTerminal(`>> ${input}`, "term-input");
588
-
589
- try {
590
- // Send input to backend
591
- const response = await fetch('/api/code/input', {
592
- method: 'POST',
593
- headers: { 'Content-Type': 'application/json' },
594
- body: JSON.stringify({
595
- input: input,
596
- context_id: executionContext
597
- })
598
- });
599
-
600
- const data = await response.json();
601
-
602
- if (data.success) {
603
- // Show new output
604
- if (data.output && data.output.trim()) {
605
- appendToTerminal(data.output, "term-output");
606
- }
607
-
608
- if (data.needsInput) {
609
- // Still waiting for more input
610
- consoleInput.focus();
611
- } else {
612
- // Execution completed
613
- appendToTerminal(">>> 程序执行完成", "term-system");
614
- finishExecution();
615
- }
616
- } else {
617
- // Handle error
618
- appendToTerminal(`错误: ${data.error}`, "term-error");
619
- if (data.traceback) {
620
- appendToTerminal(data.traceback, "term-error");
621
- }
622
- finishExecution();
623
- }
624
- } catch (error) {
625
- appendToTerminal(`系统错误: ${error.message}`, "term-error");
626
- finishExecution();
627
- }
628
- }
629
- });
630
-
631
- // Stop code execution
632
- stopCodeBtn.addEventListener('click', async () => {
633
- if (!executionContext) return;
634
-
635
- try {
636
- // Send stop request to server
637
- const response = await fetch('/api/code/stop', {
638
- method: 'POST',
639
- headers: { 'Content-Type': 'application/json' },
640
- body: JSON.stringify({ context_id: executionContext })
641
- });
642
-
643
- // Clean up UI regardless of response
644
- appendToTerminal("用户终止了执行", "term-warning");
645
- finishExecution();
646
- } catch (error) {
647
- console.error('停止执行时出错:', error);
648
- finishExecution();
649
- }
650
- });
651
-
652
- // Clear code
653
- clearButton.addEventListener('click', () => {
654
- codeArea.textContent = '# 您的代码将在这里显示';
655
- updateLineNumbers();
656
- clearTerminalOutput();
657
- initializeTerminal();
658
- consoleInputContainer.style.display = 'none';
659
- executionContext = null;
660
- isExecuting = false;
661
- runButton.style.display = 'flex';
662
- stopCodeBtn.style.display = 'none';
663
- });
664
-
665
- // Clear terminal
666
- clearTerminalBtn.addEventListener('click', () => {
667
- if (!isExecuting) {
668
- clearTerminalOutput();
669
- initializeTerminal();
670
- } else {
671
- // If execution is in progress, just add a separator
672
- appendToTerminal("\n--- 已清除终端 ---\n", "term-system");
673
- }
674
- });
675
-
676
- // Update line numbers on code changes
677
- codeArea.addEventListener('input', updateLineNumbers);
678
-
679
- // Handle tab key
680
- codeArea.addEventListener('keydown', (e) => {
681
- if (e.key === 'Tab') {
682
- e.preventDefault();
683
- document.execCommand('insertText', false, ' ');
684
- }
685
- });
686
-
687
- // Function to clean up after execution completes
688
- function finishExecution() {
689
- consoleInputContainer.style.display = 'none';
690
- executionContext = null;
691
- isExecuting = false;
692
- runButton.style.display = 'flex';
693
- stopCodeBtn.style.display = 'none';
694
- }
695
-
696
- // Check if code was provided via URL parameters
697
- const urlParams = new URLSearchParams(window.location.search);
698
- const initialCode = urlParams.get('code');
699
- if (initialCode) {
700
- try {
701
- codeArea.textContent = decodeURIComponent(initialCode);
702
- updateLineNumbers();
703
- } catch (e) {
704
- console.error('Failed to decode initial code:', e);
705
- }
706
- }
707
-
708
- // Check for messages from parent frame
709
- window.addEventListener('message', (event) => {
710
- if (event.data && event.data.type === 'setCode') {
711
- codeArea.textContent = event.data.code;
712
- updateLineNumbers();
713
- }
714
- });
715
- });
716
- </script>
717
- </body>
718
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>AI代码助手 - Python执行环境</title>
7
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
8
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css">
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/vs2015.min.css">
10
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
11
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/python.min.js"></script>
12
+ <style>
13
+ /* Base styles */
14
+ :root {
15
+ --primary-color: #4361ee;
16
+ --secondary-color: #3f37c9;
17
+ --accent-color: #4cc9f0;
18
+ --success-color: #4caf50;
19
+ --warning-color: #ff9800;
20
+ --danger-color: #f44336;
21
+ --light-color: #f8f9fa;
22
+ --dark-color: #212529;
23
+ --border-color: #dee2e6;
24
+ --border-radius: 0.375rem;
25
+ }
26
+
27
+ body {
28
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
29
+ margin: 0;
30
+ padding: 0;
31
+ height: 100vh;
32
+ background-color: #f5f7fa;
33
+ color: #333;
34
+ display: flex;
35
+ flex-direction: column;
36
+ }
37
+
38
+ /* Layout structure */
39
+ .workspace {
40
+ display: grid;
41
+ grid-template-columns: 1fr 1fr;
42
+ gap: 16px;
43
+ flex: 1;
44
+ padding: 16px;
45
+ }
46
+
47
+ @media (max-width: 992px) {
48
+ .workspace {
49
+ grid-template-columns: 1fr;
50
+ }
51
+ }
52
+
53
+ .section {
54
+ background: #fff;
55
+ border-radius: var(--border-radius);
56
+ overflow: hidden;
57
+ display: flex;
58
+ flex-direction: column;
59
+ border: 1px solid var(--border-color);
60
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
61
+ }
62
+
63
+ .section-header {
64
+ background: #f8f9fa;
65
+ padding: 12px 16px;
66
+ border-bottom: 1px solid var(--border-color);
67
+ display: flex;
68
+ justify-content: space-between;
69
+ align-items: center;
70
+ }
71
+
72
+ .section-title {
73
+ display: flex;
74
+ align-items: center;
75
+ gap: 8px;
76
+ font-size: 1rem;
77
+ font-weight: 500;
78
+ }
79
+
80
+ /* Code editor styles */
81
+ .editor-content {
82
+ flex: 1;
83
+ position: relative;
84
+ overflow: hidden;
85
+ }
86
+
87
+ .code-area {
88
+ position: absolute;
89
+ left: 40px;
90
+ right: 0;
91
+ top: 0;
92
+ bottom: 0;
93
+ padding: 12px 16px;
94
+ color: #333;
95
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
96
+ font-size: 14px;
97
+ line-height: 1.6;
98
+ overflow: auto;
99
+ }
100
+
101
+ .code-area pre {
102
+ margin: 0;
103
+ padding: 0;
104
+ background: none;
105
+ border: none;
106
+ }
107
+
108
+ .code-area code {
109
+ display: block;
110
+ padding: 0;
111
+ tab-size: 4;
112
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
113
+ outline: none;
114
+ position: relative;
115
+ min-height: 100%;
116
+ white-space: pre !important;
117
+ word-wrap: normal !important;
118
+ }
119
+
120
+ .line-numbers {
121
+ position: absolute;
122
+ left: 0;
123
+ top: 0;
124
+ bottom: 0;
125
+ width: 40px;
126
+ padding: 12px 0;
127
+ background: #f5f7fa;
128
+ border-right: 1px solid #e9ecef;
129
+ text-align: center;
130
+ color: #6c757d;
131
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
132
+ font-size: 14px;
133
+ line-height: 1.6;
134
+ user-select: none;
135
+ }
136
+
137
+ /* Terminal styles */
138
+ .output-content {
139
+ flex: 1;
140
+ background: #f8f9fa;
141
+ position: relative;
142
+ overflow: hidden;
143
+ display: flex;
144
+ flex-direction: column;
145
+ }
146
+
147
+ .terminal-window {
148
+ flex: 1;
149
+ overflow-y: auto;
150
+ background: #212529;
151
+ color: #f8f9fa;
152
+ }
153
+
154
+ #output {
155
+ color: #f8f9fa;
156
+ margin: 0;
157
+ padding: 12px 16px;
158
+ background: transparent;
159
+ border: none;
160
+ white-space: pre-wrap;
161
+ word-wrap: break-word;
162
+ line-height: 1.6;
163
+ font-size: 14px;
164
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
165
+ }
166
+
167
+ .console-input-container {
168
+ display: flex;
169
+ align-items: center;
170
+ background: #343a40;
171
+ border-top: 1px solid #495057;
172
+ padding: 8px 12px;
173
+ }
174
+
175
+ .console-prompt {
176
+ color: #4caf50;
177
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
178
+ margin-right: 8px;
179
+ font-size: 14px;
180
+ user-select: none;
181
+ }
182
+
183
+ .console-input {
184
+ flex: 1;
185
+ background: transparent;
186
+ border: none;
187
+ color: #f8f9fa;
188
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
189
+ font-size: 14px;
190
+ line-height: 1.5;
191
+ padding: 4px 0;
192
+ }
193
+
194
+ .console-input:focus {
195
+ outline: none;
196
+ }
197
+
198
+ /* Chat section */
199
+ .chat-section {
200
+ display: flex;
201
+ flex-direction: column;
202
+ height: 100%;
203
+ }
204
+
205
+ .chat-messages {
206
+ flex: 1;
207
+ overflow-y: auto;
208
+ padding: 16px;
209
+ }
210
+
211
+ .message {
212
+ margin-bottom: 16px;
213
+ padding: 12px;
214
+ border-radius: var(--border-radius);
215
+ max-width: 85%;
216
+ position: relative;
217
+ }
218
+
219
+ .message.user {
220
+ background-color: #e3f2fd;
221
+ color: #0d47a1;
222
+ align-self: flex-end;
223
+ margin-left: auto;
224
+ }
225
+
226
+ .message.bot {
227
+ background-color: #f5f5f5;
228
+ color: #333;
229
+ align-self: flex-start;
230
+ border-left: 3px solid var(--primary-color);
231
+ }
232
+
233
+ .chat-input-container {
234
+ padding: 16px;
235
+ border-top: 1px solid var(--border-color);
236
+ background-color: #f9f9f9;
237
+ }
238
+
239
+ .input-row {
240
+ display: flex;
241
+ gap: 8px;
242
+ }
243
+
244
+ .chat-input {
245
+ flex: 1;
246
+ padding: 12px;
247
+ border: 1px solid var(--border-color);
248
+ border-radius: var(--border-radius);
249
+ resize: none;
250
+ font-size: 14px;
251
+ height: 100px;
252
+ }
253
+
254
+ .chat-input:focus {
255
+ outline: none;
256
+ border-color: var(--primary-color);
257
+ box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.1);
258
+ }
259
+
260
+ /* Button styles */
261
+ .btn {
262
+ padding: 8px 16px;
263
+ border: none;
264
+ border-radius: var(--border-radius);
265
+ cursor: pointer;
266
+ font-weight: 500;
267
+ font-size: 14px;
268
+ display: flex;
269
+ align-items: center;
270
+ gap: 8px;
271
+ transition: all 0.2s ease;
272
+ }
273
+
274
+ .btn-primary {
275
+ background-color: var(--primary-color);
276
+ color: white;
277
+ }
278
+
279
+ .btn-primary:hover {
280
+ background-color: var(--secondary-color);
281
+ }
282
+
283
+ .btn-secondary {
284
+ background-color: #6c757d;
285
+ color: white;
286
+ }
287
+
288
+ .btn-secondary:hover {
289
+ background-color: #5a6268;
290
+ }
291
+
292
+ .btn-danger {
293
+ background-color: var(--danger-color);
294
+ color: white;
295
+ }
296
+
297
+ .btn-danger:hover {
298
+ background-color: #d32f2f;
299
+ }
300
+
301
+ /* Utilities */
302
+ .loading {
303
+ display: none;
304
+ align-items: center;
305
+ gap: 8px;
306
+ color: #6c757d;
307
+ font-size: 14px;
308
+ }
309
+
310
+ .loading.active {
311
+ display: flex;
312
+ }
313
+
314
+ @keyframes spin {
315
+ to { transform: rotate(360deg); }
316
+ }
317
+
318
+ .loading i {
319
+ animation: spin 1s linear infinite;
320
+ }
321
+
322
+ /* Custom scrollbar */
323
+ ::-webkit-scrollbar {
324
+ width: 8px;
325
+ height: 8px;
326
+ }
327
+
328
+ ::-webkit-scrollbar-track {
329
+ background: #f1f1f1;
330
+ }
331
+
332
+ ::-webkit-scrollbar-thumb {
333
+ background: #c1c1c1;
334
+ border-radius: 4px;
335
+ }
336
+
337
+ ::-webkit-scrollbar-thumb:hover {
338
+ background: #a8a8a8;
339
+ }
340
+
341
+ /* Terminal text styles */
342
+ .term-input {
343
+ color: #4caf50;
344
+ }
345
+
346
+ .term-output {
347
+ color: #f8f9fa;
348
+ }
349
+
350
+ .term-error {
351
+ color: #f44336;
352
+ }
353
+
354
+ .term-warning {
355
+ color: #ff9800;
356
+ }
357
+
358
+ .term-system {
359
+ color: #2196f3;
360
+ }
361
+
362
+ /* Header */
363
+ .header {
364
+ background-color: #fff;
365
+ border-bottom: 1px solid var(--border-color);
366
+ padding: 1rem;
367
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
368
+ }
369
+
370
+ .header-content {
371
+ max-width: 1200px;
372
+ margin: 0 auto;
373
+ display: flex;
374
+ justify-content: space-between;
375
+ align-items: center;
376
+ }
377
+
378
+ .header h1 {
379
+ margin: 0;
380
+ font-size: 1.5rem;
381
+ color: var(--primary-color);
382
+ }
383
+ </style>
384
+ </head>
385
+ <body>
386
+ <header class="header">
387
+ <div class="header-content">
388
+ <h1>AI代码助手</h1>
389
+ <div>
390
+ <span class="badge bg-primary">Python编程环境</span>
391
+ </div>
392
+ </div>
393
+ </header>
394
+
395
+ <div class="workspace">
396
+ <div class="section">
397
+ <div class="section-header">
398
+ <div class="section-title">
399
+ <i class="bi bi-code-square"></i>
400
+ 代码编辑器
401
+ </div>
402
+ <div style="display: flex; gap: 8px;">
403
+ <button class="btn btn-primary" id="runCode">
404
+ <i class="bi bi-play-fill"></i>
405
+ 运行
406
+ </button>
407
+ <button class="btn btn-danger" id="stopCode" style="display: none;">
408
+ <i class="bi bi-stop-fill"></i>
409
+ 停止
410
+ </button>
411
+ <button class="btn btn-secondary" id="clearCode">
412
+ <i class="bi bi-trash"></i>
413
+ 清除
414
+ </button>
415
+ </div>
416
+ </div>
417
+ <div class="editor-content">
418
+ <div class="line-numbers" id="lineNumbers">1</div>
419
+ <div class="code-area" id="codeArea">
420
+ <pre><code class="language-python" contenteditable="true" spellcheck="false" autocorrect="off" autocapitalize="off"># 您的代码将在这里显示</code></pre>
421
+ </div>
422
+ </div>
423
+ </div>
424
+
425
+ <div class="section">
426
+ <div class="section-header">
427
+ <div class="section-title">
428
+ <i class="bi bi-terminal"></i>
429
+ 终端输出
430
+ </div>
431
+ <div style="display: flex; gap: 8px;">
432
+ <button class="btn btn-secondary" id="clearTerminal">
433
+ <i class="bi bi-eraser"></i>
434
+ 清除
435
+ </button>
436
+ </div>
437
+ </div>
438
+ <div class="output-content">
439
+ <div class="terminal-window">
440
+ <pre id="output"></pre>
441
+ </div>
442
+ <div class="console-input-container" id="consoleInputContainer" style="display: none;">
443
+ <div class="console-prompt">>></div>
444
+ <input type="text" id="consoleInput" class="console-input" autocomplete="off" spellcheck="false" />
445
+ </div>
446
+ </div>
447
+ </div>
448
+ </div>
449
+
450
+ <script>
451
+ document.addEventListener('DOMContentLoaded', () => {
452
+ // Terminal elements
453
+ const terminalOutput = document.getElementById('output');
454
+ const consoleInputContainer = document.getElementById('consoleInputContainer');
455
+ const consoleInput = document.getElementById('consoleInput');
456
+ const clearTerminalBtn = document.getElementById('clearTerminal');
457
+ const stopCodeBtn = document.getElementById('stopCode');
458
+
459
+ // Editor elements
460
+ const codeArea = document.querySelector('#codeArea code');
461
+ const lineNumbers = document.getElementById('lineNumbers');
462
+ const runButton = document.getElementById('runCode');
463
+ const clearButton = document.getElementById('clearCode');
464
+
465
+ // State variables
466
+ let executionContext = null;
467
+ let isExecuting = false;
468
+ let executionStartTime = null;
469
+
470
+ // Initialize terminal
471
+ initializeTerminal();
472
+
473
+ // Initialize editor
474
+ updateLineNumbers();
475
+
476
+ // Function to update line numbers
477
+ function updateLineNumbers() {
478
+ const lines = codeArea.textContent.split('\n').length;
479
+ lineNumbers.innerHTML = Array.from({length: lines}, (_, i) => i + 1).join('<br>');
480
+ }
481
+
482
+ // Initialize terminal with welcome message
483
+ function initializeTerminal() {
484
+ clearTerminalOutput();
485
+ appendToTerminal("欢迎使用Python交互式终端", "term-system");
486
+ appendToTerminal("使用'运行'按钮执行您的代码", "term-system");
487
+ appendToTerminal("", "term-system"); // 空行
488
+ appendToTerminal(">>> 准备执行代码...", "term-system");
489
+ }
490
+
491
+ // Append text to terminal
492
+ function appendToTerminal(text, type = null) {
493
+ const lines = text.split('\n');
494
+ let html = '';
495
+
496
+ for (const line of lines) {
497
+ if (type) {
498
+ html += `<span class="${type}">${escapeHtml(line)}</span>\n`;
499
+ } else {
500
+ html += escapeHtml(line) + '\n';
501
+ }
502
+ }
503
+
504
+ terminalOutput.innerHTML += html;
505
+ terminalOutput.scrollTop = terminalOutput.scrollHeight;
506
+ }
507
+
508
+ // Clear terminal output
509
+ function clearTerminalOutput() {
510
+ terminalOutput.innerHTML = '';
511
+ }
512
+
513
+ // Escape HTML to prevent XSS
514
+ function escapeHtml(text) {
515
+ return text
516
+ .replace(/&/g, "&amp;")
517
+ .replace(/</g, "&lt;")
518
+ .replace(/>/g, "&gt;")
519
+ .replace(/"/g, "&quot;")
520
+ .replace(/'/g, "&#039;");
521
+ }
522
+
523
+ // Run code
524
+ runButton.addEventListener('click', async () => {
525
+ if (isExecuting) return; // Prevent multiple executions
526
+
527
+ const code = codeArea.textContent;
528
+
529
+ // Clear terminal and show execution start
530
+ clearTerminalOutput();
531
+ appendToTerminal("开始执行Python代码...", "term-system");
532
+
533
+ // Update UI state
534
+ isExecuting = true;
535
+ executionStartTime = performance.now();
536
+ runButton.style.display = 'none';
537
+ stopCodeBtn.style.display = 'flex';
538
+
539
+ try {
540
+ const response = await fetch('https://samlax12-agent.hf.space/api/code/execute', {
541
+ method: 'POST',
542
+ headers: { 'Content-Type': 'application/json' },
543
+ body: JSON.stringify({ code })
544
+ });
545
+
546
+ const data = await response.json();
547
+
548
+ if (data.success) {
549
+ // Show output (if any)
550
+ if (data.output && data.output.trim()) {
551
+ appendToTerminal(data.output, "term-output");
552
+ }
553
+
554
+ if (data.needsInput) {
555
+ // Code is waiting for input
556
+ executionContext = data.context_id;
557
+ consoleInputContainer.style.display = 'flex';
558
+ consoleInput.focus();
559
+ } else {
560
+ // Code has completed, no input needed
561
+ appendToTerminal("程序执行完成", "term-system");
562
+ finishExecution();
563
+ }
564
+ } else {
565
+ // Handle error
566
+ appendToTerminal(`错误: ${data.error}`, "term-error");
567
+ if (data.traceback) {
568
+ appendToTerminal(data.traceback, "term-error");
569
+ }
570
+ appendToTerminal("执行失败", "term-system");
571
+ finishExecution();
572
+ }
573
+ } catch (error) {
574
+ appendToTerminal(`系统错误: ${error.message}`, "term-error");
575
+ appendToTerminal("执行失败", "term-system");
576
+ finishExecution();
577
+ }
578
+ });
579
+
580
+ // Submit console input
581
+ consoleInput.addEventListener('keydown', async (e) => {
582
+ if (e.key === 'Enter' && executionContext) {
583
+ const input = consoleInput.value;
584
+ consoleInput.value = '';
585
+
586
+ // Add input to terminal
587
+ appendToTerminal(`>> ${input}`, "term-input");
588
+
589
+ try {
590
+ // Send input to backend
591
+ const response = await fetch('https://samlax12-agent.hf.space/api/code/input', {
592
+ method: 'POST',
593
+ headers: { 'Content-Type': 'application/json' },
594
+ body: JSON.stringify({
595
+ input: input,
596
+ context_id: executionContext
597
+ })
598
+ });
599
+
600
+ const data = await response.json();
601
+
602
+ if (data.success) {
603
+ // Show new output
604
+ if (data.output && data.output.trim()) {
605
+ appendToTerminal(data.output, "term-output");
606
+ }
607
+
608
+ if (data.needsInput) {
609
+ // Still waiting for more input
610
+ consoleInput.focus();
611
+ } else {
612
+ // Execution completed
613
+ appendToTerminal(">>> 程序执行完成", "term-system");
614
+ finishExecution();
615
+ }
616
+ } else {
617
+ // Handle error
618
+ appendToTerminal(`错误: ${data.error}`, "term-error");
619
+ if (data.traceback) {
620
+ appendToTerminal(data.traceback, "term-error");
621
+ }
622
+ finishExecution();
623
+ }
624
+ } catch (error) {
625
+ appendToTerminal(`系统错误: ${error.message}`, "term-error");
626
+ finishExecution();
627
+ }
628
+ }
629
+ });
630
+
631
+ // Stop code execution
632
+ stopCodeBtn.addEventListener('click', async () => {
633
+ if (!executionContext) return;
634
+
635
+ try {
636
+ // Send stop request to server
637
+ const response = await fetch('https://samlax12-agent.hf.space/api/code/stop', {
638
+ method: 'POST',
639
+ headers: { 'Content-Type': 'application/json' },
640
+ body: JSON.stringify({ context_id: executionContext })
641
+ });
642
+
643
+ // Clean up UI regardless of response
644
+ appendToTerminal("用户终止了执行", "term-warning");
645
+ finishExecution();
646
+ } catch (error) {
647
+ console.error('停止执行时出错:', error);
648
+ finishExecution();
649
+ }
650
+ });
651
+
652
+ // Clear code
653
+ clearButton.addEventListener('click', () => {
654
+ codeArea.textContent = '# 您的代码将在这里显示';
655
+ updateLineNumbers();
656
+ clearTerminalOutput();
657
+ initializeTerminal();
658
+ consoleInputContainer.style.display = 'none';
659
+ executionContext = null;
660
+ isExecuting = false;
661
+ runButton.style.display = 'flex';
662
+ stopCodeBtn.style.display = 'none';
663
+ });
664
+
665
+ // Clear terminal
666
+ clearTerminalBtn.addEventListener('click', () => {
667
+ if (!isExecuting) {
668
+ clearTerminalOutput();
669
+ initializeTerminal();
670
+ } else {
671
+ // If execution is in progress, just add a separator
672
+ appendToTerminal("\n--- 已清除终端 ---\n", "term-system");
673
+ }
674
+ });
675
+
676
+ // Update line numbers on code changes
677
+ codeArea.addEventListener('input', updateLineNumbers);
678
+
679
+ // Handle tab key
680
+ codeArea.addEventListener('keydown', (e) => {
681
+ if (e.key === 'Tab') {
682
+ e.preventDefault();
683
+ document.execCommand('insertText', false, ' ');
684
+ }
685
+ });
686
+
687
+ // Function to clean up after execution completes
688
+ function finishExecution() {
689
+ consoleInputContainer.style.display = 'none';
690
+ executionContext = null;
691
+ isExecuting = false;
692
+ runButton.style.display = 'flex';
693
+ stopCodeBtn.style.display = 'none';
694
+ }
695
+
696
+ // Check if code was provided via URL parameters
697
+ const urlParams = new URLSearchParams(window.location.search);
698
+ const initialCode = urlParams.get('code');
699
+ if (initialCode) {
700
+ try {
701
+ codeArea.textContent = decodeURIComponent(initialCode);
702
+ updateLineNumbers();
703
+ } catch (e) {
704
+ console.error('Failed to decode initial code:', e);
705
+ }
706
+ }
707
+
708
+ // Check for messages from parent frame
709
+ window.addEventListener('message', (event) => {
710
+ if (event.data && event.data.type === 'setCode') {
711
+ codeArea.textContent = event.data.code;
712
+ updateLineNumbers();
713
+ }
714
+ });
715
+ });
716
+ </script>
717
+ </body>
718
  </html>