AIencoder commited on
Commit
fa0ec0e
·
verified ·
1 Parent(s): 6c90f41

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +279 -218
app.py CHANGED
@@ -5,6 +5,7 @@ import torch
5
  import time
6
  import sys
7
  import subprocess
 
8
  from io import StringIO
9
  import contextlib
10
  from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
@@ -50,114 +51,237 @@ class FileSystem:
50
 
51
  fs = FileSystem()
52
 
53
- # --- Package Manager ---
54
- class PackageManager:
55
- """Handles pip install/uninstall operations for the HF Space runtime."""
56
-
57
- @staticmethod
58
- def install_package(package_name: str) -> str:
59
- """Install a package using pip at runtime."""
60
- if not package_name or not package_name.strip():
61
- return "Error: No package name provided."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
- package_name = package_name.strip()
64
 
65
- # Basic safety check - block obviously dangerous inputs
66
- dangerous = [";", "&&", "||", "|", ">", "<", "`", "$", "(", ")"]
67
- if any(ch in package_name for ch in dangerous):
68
- return "Error: Invalid characters in package name."
69
 
70
- try:
71
- result = subprocess.run(
72
- [sys.executable, "-m", "pip", "install", package_name],
73
- capture_output=True,
74
- text=True,
75
- timeout=120 # 2 min timeout for large packages
76
- )
77
-
78
- output = ""
79
- if result.stdout:
80
- # Filter to show only the important lines
81
- lines = result.stdout.strip().split("\n")
82
- important = [l for l in lines if any(kw in l.lower() for kw in
83
- ["successfully", "already satisfied", "installing", "requirement", "downloading", "error", "warning"])]
84
- output = "\n".join(important) if important else lines[-1]
85
-
86
- if result.returncode != 0:
87
- error_msg = result.stderr.strip() if result.stderr else "Unknown error"
88
- # Show last few lines of error (most relevant)
89
- error_lines = error_msg.split("\n")[-5:]
90
- return f"pip install {package_name} failed:\n" + "\n".join(error_lines)
91
-
92
- return f"✅ {output}"
93
-
94
- except subprocess.TimeoutExpired:
95
- return f"Error: Installation of '{package_name}' timed out (>120s). Package may be too large."
96
- except Exception as e:
97
- return f"Error: {str(e)}"
98
 
99
- @staticmethod
100
- def uninstall_package(package_name: str) -> str:
101
- """Uninstall a package using pip."""
102
- if not package_name or not package_name.strip():
103
- return "Error: No package name provided."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
 
105
- package_name = package_name.strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
 
107
- # Protect critical packages from uninstall
108
- protected = ["gradio", "torch", "transformers", "pip", "setuptools", "wheel"]
109
- if package_name.lower() in protected:
110
- return f"Error: '{package_name}' is a protected system package and cannot be uninstalled."
 
 
 
 
 
111
 
112
- try:
113
- result = subprocess.run(
114
- [sys.executable, "-m", "pip", "uninstall", "-y", package_name],
115
- capture_output=True,
116
- text=True,
117
- timeout=60
118
- )
119
-
120
- if result.returncode != 0:
121
- return f"Error: {result.stderr.strip()}"
122
-
123
- return f" Uninstalled '{package_name}'"
124
-
125
- except Exception as e:
126
- return f"Error: {str(e)}"
 
 
 
 
 
 
 
 
 
 
127
 
128
- @staticmethod
129
- def list_packages() -> str:
130
- """List installed packages."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  try:
132
- result = subprocess.run(
133
- [sys.executable, "-m", "pip", "list", "--format=columns"],
134
- capture_output=True,
135
- text=True,
136
- timeout=30
137
- )
138
- return result.stdout if result.stdout else "No packages found."
139
  except Exception as e:
140
- return f"Error: {str(e)}"
 
 
 
141
 
142
- @staticmethod
143
- def search_package_info(package_name: str) -> str:
144
- """Show info about an installed package."""
145
- if not package_name or not package_name.strip():
146
- return "Error: No package name provided."
147
  try:
148
  result = subprocess.run(
149
- [sys.executable, "-m", "pip", "show", package_name.strip()],
150
- capture_output=True,
151
- text=True,
152
- timeout=15
153
  )
154
- if result.returncode != 0:
155
- return f"Package '{package_name.strip()}' is not installed."
156
- return result.stdout
 
 
 
 
 
157
  except Exception as e:
158
- return f"Error: {str(e)}"
159
 
160
- pkg_mgr = PackageManager()
161
 
162
  # --- Model Loading ---
163
  @lru_cache(maxsize=1)
@@ -187,62 +311,29 @@ def load_model():
187
  print(f"Error loading model: {e}")
188
  return None
189
 
190
- # --- Logic Functions ---
191
 
192
- def run_code(code: str) -> Tuple[str, str]:
193
- """Executes Python code and captures stdout. Supports !pip commands."""
194
-
195
- # Check if code contains !pip install lines (Jupyter-style)
196
- lines = code.strip().split("\n")
197
- pip_lines = [l for l in lines if l.strip().startswith("!pip ") or l.strip().startswith("!pip3 ")]
198
- code_lines = [l for l in lines if not l.strip().startswith("!pip ") and not l.strip().startswith("!pip3 ")]
199
-
200
- output_parts = []
201
-
202
- # Handle any !pip commands first
203
- for pip_line in pip_lines:
204
- cmd = pip_line.strip().lstrip("!") # Remove the "!" prefix
205
- parts = cmd.split()
206
- if len(parts) >= 3 and parts[1] == "install":
207
- pkg = " ".join(parts[2:]) # Support version specifiers like "pygame==2.5.0"
208
- result = pkg_mgr.install_package(pkg)
209
- output_parts.append(f">>> {pip_line.strip()}\n{result}")
210
- elif len(parts) >= 3 and parts[1] == "uninstall":
211
- pkg = parts[2]
212
- result = pkg_mgr.uninstall_package(pkg)
213
- output_parts.append(f">>> {pip_line.strip()}\n{result}")
214
- elif len(parts) >= 2 and parts[1] == "list":
215
- result = pkg_mgr.list_packages()
216
- output_parts.append(f">>> {pip_line.strip()}\n{result}")
217
- else:
218
- output_parts.append(f">>> {pip_line.strip()}\nUnsupported pip command. Use: !pip install <pkg>, !pip uninstall <pkg>, !pip list")
219
-
220
- # Run remaining Python code
221
- remaining_code = "\n".join(code_lines).strip()
222
- if remaining_code:
223
- start_time = time.time()
224
- output_buffer = StringIO()
225
-
226
- try:
227
- with contextlib.redirect_stdout(output_buffer):
228
- exec(remaining_code, {}, {})
229
-
230
- exec_time = time.time() - start_time
231
- result = output_buffer.getvalue()
232
- if not result:
233
- result = "(No output)"
234
-
235
- output_parts.append(f"{result}\n[Execution time: {exec_time:.4f}s]")
236
- except Exception as e:
237
- output_parts.append(f"{output_buffer.getvalue()}\n=== ERROR ===\nTraceback (most recent call last):\n{str(e)}")
238
-
239
- combined = "\n\n".join(output_parts)
240
- return combined, ""
241
 
242
  def generate_completion(code: str) -> str:
243
  model = load_model()
244
  if not model: return "# Error: Model failed to load."
245
-
246
  prompt = f"<|system|>You are a Python expert. Complete the code. No explanations.</s><|user|>\n{code}\n</s><|assistant|>"
247
  try:
248
  result = model(prompt, num_return_sequences=1, return_full_text=False)
@@ -281,10 +372,8 @@ def generate_code(prompt_text: str) -> str:
281
  return str(e)
282
 
283
  def create_diff_view(original: str, modified: str) -> str:
284
- lines_original = original.splitlines()
285
- lines_modified = modified.splitlines()
286
  import difflib
287
- diff = difflib.unified_diff(lines_original, lines_modified, lineterm="")
288
  return "\n".join(diff)
289
 
290
  # --- Gradio UI ---
@@ -303,10 +392,20 @@ theme = gr.themes.Default(
303
  css = """
304
  * { border-radius: 0 !important; }
305
  .editor-container { height: 50vh; }
306
- .terminal { height: 15vh; background-color: #1e1e1e; color: #d4d4d4; font-family: 'JetBrains Mono', monospace; }
 
 
 
 
 
 
 
 
 
 
 
307
  .diff-view { height: 40vh; }
308
  .code-wrap { white-space: pre-wrap !important; }
309
- .pkg-output { font-family: 'JetBrains Mono', monospace; font-size: 0.85em; }
310
  """
311
 
312
  with gr.Blocks(title="Axon Pro - Python IDE") as demo:
@@ -350,14 +449,23 @@ with gr.Blocks(title="Axon Pro - Python IDE") as demo:
350
 
351
  with gr.Tabs():
352
  with gr.Tab("Terminal", id="terminal-tab"):
353
- terminal = gr.Textbox(
354
- value=">>> Ready to run Python code...\n>>> Tip: Use !pip install <package> in your code to install packages",
355
- lines=6,
356
  interactive=False,
357
- elem_classes="terminal",
358
- label="Output"
 
359
  )
360
- clear_btn = gr.Button("Clear Output", variant="secondary", size="sm")
 
 
 
 
 
 
 
 
361
 
362
  with gr.Tab("AI Chat", id="chat-tab"):
363
  chat_history = gr.Chatbot(label="Axon AI", height=300)
@@ -365,42 +473,13 @@ with gr.Blocks(title="Axon Pro - Python IDE") as demo:
365
  chat_input = gr.Textbox(placeholder="Ask to generate Python code...", scale=7, container=False)
366
  send_btn = gr.Button("Generate", variant="primary", scale=1)
367
 
368
- # --- Package Manager Tab ---
369
- with gr.Tab("📦 Packages", id="pkg-tab"):
370
- gr.Markdown("### Package Manager")
371
- gr.Markdown("Install, uninstall, and manage Python packages at runtime.")
372
-
373
- with gr.Row():
374
- with gr.Column(scale=3):
375
- pkg_name_input = gr.Textbox(
376
- placeholder="e.g. pygame, requests, numpy==1.24.0",
377
- label="Package Name",
378
- container=True
379
- )
380
- with gr.Column(scale=1):
381
- with gr.Row():
382
- install_btn = gr.Button("📥 Install", variant="primary")
383
- uninstall_btn = gr.Button("🗑️ Uninstall", variant="secondary")
384
-
385
- with gr.Row():
386
- list_btn = gr.Button("📋 List Installed", variant="secondary")
387
- info_btn = gr.Button("ℹ️ Package Info", variant="secondary")
388
-
389
- pkg_output = gr.Textbox(
390
- value="Package manager ready. Install any pip package here.\n\nYou can also use !pip install <package> directly in the editor.",
391
- label="Output",
392
- lines=12,
393
- interactive=False,
394
- elem_classes="pkg-output"
395
- )
396
-
397
  with gr.Tab("Diff View", id="diff-tab"):
398
  diff_view = gr.Code(label="AI Changes", language="python", elem_classes="diff-view code-wrap", interactive=False)
399
  with gr.Row():
400
  apply_btn = gr.Button("Apply Changes", variant="primary")
401
  discard_btn = gr.Button("Discard Changes", variant="secondary")
402
 
403
- status_bar = gr.Markdown(f"**AXON PRO v1.1** | Python 3.x | CPU Mode | TinyLlama-1.1B | pip enabled", elem_classes="status-bar")
404
 
405
  # State
406
  current_file_state = gr.State(fs.current_file)
@@ -408,7 +487,7 @@ with gr.Blocks(title="Axon Pro - Python IDE") as demo:
408
  diff_modified_state = gr.State("")
409
  diff_mode_state = gr.State(False)
410
 
411
- # --- Editor Functions ---
412
  def update_file(content):
413
  fs.save_file(content)
414
  return fs.get_current_file_content()
@@ -423,11 +502,13 @@ with gr.Blocks(title="Axon Pro - Python IDE") as demo:
423
  return gr.update(choices=fs.get_all_files(), value=name), fs.get_current_file_content()
424
  return gr.update(choices=fs.get_all_files()), fs.get_current_file_content()
425
 
426
- def run_wrapper(content):
427
- out, err = run_code(content)
428
- if err:
429
- return f"{out}\n\n=== ERROR ===\n{err}"
430
- return out
 
 
431
 
432
  def complete_wrapper(content):
433
  comp = generate_completion(content)
@@ -453,33 +534,19 @@ with gr.Blocks(title="Axon Pro - Python IDE") as demo:
453
  new_hist = history + [[prompt, "Generated code available in Diff View"]]
454
  return diff, "", generated, True, new_hist, ""
455
 
456
- # --- Package Manager Functions ---
457
- def install_wrapper(pkg_name):
458
- if not pkg_name:
459
- return "Please enter a package name."
460
- return pkg_mgr.install_package(pkg_name)
461
-
462
- def uninstall_wrapper(pkg_name):
463
- if not pkg_name:
464
- return "Please enter a package name."
465
- return pkg_mgr.uninstall_package(pkg_name)
466
-
467
- def list_wrapper():
468
- return pkg_mgr.list_packages()
469
-
470
- def info_wrapper(pkg_name):
471
- if not pkg_name:
472
- return "Please enter a package name."
473
- return pkg_mgr.search_package_info(pkg_name)
474
-
475
- # --- Wiring: Editor ---
476
  editor.change(update_file, editor, None)
477
  file_list.change(load_file, file_list, [editor, current_file_state])
478
  new_file_btn.click(create_file, new_file_name, [file_list, editor])
479
 
480
- run_btn.click(run_wrapper, editor, terminal)
 
 
 
 
 
 
481
  complete_btn.click(complete_wrapper, editor, editor)
482
- clear_btn.click(lambda: ">>> Ready...", None, terminal)
483
 
484
  explain_btn.click(
485
  explain_wrapper, editor,
@@ -503,12 +570,6 @@ with gr.Blocks(title="Axon Pro - Python IDE") as demo:
503
  discard_btn.click(
504
  lambda: (gr.update(), False), None, [editor, diff_mode_state]
505
  ).then(lambda: gr.Tabs(selected="editor-tab"), None, main_tabs)
506
-
507
- # --- Wiring: Package Manager ---
508
- install_btn.click(install_wrapper, pkg_name_input, pkg_output)
509
- uninstall_btn.click(uninstall_wrapper, pkg_name_input, pkg_output)
510
- list_btn.click(list_wrapper, None, pkg_output)
511
- info_btn.click(info_wrapper, pkg_name_input, pkg_output)
512
 
513
  if __name__ == "__main__":
514
  demo.launch(
 
5
  import time
6
  import sys
7
  import subprocess
8
+ import shlex
9
  from io import StringIO
10
  import contextlib
11
  from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
 
51
 
52
  fs = FileSystem()
53
 
54
+ # --- Terminal Engine ---
55
+ class Terminal:
56
+ """A real interactive terminal that handles pip, shell commands, and python execution."""
57
+
58
+ WELCOME = (
59
+ "Axon Pro Terminal v1.1\n"
60
+ "─────────────────────────────────────\n"
61
+ " pip install <pkg> Install packages\n"
62
+ " pip uninstall <pkg> Remove packages\n"
63
+ " pip list Show installed\n"
64
+ " python <file.py> Run a script\n"
65
+ " Any shell command ls, cat, echo, etc.\n"
66
+ "─────────────────────────────────────\n"
67
+ )
68
+
69
+ BLOCKED_COMMANDS = [
70
+ "rm -rf /", "rm -rf /*", "mkfs", "dd if=", ":(){", "fork",
71
+ "shutdown", "reboot", "halt", "poweroff", "init 0", "init 6",
72
+ ]
73
+
74
+ def __init__(self):
75
+ self.history: List[str] = [self.WELCOME]
76
+ self.env = {} # Persistent Python globals across exec calls
77
+
78
+ def get_log(self) -> str:
79
+ return "\n".join(self.history)
80
+
81
+ def _append(self, text: str):
82
+ self.history.append(text)
83
+ # Keep buffer from growing forever (~500 lines max)
84
+ total_lines = sum(s.count("\n") + 1 for s in self.history)
85
+ while total_lines > 500 and len(self.history) > 1:
86
+ removed = self.history.pop(0)
87
+ total_lines -= removed.count("\n") + 1
88
+
89
+ def _is_blocked(self, cmd: str) -> bool:
90
+ cmd_lower = cmd.lower().strip()
91
+ return any(b in cmd_lower for b in self.BLOCKED_COMMANDS)
92
+
93
+ def run_command(self, cmd: str) -> str:
94
+ """Process a terminal command and return the full terminal log."""
95
+ cmd = cmd.strip()
96
+ if not cmd:
97
+ return self.get_log()
98
 
99
+ self._append(f"$ {cmd}")
100
 
101
+ if self._is_blocked(cmd):
102
+ self._append(" Command blocked for safety.")
103
+ return self.get_log()
 
104
 
105
+ parts = cmd.split()
106
+ base = parts[0].lower()
107
+
108
+ if base == "clear":
109
+ self.history = []
110
+ return ""
111
+ elif base == "help":
112
+ self._append(self.WELCOME)
113
+ elif base in ("pip", "pip3"):
114
+ self._handle_pip(parts)
115
+ elif base in ("python", "python3"):
116
+ self._handle_python(cmd, parts)
117
+ else:
118
+ self._handle_shell(cmd)
119
+
120
+ return self.get_log()
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
+ def _handle_pip(self, parts: List[str]):
123
+ """Handle pip install/uninstall/list/show/freeze commands."""
124
+ if len(parts) < 2:
125
+ self._append("Usage: pip install <pkg> | pip uninstall <pkg> | pip list | pip show <pkg> | pip freeze")
126
+ return
127
+
128
+ subcmd = parts[1].lower()
129
+
130
+ if subcmd == "install" and len(parts) >= 3:
131
+ pkgs = parts[2:]
132
+ self._append(f"Installing: {' '.join(pkgs)}...")
133
+ try:
134
+ result = subprocess.run(
135
+ [sys.executable, "-m", "pip", "install"] + pkgs,
136
+ capture_output=True, text=True, timeout=180
137
+ )
138
+ stdout = result.stdout.strip()
139
+ stderr = result.stderr.strip()
140
+
141
+ if result.returncode == 0:
142
+ for line in stdout.split("\n"):
143
+ low = line.lower()
144
+ if any(kw in low for kw in ["successfully", "already satisfied", "requirement"]):
145
+ self._append(line)
146
+ if not any("successfully" in l.lower() or "already satisfied" in l.lower() for l in stdout.split("\n")):
147
+ self._append(stdout.split("\n")[-1] if stdout else "Done.")
148
+ self._append("✅ pip install completed")
149
+ else:
150
+ err_lines = (stderr or stdout).split("\n")[-6:]
151
+ for line in err_lines:
152
+ self._append(line)
153
+ self._append("❌ pip install failed")
154
+
155
+ except subprocess.TimeoutExpired:
156
+ self._append("❌ Timed out (>180s). Try a smaller package or check connection.")
157
+ except Exception as e:
158
+ self._append(f"❌ Error: {e}")
159
 
160
+ elif subcmd == "uninstall" and len(parts) >= 3:
161
+ pkg = parts[2]
162
+ protected = ["gradio", "torch", "transformers", "pip", "setuptools", "wheel", "numpy"]
163
+ if pkg.lower() in protected:
164
+ self._append(f"⛔ '{pkg}' is protected and cannot be uninstalled.")
165
+ return
166
+ self._append(f"Uninstalling {pkg}...")
167
+ try:
168
+ result = subprocess.run(
169
+ [sys.executable, "-m", "pip", "uninstall", "-y", pkg],
170
+ capture_output=True, text=True, timeout=60
171
+ )
172
+ if result.returncode == 0:
173
+ self._append(f"✅ Uninstalled {pkg}")
174
+ else:
175
+ self._append(f"❌ {result.stderr.strip()}")
176
+ except Exception as e:
177
+ self._append(f"❌ Error: {e}")
178
 
179
+ elif subcmd == "list":
180
+ try:
181
+ result = subprocess.run(
182
+ [sys.executable, "-m", "pip", "list", "--format=columns"],
183
+ capture_output=True, text=True, timeout=30
184
+ )
185
+ self._append(result.stdout.strip() if result.stdout else "No packages found.")
186
+ except Exception as e:
187
+ self._append(f"Error: {e}")
188
 
189
+ elif subcmd == "show" and len(parts) >= 3:
190
+ try:
191
+ result = subprocess.run(
192
+ [sys.executable, "-m", "pip", "show", parts[2]],
193
+ capture_output=True, text=True, timeout=15
194
+ )
195
+ if result.returncode == 0:
196
+ self._append(result.stdout.strip())
197
+ else:
198
+ self._append(f"Package '{parts[2]}' not found.")
199
+ except Exception as e:
200
+ self._append(f"Error: {e}")
201
+
202
+ elif subcmd == "freeze":
203
+ try:
204
+ result = subprocess.run(
205
+ [sys.executable, "-m", "pip", "freeze"],
206
+ capture_output=True, text=True, timeout=30
207
+ )
208
+ self._append(result.stdout.strip() if result.stdout else "No packages.")
209
+ except Exception as e:
210
+ self._append(f"Error: {e}")
211
+
212
+ else:
213
+ self._append(f"pip {subcmd}: unsupported. Use install, uninstall, list, show, freeze.")
214
 
215
+ def _handle_python(self, cmd: str, parts: List[str]):
216
+ """Handle: python file.py, python -c 'code', python -m module."""
217
+ if len(parts) < 2:
218
+ self._append(f"Python {sys.version.split()[0]}")
219
+ return
220
+
221
+ if parts[1] == "-c" and len(parts) >= 3:
222
+ code = cmd.split("-c", 1)[1].strip().strip("'\"")
223
+ self._exec_python(code)
224
+ elif parts[1] == "-m" and len(parts) >= 3:
225
+ try:
226
+ result = subprocess.run(
227
+ [sys.executable] + parts[1:],
228
+ capture_output=True, text=True, timeout=30
229
+ )
230
+ if result.stdout:
231
+ self._append(result.stdout.strip())
232
+ if result.stderr:
233
+ self._append(result.stderr.strip())
234
+ except subprocess.TimeoutExpired:
235
+ self._append("Process timed out (30s limit).")
236
+ except Exception as e:
237
+ self._append(f"Error: {e}")
238
+ else:
239
+ filename = parts[1]
240
+ if filename in fs.files:
241
+ self._exec_python(fs.files[filename])
242
+ elif os.path.exists(filename):
243
+ with open(filename, "r") as f:
244
+ self._exec_python(f.read())
245
+ else:
246
+ self._append(f"File not found: {filename}")
247
+
248
+ def _exec_python(self, code: str):
249
+ """Execute python code with persistent globals and capture output."""
250
+ buf = StringIO()
251
+ start = time.time()
252
  try:
253
+ with contextlib.redirect_stdout(buf):
254
+ exec(code, self.env)
255
+ elapsed = time.time() - start
256
+ out = buf.getvalue()
257
+ if out:
258
+ self._append(out.rstrip())
259
+ self._append(f"[Done in {elapsed:.4f}s]")
260
  except Exception as e:
261
+ out = buf.getvalue()
262
+ if out:
263
+ self._append(out.rstrip())
264
+ self._append(f"Error: {e}")
265
 
266
+ def _handle_shell(self, cmd: str):
267
+ """Handle generic shell commands."""
 
 
 
268
  try:
269
  result = subprocess.run(
270
+ cmd, shell=True,
271
+ capture_output=True, text=True, timeout=30
 
 
272
  )
273
+ if result.stdout:
274
+ self._append(result.stdout.rstrip())
275
+ if result.stderr:
276
+ self._append(result.stderr.rstrip())
277
+ if result.returncode != 0 and not result.stdout and not result.stderr:
278
+ self._append(f"Command exited with code {result.returncode}")
279
+ except subprocess.TimeoutExpired:
280
+ self._append("⏱ Command timed out (30s limit).")
281
  except Exception as e:
282
+ self._append(f"Error: {e}")
283
 
284
+ terminal_engine = Terminal()
285
 
286
  # --- Model Loading ---
287
  @lru_cache(maxsize=1)
 
311
  print(f"Error loading model: {e}")
312
  return None
313
 
314
+ # --- AI Functions ---
315
 
316
+ def run_editor_code(code: str) -> str:
317
+ """Run code from the ▶ Run button and pipe output into the terminal."""
318
+ buf = StringIO()
319
+ start = time.time()
320
+ try:
321
+ with contextlib.redirect_stdout(buf):
322
+ exec(code, {}, {})
323
+ elapsed = time.time() - start
324
+ result = buf.getvalue()
325
+ if not result:
326
+ result = "(No output)"
327
+ terminal_engine._append(f"$ python [editor]\n{result.rstrip()}\n[Done in {elapsed:.4f}s]")
328
+ return terminal_engine.get_log()
329
+ except Exception as e:
330
+ out = buf.getvalue()
331
+ terminal_engine._append(f"$ python [editor]\n{out}Error: {e}")
332
+ return terminal_engine.get_log()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
333
 
334
  def generate_completion(code: str) -> str:
335
  model = load_model()
336
  if not model: return "# Error: Model failed to load."
 
337
  prompt = f"<|system|>You are a Python expert. Complete the code. No explanations.</s><|user|>\n{code}\n</s><|assistant|>"
338
  try:
339
  result = model(prompt, num_return_sequences=1, return_full_text=False)
 
372
  return str(e)
373
 
374
  def create_diff_view(original: str, modified: str) -> str:
 
 
375
  import difflib
376
+ diff = difflib.unified_diff(original.splitlines(), modified.splitlines(), lineterm="")
377
  return "\n".join(diff)
378
 
379
  # --- Gradio UI ---
 
392
  css = """
393
  * { border-radius: 0 !important; }
394
  .editor-container { height: 50vh; }
395
+ .terminal-box textarea {
396
+ background-color: #0d0d0d !important;
397
+ color: #00ff41 !important;
398
+ font-family: 'JetBrains Mono', monospace !important;
399
+ font-size: 13px !important;
400
+ }
401
+ .terminal-input input {
402
+ background-color: #0d0d0d !important;
403
+ color: #00ff41 !important;
404
+ font-family: 'JetBrains Mono', monospace !important;
405
+ border: 1px solid #333 !important;
406
+ }
407
  .diff-view { height: 40vh; }
408
  .code-wrap { white-space: pre-wrap !important; }
 
409
  """
410
 
411
  with gr.Blocks(title="Axon Pro - Python IDE") as demo:
 
449
 
450
  with gr.Tabs():
451
  with gr.Tab("Terminal", id="terminal-tab"):
452
+ terminal_output = gr.Textbox(
453
+ value=terminal_engine.get_log(),
454
+ lines=10,
455
  interactive=False,
456
+ elem_classes="terminal-box",
457
+ label="Terminal",
458
+ show_label=False
459
  )
460
+ with gr.Row():
461
+ terminal_input = gr.Textbox(
462
+ placeholder="$ pip install pygame, ls, python main.py ...",
463
+ scale=8,
464
+ container=False,
465
+ elem_classes="terminal-input"
466
+ )
467
+ terminal_run_btn = gr.Button("⏎", variant="primary", scale=1, min_width=50)
468
+ clear_btn = gr.Button("Clear", variant="secondary", size="sm")
469
 
470
  with gr.Tab("AI Chat", id="chat-tab"):
471
  chat_history = gr.Chatbot(label="Axon AI", height=300)
 
473
  chat_input = gr.Textbox(placeholder="Ask to generate Python code...", scale=7, container=False)
474
  send_btn = gr.Button("Generate", variant="primary", scale=1)
475
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
476
  with gr.Tab("Diff View", id="diff-tab"):
477
  diff_view = gr.Code(label="AI Changes", language="python", elem_classes="diff-view code-wrap", interactive=False)
478
  with gr.Row():
479
  apply_btn = gr.Button("Apply Changes", variant="primary")
480
  discard_btn = gr.Button("Discard Changes", variant="secondary")
481
 
482
+ status_bar = gr.Markdown("**AXON PRO v1.1** | Python 3.x | CPU Mode | TinyLlama-1.1B | Terminal + pip", elem_classes="status-bar")
483
 
484
  # State
485
  current_file_state = gr.State(fs.current_file)
 
487
  diff_modified_state = gr.State("")
488
  diff_mode_state = gr.State(False)
489
 
490
+ # --- Functions ---
491
  def update_file(content):
492
  fs.save_file(content)
493
  return fs.get_current_file_content()
 
502
  return gr.update(choices=fs.get_all_files(), value=name), fs.get_current_file_content()
503
  return gr.update(choices=fs.get_all_files()), fs.get_current_file_content()
504
 
505
+ def terminal_command(cmd):
506
+ log = terminal_engine.run_command(cmd)
507
+ return log, ""
508
+
509
+ def clear_terminal():
510
+ terminal_engine.history = []
511
+ return ""
512
 
513
  def complete_wrapper(content):
514
  comp = generate_completion(content)
 
534
  new_hist = history + [[prompt, "Generated code available in Diff View"]]
535
  return diff, "", generated, True, new_hist, ""
536
 
537
+ # --- Wiring ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
538
  editor.change(update_file, editor, None)
539
  file_list.change(load_file, file_list, [editor, current_file_state])
540
  new_file_btn.click(create_file, new_file_name, [file_list, editor])
541
 
542
+ # Terminal — type commands and hit enter or click ⏎
543
+ terminal_input.submit(terminal_command, terminal_input, [terminal_output, terminal_input])
544
+ terminal_run_btn.click(terminal_command, terminal_input, [terminal_output, terminal_input])
545
+ clear_btn.click(clear_terminal, None, terminal_output)
546
+
547
+ # Run button pipes editor code into terminal
548
+ run_btn.click(run_editor_code, editor, terminal_output)
549
  complete_btn.click(complete_wrapper, editor, editor)
 
550
 
551
  explain_btn.click(
552
  explain_wrapper, editor,
 
570
  discard_btn.click(
571
  lambda: (gr.update(), False), None, [editor, diff_mode_state]
572
  ).then(lambda: gr.Tabs(selected="editor-tab"), None, main_tabs)
 
 
 
 
 
 
573
 
574
  if __name__ == "__main__":
575
  demo.launch(