Spaces:
Running
Running
Commit ·
47e3df3
1
Parent(s): 996fe90
commit initial 09-12-2025 31
Browse files- src/App.js +124 -67
src/App.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
import { useState, useEffect, useRef } from "react";
|
| 2 |
import Editor from "@monaco-editor/react";
|
| 3 |
import { askAgent } from "./agent/assistant";
|
|
@@ -17,7 +18,7 @@ import { downloadProjectZip } from "./zipExport";
|
|
| 17 |
import { parseProblems } from "./problemParser";
|
| 18 |
import "./App.css";
|
| 19 |
import "xterm/css/xterm.css";
|
| 20 |
-
import XTerm from "./Terminal"; // your
|
| 21 |
|
| 22 |
// =================== SUPPORTED LANGUAGES ===================
|
| 23 |
const LANGUAGE_OPTIONS = [
|
|
@@ -34,7 +35,8 @@ const LANGUAGE_OPTIONS = [
|
|
| 34 |
|
| 35 |
const RUNNABLE_LANGS = ["python", "javascript", "java"];
|
| 36 |
|
| 37 |
-
//
|
|
|
|
| 38 |
function outputLooksForInput(output) {
|
| 39 |
if (!output) return false;
|
| 40 |
const o = output.toString();
|
|
@@ -47,30 +49,69 @@ function outputLooksForInput(output) {
|
|
| 47 |
/: $/,
|
| 48 |
/:\n$/,
|
| 49 |
/> $/,
|
|
|
|
|
|
|
|
|
|
| 50 |
];
|
| 51 |
return patterns.some((p) => p.test(o));
|
| 52 |
}
|
| 53 |
|
| 54 |
-
//
|
| 55 |
function codeNeedsInput(code, langId) {
|
| 56 |
if (!code) return false;
|
| 57 |
try {
|
| 58 |
const c = code.toString();
|
|
|
|
| 59 |
if (langId === "python") {
|
| 60 |
-
|
|
|
|
|
|
|
|
|
|
| 61 |
}
|
|
|
|
| 62 |
if (langId === "java") {
|
| 63 |
-
|
|
|
|
|
|
|
|
|
|
| 64 |
}
|
|
|
|
| 65 |
if (langId === "javascript") {
|
| 66 |
-
|
| 67 |
}
|
| 68 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
} catch {
|
| 70 |
return false;
|
| 71 |
}
|
| 72 |
}
|
| 73 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
// =================== APP ===================
|
| 75 |
function App() {
|
| 76 |
const [tree, setTree] = useState(loadTree());
|
|
@@ -83,7 +124,7 @@ function App() {
|
|
| 83 |
const [prompt, setPrompt] = useState("");
|
| 84 |
const [explanation, setExplanation] = useState("");
|
| 85 |
|
| 86 |
-
// legacy
|
| 87 |
const [terminalInput, setTerminalInput] = useState("");
|
| 88 |
const [stdin, setStdin] = useState("");
|
| 89 |
const [problems, setProblems] = useState([]);
|
|
@@ -109,7 +150,7 @@ function App() {
|
|
| 109 |
LANGUAGE_OPTIONS.find((l) => currentNode?.name?.endsWith(l.ext)) ||
|
| 110 |
LANGUAGE_OPTIONS[0];
|
| 111 |
|
| 112 |
-
// ----------
|
| 113 |
const collectFolderPaths = (node, acc = []) => {
|
| 114 |
if (!node) return acc;
|
| 115 |
if (node.type === "folder") acc.push(node.path || "");
|
|
@@ -117,12 +158,10 @@ function App() {
|
|
| 117 |
return acc;
|
| 118 |
};
|
| 119 |
|
| 120 |
-
// ---------- File / Folder actions ----------
|
| 121 |
const handleNewFile = () => {
|
| 122 |
const filename = window.prompt("Filename (with extension):", "untitled.js");
|
| 123 |
if (!filename) return;
|
| 124 |
|
| 125 |
-
// Determine default parent
|
| 126 |
const selected = getNodeByPath(tree, activePath);
|
| 127 |
let parentPath = "";
|
| 128 |
if (selected?.type === "folder") parentPath = selected.path;
|
|
@@ -230,8 +269,9 @@ function App() {
|
|
| 230 |
setTerminalInput("");
|
| 231 |
};
|
| 232 |
|
| 233 |
-
//
|
| 234 |
const runCodeWithUpdatedInput = async (inputLine) => {
|
|
|
|
| 235 |
const newAccum = (accumStdin || "") + inputLine + "\n";
|
| 236 |
setAccumStdin(newAccum);
|
| 237 |
|
|
@@ -257,6 +297,8 @@ function App() {
|
|
| 257 |
setProblems(res.error ? parseProblems(res.output) : []);
|
| 258 |
if (outputLooksForInput(out)) {
|
| 259 |
setAwaitingInput(true);
|
|
|
|
|
|
|
| 260 |
} else {
|
| 261 |
setAwaitingInput(false);
|
| 262 |
}
|
|
@@ -268,7 +310,7 @@ function App() {
|
|
| 268 |
}
|
| 269 |
};
|
| 270 |
|
| 271 |
-
// Initial
|
| 272 |
const handleRun = async () => {
|
| 273 |
const node = getNodeByPath(tree, activePath);
|
| 274 |
if (!node || node.type !== "file") {
|
|
@@ -281,22 +323,21 @@ function App() {
|
|
| 281 |
return;
|
| 282 |
}
|
| 283 |
|
| 284 |
-
// If code likely needs input and we have no accumulated stdin, don't run — wait for user input in XTerm.
|
| 285 |
const needs = codeNeedsInput(node.content, selectedLang);
|
| 286 |
const haveAccum = !!(accumStdin && accumStdin.length > 0);
|
| 287 |
const haveLegacyStdin = !!(stdin && stdin.length > 0);
|
| 288 |
|
| 289 |
if (needs && !haveAccum && !haveLegacyStdin) {
|
| 290 |
-
// Show
|
| 291 |
-
setOutput("[Interactive program detected —
|
| 292 |
setAwaitingInput(true);
|
| 293 |
-
|
|
|
|
| 294 |
}
|
| 295 |
|
| 296 |
-
// Otherwise run
|
| 297 |
const stdinToSend = accumStdin || stdin || "";
|
| 298 |
-
|
| 299 |
-
resetTerminal(true); // keep accumStdin
|
| 300 |
setOutput(`[Running with stdin length=${stdinToSend ? stdinToSend.length : 0}]\n`);
|
| 301 |
setIsRunning(true);
|
| 302 |
setProblems([]);
|
|
@@ -307,53 +348,31 @@ function App() {
|
|
| 307 |
setProblems(res.error ? parseProblems(res.output) : []);
|
| 308 |
if (outputLooksForInput(out)) {
|
| 309 |
setAwaitingInput(true);
|
|
|
|
| 310 |
} else {
|
| 311 |
setAwaitingInput(false);
|
| 312 |
}
|
| 313 |
} catch (err) {
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
// Allow user to type into terminal after EOF errors
|
| 317 |
setAwaitingInput(true);
|
|
|
|
| 318 |
} finally {
|
| 319 |
setIsRunning(false);
|
| 320 |
}
|
| 321 |
};
|
| 322 |
|
| 323 |
-
//
|
| 324 |
const sendTerminalInput = async () => {
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
return;
|
| 330 |
-
}
|
| 331 |
-
|
| 332 |
-
const userText = terminalInput;
|
| 333 |
-
setOutput((prev) => prev + `\n> ${userText}`);
|
| 334 |
-
const newAccum = (accumStdin || "") + userText + "\n";
|
| 335 |
-
setAccumStdin(newAccum);
|
| 336 |
setTerminalInput("");
|
| 337 |
-
|
| 338 |
-
try {
|
| 339 |
-
const selectedLang = LANGUAGE_OPTIONS.find((l) => node.name.endsWith(l.ext))?.id;
|
| 340 |
-
const res = await runCode(node.content, selectedLang, newAccum);
|
| 341 |
-
const out = res.output ?? "";
|
| 342 |
-
setOutput(out);
|
| 343 |
-
setProblems(res.error ? parseProblems(res.output) : []);
|
| 344 |
-
if (outputLooksForInput(out)) {
|
| 345 |
-
setAwaitingInput(true);
|
| 346 |
-
} else {
|
| 347 |
-
setAwaitingInput(false);
|
| 348 |
-
}
|
| 349 |
-
} catch (err) {
|
| 350 |
-
setOutput((prev) => prev + "\n" + String(err));
|
| 351 |
-
setAwaitingInput(false);
|
| 352 |
-
} finally {
|
| 353 |
-
setIsRunning(false);
|
| 354 |
-
}
|
| 355 |
};
|
| 356 |
|
|
|
|
| 357 |
const onTerminalKeyDown = (e) => {
|
| 358 |
if (e.key === "Enter") {
|
| 359 |
e.preventDefault();
|
|
@@ -362,7 +381,7 @@ function App() {
|
|
| 362 |
}
|
| 363 |
};
|
| 364 |
|
| 365 |
-
// ---------- Agent functions ----------
|
| 366 |
const handleAskFix = async () => {
|
| 367 |
const node = getNodeByPath(tree, activePath);
|
| 368 |
if (!node || node.type !== "file") {
|
|
@@ -537,10 +556,45 @@ function App() {
|
|
| 537 |
<XTerm
|
| 538 |
output={output}
|
| 539 |
onData={(line) => {
|
|
|
|
| 540 |
runCodeWithUpdatedInput(line);
|
| 541 |
}}
|
| 542 |
/>
|
| 543 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 544 |
{!awaitingInput && (
|
| 545 |
<div style={{ display: "flex", gap: 6, marginTop: 6 }}>
|
| 546 |
<input className="ide-input-box" placeholder="(Optional) Program input for single-run" value={stdin} onChange={(e) => setStdin(e.target.value)} />
|
|
@@ -548,19 +602,22 @@ function App() {
|
|
| 548 |
</div>
|
| 549 |
)}
|
| 550 |
|
| 551 |
-
{
|
| 552 |
-
|
| 553 |
-
|
| 554 |
-
|
| 555 |
-
|
| 556 |
-
|
| 557 |
-
|
| 558 |
-
|
| 559 |
-
|
| 560 |
-
|
| 561 |
-
|
| 562 |
-
|
| 563 |
-
|
|
|
|
|
|
|
|
|
|
| 564 |
</div>
|
| 565 |
|
| 566 |
{problems.length > 0 && (
|
|
|
|
| 1 |
+
// App.js (complete updated file)
|
| 2 |
import { useState, useEffect, useRef } from "react";
|
| 3 |
import Editor from "@monaco-editor/react";
|
| 4 |
import { askAgent } from "./agent/assistant";
|
|
|
|
| 18 |
import { parseProblems } from "./problemParser";
|
| 19 |
import "./App.css";
|
| 20 |
import "xterm/css/xterm.css";
|
| 21 |
+
import XTerm from "./Terminal"; // keep your existing XTerm wrapper
|
| 22 |
|
| 23 |
// =================== SUPPORTED LANGUAGES ===================
|
| 24 |
const LANGUAGE_OPTIONS = [
|
|
|
|
| 35 |
|
| 36 |
const RUNNABLE_LANGS = ["python", "javascript", "java"];
|
| 37 |
|
| 38 |
+
// =================== Heuristics ===================
|
| 39 |
+
// output patterns that indicate the program is waiting for console input
|
| 40 |
function outputLooksForInput(output) {
|
| 41 |
if (!output) return false;
|
| 42 |
const o = output.toString();
|
|
|
|
| 49 |
/: $/,
|
| 50 |
/:\n$/,
|
| 51 |
/> $/,
|
| 52 |
+
/awaiting input/i,
|
| 53 |
+
/provide input/i,
|
| 54 |
+
/stdin/i,
|
| 55 |
];
|
| 56 |
return patterns.some((p) => p.test(o));
|
| 57 |
}
|
| 58 |
|
| 59 |
+
// code-level heuristics to detect input() or equivalent calls
|
| 60 |
function codeNeedsInput(code, langId) {
|
| 61 |
if (!code) return false;
|
| 62 |
try {
|
| 63 |
const c = code.toString();
|
| 64 |
+
// Python
|
| 65 |
if (langId === "python") {
|
| 66 |
+
// input(), sys.stdin.read(), sys.stdin.readline()
|
| 67 |
+
if (/\binput\s*\(/i.test(c)) return true;
|
| 68 |
+
if (/\bsys\.stdin\.(read|readline|readlines)\s*\(/i.test(c)) return true;
|
| 69 |
+
if (/\braw_input\s*\(/i.test(c)) return true; // py2
|
| 70 |
}
|
| 71 |
+
// Java
|
| 72 |
if (langId === "java") {
|
| 73 |
+
if (/\bScanner\s*\(/i.test(c)) return true;
|
| 74 |
+
if (/\bBufferedReader\b.*readLine/i.test(c)) return true;
|
| 75 |
+
if (/\bSystem\.console\(\)/i.test(c)) return true;
|
| 76 |
+
if (/\bnext(Int|Line|Double|)\b/i.test(c)) return true;
|
| 77 |
}
|
| 78 |
+
// JavaScript/Node
|
| 79 |
if (langId === "javascript") {
|
| 80 |
+
if (/process\.stdin|readline|readlineSync|prompt\(|require\(['"]readline['"]\)/i.test(c)) return true;
|
| 81 |
}
|
| 82 |
+
// C/C++/C: scanf / cin
|
| 83 |
+
if (langId === "cpp" || langId === "c") {
|
| 84 |
+
if (/\bscanf\s*\(/i.test(c)) return true;
|
| 85 |
+
if (/\bstd::cin\b|cin\s*>>/i.test(c)) return true;
|
| 86 |
+
if (/\bgets?\s*\(/i.test(c)) return true;
|
| 87 |
+
}
|
| 88 |
+
// Generic fallback
|
| 89 |
+
if (/\binput\b|\bscanf\b|\bscanf_s\b|\bcin\b|\bScanner\b|readLine|readline/i.test(c)) return true;
|
| 90 |
+
return false;
|
| 91 |
} catch {
|
| 92 |
return false;
|
| 93 |
}
|
| 94 |
}
|
| 95 |
|
| 96 |
+
// Helper: focus xterm's hidden textarea (works with xterm.js default markup)
|
| 97 |
+
function focusXtermHelper() {
|
| 98 |
+
setTimeout(() => {
|
| 99 |
+
const ta = document.querySelector("#terminal-container .xterm-helper-textarea");
|
| 100 |
+
if (ta) {
|
| 101 |
+
try {
|
| 102 |
+
ta.focus();
|
| 103 |
+
// move caret to end
|
| 104 |
+
const len = ta.value?.length ?? 0;
|
| 105 |
+
ta.setSelectionRange(len, len);
|
| 106 |
+
} catch {}
|
| 107 |
+
} else {
|
| 108 |
+
// Fallback: focus container to encourage user to click
|
| 109 |
+
const cont = document.getElementById("terminal-container");
|
| 110 |
+
if (cont) cont.focus();
|
| 111 |
+
}
|
| 112 |
+
}, 120); // small delay to ensure xterm is ready
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
// =================== APP ===================
|
| 116 |
function App() {
|
| 117 |
const [tree, setTree] = useState(loadTree());
|
|
|
|
| 124 |
const [prompt, setPrompt] = useState("");
|
| 125 |
const [explanation, setExplanation] = useState("");
|
| 126 |
|
| 127 |
+
// legacy small-input & helpers (kept for convenience)
|
| 128 |
const [terminalInput, setTerminalInput] = useState("");
|
| 129 |
const [stdin, setStdin] = useState("");
|
| 130 |
const [problems, setProblems] = useState([]);
|
|
|
|
| 150 |
LANGUAGE_OPTIONS.find((l) => currentNode?.name?.endsWith(l.ext)) ||
|
| 151 |
LANGUAGE_OPTIONS[0];
|
| 152 |
|
| 153 |
+
// ---------- File/Folder helpers (unchanged) ----------
|
| 154 |
const collectFolderPaths = (node, acc = []) => {
|
| 155 |
if (!node) return acc;
|
| 156 |
if (node.type === "folder") acc.push(node.path || "");
|
|
|
|
| 158 |
return acc;
|
| 159 |
};
|
| 160 |
|
|
|
|
| 161 |
const handleNewFile = () => {
|
| 162 |
const filename = window.prompt("Filename (with extension):", "untitled.js");
|
| 163 |
if (!filename) return;
|
| 164 |
|
|
|
|
| 165 |
const selected = getNodeByPath(tree, activePath);
|
| 166 |
let parentPath = "";
|
| 167 |
if (selected?.type === "folder") parentPath = selected.path;
|
|
|
|
| 269 |
setTerminalInput("");
|
| 270 |
};
|
| 271 |
|
| 272 |
+
// Core run with updated input (used by XTerm onData and Send button)
|
| 273 |
const runCodeWithUpdatedInput = async (inputLine) => {
|
| 274 |
+
// append newline like a real console
|
| 275 |
const newAccum = (accumStdin || "") + inputLine + "\n";
|
| 276 |
setAccumStdin(newAccum);
|
| 277 |
|
|
|
|
| 297 |
setProblems(res.error ? parseProblems(res.output) : []);
|
| 298 |
if (outputLooksForInput(out)) {
|
| 299 |
setAwaitingInput(true);
|
| 300 |
+
// keep focus
|
| 301 |
+
focusXtermHelper();
|
| 302 |
} else {
|
| 303 |
setAwaitingInput(false);
|
| 304 |
}
|
|
|
|
| 310 |
}
|
| 311 |
};
|
| 312 |
|
| 313 |
+
// Initial Run: do NOT run if code needs interactive input and there's no accumulated stdin.
|
| 314 |
const handleRun = async () => {
|
| 315 |
const node = getNodeByPath(tree, activePath);
|
| 316 |
if (!node || node.type !== "file") {
|
|
|
|
| 323 |
return;
|
| 324 |
}
|
| 325 |
|
|
|
|
| 326 |
const needs = codeNeedsInput(node.content, selectedLang);
|
| 327 |
const haveAccum = !!(accumStdin && accumStdin.length > 0);
|
| 328 |
const haveLegacyStdin = !!(stdin && stdin.length > 0);
|
| 329 |
|
| 330 |
if (needs && !haveAccum && !haveLegacyStdin) {
|
| 331 |
+
// Show instruction and enable typing in XTerm; do not perform a run with empty stdin (avoids EOFError)
|
| 332 |
+
setOutput("[Interactive program detected — click Start interactive session or type in terminal]");
|
| 333 |
setAwaitingInput(true);
|
| 334 |
+
focusXtermHelper();
|
| 335 |
+
return;
|
| 336 |
}
|
| 337 |
|
| 338 |
+
// Otherwise perform run with existing accumulated or legacy input
|
| 339 |
const stdinToSend = accumStdin || stdin || "";
|
| 340 |
+
resetTerminal(true);
|
|
|
|
| 341 |
setOutput(`[Running with stdin length=${stdinToSend ? stdinToSend.length : 0}]\n`);
|
| 342 |
setIsRunning(true);
|
| 343 |
setProblems([]);
|
|
|
|
| 348 |
setProblems(res.error ? parseProblems(res.output) : []);
|
| 349 |
if (outputLooksForInput(out)) {
|
| 350 |
setAwaitingInput(true);
|
| 351 |
+
focusXtermHelper();
|
| 352 |
} else {
|
| 353 |
setAwaitingInput(false);
|
| 354 |
}
|
| 355 |
} catch (err) {
|
| 356 |
+
setOutput(String(err));
|
| 357 |
+
// allow interactive continuation after EOF
|
|
|
|
| 358 |
setAwaitingInput(true);
|
| 359 |
+
focusXtermHelper();
|
| 360 |
} finally {
|
| 361 |
setIsRunning(false);
|
| 362 |
}
|
| 363 |
};
|
| 364 |
|
| 365 |
+
// Send from the small input box: delegate to runCodeWithUpdatedInput to keep behavior uniform
|
| 366 |
const sendTerminalInput = async () => {
|
| 367 |
+
if (!terminalInput || terminalInput.length === 0) return;
|
| 368 |
+
// echo in output for small box then run
|
| 369 |
+
setOutput((prev) => prev + `\n> ${terminalInput}`);
|
| 370 |
+
const line = terminalInput;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 371 |
setTerminalInput("");
|
| 372 |
+
await runCodeWithUpdatedInput(line);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 373 |
};
|
| 374 |
|
| 375 |
+
// handle press Enter for small input
|
| 376 |
const onTerminalKeyDown = (e) => {
|
| 377 |
if (e.key === "Enter") {
|
| 378 |
e.preventDefault();
|
|
|
|
| 381 |
}
|
| 382 |
};
|
| 383 |
|
| 384 |
+
// ---------- Agent functions (unchanged) ----------
|
| 385 |
const handleAskFix = async () => {
|
| 386 |
const node = getNodeByPath(tree, activePath);
|
| 387 |
if (!node || node.type !== "file") {
|
|
|
|
| 556 |
<XTerm
|
| 557 |
output={output}
|
| 558 |
onData={(line) => {
|
| 559 |
+
// Called when XTerm wrapper detects a full line (ENTER)
|
| 560 |
runCodeWithUpdatedInput(line);
|
| 561 |
}}
|
| 562 |
/>
|
| 563 |
|
| 564 |
+
{/* When interactive program detected and waiting for user input */}
|
| 565 |
+
{awaitingInput && (
|
| 566 |
+
<div style={{ marginTop: 8, padding: 8, background: "#252526", border: "1px solid #333", borderRadius: 6 }}>
|
| 567 |
+
<div style={{ marginBottom: 6, color: "#ddd" }}>
|
| 568 |
+
This program appears to be interactive and requires console input.
|
| 569 |
+
</div>
|
| 570 |
+
<div style={{ display: "flex", gap: 8 }}>
|
| 571 |
+
<button
|
| 572 |
+
onClick={() => {
|
| 573 |
+
focusXtermHelper();
|
| 574 |
+
// ensure terminal is ready to accept typed input
|
| 575 |
+
}}
|
| 576 |
+
className="ide-button"
|
| 577 |
+
>
|
| 578 |
+
▶ Focus Terminal
|
| 579 |
+
</button>
|
| 580 |
+
<button
|
| 581 |
+
onClick={() => {
|
| 582 |
+
// start interactive session: clear output but keep accum
|
| 583 |
+
resetTerminal(true);
|
| 584 |
+
setOutput("[Interactive session started — type into the terminal]");
|
| 585 |
+
setAwaitingInput(true);
|
| 586 |
+
focusXtermHelper();
|
| 587 |
+
}}
|
| 588 |
+
className="ide-button"
|
| 589 |
+
>
|
| 590 |
+
▶ Start interactive session
|
| 591 |
+
</button>
|
| 592 |
+
<div style={{ color: "#999", alignSelf: "center" }}>Type & press Enter in terminal or use Send below.</div>
|
| 593 |
+
</div>
|
| 594 |
+
</div>
|
| 595 |
+
)}
|
| 596 |
+
|
| 597 |
+
{/* If not awaiting input show legacy small input for single-run */}
|
| 598 |
{!awaitingInput && (
|
| 599 |
<div style={{ display: "flex", gap: 6, marginTop: 6 }}>
|
| 600 |
<input className="ide-input-box" placeholder="(Optional) Program input for single-run" value={stdin} onChange={(e) => setStdin(e.target.value)} />
|
|
|
|
| 602 |
</div>
|
| 603 |
)}
|
| 604 |
|
| 605 |
+
{/* Always show small input/send option so users can type without xterm if they prefer */}
|
| 606 |
+
<div style={{ display: "flex", gap: 6, marginTop: 8 }}>
|
| 607 |
+
<input
|
| 608 |
+
className="ide-input-box"
|
| 609 |
+
placeholder={awaitingInput ? "Type input and press Send (or press Enter in terminal)" : "Optional input (press Send to append and run)"}
|
| 610 |
+
value={terminalInput}
|
| 611 |
+
onChange={(e) => setTerminalInput(e.target.value)}
|
| 612 |
+
onKeyDown={(e) => {
|
| 613 |
+
if (e.key === "Enter") {
|
| 614 |
+
e.preventDefault();
|
| 615 |
+
sendTerminalInput();
|
| 616 |
+
}
|
| 617 |
+
}}
|
| 618 |
+
/>
|
| 619 |
+
<button onClick={sendTerminalInput} className="ide-button" disabled={isRunning}>{isRunning ? "⏳" : "Send"}</button>
|
| 620 |
+
</div>
|
| 621 |
</div>
|
| 622 |
|
| 623 |
{problems.length > 0 && (
|