Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -93,7 +93,6 @@ class PTYTerminal:
|
|
| 93 |
# Child process — become the shell
|
| 94 |
os.close(pid)
|
| 95 |
os.setsid()
|
| 96 |
-
# Set slave as controlling terminal
|
| 97 |
slave_fd = fd
|
| 98 |
fcntl.ioctl(slave_fd, termios.TIOCSCTTY, 0)
|
| 99 |
os.dup2(slave_fd, 0)
|
|
@@ -102,21 +101,18 @@ class PTYTerminal:
|
|
| 102 |
if slave_fd > 2:
|
| 103 |
os.close(slave_fd)
|
| 104 |
env = os.environ.copy()
|
| 105 |
-
env["TERM"] = "dumb"
|
| 106 |
-
env["PS1"] = "$ "
|
| 107 |
env["DEBIAN_FRONTEND"] = "noninteractive"
|
| 108 |
os.execvpe("/bin/bash", ["/bin/bash", "--norc", "--noprofile", "-i"], env)
|
| 109 |
else:
|
| 110 |
# Parent process
|
| 111 |
os.close(fd)
|
| 112 |
self.master_fd = pid
|
| 113 |
-
# Set non-blocking reads
|
| 114 |
flags = fcntl.fcntl(self.master_fd, fcntl.F_GETFL)
|
| 115 |
fcntl.fcntl(self.master_fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
|
| 116 |
-
# Set terminal size (cols x rows)
|
| 117 |
winsize = struct.pack("HHHH", 40, 120, 0, 0)
|
| 118 |
fcntl.ioctl(self.master_fd, termios.TIOCSWINSZ, winsize)
|
| 119 |
-
# Drain initial bash output
|
| 120 |
time.sleep(0.3)
|
| 121 |
self._read_output()
|
| 122 |
except Exception as e:
|
|
@@ -139,7 +135,7 @@ class PTYTerminal:
|
|
| 139 |
chunk = os.read(self.master_fd, 4096)
|
| 140 |
if chunk:
|
| 141 |
output.append(chunk.decode("utf-8", errors="replace"))
|
| 142 |
-
deadline = time.time() + 0.15
|
| 143 |
else:
|
| 144 |
break
|
| 145 |
else:
|
|
@@ -152,7 +148,6 @@ class PTYTerminal:
|
|
| 152 |
def _clean_output(self, text: str) -> str:
|
| 153 |
"""Strip ANSI escape codes and control characters."""
|
| 154 |
cleaned = self.STRIP_ANSI.sub("", text)
|
| 155 |
-
# Remove null bytes and other control chars (keep newlines/tabs)
|
| 156 |
cleaned = "".join(ch for ch in cleaned if ch == "\n" or ch == "\t" or (ord(ch) >= 32))
|
| 157 |
return cleaned
|
| 158 |
|
|
@@ -185,53 +180,46 @@ class PTYTerminal:
|
|
| 185 |
|
| 186 |
self._append_line(f"$ {cmd}")
|
| 187 |
|
| 188 |
-
# Write the command to bash
|
| 189 |
try:
|
| 190 |
os.write(self.master_fd, (cmd + "\n").encode())
|
| 191 |
except OSError as e:
|
| 192 |
self._append_line(f"[Write Error] {e}")
|
| 193 |
return self.get_log()
|
| 194 |
|
| 195 |
-
# Determine how long to wait based on command type
|
| 196 |
parts = cmd.split()
|
| 197 |
base = parts[0].lower() if parts else ""
|
| 198 |
long_commands = ["pip", "pip3", "npm", "npx", "apt-get", "apt", "git", "wget",
|
| 199 |
"curl", "make", "cmake", "cargo", "yarn", "conda"]
|
| 200 |
|
| 201 |
if base in long_commands:
|
| 202 |
-
wait_time = 180
|
| 203 |
elif base in ("python", "python3", "node"):
|
| 204 |
wait_time = 60
|
| 205 |
else:
|
| 206 |
wait_time = 15
|
| 207 |
|
| 208 |
-
# Read output with smart timeout — stop early when command finishes
|
| 209 |
all_output = []
|
| 210 |
start = time.time()
|
| 211 |
idle_count = 0
|
| 212 |
-
|
| 213 |
while time.time() - start < wait_time:
|
| 214 |
chunk = self._read_output(0.3)
|
| 215 |
if chunk:
|
| 216 |
idle_count = 0
|
| 217 |
all_output.append(chunk)
|
| 218 |
-
# Check if we see our prompt back (command finished)
|
| 219 |
combined = "".join(all_output)
|
| 220 |
if combined.rstrip().endswith("$"):
|
| 221 |
break
|
| 222 |
else:
|
| 223 |
idle_count += 1
|
| 224 |
-
# For short commands, bail after a brief idle
|
| 225 |
if base not in long_commands and idle_count >= 3:
|
| 226 |
break
|
| 227 |
-
# For long commands, bail after longer idle
|
| 228 |
if base in long_commands and idle_count >= 10:
|
| 229 |
break
|
| 230 |
|
| 231 |
raw = "".join(all_output)
|
| 232 |
cleaned = self._clean_output(raw)
|
| 233 |
|
| 234 |
-
# Remove the echoed command from output (bash echoes what you type)
|
| 235 |
lines = cleaned.split("\n")
|
| 236 |
filtered = []
|
| 237 |
skip_echo = True
|
|
@@ -240,7 +228,6 @@ class PTYTerminal:
|
|
| 240 |
if skip_echo and stripped == cmd.strip():
|
| 241 |
skip_echo = False
|
| 242 |
continue
|
| 243 |
-
# Skip bare prompt lines
|
| 244 |
if stripped == "$" or stripped == "$ ":
|
| 245 |
continue
|
| 246 |
filtered.append(line)
|
|
@@ -252,11 +239,10 @@ class PTYTerminal:
|
|
| 252 |
return self.get_log()
|
| 253 |
|
| 254 |
def run_editor_code(self, code: str) -> str:
|
| 255 |
-
"""Execute code from the editor by writing
|
| 256 |
with self.lock:
|
| 257 |
self._append_line("$ python [editor]")
|
| 258 |
-
|
| 259 |
-
# Write code to a temp file and run it through the PTY
|
| 260 |
tmp_path = "/tmp/_axon_editor_run.py"
|
| 261 |
try:
|
| 262 |
with open(tmp_path, "w") as f:
|
|
@@ -266,20 +252,17 @@ class PTYTerminal:
|
|
| 266 |
self._append_line(f"[Error] Could not write temp file: {e}")
|
| 267 |
return self.get_log()
|
| 268 |
|
| 269 |
-
# Run through the shell (but bypass the echo/prompt logic — use subprocess for cleaner output)
|
| 270 |
try:
|
| 271 |
result = subprocess.run(
|
| 272 |
[sys.executable, tmp_path],
|
| 273 |
capture_output=True, text=True, timeout=30
|
| 274 |
)
|
| 275 |
-
|
| 276 |
-
|
| 277 |
self._append_line(result.stdout.rstrip())
|
| 278 |
-
|
| 279 |
-
with self.lock:
|
| 280 |
self._append_line(result.stderr.rstrip())
|
| 281 |
-
|
| 282 |
-
with self.lock:
|
| 283 |
self._append_line("(No output)")
|
| 284 |
except subprocess.TimeoutExpired:
|
| 285 |
with self.lock:
|
|
@@ -430,7 +413,6 @@ with gr.Blocks(title="Axon Pro - Python IDE") as demo:
|
|
| 430 |
with gr.Tabs() as main_tabs:
|
| 431 |
with gr.Tab("Editor", id="editor-tab"):
|
| 432 |
with gr.Row(equal_height=True):
|
| 433 |
-
# Sidebar
|
| 434 |
with gr.Column(scale=1, min_width=200):
|
| 435 |
gr.Markdown("### 📁 Explorer")
|
| 436 |
file_list = gr.Dropdown(
|
|
@@ -447,7 +429,6 @@ with gr.Blocks(title="Axon Pro - Python IDE") as demo:
|
|
| 447 |
placeholder="script.py", label="New File", container=False
|
| 448 |
)
|
| 449 |
|
| 450 |
-
# Editor
|
| 451 |
with gr.Column(scale=4):
|
| 452 |
editor = gr.Code(
|
| 453 |
value=fs.get_current_file_content(),
|
|
@@ -491,7 +472,7 @@ with gr.Blocks(title="Axon Pro - Python IDE") as demo:
|
|
| 491 |
|
| 492 |
with gr.Tab("AI Chat", id="chat-tab"):
|
| 493 |
chat_history = gr.Chatbot(
|
| 494 |
-
label="Axon AI", height=300
|
| 495 |
)
|
| 496 |
with gr.Row():
|
| 497 |
chat_input = gr.Textbox(
|
|
@@ -525,7 +506,7 @@ with gr.Blocks(title="Axon Pro - Python IDE") as demo:
|
|
| 525 |
diff_modified_state = gr.State("")
|
| 526 |
diff_mode_state = gr.State(False)
|
| 527 |
|
| 528 |
-
# ---
|
| 529 |
def update_file(content):
|
| 530 |
fs.save_file(content)
|
| 531 |
return fs.get_current_file_content()
|
|
@@ -586,7 +567,6 @@ with gr.Blocks(title="Axon Pro - Python IDE") as demo:
|
|
| 586 |
file_list.change(load_file, file_list, [editor, current_file_state])
|
| 587 |
new_file_btn.click(create_file, new_file_name, [file_list, editor])
|
| 588 |
|
| 589 |
-
# Terminal
|
| 590 |
terminal_input.submit(
|
| 591 |
terminal_command, terminal_input, [terminal_output, terminal_input]
|
| 592 |
)
|
|
@@ -595,11 +575,9 @@ with gr.Blocks(title="Axon Pro - Python IDE") as demo:
|
|
| 595 |
)
|
| 596 |
clear_btn.click(clear_terminal_fn, None, terminal_output)
|
| 597 |
|
| 598 |
-
# Run button
|
| 599 |
run_btn.click(run_editor_wrapper, editor, terminal_output)
|
| 600 |
complete_btn.click(complete_wrapper, editor, editor)
|
| 601 |
|
| 602 |
-
# AI features → Diff tab
|
| 603 |
explain_btn.click(
|
| 604 |
explain_wrapper,
|
| 605 |
editor,
|
|
@@ -615,30 +593,15 @@ with gr.Blocks(title="Axon Pro - Python IDE") as demo:
|
|
| 615 |
chat_input.submit(
|
| 616 |
generate_wrapper,
|
| 617 |
[chat_input, chat_history],
|
| 618 |
-
[
|
| 619 |
-
diff_view,
|
| 620 |
-
diff_original_state,
|
| 621 |
-
diff_modified_state,
|
| 622 |
-
diff_mode_state,
|
| 623 |
-
chat_history,
|
| 624 |
-
chat_input,
|
| 625 |
-
],
|
| 626 |
).then(lambda: gr.Tabs(selected="diff-tab"), None, main_tabs)
|
| 627 |
|
| 628 |
send_btn.click(
|
| 629 |
generate_wrapper,
|
| 630 |
[chat_input, chat_history],
|
| 631 |
-
[
|
| 632 |
-
diff_view,
|
| 633 |
-
diff_original_state,
|
| 634 |
-
diff_modified_state,
|
| 635 |
-
diff_mode_state,
|
| 636 |
-
chat_history,
|
| 637 |
-
chat_input,
|
| 638 |
-
],
|
| 639 |
).then(lambda: gr.Tabs(selected="diff-tab"), None, main_tabs)
|
| 640 |
|
| 641 |
-
# Diff actions
|
| 642 |
apply_btn.click(
|
| 643 |
lambda mod: (mod, False), diff_modified_state, [editor, diff_mode_state]
|
| 644 |
).then(lambda: gr.Tabs(selected="editor-tab"), None, main_tabs)
|
|
|
|
| 93 |
# Child process — become the shell
|
| 94 |
os.close(pid)
|
| 95 |
os.setsid()
|
|
|
|
| 96 |
slave_fd = fd
|
| 97 |
fcntl.ioctl(slave_fd, termios.TIOCSCTTY, 0)
|
| 98 |
os.dup2(slave_fd, 0)
|
|
|
|
| 101 |
if slave_fd > 2:
|
| 102 |
os.close(slave_fd)
|
| 103 |
env = os.environ.copy()
|
| 104 |
+
env["TERM"] = "dumb"
|
| 105 |
+
env["PS1"] = "$ "
|
| 106 |
env["DEBIAN_FRONTEND"] = "noninteractive"
|
| 107 |
os.execvpe("/bin/bash", ["/bin/bash", "--norc", "--noprofile", "-i"], env)
|
| 108 |
else:
|
| 109 |
# Parent process
|
| 110 |
os.close(fd)
|
| 111 |
self.master_fd = pid
|
|
|
|
| 112 |
flags = fcntl.fcntl(self.master_fd, fcntl.F_GETFL)
|
| 113 |
fcntl.fcntl(self.master_fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
|
|
|
|
| 114 |
winsize = struct.pack("HHHH", 40, 120, 0, 0)
|
| 115 |
fcntl.ioctl(self.master_fd, termios.TIOCSWINSZ, winsize)
|
|
|
|
| 116 |
time.sleep(0.3)
|
| 117 |
self._read_output()
|
| 118 |
except Exception as e:
|
|
|
|
| 135 |
chunk = os.read(self.master_fd, 4096)
|
| 136 |
if chunk:
|
| 137 |
output.append(chunk.decode("utf-8", errors="replace"))
|
| 138 |
+
deadline = time.time() + 0.15
|
| 139 |
else:
|
| 140 |
break
|
| 141 |
else:
|
|
|
|
| 148 |
def _clean_output(self, text: str) -> str:
|
| 149 |
"""Strip ANSI escape codes and control characters."""
|
| 150 |
cleaned = self.STRIP_ANSI.sub("", text)
|
|
|
|
| 151 |
cleaned = "".join(ch for ch in cleaned if ch == "\n" or ch == "\t" or (ord(ch) >= 32))
|
| 152 |
return cleaned
|
| 153 |
|
|
|
|
| 180 |
|
| 181 |
self._append_line(f"$ {cmd}")
|
| 182 |
|
|
|
|
| 183 |
try:
|
| 184 |
os.write(self.master_fd, (cmd + "\n").encode())
|
| 185 |
except OSError as e:
|
| 186 |
self._append_line(f"[Write Error] {e}")
|
| 187 |
return self.get_log()
|
| 188 |
|
|
|
|
| 189 |
parts = cmd.split()
|
| 190 |
base = parts[0].lower() if parts else ""
|
| 191 |
long_commands = ["pip", "pip3", "npm", "npx", "apt-get", "apt", "git", "wget",
|
| 192 |
"curl", "make", "cmake", "cargo", "yarn", "conda"]
|
| 193 |
|
| 194 |
if base in long_commands:
|
| 195 |
+
wait_time = 180
|
| 196 |
elif base in ("python", "python3", "node"):
|
| 197 |
wait_time = 60
|
| 198 |
else:
|
| 199 |
wait_time = 15
|
| 200 |
|
|
|
|
| 201 |
all_output = []
|
| 202 |
start = time.time()
|
| 203 |
idle_count = 0
|
| 204 |
+
|
| 205 |
while time.time() - start < wait_time:
|
| 206 |
chunk = self._read_output(0.3)
|
| 207 |
if chunk:
|
| 208 |
idle_count = 0
|
| 209 |
all_output.append(chunk)
|
|
|
|
| 210 |
combined = "".join(all_output)
|
| 211 |
if combined.rstrip().endswith("$"):
|
| 212 |
break
|
| 213 |
else:
|
| 214 |
idle_count += 1
|
|
|
|
| 215 |
if base not in long_commands and idle_count >= 3:
|
| 216 |
break
|
|
|
|
| 217 |
if base in long_commands and idle_count >= 10:
|
| 218 |
break
|
| 219 |
|
| 220 |
raw = "".join(all_output)
|
| 221 |
cleaned = self._clean_output(raw)
|
| 222 |
|
|
|
|
| 223 |
lines = cleaned.split("\n")
|
| 224 |
filtered = []
|
| 225 |
skip_echo = True
|
|
|
|
| 228 |
if skip_echo and stripped == cmd.strip():
|
| 229 |
skip_echo = False
|
| 230 |
continue
|
|
|
|
| 231 |
if stripped == "$" or stripped == "$ ":
|
| 232 |
continue
|
| 233 |
filtered.append(line)
|
|
|
|
| 239 |
return self.get_log()
|
| 240 |
|
| 241 |
def run_editor_code(self, code: str) -> str:
|
| 242 |
+
"""Execute code from the editor by writing to a temp file."""
|
| 243 |
with self.lock:
|
| 244 |
self._append_line("$ python [editor]")
|
| 245 |
+
|
|
|
|
| 246 |
tmp_path = "/tmp/_axon_editor_run.py"
|
| 247 |
try:
|
| 248 |
with open(tmp_path, "w") as f:
|
|
|
|
| 252 |
self._append_line(f"[Error] Could not write temp file: {e}")
|
| 253 |
return self.get_log()
|
| 254 |
|
|
|
|
| 255 |
try:
|
| 256 |
result = subprocess.run(
|
| 257 |
[sys.executable, tmp_path],
|
| 258 |
capture_output=True, text=True, timeout=30
|
| 259 |
)
|
| 260 |
+
with self.lock:
|
| 261 |
+
if result.stdout.strip():
|
| 262 |
self._append_line(result.stdout.rstrip())
|
| 263 |
+
if result.stderr.strip():
|
|
|
|
| 264 |
self._append_line(result.stderr.rstrip())
|
| 265 |
+
if not result.stdout.strip() and not result.stderr.strip():
|
|
|
|
| 266 |
self._append_line("(No output)")
|
| 267 |
except subprocess.TimeoutExpired:
|
| 268 |
with self.lock:
|
|
|
|
| 413 |
with gr.Tabs() as main_tabs:
|
| 414 |
with gr.Tab("Editor", id="editor-tab"):
|
| 415 |
with gr.Row(equal_height=True):
|
|
|
|
| 416 |
with gr.Column(scale=1, min_width=200):
|
| 417 |
gr.Markdown("### 📁 Explorer")
|
| 418 |
file_list = gr.Dropdown(
|
|
|
|
| 429 |
placeholder="script.py", label="New File", container=False
|
| 430 |
)
|
| 431 |
|
|
|
|
| 432 |
with gr.Column(scale=4):
|
| 433 |
editor = gr.Code(
|
| 434 |
value=fs.get_current_file_content(),
|
|
|
|
| 472 |
|
| 473 |
with gr.Tab("AI Chat", id="chat-tab"):
|
| 474 |
chat_history = gr.Chatbot(
|
| 475 |
+
label="Axon AI", height=300
|
| 476 |
)
|
| 477 |
with gr.Row():
|
| 478 |
chat_input = gr.Textbox(
|
|
|
|
| 506 |
diff_modified_state = gr.State("")
|
| 507 |
diff_mode_state = gr.State(False)
|
| 508 |
|
| 509 |
+
# --- Handlers ---
|
| 510 |
def update_file(content):
|
| 511 |
fs.save_file(content)
|
| 512 |
return fs.get_current_file_content()
|
|
|
|
| 567 |
file_list.change(load_file, file_list, [editor, current_file_state])
|
| 568 |
new_file_btn.click(create_file, new_file_name, [file_list, editor])
|
| 569 |
|
|
|
|
| 570 |
terminal_input.submit(
|
| 571 |
terminal_command, terminal_input, [terminal_output, terminal_input]
|
| 572 |
)
|
|
|
|
| 575 |
)
|
| 576 |
clear_btn.click(clear_terminal_fn, None, terminal_output)
|
| 577 |
|
|
|
|
| 578 |
run_btn.click(run_editor_wrapper, editor, terminal_output)
|
| 579 |
complete_btn.click(complete_wrapper, editor, editor)
|
| 580 |
|
|
|
|
| 581 |
explain_btn.click(
|
| 582 |
explain_wrapper,
|
| 583 |
editor,
|
|
|
|
| 593 |
chat_input.submit(
|
| 594 |
generate_wrapper,
|
| 595 |
[chat_input, chat_history],
|
| 596 |
+
[diff_view, diff_original_state, diff_modified_state, diff_mode_state, chat_history, chat_input],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 597 |
).then(lambda: gr.Tabs(selected="diff-tab"), None, main_tabs)
|
| 598 |
|
| 599 |
send_btn.click(
|
| 600 |
generate_wrapper,
|
| 601 |
[chat_input, chat_history],
|
| 602 |
+
[diff_view, diff_original_state, diff_modified_state, diff_mode_state, chat_history, chat_input],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 603 |
).then(lambda: gr.Tabs(selected="diff-tab"), None, main_tabs)
|
| 604 |
|
|
|
|
| 605 |
apply_btn.click(
|
| 606 |
lambda mod: (mod, False), diff_modified_state, [editor, diff_mode_state]
|
| 607 |
).then(lambda: gr.Tabs(selected="editor-tab"), None, main_tabs)
|