AIencoder commited on
Commit
00edd64
·
verified ·
1 Parent(s): 9d6dfd1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +613 -188
app.py CHANGED
@@ -45,9 +45,9 @@ class VirtualDisplay:
45
  subprocess.run(["pkill", "-f", f"Xvfb {self.display}"], capture_output=True, timeout=5)
46
  time.sleep(0.2)
47
  self.xvfb_proc = subprocess.Popen(
48
- ["Xvfb", self.display, "-screen", "0", f"{SCREEN_W}x{SCREEN_H}x24", "-ac", "-nolisten", "tcp"],
49
- stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
50
- )
51
  time.sleep(0.5)
52
  if self.xvfb_proc.poll() is None:
53
  print(f"[Xvfb] Running on {self.display}")
@@ -60,23 +60,23 @@ class VirtualDisplay:
60
  def capture(self):
61
  if not self.is_running: return None
62
  tmp = tempfile.NamedTemporaryFile(suffix=".png", delete=False); tmp.close()
63
- # Try multiple capture methods
64
- cmds = [
65
  ["scrot", "--display", self.display, tmp.name],
66
- f"import -window root -display {self.display} {tmp.name}"
67
- ]
68
- for cmd in cmds:
69
  try:
70
  kw = {"shell": True} if isinstance(cmd, str) else {}
71
- r = subprocess.run(cmd, capture_output=True, timeout=2, env={**os.environ, "DISPLAY": self.display}, **kw)
 
72
  if r.returncode == 0 and os.path.exists(tmp.name) and os.path.getsize(tmp.name) > 100:
73
  return tmp.name
74
  except: pass
 
 
75
  return None
76
 
77
  @property
78
  def is_running(self): return self.xvfb_proc is not None and self.xvfb_proc.poll() is None
79
-
80
  def cleanup(self):
81
  if self.xvfb_proc:
82
  self.xvfb_proc.terminate()
@@ -91,7 +91,7 @@ vdisplay = VirtualDisplay()
91
  class GUIProcessManager:
92
  def __init__(self):
93
  self.process = None
94
-
95
  def launch(self, code):
96
  self.stop()
97
  tmp = "/tmp/_axon_gui_run.py"
@@ -127,8 +127,8 @@ gui_mgr = GUIProcessManager()
127
  class FileSystem:
128
  def __init__(self):
129
  self.files: Dict[str, str] = {
130
- "main.py": '# Start coding here\nprint("Hello World")',
131
- "utils.py": "def add(a, b): return a + b",
132
  }
133
  self.current_file = "main.py"
134
  self._sync_dir = "/tmp/axon_workspace"
@@ -152,8 +152,7 @@ class FileSystem:
152
  if f in self.files: self.current_file = f
153
  def create_file(self, f, c=""):
154
  if f and f not in self.files:
155
- self.files[f] = c; self.current_file = f
156
- self._sync_to_disk()
157
  def delete_file(self, f):
158
  if f in self.files and len(self.files) > 1:
159
  del self.files[f]
@@ -179,12 +178,17 @@ class SnippetLibrary:
179
  with open(SNIPPETS_FILE) as f: self.snippets = json.load(f)
180
  except: self.snippets = {}
181
  def _save(self):
182
- with open(SNIPPETS_FILE, "w") as f: json.dump(self.snippets, f)
 
 
183
  def add(self, name, code, tags=""):
184
  if not name.strip(): return "Error: name required"
185
  self.snippets[name.strip()] = {"code": code, "tags": tags, "created": time.strftime("%Y-%m-%d")}
186
  self._save(); return f"Saved: {name}"
187
  def get(self, name): return self.snippets.get(name, {}).get("code", "")
 
 
 
188
  def get_names(self): return list(self.snippets.keys())
189
 
190
  snippets = SnippetLibrary()
@@ -195,15 +199,23 @@ snippets = SnippetLibrary()
195
  class CodeAnalyzer:
196
  @staticmethod
197
  def analyze(code: str) -> str:
198
- if not code.strip(): return "Empty file"
199
  try: tree = ast.parse(code)
200
- except SyntaxError as e: return f"⚠ Syntax error: line {e.lineno}: {e.msg}"
201
  lines = [f"📊 {len(code.splitlines())} lines"]
 
202
  for node in ast.walk(tree):
203
- if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
204
- lines.append(f"⚡ def {node.name}(...)")
 
 
 
 
 
205
  elif isinstance(node, ast.ClassDef):
206
- lines.append(f"🏗 class {node.name}")
 
 
207
  return "\n".join(lines)
208
 
209
  analyzer = CodeAnalyzer()
@@ -214,56 +226,190 @@ class FindReplace:
214
  if not query: return ""
215
  res = []
216
  for i, line in enumerate(code.splitlines(), 1):
217
- if (query in line) if case else (query.lower() in line.lower()):
218
- res.append(f"L{i}: {line.strip()}")
219
- return "\n".join(res) if res else "No matches."
 
 
 
 
 
 
 
 
 
 
 
220
 
221
  @staticmethod
222
  def replace_in_file(code, find, replace, case):
223
  if not find: return code, "Empty find."
224
  flags = 0 if case else re.IGNORECASE
225
  new_code, count = re.subn(re.escape(find), replace, code, flags=flags)
226
- return new_code, f"Replaced {count}."
227
 
228
  finder = FindReplace()
229
 
230
  # ═══════════════════════════════════════
231
- # Terminal
232
  # ═══════════════════════════════════════
233
  class PTYTerminal:
 
 
234
  def __init__(self):
235
  self.master_fd = None; self.pid = None
236
- self.lock = threading.Lock(); self.log = []
 
 
 
237
  self._spawn()
 
 
 
 
 
 
 
238
  def _spawn(self):
239
  try:
240
- pid, fd = pty.openpty(); self.pid = os.fork()
 
241
  if self.pid == 0:
242
- os.setsid(); os.dup2(fd,0); os.dup2(fd,1); os.dup2(fd,2)
243
- if fd>2: os.close(fd)
244
- os.execvpe("/bin/bash", ["/bin/bash","--norc","-i"], {**os.environ, "PS1":"$ ", "TERM":"dumb"})
 
 
 
 
 
 
 
245
  else:
246
- os.close(fd); self.master_fd = pid
247
- fcntl.fcntl(self.master_fd, fcntl.F_SETFL, os.O_NONBLOCK)
248
- except: pass
249
- def read(self):
 
 
 
 
 
 
 
 
 
 
 
 
 
250
  if not self.master_fd: return ""
251
- try:
252
- out = b""
253
- while True:
254
- r,_,_ = select.select([self.master_fd],[],[],0.01)
255
- if not r: break
256
- out += os.read(self.master_fd, 1024)
257
- if out:
258
- txt = out.decode(errors="replace")
259
- self.log.append(txt)
260
- return "".join(self.log)[-2000:]
261
- except: return "".join(self.log)[-2000:]
262
- return "".join(self.log)[-2000:]
263
- def write(self, cmd):
264
- if self.master_fd: os.write(self.master_fd, (cmd+"\n").encode())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
  def cleanup(self):
266
- if self.pid: os.kill(self.pid, signal.SIGTERM)
 
 
 
 
 
267
 
268
  terminal = PTYTerminal()
269
 
@@ -272,188 +418,467 @@ terminal = PTYTerminal()
272
  # ═══════════════════════════════════════
273
  @lru_cache(maxsize=1)
274
  def load_model():
275
- print("Loading Model...")
 
276
  try:
277
  tok = AutoTokenizer.from_pretrained(MODEL_NAME)
278
- mdl = AutoModelForCausalLM.from_pretrained(MODEL_NAME, dtype=DTYPE, device_map=DEVICE)
279
- return pipeline("text-generation", model=mdl, tokenizer=tok)
280
- except: return None
281
-
282
- def ai_gen(prompt, code):
 
 
 
 
 
283
  pipe = load_model()
284
- if not pipe: return "Model load failed."
285
- formatted = f"<|system|>You are a Python coding assistant.</s><|user|>{prompt}\n{code}</s><|assistant|>"
286
- try: return pipe(formatted, max_new_tokens=256, temperature=0.2)[0]["generated_text"].split("<|assistant|>")[-1]
287
- except Exception as e: return str(e)
 
 
 
288
 
289
  # ═══════════════════════════════════════
290
- # UI Setup
 
 
 
 
 
 
 
291
  # ═══════════════════════════════════════
292
  theme = gr.themes.Default(
293
  primary_hue="cyan", neutral_hue="gray",
294
  font=[gr.themes.GoogleFont("IBM Plex Mono"), "monospace"]
295
- ).set(body_background_fill="#080808", body_text_color="#e0e0e0")
 
 
 
 
 
 
 
 
 
296
 
297
  css = """
 
298
  .gradio-container { max-width: 100% !important; }
299
- textarea { font-family: 'IBM Plex Mono', monospace !important; font-size: 12px !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
300
  """
301
 
 
 
 
302
  with gr.Blocks(title="Axon Pro", theme=theme, css=css) as demo:
303
-
304
- with gr.Row():
305
- gr.Markdown("# ⬡ AXON PRO v4")
306
 
307
- with gr.Row():
308
- # Left Sidebar
 
 
 
 
309
  with gr.Column(scale=1, min_width=200):
310
  with gr.Accordion("📁 EXPLORER", open=True):
311
- file_list = gr.Dropdown(choices=fs.get_all_files(), value=fs.current_file, label="Files")
 
312
  new_file_txt = gr.Textbox(placeholder="new_file.py", show_label=False)
313
  with gr.Row():
314
- new_btn = gr.Button("NEW", size="sm")
315
- save_btn = gr.Button("SAVE", size="sm")
316
- del_btn = gr.Button("DEL", size="sm", variant="stop")
317
-
318
  with gr.Accordion("🗺 STRUCTURE", open=True):
319
- structure_view = gr.Textbox(label="", lines=10, interactive=False)
320
-
 
 
 
321
  with gr.Accordion("📋 SNIPPETS", open=False):
322
- snip_list = gr.Dropdown(choices=snippets.get_names(), label="Saved")
323
- snip_name = gr.Textbox(placeholder="Name", show_label=False)
 
 
324
  with gr.Row():
325
- snip_save_btn = gr.Button("SAVE", size="sm")
326
- snip_load_btn = gr.Button("INSERT", size="sm")
327
- status_msg = gr.Markdown("")
 
328
 
329
- # Main Editor Area
330
  with gr.Column(scale=4):
331
- editor = gr.Code(value=fs.get_current_file_content(), language="python", lines=20)
332
-
 
 
333
  with gr.Row():
334
- run_btn = gr.Button("▶ RUN CLI", variant="primary")
335
- run_gui_btn = gr.Button("🖥 RUN GUI", variant="primary")
336
- stop_btn = gr.Button(" STOP", variant="stop")
337
- ai_complete = gr.Button(" COMPLETE")
338
- ai_explain = gr.Button("🤔 EXPLAIN")
339
-
340
- with gr.Tabs():
341
- with gr.Tab("TERMINAL"):
342
- term_out = gr.Textbox(lines=12, label="Output", interactive=False)
343
- term_in = gr.Textbox(placeholder="$ Command...", show_label=False)
344
- term_run = gr.Button("Enter")
345
-
346
- with gr.Tab("FIND"):
 
 
 
 
 
 
 
 
 
 
 
 
 
347
  with gr.Row():
348
- find_query = gr.Textbox(label="Find")
349
- replace_query = gr.Textbox(label="Replace")
350
- find_case = gr.Checkbox(label="Case Sensitive")
 
 
 
 
 
351
  with gr.Row():
352
- find_btn = gr.Button("Find")
353
- replace_btn = gr.Button("Replace All")
354
- find_results = gr.Textbox(label="Results", lines=5)
355
 
356
- with gr.Tab("DISPLAY"):
357
- display_image = gr.Image(label="Virtual Display", interactive=False, type="filepath")
 
358
  with gr.Row():
359
- capture_btn = gr.Button("📸 Refresh Screen")
360
- auto_capture = gr.Checkbox(label="Auto Refresh (2s)")
361
- auto_timer = gr.Timer(value=2.0, active=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
362
 
363
  # ═══════════════════════════════════════
364
- # LOGIC & WIRING
365
  # ═══════════════════════════════════════
366
 
367
- # --- File System Handlers ---
368
- def load_file(f):
369
- fs.set_current_file(f)
370
- code = fs.get_current_file_content()
371
- return code, analyzer.analyze(code)
372
 
373
- def create_file(name):
 
 
 
 
 
 
 
 
374
  fs.create_file(name)
375
- return gr.Dropdown(choices=fs.get_all_files(), value=name), "", ""
 
 
376
 
377
- def save_file(code):
378
  fs.save_file(code)
379
- return "Saved."
380
-
381
- def delete_file(f):
382
- fs.delete_file(f)
383
- new_f = fs.current_file
384
- new_c = fs.get_current_file_content()
385
- return gr.Dropdown(choices=fs.get_all_files(), value=new_f), new_c
386
-
387
- file_list.change(load_file, file_list, [editor, structure_view])
388
- new_btn.click(create_file, new_file_txt, [file_list, new_file_txt, editor])
389
- save_btn.click(save_file, editor, status_msg)
390
- del_btn.click(delete_file, file_list, [file_list, editor])
391
- editor.change(lambda c: analyzer.analyze(c), editor, structure_view)
392
-
393
- # --- Execution Handlers ---
394
- def run_cli(code):
395
  fs.save_file(code)
396
- cmd = f"python3 {fs._sync_dir}/{fs.current_file}"
397
- terminal.write(cmd)
398
- time.sleep(0.5)
399
- return terminal.read()
400
 
401
- def run_gui(code):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
402
  fs.save_file(code)
403
- return gui_mgr.launch(code)
404
-
405
- run_btn.click(run_cli, editor, term_out)
406
- run_gui_btn.click(run_gui, editor, term_out)
407
- stop_btn.click(gui_mgr.stop, None, term_out)
408
-
409
- # --- AI Handlers ---
410
- ai_complete.click(lambda c: ai_gen("Complete this code:", c), editor, editor)
411
- ai_explain.click(lambda c: ai_gen("Explain this code:", c), editor, term_out)
412
-
413
- # --- Terminal Handlers ---
414
- def term_cmd(cmd):
415
- terminal.write(cmd)
416
- time.sleep(0.2)
417
- return "", terminal.read()
418
-
419
- term_run.click(term_cmd, term_in, [term_in, term_out])
420
- term_in.submit(term_cmd, term_in, [term_in, term_out])
421
-
422
- # --- Find/Replace Handlers ---
423
- find_btn.click(finder.find_in_file, [editor, find_query, find_case], find_results)
424
-
425
- def replace_h(c, f, r, case):
426
- new_c, msg = finder.replace_in_file(c, f, r, case)
427
- return new_c, msg
428
-
429
- replace_btn.click(replace_h, [editor, find_query, replace_query, find_case], [editor, find_results])
430
-
431
- # --- Snippet Handlers ---
432
- snip_save_btn.click(snippets.add, [snip_name, editor], status_msg)
433
-
434
- def insert_snip(name, code):
435
- return code + "\n" + snippets.get(name)
436
-
437
- snip_load_btn.click(insert_snip, [snip_list, editor], editor)
438
-
439
- # --- Display Handlers ---
440
- def capture():
441
- path = vdisplay.capture()
442
- return path if path else None
443
-
444
- capture_btn.click(capture, None, display_image)
445
- auto_capture.change(lambda x: gr.Timer(active=x), auto_capture, auto_timer)
446
- auto_timer.tick(capture, None, display_image)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
447
 
448
  if __name__ == "__main__":
449
  import atexit
450
  atexit.register(terminal.cleanup)
451
  atexit.register(gui_mgr.stop)
452
  atexit.register(vdisplay.cleanup)
453
-
454
- demo.queue().launch(
455
- server_name="0.0.0.0",
456
- server_port=7860,
457
- ssr_mode=False,
458
- allowed_paths=["/tmp"]
459
- )
 
45
  subprocess.run(["pkill", "-f", f"Xvfb {self.display}"], capture_output=True, timeout=5)
46
  time.sleep(0.2)
47
  self.xvfb_proc = subprocess.Popen(
48
+ ["Xvfb", self.display, "-screen", "0", f"{SCREEN_W}x{SCREEN_H}x24",
49
+ "-ac", "-nolisten", "tcp"],
50
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
51
  time.sleep(0.5)
52
  if self.xvfb_proc.poll() is None:
53
  print(f"[Xvfb] Running on {self.display}")
 
60
  def capture(self):
61
  if not self.is_running: return None
62
  tmp = tempfile.NamedTemporaryFile(suffix=".png", delete=False); tmp.close()
63
+ for cmd in [
 
64
  ["scrot", "--display", self.display, tmp.name],
65
+ f"import -window root -display {self.display} {tmp.name}",
66
+ ]:
 
67
  try:
68
  kw = {"shell": True} if isinstance(cmd, str) else {}
69
+ r = subprocess.run(cmd, capture_output=True, timeout=3,
70
+ env={**os.environ, "DISPLAY": self.display}, **kw)
71
  if r.returncode == 0 and os.path.exists(tmp.name) and os.path.getsize(tmp.name) > 100:
72
  return tmp.name
73
  except: pass
74
+ try: os.unlink(tmp.name)
75
+ except: pass
76
  return None
77
 
78
  @property
79
  def is_running(self): return self.xvfb_proc is not None and self.xvfb_proc.poll() is None
 
80
  def cleanup(self):
81
  if self.xvfb_proc:
82
  self.xvfb_proc.terminate()
 
91
  class GUIProcessManager:
92
  def __init__(self):
93
  self.process = None
94
+
95
  def launch(self, code):
96
  self.stop()
97
  tmp = "/tmp/_axon_gui_run.py"
 
127
  class FileSystem:
128
  def __init__(self):
129
  self.files: Dict[str, str] = {
130
+ "main.py": '# Start coding here\nfrom utils import add\n\nresult = add(3, 7)\nprint(f"3 + 7 = {result}")',
131
+ "utils.py": "def add(a, b):\n return a + b\n\ndef multiply(a, b):\n return a * b",
132
  }
133
  self.current_file = "main.py"
134
  self._sync_dir = "/tmp/axon_workspace"
 
152
  if f in self.files: self.current_file = f
153
  def create_file(self, f, c=""):
154
  if f and f not in self.files:
155
+ self.files[f] = c; self.current_file = f; self._sync_to_disk()
 
156
  def delete_file(self, f):
157
  if f in self.files and len(self.files) > 1:
158
  del self.files[f]
 
178
  with open(SNIPPETS_FILE) as f: self.snippets = json.load(f)
179
  except: self.snippets = {}
180
  def _save(self):
181
+ try:
182
+ with open(SNIPPETS_FILE, "w") as f: json.dump(self.snippets, f)
183
+ except: pass
184
  def add(self, name, code, tags=""):
185
  if not name.strip(): return "Error: name required"
186
  self.snippets[name.strip()] = {"code": code, "tags": tags, "created": time.strftime("%Y-%m-%d")}
187
  self._save(); return f"Saved: {name}"
188
  def get(self, name): return self.snippets.get(name, {}).get("code", "")
189
+ def delete(self, name):
190
+ if name in self.snippets: del self.snippets[name]; self._save(); return f"Deleted: {name}"
191
+ return "Not found"
192
  def get_names(self): return list(self.snippets.keys())
193
 
194
  snippets = SnippetLibrary()
 
199
  class CodeAnalyzer:
200
  @staticmethod
201
  def analyze(code: str) -> str:
202
+ if not code or not code.strip(): return "Empty file"
203
  try: tree = ast.parse(code)
204
+ except SyntaxError as e: return f"⚠ Syntax error line {e.lineno}: {e.msg}"
205
  lines = [f"📊 {len(code.splitlines())} lines"]
206
+ imports = []
207
  for node in ast.walk(tree):
208
+ if isinstance(node, ast.Import):
209
+ for a in node.names: imports.append(a.name)
210
+ elif isinstance(node, ast.ImportFrom):
211
+ imports.append(f"from {node.module}")
212
+ elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
213
+ args = ", ".join(a.arg for a in node.args.args)
214
+ lines.append(f" L{node.lineno} ⚡ def {node.name}({args})")
215
  elif isinstance(node, ast.ClassDef):
216
+ lines.append(f" L{node.lineno} 🏗 class {node.name}")
217
+ if imports:
218
+ lines.insert(1, f"📦 {', '.join(imports)}")
219
  return "\n".join(lines)
220
 
221
  analyzer = CodeAnalyzer()
 
226
  if not query: return ""
227
  res = []
228
  for i, line in enumerate(code.splitlines(), 1):
229
+ match = (query in line) if case else (query.lower() in line.lower())
230
+ if match:
231
+ res.append(f" L{i}: {line.strip()}")
232
+ return f"Found {len(res)} match(es):\n" + "\n".join(res) if res else "No matches."
233
+
234
+ @staticmethod
235
+ def find_all_files(files, query):
236
+ if not query: return ""
237
+ res = []
238
+ for fname, content in files.items():
239
+ for i, line in enumerate(content.splitlines(), 1):
240
+ if query.lower() in line.lower():
241
+ res.append(f" {fname}:L{i}: {line.strip()}")
242
+ return f"Found {len(res)} across all files:\n" + "\n".join(res) if res else "No matches."
243
 
244
  @staticmethod
245
  def replace_in_file(code, find, replace, case):
246
  if not find: return code, "Empty find."
247
  flags = 0 if case else re.IGNORECASE
248
  new_code, count = re.subn(re.escape(find), replace, code, flags=flags)
249
+ return new_code, f"Replaced {count} occurrence(s)."
250
 
251
  finder = FindReplace()
252
 
253
  # ═══════════════════════════════════════
254
+ # PTY Terminal (fixed)
255
  # ═══════════════════════════════════════
256
  class PTYTerminal:
257
+ STRIP_ANSI = re.compile(r'\x1b\[[0-9;]*[a-zA-Z]|\x1b\].*?\x07|\x1b\[.*?[@-~]|\r')
258
+
259
  def __init__(self):
260
  self.master_fd = None; self.pid = None
261
+ self.lock = threading.Lock()
262
+ self.log_lines: List[str] = []
263
+ self.max_lines = 800
264
+ self.cmd_history: List[str] = []
265
  self._spawn()
266
+ self._append("AXON TERMINAL v4.0")
267
+ self._append("══════════════════════════════════════")
268
+ self._append("Full PTY shell · Cross-file imports")
269
+ self._append(" pip/npm/apt-get/git/curl/make")
270
+ self._append(" GUI apps → Display tab")
271
+ self._append("══════════════════════════════════════")
272
+
273
  def _spawn(self):
274
  try:
275
+ pid, fd = pty.openpty()
276
+ self.pid = os.fork()
277
  if self.pid == 0:
278
+ # Child become the shell
279
+ os.close(pid)
280
+ os.setsid()
281
+ fcntl.ioctl(fd, termios.TIOCSCTTY, 0)
282
+ os.dup2(fd, 0); os.dup2(fd, 1); os.dup2(fd, 2)
283
+ if fd > 2: os.close(fd)
284
+ env = os.environ.copy()
285
+ env.update({"TERM": "dumb", "PS1": "$ ",
286
+ "DEBIAN_FRONTEND": "noninteractive", "DISPLAY": DISPLAY_NUM})
287
+ os.execvpe("/bin/bash", ["/bin/bash", "--norc", "--noprofile", "-i"], env)
288
  else:
289
+ # Parent hold the master fd
290
+ os.close(fd)
291
+ self.master_fd = pid
292
+ # Set non-blocking properly
293
+ flags = fcntl.fcntl(self.master_fd, fcntl.F_GETFL)
294
+ fcntl.fcntl(self.master_fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
295
+ # Set terminal size
296
+ fcntl.ioctl(self.master_fd, termios.TIOCSWINSZ,
297
+ struct.pack("HHHH", 40, 120, 0, 0))
298
+ time.sleep(0.3)
299
+ self._read_raw(0.2) # Drain initial output
300
+ except Exception as e:
301
+ self._append(f"[Shell Error] {e}")
302
+ self.master_fd = None
303
+
304
+ def _read_raw(self, timeout=0.1) -> str:
305
+ """Read raw bytes from PTY."""
306
  if not self.master_fd: return ""
307
+ out = []; deadline = time.time() + timeout
308
+ while True:
309
+ rem = deadline - time.time()
310
+ if rem <= 0: break
311
+ try:
312
+ r, _, _ = select.select([self.master_fd], [], [], min(rem, 0.05))
313
+ if r:
314
+ c = os.read(self.master_fd, 4096)
315
+ if c:
316
+ out.append(c.decode("utf-8", errors="replace"))
317
+ deadline = time.time() + 0.15
318
+ else: break
319
+ elif out: break
320
+ except OSError: break
321
+ return "".join(out)
322
+
323
+ def _clean(self, text):
324
+ """Strip ANSI escape codes."""
325
+ c = self.STRIP_ANSI.sub("", text)
326
+ return "".join(ch for ch in c if ch in "\n\t" or ord(ch) >= 32)
327
+
328
+ def _append(self, text):
329
+ self.log_lines.append(text)
330
+ while len(self.log_lines) > self.max_lines:
331
+ self.log_lines.pop(0)
332
+
333
+ def get_log(self): return "\n".join(self.log_lines)
334
+
335
+ def run_command(self, cmd):
336
+ cmd = cmd.strip()
337
+ if not cmd: return self.get_log()
338
+
339
+ # Save history
340
+ if not self.cmd_history or self.cmd_history[-1] != cmd:
341
+ self.cmd_history.append(cmd)
342
+ if len(self.cmd_history) > 100: self.cmd_history.pop(0)
343
+
344
+ with self.lock:
345
+ if cmd.lower() == "clear":
346
+ self.log_lines = []; return ""
347
+
348
+ if cmd.lower() == "history":
349
+ self._append("Command history:")
350
+ for i, c in enumerate(self.cmd_history[-20:], 1):
351
+ self._append(f" {i:3d} {c}")
352
+ return self.get_log()
353
+
354
+ if not self.master_fd:
355
+ self._append(f"$ {cmd}")
356
+ self._append("[Error] No shell.")
357
+ return self.get_log()
358
+
359
+ # Drain stale output
360
+ self._read_raw(0.05)
361
+ self._append(f"$ {cmd}")
362
+
363
+ try:
364
+ os.write(self.master_fd, (cmd + "\n").encode())
365
+ except OSError as e:
366
+ self._append(f"[Write Error] {e}")
367
+ return self.get_log()
368
+
369
+ # Determine timeout based on command type
370
+ parts = cmd.split()
371
+ base = parts[0].lower() if parts else ""
372
+ long_cmds = ["pip","pip3","npm","npx","apt-get","apt","git",
373
+ "wget","curl","make","cmake","cargo","yarn","conda"]
374
+ wait = 180 if base in long_cmds else (60 if base in ("python","python3","node") else 15)
375
+
376
+ # Collect output
377
+ chunks = []; start = time.time(); idle = 0
378
+ while time.time() - start < wait:
379
+ c = self._read_raw(0.3)
380
+ if c:
381
+ idle = 0; chunks.append(c)
382
+ if "".join(chunks).rstrip().endswith("$"): break
383
+ else:
384
+ idle += 1
385
+ if base not in long_cmds and idle >= 3: break
386
+ if base in long_cmds and idle >= 10: break
387
+
388
+ # Clean and filter
389
+ raw = self._clean("".join(chunks))
390
+ lines = raw.split("\n")
391
+ filtered = []; skip_echo = True
392
+ for line in lines:
393
+ s = line.strip()
394
+ if skip_echo and s == cmd.strip():
395
+ skip_echo = False; continue
396
+ if s in ("$", "$ "): continue
397
+ filtered.append(line)
398
+
399
+ result = "\n".join(filtered).strip()
400
+ if result: self._append(result)
401
+ return self.get_log()
402
+
403
+ def get_history(self) -> List[str]:
404
+ return list(reversed(self.cmd_history[-30:]))
405
+
406
  def cleanup(self):
407
+ if self.pid and self.pid > 0:
408
+ try: os.kill(self.pid, signal.SIGTERM)
409
+ except: pass
410
+ if self.master_fd:
411
+ try: os.close(self.master_fd)
412
+ except: pass
413
 
414
  terminal = PTYTerminal()
415
 
 
418
  # ═══════════════════════════════════════
419
  @lru_cache(maxsize=1)
420
  def load_model():
421
+ print("Loading TinyLlama...")
422
+ t0 = time.time()
423
  try:
424
  tok = AutoTokenizer.from_pretrained(MODEL_NAME)
425
+ mdl = AutoModelForCausalLM.from_pretrained(MODEL_NAME, dtype=DTYPE,
426
+ device_map=DEVICE, low_cpu_mem_usage=True)
427
+ gc = GenerationConfig(max_new_tokens=256, temperature=0.2, top_p=0.95, do_sample=True)
428
+ pipe = pipeline("text-generation", model=mdl, tokenizer=tok, generation_config=gc)
429
+ print(f"Loaded in {time.time()-t0:.1f}s")
430
+ return pipe
431
+ except Exception as e:
432
+ print(f"Model error: {e}"); return None
433
+
434
+ def ai_gen(system_prompt, code, max_tokens=256):
435
  pipe = load_model()
436
+ if not pipe: return "Error: model failed to load"
437
+ prompt = f"<|system|>{system_prompt}</s><|user|>\n{code}\n</s><|assistant|>"
438
+ try:
439
+ result = pipe(prompt, max_new_tokens=max_tokens, return_full_text=False)
440
+ return result[0]["generated_text"].strip()
441
+ except Exception as e:
442
+ return f"Error: {e}"
443
 
444
  # ═══════════════════════════════════════
445
+ # GUI code detection
446
+ # ═══════════════════════════════════════
447
+ GUI_HINTS = ["pygame", "tkinter", "turtle", "pyglet", "arcade", "kivy",
448
+ "display.set_mode", "mainloop()"]
449
+ def is_gui_code(c): return any(h in c for h in GUI_HINTS)
450
+
451
+ # ═══════════════════════════════════════
452
+ # Theme + CSS
453
  # ═══════════════════════════════════════
454
  theme = gr.themes.Default(
455
  primary_hue="cyan", neutral_hue="gray",
456
  font=[gr.themes.GoogleFont("IBM Plex Mono"), "monospace"]
457
+ ).set(
458
+ body_background_fill="#080808", body_text_color="#e0e0e0",
459
+ button_primary_background_fill="#0ff", button_primary_background_fill_hover="#0dd",
460
+ button_primary_text_color="#000",
461
+ button_secondary_background_fill="#1a1a1a", button_secondary_background_fill_hover="#2a2a2a",
462
+ button_secondary_text_color="#0ff", button_secondary_border_color="#0ff4",
463
+ block_background_fill="#0d0d0d", block_border_color="#1a1a1a",
464
+ block_label_text_color="#0f8",
465
+ input_background_fill="#0a0a0a", input_border_color="#1a3a2a", input_placeholder_color="#0f83",
466
+ )
467
 
468
  css = """
469
+ * { border-radius: 0 !important; }
470
  .gradio-container { max-width: 100% !important; }
471
+
472
+ /* Header */
473
+ .axon-header {
474
+ background: linear-gradient(90deg, #080808, #0a1a1a, #080808);
475
+ border-bottom: 1px solid #0ff3; padding: 12px 20px !important; margin-bottom: 8px;
476
+ }
477
+ .axon-header h1 {
478
+ font-family: 'IBM Plex Mono', monospace !important;
479
+ background: linear-gradient(90deg, #0ff, #0f8);
480
+ -webkit-background-clip: text; -webkit-text-fill-color: transparent;
481
+ font-size: 1.4em !important; letter-spacing: 3px; margin: 0 !important;
482
+ }
483
+ .axon-header p { color: #0f84 !important; font-size: 0.75em; letter-spacing: 1px; }
484
+
485
+ /* Terminal */
486
+ .term-box textarea {
487
+ background: #050505 !important; color: #00ff41 !important;
488
+ font-family: 'IBM Plex Mono', monospace !important;
489
+ font-size: 12px !important; line-height: 1.5 !important;
490
+ border: 1px solid #0f31 !important; text-shadow: 0 0 4px #0f32;
491
+ }
492
+ .term-input input {
493
+ background: #050505 !important; color: #00ff41 !important;
494
+ font-family: 'IBM Plex Mono', monospace !important;
495
+ border: 1px solid #0f32 !important; font-size: 12px !important;
496
+ }
497
+ .term-input input::placeholder { color: #0f83 !important; }
498
+ .term-input input:focus { border-color: #0ff !important; box-shadow: 0 0 8px #0ff2 !important; }
499
+
500
+ /* Editor */
501
+ .code-editor textarea, .code-editor .cm-editor {
502
+ font-family: 'IBM Plex Mono', monospace !important; font-size: 13px !important;
503
+ }
504
+
505
+ /* Buttons */
506
+ .tb {
507
+ font-size: 0.8em !important; padding: 6px 12px !important; letter-spacing: 0.5px;
508
+ text-transform: uppercase; font-weight: 600 !important;
509
+ border: 1px solid transparent !important; transition: all 0.2s ease !important;
510
+ }
511
+ .tb:hover { border-color: #0ff !important; box-shadow: 0 0 12px #0ff2 !important; }
512
+
513
+ /* Accordion */
514
+ .gr-accordion { border: 1px solid #1a2a2a !important; background: #0a0a0a !important; }
515
+ .gr-accordion > .label-wrap { background: #0d0d0d !important; border-bottom: 1px solid #1a2a2a !important; }
516
+ .gr-accordion > .label-wrap:hover { background: #121a1a !important; }
517
+ .gr-accordion > .label-wrap span {
518
+ color: #0ff !important; font-family: 'IBM Plex Mono', monospace !important;
519
+ letter-spacing: 1px; font-size: 0.85em;
520
+ }
521
+
522
+ /* Tabs */
523
+ .tabs > .tab-nav > button {
524
+ font-family: 'IBM Plex Mono', monospace !important;
525
+ letter-spacing: 1px; font-size: 0.8em; text-transform: uppercase;
526
+ color: #888 !important; border-bottom: 2px solid transparent !important;
527
+ }
528
+ .tabs > .tab-nav > button.selected {
529
+ color: #0ff !important; border-bottom-color: #0ff !important; text-shadow: 0 0 8px #0ff4;
530
+ }
531
+
532
+ /* Structure + find */
533
+ .struct-box textarea { background: #050505 !important; color: #0ff !important;
534
+ font-family: 'IBM Plex Mono', monospace !important; font-size: 11px !important; }
535
+ .find-box textarea { background: #050505 !important; color: #ffab40 !important;
536
+ font-family: 'IBM Plex Mono', monospace !important; font-size: 11px !important; }
537
+
538
+ /* Status bar */
539
+ .status-bar {
540
+ background: #0a0a0a !important; border-top: 1px solid #1a2a2a;
541
+ padding: 4px 12px !important; font-size: 11px !important; color: #0f84 !important;
542
+ }
543
+ .status-bar strong { color: #0ff !important; }
544
+
545
+ /* Misc */
546
+ ::-webkit-scrollbar { width: 6px; height: 6px; }
547
+ ::-webkit-scrollbar-track { background: #0a0a0a; }
548
+ ::-webkit-scrollbar-thumb { background: #0f83; }
549
+ input, textarea, select { color: #0f8 !important; }
550
+ .stop-btn { background: #a00 !important; color: #fff !important; border: none !important; }
551
+ .stop-btn:hover { background: #c00 !important; }
552
  """
553
 
554
+ # ═══════════════════════════════════════
555
+ # UI
556
+ # ═══════════════════════════════════════
557
  with gr.Blocks(title="Axon Pro", theme=theme, css=css) as demo:
 
 
 
558
 
559
+ with gr.Row(elem_classes="axon-header"):
560
+ gr.Markdown("# AXON PRO\n\nPYTHON AI IDE — v4.0")
561
+
562
+ with gr.Row(equal_height=False):
563
+
564
+ # ══ LEFT SIDEBAR ══
565
  with gr.Column(scale=1, min_width=200):
566
  with gr.Accordion("📁 EXPLORER", open=True):
567
+ file_list = gr.Dropdown(choices=fs.get_all_files(), value=fs.current_file,
568
+ label="Files", interactive=True)
569
  new_file_txt = gr.Textbox(placeholder="new_file.py", show_label=False)
570
  with gr.Row():
571
+ new_btn = gr.Button("NEW", size="sm", elem_classes="tb")
572
+ save_btn = gr.Button("SAVE", size="sm", elem_classes="tb")
573
+ del_btn = gr.Button("DEL", size="sm", elem_classes="tb stop-btn")
574
+
575
  with gr.Accordion("🗺 STRUCTURE", open=True):
576
+ structure_view = gr.Textbox(
577
+ value=analyzer.analyze(fs.get_current_file_content()),
578
+ label="", lines=10, interactive=False, elem_classes="struct-box",
579
+ show_label=False)
580
+
581
  with gr.Accordion("📋 SNIPPETS", open=False):
582
+ snip_list = gr.Dropdown(choices=snippets.get_names(), label="Saved",
583
+ interactive=True)
584
+ snip_name = gr.Textbox(placeholder="Snippet name", show_label=False)
585
+ snip_tags = gr.Textbox(placeholder="Tags: api, util, loop", show_label=False)
586
  with gr.Row():
587
+ snip_save = gr.Button("SAVE", size="sm", elem_classes="tb")
588
+ snip_insert = gr.Button("INSERT", size="sm", elem_classes="tb")
589
+ snip_del = gr.Button("DEL", size="sm", elem_classes="tb stop-btn")
590
+ snip_status = gr.Markdown("")
591
 
592
+ # ══ CENTER ══
593
  with gr.Column(scale=4):
594
+ editor = gr.Code(value=fs.get_current_file_content(),
595
+ label=f" {fs.current_file}", language="python",
596
+ lines=20, interactive=True, elem_classes="code-editor")
597
+
598
  with gr.Row():
599
+ run_btn = gr.Button("▶ RUN", variant="primary", elem_classes="tb")
600
+ stop_btn = gr.Button(" STOP", size="sm", elem_classes="tb stop-btn")
601
+ ai_complete = gr.Button(" COMPLETE", elem_classes="tb")
602
+ ai_explain = gr.Button(" EXPLAIN", elem_classes="tb")
603
+ ai_refactor = gr.Button(" REFACTOR", elem_classes="tb")
604
+
605
+ with gr.Tabs() as bottom_tabs:
606
+
607
+ with gr.Tab("⌘ TERMINAL", id="term-tab"):
608
+ term_out = gr.Textbox(value=terminal.get_log(), lines=12, max_lines=25,
609
+ interactive=False, elem_classes="term-box",
610
+ label="", show_label=False)
611
+ with gr.Row():
612
+ term_in = gr.Textbox(placeholder="$ pip install, git clone, ls ...",
613
+ show_label=False, elem_classes="term-input")
614
+ with gr.Row():
615
+ clear_btn = gr.Button("CLEAR", size="sm", elem_classes="tb")
616
+ hist_dd = gr.Dropdown(choices=[], label="", interactive=True,
617
+ scale=3, container=False)
618
+ hist_btn = gr.Button("↻ HISTORY", size="sm", elem_classes="tb")
619
+
620
+ with gr.Tab("🔍 FIND", id="find-tab"):
621
+ with gr.Row():
622
+ find_query = gr.Textbox(label="Find", scale=4)
623
+ replace_query = gr.Textbox(label="Replace", scale=4)
624
+ find_case = gr.Checkbox(label="Case", value=False)
625
  with gr.Row():
626
+ find_btn = gr.Button("FIND", size="sm", elem_classes="tb")
627
+ find_all_btn = gr.Button("FIND ALL FILES", size="sm", elem_classes="tb")
628
+ replace_btn = gr.Button("REPLACE ALL", size="sm", elem_classes="tb")
629
+ find_results = gr.Textbox(value="", lines=6, interactive=False,
630
+ elem_classes="find-box", label="", show_label=False)
631
+
632
+ with gr.Tab("💬 AI CHAT", id="chat-tab"):
633
+ chat_history = gr.Chatbot(label="", height=250)
634
  with gr.Row():
635
+ chat_input = gr.Textbox(placeholder="Describe code you want...",
636
+ scale=8, container=False)
637
+ send_btn = gr.Button("GEN", variant="primary", scale=1, elem_classes="tb")
638
 
639
+ with gr.Tab("⇄ DIFF", id="diff-tab"):
640
+ diff_view = gr.Code(label="AI Changes", language="python",
641
+ interactive=False, lines=15)
642
  with gr.Row():
643
+ apply_btn = gr.Button(" APPLY", variant="primary", elem_classes="tb")
644
+ discard_btn = gr.Button(" DISCARD", elem_classes="tb")
645
+
646
+ # ══ RIGHT: DISPLAY ══
647
+ with gr.Column(scale=2, min_width=250):
648
+ with gr.Accordion("🖥 DISPLAY OUTPUT", open=True):
649
+ display_image = gr.Image(label="", type="filepath", interactive=False, height=420)
650
+ with gr.Row():
651
+ capture_btn = gr.Button("📸 CAPTURE", size="sm", elem_classes="tb")
652
+ auto_capture = gr.Checkbox(label="Auto (2s)", value=False)
653
+ gui_status = gr.Markdown(
654
+ f"<small>Xvfb: {'● ON' if vdisplay.is_running else '○ OFF'} "
655
+ f"| {SCREEN_W}x{SCREEN_H}</small>")
656
+
657
+ status_bar = gr.Markdown(
658
+ f"**AXON PRO v4.0** │ Python {sys.version.split()[0]} │ CPU │ "
659
+ f"TinyLlama-1.1B │ PTY + Xvfb │ Snippets + Structure + Find",
660
+ elem_classes="status-bar")
661
+
662
+ # State
663
+ diff_original = gr.State("")
664
+ diff_modified = gr.State("")
665
+ auto_timer = gr.Timer(2, active=False)
666
 
667
  # ═══════════════════════════════════════
668
+ # HANDLERS
669
  # ═══════════════════════════════════════
670
 
671
+ # --- File ops ---
672
+ def on_editor_change(content):
673
+ fs.save_file(content)
674
+ return analyzer.analyze(content)
 
675
 
676
+ def on_file_select(filename):
677
+ fs.set_current_file(filename)
678
+ c = fs.get_current_file_content()
679
+ return c, gr.update(label=f" {filename}"), analyzer.analyze(c)
680
+
681
+ def on_new_file(name):
682
+ if not name or not name.strip():
683
+ return gr.update(), gr.update(), gr.update(), gr.update()
684
+ name = name.strip()
685
  fs.create_file(name)
686
+ c = fs.get_current_file_content()
687
+ return (gr.update(choices=fs.get_all_files(), value=name),
688
+ c, gr.update(label=f" {name}"), analyzer.analyze(c))
689
 
690
+ def on_save(code):
691
  fs.save_file(code)
692
+ return (f"**AXON PRO v4.0** │ Python {sys.version.split()[0]} │ CPU │ "
693
+ f"TinyLlama-1.1B │ ✓ Saved {fs.current_file}")
694
+
695
+ def on_delete():
696
+ name = fs.current_file
697
+ if fs.delete_file(name):
698
+ c = fs.get_current_file_content()
699
+ return (gr.update(choices=fs.get_all_files(), value=fs.current_file),
700
+ c, gr.update(label=f" {fs.current_file}"), analyzer.analyze(c))
701
+ return gr.update(), gr.update(), gr.update(), gr.update()
702
+
703
+ # --- Run (smart: auto-detects GUI vs CLI) ---
704
+ def on_run(code):
 
 
 
705
  fs.save_file(code)
 
 
 
 
706
 
707
+ if is_gui_code(code):
708
+ # GUI app — launch as background process, capture display
709
+ msg = gui_mgr.launch(code)
710
+ terminal._append(msg)
711
+ time.sleep(1.5)
712
+ ss = vdisplay.capture()
713
+ return terminal.get_log(), ss, f"<small>Xvfb: ● ON | {gui_mgr.get_status()}</small>"
714
+
715
+ # Normal script — run with subprocess, capture output
716
+ terminal._append("$ python [editor]")
717
+ tmp = "/tmp/_axon_run.py"
718
+ with open(tmp, "w") as f: f.write(code)
719
+ try:
720
+ env = os.environ.copy()
721
+ env["DISPLAY"] = DISPLAY_NUM
722
+ env["PYTHONPATH"] = fs._sync_dir + ":" + env.get("PYTHONPATH", "")
723
+ r = subprocess.run([sys.executable, tmp], capture_output=True, text=True,
724
+ timeout=30, env=env, cwd=fs._sync_dir)
725
+ if r.stdout.strip(): terminal._append(r.stdout.rstrip())
726
+ if r.stderr.strip(): terminal._append(r.stderr.rstrip())
727
+ if not r.stdout.strip() and not r.stderr.strip(): terminal._append("(No output)")
728
+ except subprocess.TimeoutExpired: terminal._append("[Timed out 30s]")
729
+ except Exception as e: terminal._append(f"[Error] {e}")
730
+ return terminal.get_log(), gr.update(), gr.update()
731
+
732
+ def on_stop():
733
+ msg = gui_mgr.stop(); terminal._append(msg)
734
+ return terminal.get_log(), f"<small>Xvfb: {'● ON' if vdisplay.is_running else '○ OFF'}</small>"
735
+
736
+ # --- Terminal ---
737
+ def on_term_cmd(cmd):
738
+ return terminal.run_command(cmd), ""
739
+
740
+ def on_clear():
741
+ terminal.log_lines = []; return ""
742
+
743
+ def on_refresh_hist():
744
+ return gr.update(choices=terminal.get_history())
745
+
746
+ def on_select_hist(cmd):
747
+ return cmd if cmd else gr.update()
748
+
749
+ # --- AI ---
750
+ def on_complete(code):
751
+ result = ai_gen("Complete this Python code. Only output the completion, no explanations.", code)
752
+ new_code = code + "\n" + result
753
+ return new_code, analyzer.analyze(new_code)
754
+
755
+ def on_explain(code):
756
+ explanation = ai_gen("Explain this Python code concisely.", code, 512)
757
+ terminal._append(f"[AI Explanation]\n{explanation}")
758
+ return terminal.get_log()
759
+
760
+ def on_refactor(code):
761
+ refactored = ai_gen("Refactor this Python code for PEP 8 and best practices. Output only code.", code, 512)
762
  fs.save_file(code)
763
+ import difflib
764
+ diff = "\n".join(difflib.unified_diff(code.splitlines(), refactored.splitlines(), lineterm=""))
765
+ return diff, code, refactored
766
+
767
+ def on_generate(prompt_text, history):
768
+ generated = ai_gen(f"Write Python code for: {prompt_text}", "", 512)
769
+ import difflib
770
+ diff = "\n".join(difflib.unified_diff([], generated.splitlines(), lineterm=""))
771
+ new_h = history + [
772
+ {"role": "user", "content": prompt_text},
773
+ {"role": "assistant", "content": "Code generated → check DIFF tab"},
774
+ ]
775
+ return diff, "", generated, new_h, ""
776
+
777
+ def on_apply(modified):
778
+ return modified, analyzer.analyze(modified)
779
+
780
+ # --- Snippets ---
781
+ def on_snip_save(name, tags, code):
782
+ msg = snippets.add(name, code, tags)
783
+ return msg, gr.update(choices=snippets.get_names())
784
+
785
+ def on_snip_insert(name, code):
786
+ snip_code = snippets.get(name)
787
+ if snip_code:
788
+ # Append snippet at cursor position (end of file)
789
+ return code + "\n\n" + snip_code if code.strip() else snip_code
790
+ return code
791
+
792
+ def on_snip_del(name):
793
+ msg = snippets.delete(name)
794
+ return msg, gr.update(choices=snippets.get_names())
795
+
796
+ # --- Find ---
797
+ def on_find(code, query, case):
798
+ return finder.find_in_file(code, query, case)
799
+
800
+ def on_find_all(query):
801
+ return finder.find_all_files(fs.files, query)
802
+
803
+ def on_replace(code, fq, rq, case):
804
+ new_code, msg = finder.replace_in_file(code, fq, rq, case)
805
+ fs.save_file(new_code)
806
+ return new_code, msg, analyzer.analyze(new_code)
807
+
808
+ # --- Display ---
809
+ def on_capture():
810
+ return vdisplay.capture()
811
+
812
+ def on_auto_tick():
813
+ if gui_mgr.process and gui_mgr.process.poll() is None:
814
+ return vdisplay.capture()
815
+ return gr.update()
816
+
817
+ def on_auto_toggle(checked):
818
+ return gr.Timer(2, active=checked)
819
+
820
+ # ═══════════════════════════════════════
821
+ # WIRING
822
+ # ═══════════════════════════════════════
823
+
824
+ # Files
825
+ editor.change(on_editor_change, editor, structure_view)
826
+ file_list.change(on_file_select, file_list, [editor, editor, structure_view])
827
+ new_btn.click(on_new_file, new_file_txt, [file_list, editor, editor, structure_view])
828
+ save_btn.click(on_save, editor, status_bar)
829
+ del_btn.click(on_delete, None, [file_list, editor, editor, structure_view])
830
+
831
+ # Run
832
+ run_btn.click(on_run, editor, [term_out, display_image, gui_status])
833
+ stop_btn.click(on_stop, None, [term_out, gui_status])
834
+
835
+ # Terminal
836
+ term_in.submit(on_term_cmd, term_in, [term_out, term_in])
837
+ clear_btn.click(on_clear, None, term_out)
838
+ hist_btn.click(on_refresh_hist, None, hist_dd)
839
+ hist_dd.change(on_select_hist, hist_dd, term_in)
840
+
841
+ # AI
842
+ ai_complete.click(on_complete, editor, [editor, structure_view])
843
+ ai_explain.click(on_explain, editor, term_out)
844
+ ai_refactor.click(on_refactor, editor, [diff_view, diff_original, diff_modified]
845
+ ).then(lambda: gr.Tabs(selected="diff-tab"), None, bottom_tabs)
846
+
847
+ # Chat
848
+ chat_input.submit(on_generate, [chat_input, chat_history],
849
+ [diff_view, diff_original, diff_modified, chat_history, chat_input]
850
+ ).then(lambda: gr.Tabs(selected="diff-tab"), None, bottom_tabs)
851
+ send_btn.click(on_generate, [chat_input, chat_history],
852
+ [diff_view, diff_original, diff_modified, chat_history, chat_input]
853
+ ).then(lambda: gr.Tabs(selected="diff-tab"), None, bottom_tabs)
854
+
855
+ # Diff
856
+ apply_btn.click(on_apply, diff_modified, [editor, structure_view]
857
+ ).then(lambda: gr.Tabs(selected="term-tab"), None, bottom_tabs)
858
+ discard_btn.click(lambda: (gr.update(), gr.update()), None, [editor, structure_view]
859
+ ).then(lambda: gr.Tabs(selected="term-tab"), None, bottom_tabs)
860
+
861
+ # Snippets
862
+ snip_save.click(on_snip_save, [snip_name, snip_tags, editor], [snip_status, snip_list])
863
+ snip_insert.click(on_snip_insert, [snip_list, editor], editor)
864
+ snip_del.click(on_snip_del, snip_list, [snip_status, snip_list])
865
+
866
+ # Find
867
+ find_btn.click(on_find, [editor, find_query, find_case], find_results)
868
+ find_all_btn.click(on_find_all, find_query, find_results)
869
+ replace_btn.click(on_replace, [editor, find_query, replace_query, find_case],
870
+ [editor, find_results, structure_view])
871
+
872
+ # Display
873
+ capture_btn.click(on_capture, None, display_image)
874
+ auto_capture.change(on_auto_toggle, auto_capture, auto_timer)
875
+ auto_timer.tick(on_auto_tick, None, display_image)
876
+
877
 
878
  if __name__ == "__main__":
879
  import atexit
880
  atexit.register(terminal.cleanup)
881
  atexit.register(gui_mgr.stop)
882
  atexit.register(vdisplay.cleanup)
883
+ demo.queue().launch(server_name="0.0.0.0", server_port=7860,
884
+ ssr_mode=False, allowed_paths=["/tmp"])