AIencoder commited on
Commit
8c7f65b
·
verified ·
1 Parent(s): a2b9b07

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +432 -890
app.py CHANGED
@@ -1,955 +1,459 @@
1
- import warnings
2
- warnings.filterwarnings("ignore", category=FutureWarning, message=".*torch.distributed.reduce_op.*")
3
  import gradio as gr
4
- import torch
5
- import time
6
  import sys
7
  import subprocess
8
- import os
9
- import pty
10
- import select
11
- import signal
12
- import fcntl
13
- import struct
14
- import termios
15
  import threading
16
- import re
17
  import ast
18
- import json
19
- import types
 
20
  import tempfile
21
- from pathlib import Path
22
- from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline, GenerationConfig
23
- from typing import List, Dict, Optional
24
- from functools import lru_cache
25
-
26
- os.environ["TOKENIZERS_PARALLELISM"] = "false"
27
-
28
- MODEL_NAME = "TinyLlama/TinyLlama-1.1B-Chat-v1.0"
29
- DEVICE = "cpu"
30
- DTYPE = torch.float32
31
- DISPLAY_NUM = ":99"
32
- SCREEN_W, SCREEN_H = 800, 600
33
- SNIPPETS_FILE = "/tmp/axon_snippets.json"
34
 
35
  # ═══════════════════════════════════════
36
- # Virtual Display
37
  # ═══════════════════════════════════════
38
- class VirtualDisplay:
 
 
39
  def __init__(self):
40
- self.xvfb_proc = None
41
- self.display = DISPLAY_NUM
42
- self._ensure_deps()
43
- self._start_xvfb()
44
-
45
- def _ensure_deps(self):
46
- needed = []
47
- for cmd in ["Xvfb", "xdpyinfo"]:
48
- if subprocess.run(["which", cmd], capture_output=True).returncode != 0:
49
- needed.extend(["xvfb", "x11-utils"]); break
50
- for cmd in ["scrot", "import"]:
51
- if subprocess.run(["which", cmd], capture_output=True).returncode == 0: break
52
- else:
53
- needed.extend(["scrot", "imagemagick"])
54
- if needed:
55
- print(f"[Xvfb] Installing: {set(needed)}")
56
- subprocess.run(["apt-get", "update", "-qq"], capture_output=True, timeout=60)
57
- subprocess.run(["apt-get", "install", "-y", "-qq"] + list(set(needed)),
58
- capture_output=True, timeout=120)
59
-
60
- def _start_xvfb(self):
61
  try:
62
- subprocess.run(["pkill", "-f", f"Xvfb {self.display}"], capture_output=True, timeout=5)
63
- time.sleep(0.2)
64
- self.xvfb_proc = subprocess.Popen(
65
- ["Xvfb", self.display, "-screen", "0", f"{SCREEN_W}x{SCREEN_H}x24",
66
- "-ac", "-nolisten", "tcp"],
67
- stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
68
- time.sleep(0.5)
69
- if self.xvfb_proc.poll() is None:
70
- print(f"[Xvfb] Running on {self.display}")
71
- os.environ["DISPLAY"] = self.display
72
- else:
73
- self.xvfb_proc = None
74
  except Exception as e:
75
- print(f"[Xvfb] {e}"); self.xvfb_proc = None
76
 
77
  def capture(self):
78
- if not self.is_running: return None
79
- tmp = tempfile.NamedTemporaryFile(suffix=".png", delete=False); tmp.close()
80
- for cmd in [
81
- ["scrot", "--display", self.display, tmp.name],
82
- f"xwd -root -display {self.display} | convert xwd:- png:{tmp.name}",
83
- ["import", "-window", "root", "-display", self.display, tmp.name],
84
- ]:
85
- try:
86
- kw = {"shell": True} if isinstance(cmd, str) else {}
87
- r = subprocess.run(cmd, capture_output=True, timeout=5,
88
- env={**os.environ, "DISPLAY": self.display}, **kw)
89
- if r.returncode == 0 and os.path.exists(tmp.name) and os.path.getsize(tmp.name) > 200:
90
- return tmp.name
91
- except: pass
92
- try: os.unlink(tmp.name)
93
- except: pass
94
- return None
 
95
 
96
- @property
97
- def is_running(self): return self.xvfb_proc is not None and self.xvfb_proc.poll() is None
98
  def cleanup(self):
99
- if self.xvfb_proc:
100
- self.xvfb_proc.terminate()
101
- try: self.xvfb_proc.wait(timeout=3)
102
- except: self.xvfb_proc.kill()
103
-
104
- vdisplay = VirtualDisplay()
105
 
106
- # ═══════════════════════════════════════
107
- # GUI Process Manager
108
- # ═══════════════════════════════════════
109
- class GUIProcessManager:
110
- def __init__(self):
111
- self.process = None
112
- def launch(self, code):
113
- self.stop()
114
- tmp = "/tmp/_axon_gui_run.py"
115
- with open(tmp, "w") as f: f.write(code)
116
- env = os.environ.copy()
117
- env.update({"DISPLAY": DISPLAY_NUM, "SDL_VIDEODRIVER": "x11"})
118
- try:
119
- self.process = subprocess.Popen([sys.executable, tmp], stdout=subprocess.PIPE,
120
- stderr=subprocess.PIPE, env=env, preexec_fn=os.setsid)
121
- time.sleep(1.0)
122
- if self.process.poll() is not None:
123
- out, err = self.process.communicate(timeout=2)
124
- msg = (out.decode(errors="replace") + "\n" + err.decode(errors="replace")).strip()
125
- return f"[Exited immediately]\n{msg}" if msg else "[Exited]"
126
- return f"[GUI launched — PID {self.process.pid}] CAPTURE to see output."
127
- except Exception as e: return f"[Error] {e}"
128
- def stop(self):
129
- if self.process and self.process.poll() is None:
130
- pid = self.process.pid
131
- try: os.killpg(os.getpgid(pid), signal.SIGTERM); self.process.wait(timeout=3)
132
- except:
133
- try: os.killpg(os.getpgid(pid), signal.SIGKILL)
134
- except: pass
135
- self.process = None; return f"[Stopped PID {pid}]"
136
- self.process = None; return "[No process]"
137
- @property
138
- def is_running(self): return self.process is not None and self.process.poll() is None
139
- def get_status(self): return f"● PID {self.process.pid}" if self.is_running else "○ idle"
140
-
141
- gui_mgr = GUIProcessManager()
142
-
143
- # ═══════════════════════════════════════
144
- # File System (with cross-file imports)
145
- # ═══════════════════════════════════════
146
  class FileSystem:
147
- def __init__(self):
148
- self.files: Dict[str, str] = {
149
- "main.py": '# Start coding here\nfrom utils import add\n\nresult = add(3, 7)\nprint(f"3 + 7 = {result}")',
150
- "utils.py": "# Utility functions\ndef add(a, b):\n return a + b\n\ndef multiply(a, b):\n return a * b",
151
- "notes.txt": "Project requirements:\n- Build a cool AI app\n- Use Gradio"
152
- }
153
- self.current_file = "main.py"
154
- self._sync_dir = "/tmp/axon_workspace"
155
- self._sync_to_disk()
156
-
157
- def _sync_to_disk(self):
158
- """Write all virtual files to disk so cross-file imports work."""
159
- os.makedirs(self._sync_dir, exist_ok=True)
160
- for name, content in self.files.items():
161
- path = os.path.join(self._sync_dir, name)
162
- with open(path, "w") as f:
163
- f.write(content)
 
 
 
 
 
 
 
 
 
 
 
 
 
164
 
165
  def save_file(self, content):
166
- if self.current_file:
167
- self.files[self.current_file] = content
168
- # Sync to disk for imports
169
- path = os.path.join(self._sync_dir, self.current_file)
170
- os.makedirs(os.path.dirname(path) if "/" in self.current_file else self._sync_dir, exist_ok=True)
171
- with open(path, "w") as f:
172
- f.write(content)
173
-
174
- def get_current_file_content(self): return self.files.get(self.current_file, "")
175
- def set_current_file(self, f):
176
- if f in self.files: self.current_file = f
177
- def create_file(self, f, c=""):
178
- if f not in self.files:
179
- self.files[f] = c; self.current_file = f
180
- self._sync_to_disk()
181
- def delete_file(self, f):
182
- if f in self.files and len(self.files) > 1:
183
- del self.files[f]
184
- path = os.path.join(self._sync_dir, f)
185
- if os.path.exists(path): os.unlink(path)
186
- self.current_file = list(self.files.keys())[0]
187
- return True
188
- return False
189
- def rename_file(self, old, new):
190
- if old in self.files and new not in self.files:
191
- self.files[new] = self.files.pop(old)
192
- old_path = os.path.join(self._sync_dir, old)
193
- if os.path.exists(old_path): os.unlink(old_path)
194
- self._sync_to_disk()
195
- if self.current_file == old: self.current_file = new
196
  return True
197
  return False
198
- def get_all_files(self): return list(self.files.keys())
199
 
200
- fs = FileSystem()
201
-
202
- # ═══════════════════════════════════════
203
- # Snippet Library
204
- # ═══════════════════════════════════════
205
- class SnippetLibrary:
206
  def __init__(self):
207
- self.snippets: Dict[str, dict] = {}
208
- self._load()
209
-
210
- def _load(self):
 
211
  try:
212
- if os.path.exists(SNIPPETS_FILE):
213
- with open(SNIPPETS_FILE) as f:
214
- self.snippets = json.load(f)
215
- except: self.snippets = {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
 
217
- def _save(self):
 
 
 
 
 
 
 
218
  try:
219
- with open(SNIPPETS_FILE, "w") as f:
220
- json.dump(self.snippets, f, indent=2)
221
- except: pass
222
-
223
- def add(self, name: str, code: str, tags: str = "") -> str:
224
- if not name.strip(): return "Error: name required"
225
- self.snippets[name.strip()] = {
226
- "code": code,
227
- "tags": [t.strip() for t in tags.split(",") if t.strip()],
228
- "created": time.strftime("%Y-%m-%d %H:%M"),
229
- }
230
- self._save()
231
- return f"Saved snippet: {name.strip()}"
232
-
233
- def get(self, name: str) -> str:
234
- s = self.snippets.get(name)
235
- return s["code"] if s else ""
236
-
237
- def delete(self, name: str) -> str:
238
- if name in self.snippets:
239
- del self.snippets[name]
240
- self._save()
241
- return f"Deleted: {name}"
242
- return "Not found"
243
-
244
- def search(self, query: str) -> List[str]:
245
- q = query.lower()
246
- results = []
247
- for name, data in self.snippets.items():
248
- if q in name.lower() or any(q in t.lower() for t in data.get("tags", [])):
249
- results.append(name)
250
- return results if query else list(self.snippets.keys())
251
-
252
- def list_all(self) -> str:
253
- if not self.snippets: return "No snippets saved yet."
254
- lines = []
255
- for name, data in self.snippets.items():
256
- tags = ", ".join(data.get("tags", []))
257
- lines.append(f" {name} [{tags}] ({data.get('created', '?')})")
258
- return "\n".join(lines)
259
-
260
- def get_names(self) -> List[str]:
261
- return list(self.snippets.keys())
262
 
263
- snippets = SnippetLibrary()
 
 
 
 
 
264
 
265
- # ═══════════════════════════════════════
266
- # Code Structure Analyzer
267
- # ═══════════════════════════════════════
268
  class CodeAnalyzer:
269
- """Parses Python code and extracts structure (functions, classes, imports)."""
270
-
271
- @staticmethod
272
- def analyze(code: str) -> str:
273
- if not code.strip(): return "Empty file"
274
  try:
275
  tree = ast.parse(code)
276
- except SyntaxError as e:
277
- return f"⚠ Syntax error: line {e.lineno}: {e.msg}"
278
-
279
- lines = []
280
- imports = []
281
- functions = []
282
- classes = []
283
-
284
- for node in ast.walk(tree):
285
- if isinstance(node, ast.Import):
286
- for alias in node.names:
287
- imports.append(alias.name)
288
- elif isinstance(node, ast.ImportFrom):
289
- imports.append(f"from {node.module} import {', '.join(a.name for a in node.names)}")
290
- elif isinstance(node, ast.FunctionDef) or isinstance(node, ast.AsyncFunctionDef):
291
- # Check if it's a method (inside a class)
292
- args = ", ".join(a.arg for a in node.args.args)
293
- prefix = "async " if isinstance(node, ast.AsyncFunctionDef) else ""
294
- decorators = [f"@{ast.dump(d)}" for d in node.decorator_list] if hasattr(node, 'decorator_list') else []
295
- ret = ""
296
- if node.returns:
297
- try: ret = f" → {ast.unparse(node.returns)}"
298
- except: ret = " ?"
299
- functions.append((node.lineno, f"{prefix}def {node.name}({args}){ret}"))
300
- elif isinstance(node, ast.ClassDef):
301
- bases = ", ".join(ast.unparse(b) for b in node.bases) if node.bases else ""
302
- bases_str = f"({bases})" if bases else ""
303
- methods = []
304
- for item in node.body:
305
- if isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef)):
306
- methods.append(item.name)
307
- classes.append((node.lineno, node.name + bases_str, methods))
308
-
309
- # Count stats
310
- total_lines = len(code.splitlines())
311
- non_empty = len([l for l in code.splitlines() if l.strip()])
312
-
313
- # Build output
314
- lines.append(f"📊 {total_lines} lines ({non_empty} non-empty)")
315
- lines.append("")
316
-
317
- if imports:
318
- lines.append(f"📦 IMPORTS ({len(imports)})")
319
- for i in imports:
320
- lines.append(f" {i}")
321
- lines.append("")
322
-
323
- if classes:
324
- lines.append(f"🏗 CLASSES ({len(classes)})")
325
- for lineno, name, methods in classes:
326
- lines.append(f" L{lineno}: class {name}")
327
- for m in methods:
328
- lines.append(f" .{m}()")
329
- lines.append("")
330
-
331
- if functions:
332
- # Filter out class methods (they're shown under classes)
333
- class_methods = set()
334
- for _, _, methods in classes:
335
- class_methods.update(methods)
336
- standalone = [(ln, f) for ln, f in functions if not any(
337
- f.split("(")[0].replace("def ", "").replace("async ", "").strip() in class_methods
338
- for _ in [None]
339
- )]
340
- # Actually just show all functions for simplicity
341
- lines.append(f"⚡ FUNCTIONS ({len(functions)})")
342
- for lineno, sig in functions:
343
- lines.append(f" L{lineno}: {sig}")
344
-
345
- if not imports and not functions and not classes:
346
- lines.append("(No functions, classes, or imports found)")
347
-
348
- return "\n".join(lines)
349
 
350
- analyzer = CodeAnalyzer()
 
 
 
 
 
 
 
 
351
 
352
- # ═══════════════════════════════════════
353
- # Find & Replace
354
- # ═══════════════════════════════════════
355
  class FindReplace:
356
- @staticmethod
357
- def find_in_file(code: str, query: str, case_sensitive: bool = False) -> str:
358
- if not query: return ""
359
- lines = code.splitlines()
 
 
 
 
360
  results = []
361
- for i, line in enumerate(lines, 1):
362
- check_line = line if case_sensitive else line.lower()
363
- check_query = query if case_sensitive else query.lower()
364
- if check_query in check_line:
365
- results.append(f" L{i}: {line.strip()}")
366
- if results:
367
- return f"Found {len(results)} match(es):\n" + "\n".join(results)
368
- return "No matches found."
369
-
370
- @staticmethod
371
- def find_in_all_files(files: Dict[str, str], query: str) -> str:
372
- if not query: return ""
373
- results = []
374
- for fname, content in files.items():
375
- lines = content.splitlines()
376
- for i, line in enumerate(lines, 1):
377
- if query.lower() in line.lower():
378
- results.append(f" {fname}:L{i}: {line.strip()}")
379
- if results:
380
- return f"Found {len(results)} match(es) across all files:\n" + "\n".join(results)
381
- return "No matches found."
382
-
383
- @staticmethod
384
- def replace_in_file(code: str, find: str, replace: str, case_sensitive: bool = False) -> tuple:
385
- if not find: return code, "Nothing to find."
386
- if case_sensitive:
387
- count = code.count(find)
388
- new_code = code.replace(find, replace)
389
- else:
390
- pattern = re.compile(re.escape(find), re.IGNORECASE)
391
- matches = pattern.findall(code)
392
- count = len(matches)
393
- new_code = pattern.sub(replace, code)
394
- return new_code, f"Replaced {count} occurrence(s)."
395
 
396
- finder = FindReplace()
 
 
 
 
397
 
398
  # ═══════════════════════════════════════
399
- # PTY Terminal (with history)
400
  # ═══════════════════════════════════════
401
- class PTYTerminal:
402
- STRIP_ANSI = re.compile(r'\x1b\[[0-9;]*[a-zA-Z]|\x1b\].*?\x07|\x1b\[.*?[@-~]|\r')
403
- def __init__(self):
404
- self.master_fd = None; self.pid = None
405
- self.lock = threading.Lock(); self.log_lines = []; self.max_lines = 800
406
- self.cmd_history: List[str] = []
407
- self.max_history = 100
408
- self._spawn_shell()
409
- self._append("AXON TERMINAL v4.0")
410
- self._append("══════════════════════════════════════")
411
- self._append("Full PTY shell · Cross-file imports")
412
- self._append(" pip/npm/apt-get/git/curl/make")
413
- self._append(" GUI apps → Display · History → ↑↓")
414
- self._append("══════════════════════════════════════")
415
-
416
- def _spawn_shell(self):
417
- try:
418
- pid, fd = pty.openpty(); self.pid = os.fork()
419
- if self.pid == 0:
420
- os.close(pid); os.setsid(); fcntl.ioctl(fd, termios.TIOCSCTTY, 0)
421
- os.dup2(fd,0); os.dup2(fd,1); os.dup2(fd,2)
422
- if fd>2: os.close(fd)
423
- env = os.environ.copy()
424
- env.update({"TERM":"dumb","PS1":"$ ","DEBIAN_FRONTEND":"noninteractive","DISPLAY":DISPLAY_NUM})
425
- os.execvpe("/bin/bash",["/bin/bash","--norc","--noprofile","-i"],env)
426
- else:
427
- os.close(fd); self.master_fd = pid
428
- fl = fcntl.fcntl(self.master_fd, fcntl.F_GETFL)
429
- fcntl.fcntl(self.master_fd, fcntl.F_SETFL, fl|os.O_NONBLOCK)
430
- fcntl.ioctl(self.master_fd, termios.TIOCSWINSZ, struct.pack("HHHH",40,120,0,0))
431
- time.sleep(0.3); self._read(0.2)
432
- except Exception as e: self._append(f"[Shell Error] {e}"); self.master_fd=None
433
-
434
- def _read(self, timeout=0.1):
435
- if not self.master_fd: return ""
436
- out=[]; dl=time.time()+timeout
437
- while True:
438
- r=dl-time.time()
439
- if r<=0: break
440
- try:
441
- rd,_,_=select.select([self.master_fd],[],[],min(r,0.05))
442
- if rd:
443
- c=os.read(self.master_fd,4096)
444
- if c: out.append(c.decode("utf-8",errors="replace")); dl=time.time()+0.15
445
- else: break
446
- elif out: break
447
- except OSError: break
448
- return "".join(out)
449
-
450
- def _clean(self, t):
451
- c=self.STRIP_ANSI.sub("",t); return "".join(ch for ch in c if ch in "\n\t" or ord(ch)>=32)
452
- def _append(self, t):
453
- self.log_lines.append(t)
454
- while len(self.log_lines)>self.max_lines: self.log_lines.pop(0)
455
- def get_log(self): return "\n".join(self.log_lines)
456
-
457
- def run_command(self, cmd):
458
- cmd=cmd.strip()
459
- if not cmd: return self.get_log()
460
- # Save to history
461
- if not self.cmd_history or self.cmd_history[-1] != cmd:
462
- self.cmd_history.append(cmd)
463
- if len(self.cmd_history) > self.max_history:
464
- self.cmd_history.pop(0)
465
- with self.lock:
466
- if cmd.lower()=="clear": self.log_lines=[]; return ""
467
- if cmd.lower()=="history":
468
- self._append("Command history:")
469
- for i, c in enumerate(self.cmd_history[-20:], 1):
470
- self._append(f" {i:3d} {c}")
471
- return self.get_log()
472
- if not self.master_fd:
473
- self._append(f"$ {cmd}"); self._append("[Error] No shell."); return self.get_log()
474
- self._read(0.05); self._append(f"$ {cmd}")
475
- try: os.write(self.master_fd,(cmd+"\n").encode())
476
- except OSError as e: self._append(f"[Write Error] {e}"); return self.get_log()
477
- parts=cmd.split(); base=parts[0].lower() if parts else ""
478
- lng=["pip","pip3","npm","npx","apt-get","apt","git","wget","curl","make","cmake","cargo","yarn","conda"]
479
- wait=180 if base in lng else(60 if base in("python","python3","node")else 15)
480
- chunks=[]; start=time.time(); idle=0
481
- while time.time()-start<wait:
482
- c=self._read(0.3)
483
- if c: idle=0; chunks.append(c)
484
- else:
485
- idle+=1
486
- if base not in lng and idle>=3: break
487
- if base in lng and idle>=10: break
488
- if chunks and "".join(chunks).rstrip().endswith("$"): break
489
- raw=self._clean("".join(chunks)); lines=raw.split("\n"); filtered=[]; skip=True
490
- for l in lines:
491
- s=l.strip()
492
- if skip and s==cmd.strip(): skip=False; continue
493
- if s in("$","$ "): continue
494
- filtered.append(l)
495
- result="\n".join(filtered).strip()
496
- if result: self._append(result)
497
- return self.get_log()
498
 
499
- def get_history_choices(self):
500
- return list(reversed(self.cmd_history[-30:]))
501
 
502
- def cleanup(self):
503
- if self.pid and self.pid>0:
504
- try: os.kill(self.pid, signal.SIGTERM)
505
- except: pass
506
- if self.master_fd:
507
- try: os.close(self.master_fd)
508
- except: pass
509
-
510
- terminal = PTYTerminal()
511
 
512
  # ═══════════════════════════════════════
513
- # Model
514
  # ═══════════════════════════════════════
515
- @lru_cache(maxsize=1)
516
- def load_model():
517
- print("Loading TinyLlama..."); t0=time.time()
518
- try:
519
- tok=AutoTokenizer.from_pretrained(MODEL_NAME)
520
- mdl=AutoModelForCausalLM.from_pretrained(MODEL_NAME,dtype=DTYPE,device_map=DEVICE,low_cpu_mem_usage=True)
521
- gc=GenerationConfig(max_new_tokens=256,temperature=0.2,top_p=0.95,do_sample=True)
522
- pipe=pipeline("text-generation",model=mdl,tokenizer=tok,generation_config=gc)
523
- print(f"Loaded in {time.time()-t0:.1f}s"); return pipe
524
- except Exception as e: print(f"Model error: {e}"); return None
525
-
526
- def _llm(s,u,mt=512):
527
- m=load_model()
528
- if not m: return "Error: model not loaded"
529
- try: return m(f"<|system|>{s}</s><|user|>\n{u}\n</s><|assistant|>",max_new_tokens=mt)[0]["generated_text"].strip()
530
- except Exception as e: return str(e)
531
-
532
- def generate_completion(c): return _llm("Complete this Python code. No explanations.",c,256)
533
- def explain_code(c): return _llm("Explain this Python code concisely.",c)
534
- def refactor_code(c): return _llm("Refactor for PEP 8 and best practices.",c)
535
- def generate_code(p): return _llm(f"Write Python code for: {p}","")
536
- def create_diff_view(o,m):
537
- import difflib; return "\n".join(difflib.unified_diff(o.splitlines(),m.splitlines(),lineterm=""))
538
-
539
- GUI_HINTS=["pygame","tkinter","turtle","pyglet","arcade","kivy","wx","Gtk","QApplication",
540
- "display.set_mode","mainloop()"]
541
- def is_gui_code(c): return any(h in c for h in GUI_HINTS)
542
 
543
- # ═══════════════════════════════════════
544
- # Theme + CSS
545
- # ═══════════════════════════════════════
546
- theme = gr.themes.Default(
547
- font=[gr.themes.GoogleFont("IBM Plex Mono"),"monospace"],
548
- primary_hue="cyan", neutral_hue="gray",
549
- ).set(
550
- body_background_fill="#080808", body_text_color="#e0e0e0",
551
- button_primary_background_fill="#0ff", button_primary_background_fill_hover="#0dd",
552
- button_primary_text_color="#000",
553
- button_secondary_background_fill="#1a1a1a", button_secondary_background_fill_hover="#2a2a2a",
554
- button_secondary_text_color="#0ff", button_secondary_border_color="#0ff4",
555
- block_background_fill="#0d0d0d", block_border_color="#1a1a1a",
556
- block_label_text_color="#0f8",
557
- input_background_fill="#0a0a0a", input_border_color="#1a3a2a", input_placeholder_color="#0f83",
558
  )
559
 
560
  css = """
561
- * { border-radius: 0 !important; }
562
- .gradio-container { max-width: 100% !important; }
563
- .axon-header {
564
- background: linear-gradient(90deg, #080808 0%, #0a1a1a 50%, #080808 100%);
565
- border-bottom: 1px solid #0ff3; padding: 12px 20px !important; margin-bottom: 8px;
566
- }
567
- .axon-header h1 {
568
- font-family: 'IBM Plex Mono', monospace !important;
569
- background: linear-gradient(90deg, #0ff, #0f8);
570
- -webkit-background-clip: text; -webkit-text-fill-color: transparent;
571
- font-size: 1.4em !important; letter-spacing: 3px; margin: 0 !important;
572
- }
573
- .axon-header p { color: #0f84 !important; font-size: 0.75em; letter-spacing: 1px; }
574
- .terminal-box textarea {
575
- background: #050505 !important; color: #00ff41 !important;
576
- font-family: 'IBM Plex Mono', monospace !important;
577
- font-size: 12px !important; line-height: 1.5 !important;
578
- border: 1px solid #0f31 !important; text-shadow: 0 0 4px #0f32;
579
- }
580
- .terminal-input input {
581
- background: #050505 !important; color: #00ff41 !important;
582
- font-family: 'IBM Plex Mono', monospace !important;
583
- border: 1px solid #0f32 !important; font-size: 12px !important;
584
- }
585
- .terminal-input input::placeholder { color: #0f83 !important; }
586
- .terminal-input input:focus { border-color: #0ff !important; box-shadow: 0 0 8px #0ff2 !important; }
587
- .code-editor textarea, .code-editor .cm-editor {
588
- font-family: 'IBM Plex Mono', monospace !important; font-size: 13px !important;
589
- }
590
- .toolbar-btn {
591
- font-size: 0.8em !important; padding: 6px 12px !important; letter-spacing: 0.5px;
592
- text-transform: uppercase; font-weight: 600 !important;
593
- border: 1px solid transparent !important; transition: all 0.2s ease !important;
594
- }
595
- .toolbar-btn:hover { border-color: #0ff !important; box-shadow: 0 0 12px #0ff2 !important; }
596
- .gr-accordion { border: 1px solid #1a2a2a !important; background: #0a0a0a !important; }
597
- .gr-accordion > .label-wrap {
598
- background: #0d0d0d !important; border-bottom: 1px solid #1a2a2a !important;
599
- }
600
- .gr-accordion > .label-wrap:hover { background: #121a1a !important; }
601
- .gr-accordion > .label-wrap span {
602
- color: #0ff !important; font-family: 'IBM Plex Mono', monospace !important;
603
- letter-spacing: 1px; font-size: 0.85em;
604
- }
605
- .tabs > .tab-nav > button {
606
- font-family: 'IBM Plex Mono', monospace !important;
607
- letter-spacing: 1px; font-size: 0.8em; text-transform: uppercase;
608
- color: #888 !important; border-bottom: 2px solid transparent !important;
609
- }
610
- .tabs > .tab-nav > button.selected {
611
- color: #0ff !important; border-bottom-color: #0ff !important; text-shadow: 0 0 8px #0ff4;
612
- }
613
- .status-bar {
614
- background: #0a0a0a !important; border-top: 1px solid #1a2a2a;
615
- padding: 4px 12px !important; font-size: 11px !important; color: #0f84 !important;
616
- }
617
- .status-bar strong { color: #0ff !important; }
618
- ::-webkit-scrollbar { width: 6px; height: 6px; }
619
- ::-webkit-scrollbar-track { background: #0a0a0a; }
620
- ::-webkit-scrollbar-thumb { background: #0f83; }
621
- input, textarea, select { color: #0f8 !important; }
622
- .stop-btn { background: #a00 !important; color: #fff !important; border: none !important; }
623
- .stop-btn:hover { background: #c00 !important; }
624
- /* Structure map */
625
- .structure-box textarea {
626
- background: #050505 !important; color: #0ff !important;
627
- font-family: 'IBM Plex Mono', monospace !important; font-size: 11px !important;
628
- }
629
- /* Snippet code */
630
- .snippet-code textarea {
631
- background: #050505 !important; color: #c8e6c9 !important;
632
- font-family: 'IBM Plex Mono', monospace !important; font-size: 12px !important;
633
- }
634
- /* Find results */
635
- .find-results textarea {
636
- background: #050505 !important; color: #ffab40 !important;
637
- font-family: 'IBM Plex Mono', monospace !important; font-size: 11px !important;
638
- }
639
- /* History dropdown */
640
- .history-dd { font-size: 12px !important; }
641
  """
642
 
643
  # ═══════════════════════════════════════
644
- # UI
645
  # ═══════════════════════════════════════
646
- with gr.Blocks(title="Axon Pro — Python AI IDE") as demo:
647
-
648
- with gr.Row(elem_classes="axon-header"):
649
- gr.Markdown("# ⬡ AXON PRO\n\nPYTHON AI IDE — v4.0")
650
-
651
- with gr.Row(equal_height=False):
652
-
653
- # ══ LEFT SIDEBAR ══
654
- with gr.Column(scale=1, min_width=200):
655
-
656
- # File Explorer
657
- with gr.Accordion("📁 EXPLORER", open=True):
658
- file_list = gr.Dropdown(choices=fs.get_all_files(), value=fs.current_file,
659
- label="", interactive=True, container=False)
660
- new_file_name = gr.Textbox(placeholder="filename.py", label="", container=False)
661
- with gr.Row():
662
- new_file_btn = gr.Button("+ NEW", variant="secondary", size="sm", elem_classes="toolbar-btn")
663
- save_btn = gr.Button("SAVE", variant="secondary", size="sm", elem_classes="toolbar-btn")
664
- del_file_btn = gr.Button("✗ DEL", variant="secondary", size="sm", elem_classes="toolbar-btn stop-btn")
665
-
666
- # Code Structure
667
- with gr.Accordion("🗺 STRUCTURE", open=True):
668
- structure_view = gr.Textbox(
669
- value=analyzer.analyze(fs.get_current_file_content()),
670
- lines=10, max_lines=20, interactive=False,
671
- elem_classes="structure-box", label="", show_label=False)
672
-
673
- # Snippets
674
- with gr.Accordion("📋 SNIPPETS", open=False):
675
- snippet_list = gr.Dropdown(choices=snippets.get_names(), label="Saved Snippets",
676
- interactive=True, container=False, elem_classes="history-dd")
677
- snippet_search = gr.Textbox(placeholder="Search snippets...", label="", container=False)
678
- with gr.Row():
679
- snippet_insert_btn = gr.Button("INSERT", variant="primary", size="sm", elem_classes="toolbar-btn")
680
- snippet_del_btn = gr.Button("DEL", variant="secondary", size="sm", elem_classes="toolbar-btn stop-btn")
681
- with gr.Accordion("Save New Snippet", open=False):
682
- snippet_name = gr.Textbox(placeholder="snippet name", label="", container=False)
683
- snippet_tags = gr.Textbox(placeholder="tags: api, util, loop", label="", container=False)
684
- snippet_save_btn = gr.Button("SAVE SNIPPET", variant="primary", size="sm", elem_classes="toolbar-btn")
685
- snippet_status = gr.Markdown("")
686
 
687
- # ══ CENTER ══
688
- with gr.Column(scale=4):
689
- editor = gr.Code(value=fs.get_current_file_content(), label=f" {fs.current_file}",
690
- language="python", lines=22, interactive=True, elem_classes="code-editor")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
691
 
 
 
 
 
 
 
 
692
  with gr.Row():
693
- run_btn = gr.Button("▶ RUN", variant="primary", elem_classes="toolbar-btn")
694
- run_gui_btn = gr.Button("🖥 GUI", variant="primary", elem_classes="toolbar-btn")
695
- stop_gui_btn = gr.Button(" STOP", variant="secondary", size="sm", elem_classes="toolbar-btn stop-btn")
696
- complete_btn = gr.Button(" COMPLETE", variant="secondary", elem_classes="toolbar-btn")
697
- explain_btn = gr.Button("◈ EXPLAIN", variant="secondary", elem_classes="toolbar-btn")
698
- refactor_btn = gr.Button("⟲ REFACTOR", variant="secondary", elem_classes="toolbar-btn")
699
-
700
- with gr.Tabs() as bottom_tabs:
701
-
702
- with gr.Tab("⌘ TERMINAL", id="terminal-tab"):
703
- terminal_output = gr.Textbox(value=terminal.get_log(), lines=10, max_lines=25,
704
- interactive=False, elem_classes="terminal-box", label="", show_label=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
705
  with gr.Row():
706
- terminal_input = gr.Textbox(
707
- placeholder="$ pip install pygame | npm i | git clone ...",
708
- scale=8, container=False, elem_classes="terminal-input")
709
- terminal_run_btn = gr.Button("⏎", variant="primary", scale=1, min_width=45)
710
  with gr.Row():
711
- clear_btn = gr.Button("CLEAR", variant="secondary", size="sm", elem_classes="toolbar-btn")
712
- history_dd = gr.Dropdown(choices=[], label="", container=False,
713
- interactive=True, scale=3, elem_classes="history-dd")
714
- history_refresh = gr.Button("↻ HISTORY", variant="secondary", size="sm",
715
- elem_classes="toolbar-btn")
716
 
717
- with gr.Tab("🔍 FIND", id="find-tab"):
 
 
 
 
 
 
 
 
718
  with gr.Row():
719
- find_query = gr.Textbox(placeholder="Search for...", label="Find", scale=4)
720
- replace_query = gr.Textbox(placeholder="Replace with...", label="Replace", scale=4)
721
- find_case = gr.Checkbox(label="Case sensitive", value=False)
 
 
 
 
 
722
  with gr.Row():
723
- find_btn = gr.Button("FIND", variant="primary", size="sm", elem_classes="toolbar-btn")
724
- find_all_btn = gr.Button("FIND ALL FILES", variant="secondary", size="sm", elem_classes="toolbar-btn")
725
- replace_btn = gr.Button("REPLACE ALL", variant="secondary", size="sm", elem_classes="toolbar-btn")
726
- find_results = gr.Textbox(value="", lines=6, interactive=False,
727
- elem_classes="find-results", label="", show_label=False)
728
-
729
- with gr.Tab("💬 AI CHAT", id="chat-tab"):
730
- chat_history = gr.Chatbot(label="", height=250)
731
  with gr.Row():
732
- chat_input = gr.Textbox(placeholder="Describe code you want...",
733
- scale=8, container=False)
734
- send_btn = gr.Button("GEN", variant="primary", scale=1, elem_classes="toolbar-btn")
 
735
 
736
- with gr.Tab("⇄ DIFF", id="diff-tab"):
737
- diff_view = gr.Code(label="AI Changes", language="python", interactive=False, lines=15)
 
738
  with gr.Row():
739
- apply_btn = gr.Button(" APPLY", variant="primary", elem_classes="toolbar-btn")
740
- discard_btn = gr.Button("✗ DISCARD", variant="secondary", elem_classes="toolbar-btn")
741
-
742
- # ══ RIGHT: DISPLAY ══
743
- with gr.Column(scale=2, min_width=250):
744
- with gr.Accordion("🖥 DISPLAY OUTPUT", open=True):
745
- display_image = gr.Image(label="", type="filepath", interactive=False, height=420)
746
- with gr.Row():
747
- capture_btn = gr.Button("📸 CAPTURE", variant="secondary", size="sm", elem_classes="toolbar-btn")
748
- auto_capture = gr.Checkbox(label="Auto (2s)", value=False)
749
- gui_status = gr.Markdown(
750
- f"<small>Xvfb: {'● ON' if vdisplay.is_running else '○ OFF'} "
751
- f"| {SCREEN_W}x{SCREEN_H} | {gui_mgr.get_status()}</small>")
752
-
753
- status_bar = gr.Markdown(
754
- f"**AXON PRO v4.0** │ Python {sys.version.split()[0]} │ CPU │ "
755
- f"TinyLlama-1.1B │ PTY + Xvfb │ Snippets + Structure + Find",
756
- elem_classes="status-bar")
757
-
758
- # State
759
- diff_original_state = gr.State("")
760
- diff_modified_state = gr.State("")
761
- auto_timer = gr.Timer(2, active=False)
762
 
763
  # ═══════════════════════════════════════
764
- # HANDLERS
765
  # ═══════════════════════════════════════
 
 
 
 
 
 
766
 
767
- def update_file(content):
768
- fs.save_file(content)
769
- return analyzer.analyze(content)
770
 
771
- def load_file(filename):
772
- fs.set_current_file(filename)
773
- content = fs.get_current_file_content()
774
- return content, gr.update(label=f" {filename}"), analyzer.analyze(content)
775
 
776
- def create_file_fn(name):
777
- if name and ("." in name or name.endswith("")):
778
- fs.create_file(name)
779
- content = fs.get_current_file_content()
780
- return (gr.update(choices=fs.get_all_files(), value=name),
781
- content, gr.update(label=f" {name}"), analyzer.analyze(content))
782
- return gr.update(choices=fs.get_all_files()), fs.get_current_file_content(), gr.update(), gr.update()
783
 
784
- def delete_file_fn():
785
- name = fs.current_file
786
  if fs.delete_file(name):
 
787
  content = fs.get_current_file_content()
788
- return (gr.update(choices=fs.get_all_files(), value=fs.current_file),
789
- content, gr.update(label=f" {fs.current_file}"),
790
- analyzer.analyze(content))
791
- return gr.update(), fs.get_current_file_content(), gr.update(), gr.update()
792
-
793
- def terminal_cmd(cmd):
794
- return terminal.run_command(cmd), ""
795
-
796
- def clear_term(): terminal.log_lines=[]; return ""
797
-
798
- def refresh_history():
799
- return gr.update(choices=terminal.get_history_choices())
800
-
801
- def select_history(cmd):
802
- if cmd: return cmd
803
- return gr.update()
804
-
805
- # Run with cross-file import support
806
- def run_editor(content):
807
- fs.save_file(content)
808
- if is_gui_code(content): return run_gui(content)[0]
809
- terminal._append("$ python [editor]")
810
- tmp = "/tmp/_axon_run.py"
811
- with open(tmp, "w") as f: f.write(content)
812
  try:
813
- env = os.environ.copy()
814
- env["DISPLAY"] = DISPLAY_NUM
815
- # Add workspace to PYTHONPATH so cross-file imports work
816
- env["PYTHONPATH"] = fs._sync_dir + ":" + env.get("PYTHONPATH", "")
817
- r = subprocess.run([sys.executable, tmp], capture_output=True, text=True,
818
- timeout=30, env=env, cwd=fs._sync_dir)
819
- if r.stdout.strip(): terminal._append(r.stdout.rstrip())
820
- if r.stderr.strip(): terminal._append(r.stderr.rstrip())
821
- if not r.stdout.strip() and not r.stderr.strip(): terminal._append("(No output)")
822
- except subprocess.TimeoutExpired: terminal._append("[Timed out 30s]")
823
- except Exception as e: terminal._append(f"[Error] {e}")
824
- return terminal.get_log()
825
-
826
- def run_gui(content):
827
- fs.save_file(content); msg=gui_mgr.launch(content); terminal._append(msg)
828
- time.sleep(1.5); ss=vdisplay.capture()
829
- st=f"<small>Xvfb: ● ON | {SCREEN_W}x{SCREEN_H} | {gui_mgr.get_status()}</small>"
830
- return terminal.get_log(), ss, st
831
-
832
- def stop_gui():
833
- msg=gui_mgr.stop(); terminal._append(msg)
834
- st=f"<small>Xvfb: {'● ON' if vdisplay.is_running else '○ OFF'} | {SCREEN_W}x{SCREEN_H} | {gui_mgr.get_status()}</small>"
835
- return terminal.get_log(), st
836
-
837
- def capture_display(): return vdisplay.capture()
838
- def auto_capture_tick():
839
- if gui_mgr.is_running: return vdisplay.capture()
840
- return gr.update()
841
- def toggle_auto(c): return gr.Timer(2, active=c)
842
-
843
- def complete_fn(c):
844
- result = c+"\n"+generate_completion(c)
845
- return result, analyzer.analyze(result)
846
- def explain_fn(c):
847
- e=explain_code(c); fs.save_file(c); m=f'"""\nEXPLANATION:\n{e}\n"""\n\n{c}'
848
- return create_diff_view(c,m),c,m
849
- def refactor_fn(c):
850
- r=refactor_code(c); fs.save_file(c); return create_diff_view(c,r),c,r
851
- def generate_fn(p,h):
852
- g=generate_code(p); d=create_diff_view("",g)
853
- nh=h+[{"role":"user","content":p},{"role":"assistant","content":"Code generated → DIFF tab"}]
854
- return d,"",g,nh,""
855
-
856
- # Snippets
857
- def save_snippet(name, tags, editor_content):
858
- msg = snippets.add(name, editor_content, tags)
859
- return msg, gr.update(choices=snippets.get_names())
860
-
861
- def insert_snippet(name):
862
- code = snippets.get(name)
863
- return code if code else gr.update()
864
-
865
- def delete_snippet(name):
866
- msg = snippets.delete(name)
867
- return msg, gr.update(choices=snippets.get_names())
868
-
869
- def search_snippets(query):
870
- results = snippets.search(query)
871
- return gr.update(choices=results)
872
-
873
- # Find & Replace
874
- def do_find(code, query, case):
875
- return finder.find_in_file(code, query, case)
876
-
877
- def do_find_all(query):
878
- return finder.find_in_all_files(fs.files, query)
879
-
880
- def do_replace(code, find_q, replace_q, case):
881
- new_code, msg = finder.replace_in_file(code, find_q, replace_q, case)
882
- fs.save_file(new_code)
883
- return new_code, msg, analyzer.analyze(new_code)
884
-
885
- # ═══════════════════════════════════════
886
- # WIRING
887
- # ═══════════════════════════════════════
888
-
889
- # File operations
890
- editor.change(update_file, editor, structure_view)
891
- file_list.change(load_file, file_list, [editor, editor, structure_view])
892
- new_file_btn.click(create_file_fn, new_file_name, [file_list, editor, editor, structure_view])
893
- del_file_btn.click(delete_file_fn, None, [file_list, editor, editor, structure_view])
894
 
895
- # Terminal
896
- terminal_input.submit(terminal_cmd, terminal_input, [terminal_output, terminal_input])
897
- terminal_run_btn.click(terminal_cmd, terminal_input, [terminal_output, terminal_input])
898
- clear_btn.click(clear_term, None, terminal_output)
899
- history_refresh.click(refresh_history, None, history_dd)
900
- history_dd.change(select_history, history_dd, terminal_input)
901
-
902
- # Run
903
- run_btn.click(run_editor, editor, terminal_output)
904
- run_gui_btn.click(run_gui, editor, [terminal_output, display_image, gui_status])
905
- stop_gui_btn.click(stop_gui, None, [terminal_output, gui_status])
906
-
907
- # AI
908
- complete_btn.click(complete_fn, editor, [editor, structure_view])
909
- explain_btn.click(explain_fn, editor, [diff_view, diff_original_state, diff_modified_state]
910
- ).then(lambda: gr.Tabs(selected="diff-tab"), None, bottom_tabs)
911
- refactor_btn.click(refactor_fn, editor, [diff_view, diff_original_state, diff_modified_state]
912
- ).then(lambda: gr.Tabs(selected="diff-tab"), None, bottom_tabs)
913
- chat_input.submit(generate_fn, [chat_input, chat_history],
914
- [diff_view, diff_original_state, diff_modified_state, chat_history, chat_input]
915
- ).then(lambda: gr.Tabs(selected="diff-tab"), None, bottom_tabs)
916
- send_btn.click(generate_fn, [chat_input, chat_history],
917
- [diff_view, diff_original_state, diff_modified_state, chat_history, chat_input]
918
- ).then(lambda: gr.Tabs(selected="diff-tab"), None, bottom_tabs)
919
-
920
- # Diff
921
- apply_btn.click(lambda mod: (mod, analyzer.analyze(mod)), diff_modified_state, [editor, structure_view]
922
- ).then(lambda: gr.Tabs(selected="terminal-tab"), None, bottom_tabs)
923
- discard_btn.click(lambda: (gr.update(), gr.update()), None, [editor, structure_view]
924
- ).then(lambda: gr.Tabs(selected="terminal-tab"), None, bottom_tabs)
925
 
926
- # Snippets
927
- snippet_save_btn.click(save_snippet, [snippet_name, snippet_tags, editor], [snippet_status, snippet_list])
928
- snippet_insert_btn.click(insert_snippet, snippet_list, editor)
929
- snippet_del_btn.click(delete_snippet, snippet_list, [snippet_status, snippet_list])
930
- snippet_search.change(search_snippets, snippet_search, snippet_list)
931
 
932
- # Find & Replace
933
- find_btn.click(do_find, [editor, find_query, find_case], find_results)
934
- find_all_btn.click(do_find_all, find_query, find_results)
935
- replace_btn.click(do_replace, [editor, find_query, replace_query, find_case],
936
- [editor, find_results, structure_view])
937
- # ... [Keep your existing FIND tab code] ...
938
-
939
- # ═══ MISSING TAB: GUI DISPLAY ═══
940
- with gr.Tab("🖥 DISPLAY", id="display-tab"):
941
- display_image = gr.Image(label="Virtual Display Output", interactive=False, type="filepath")
942
- with gr.Row():
943
- capture_btn = gr.Button("📸 CAPTURE SCREEN", variant="primary")
944
- auto_capture = gr.Checkbox(label="Auto-refresh (2s)")
945
- # Hidden timer for auto-refresh
946
- auto_timer = gr.Timer(value=2.0, active=False)
947
 
948
- # ═══════════════════════════════════════
949
- # BRIDGE FUNCTIONS (These were missing)
950
- # ═══════════════════════════════════════
951
-
952
- # Wrappers for Find/Replace
953
  def do_find(code, query, case):
954
  return finder.find_in_file(code, query, case)
955
 
@@ -958,16 +462,14 @@ with gr.Blocks(title="Axon Pro — Python AI IDE") as demo:
958
 
959
  def do_replace(code, find, replace, case):
960
  new_code, msg = finder.replace_in_file(code, find, replace, case)
961
- # We need to update the editor, the results box, AND the structure view
 
962
  new_structure = analyzer.analyze(new_code)
963
  return new_code, msg, new_structure
964
 
965
- # Wrappers for Display
966
  def capture_display():
967
  path = vdisplay.capture()
968
- if not path:
969
- return None
970
- return path
971
 
972
  def toggle_auto(checkbox_state):
973
  return gr.Timer(active=checkbox_state)
@@ -975,33 +477,73 @@ with gr.Blocks(title="Axon Pro — Python AI IDE") as demo:
975
  def auto_capture_tick():
976
  return capture_display()
977
 
 
 
 
 
 
 
 
 
 
 
 
 
 
978
  # ═══════════════════════════════════════
979
- # EVENT WIRING
980
  # ═══════════════════════════════════════
981
 
982
- # Find & Replace Events
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
983
  find_btn.click(fn=do_find, inputs=[editor, find_query, find_case], outputs=find_results)
984
  find_all_btn.click(fn=do_find_all, inputs=[find_query], outputs=find_results)
985
-
986
  replace_btn.click(fn=do_replace,
987
  inputs=[editor, find_query, replace_query, find_case],
988
  outputs=[editor, find_results, structure_view])
989
 
990
- # Display Events
991
  capture_btn.click(fn=capture_display, inputs=None, outputs=display_image)
992
  auto_capture.change(fn=toggle_auto, inputs=auto_capture, outputs=auto_timer)
993
  auto_timer.tick(fn=auto_capture_tick, inputs=None, outputs=display_image)
994
 
995
- # Note: Ensure you have wired up the other buttons from the previous
996
- # part of the code (Run, Stop, Complete, etc.) or they won't work either!
 
997
 
998
  if __name__ == "__main__":
999
- import atexit
1000
  atexit.register(terminal.cleanup)
1001
  atexit.register(gui_mgr.stop)
1002
  atexit.register(vdisplay.cleanup)
1003
 
1004
- # Use queue=True for timers and long running tasks
1005
- demo.queue().launch(server_name="0.0.0.0", server_port=7860,
1006
- theme=theme, css=css, ssr_mode=False,
1007
- allowed_paths=["/tmp"])
 
 
 
 
 
 
1
  import gradio as gr
2
+ import os
 
3
  import sys
4
  import subprocess
 
 
 
 
 
 
 
5
  import threading
6
+ import time
7
  import ast
8
+ import re
9
+ import signal
10
+ import shutil
11
  import tempfile
12
+ import atexit
13
+ from PIL import Image
 
 
 
 
 
 
 
 
 
 
 
14
 
15
  # ═══════════════════════════════════════
16
+ # 1. HELPER CLASSES
17
  # ═══════════════════════════════════════
18
+
19
+ class VirtualDisplayManager:
20
+ """Manages a virtual display (Xvfb) for capturing GUI output."""
21
  def __init__(self):
22
+ self.display = None
23
+ self.is_active = False
24
+ self.check_xvfb()
25
+
26
+ def check_xvfb(self):
27
+ # Check if Xvfb is installed (Linux only)
28
+ self.has_xvfb = shutil.which('Xvfb') is not None
29
+
30
+ def start(self):
31
+ if not self.has_xvfb:
32
+ print("Xvfb not found. Virtual display disabled.")
33
+ return
 
 
 
 
 
 
 
 
 
34
  try:
35
+ from pyvirtualdisplay import Display
36
+ self.display = Display(visible=0, size=(1280, 720))
37
+ self.display.start()
38
+ os.environ["DISPLAY"] = f":{self.display.display}"
39
+ self.is_active = True
40
+ print(f"Virtual Display started on {os.environ['DISPLAY']}")
 
 
 
 
 
 
41
  except Exception as e:
42
+ print(f"Failed to start virtual display: {e}")
43
 
44
  def capture(self):
45
+ if not self.is_active:
46
+ return None
47
+ try:
48
+ # Capture screenshot of the virtual display
49
+ # Using xwd and converting to png is standard, but let's try PIL grab if possible
50
+ # or a simple screenshot tool if installed.
51
+ # Fallback: ImageGrab works on Linux if DISPLAY is set correctly and dependencies exist.
52
+ from PIL import ImageGrab
53
+ img = ImageGrab.grab()
54
+
55
+ # Save to a temporary path for Gradio to display
56
+ fd, path = tempfile.mkstemp(suffix=".png")
57
+ os.close(fd)
58
+ img.save(path)
59
+ return path
60
+ except Exception as e:
61
+ print(f"Capture failed: {e}")
62
+ return None
63
 
 
 
64
  def cleanup(self):
65
+ if self.display:
66
+ self.display.stop()
 
 
 
 
67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  class FileSystem:
69
+ """Manages file operations."""
70
+ def __init__(self, root="."):
71
+ self.root = os.path.abspath(root)
72
+ self.current_file = None
73
+ self.files = []
74
+ self.refresh()
75
+
76
+ def refresh(self):
77
+ # List only .py files for simplicity, or all text files
78
+ self.files = [f for f in os.listdir(self.root) if f.endswith('.py') or f.endswith('.txt') or f.endswith('.md')]
79
+ self.files.sort()
80
+ if not self.current_file and self.files:
81
+ self.current_file = self.files[0]
82
+ return self.files
83
+
84
+ def get_all_files(self):
85
+ self.refresh()
86
+ return self.files
87
+
88
+ def set_current_file(self, filename):
89
+ if filename in self.files:
90
+ self.current_file = filename
91
+
92
+ def get_current_file_content(self):
93
+ if not self.current_file: return ""
94
+ try:
95
+ with open(os.path.join(self.root, self.current_file), 'r', encoding='utf-8') as f:
96
+ return f.read()
97
+ except:
98
+ return "Error reading file."
99
 
100
  def save_file(self, content):
101
+ if not self.current_file: return
102
+ with open(os.path.join(self.root, self.current_file), 'w', encoding='utf-8') as f:
103
+ f.write(content)
104
+
105
+ def create_file(self, name):
106
+ if not name: return
107
+ path = os.path.join(self.root, name)
108
+ if not os.path.exists(path):
109
+ with open(path, 'w') as f:
110
+ f.write("# New file\n")
111
+ self.refresh()
112
+ self.current_file = name
113
+
114
+ def delete_file(self, name):
115
+ path = os.path.join(self.root, name)
116
+ if os.path.exists(path):
117
+ os.remove(path)
118
+ self.current_file = None
119
+ self.refresh()
 
 
 
 
 
 
 
 
 
 
 
120
  return True
121
  return False
 
122
 
123
+ class TerminalManager:
124
+ """Manages running shell commands."""
 
 
 
 
125
  def __init__(self):
126
+ self.history = []
127
+
128
+ def run_command(self, command):
129
+ if not command.strip(): return "No command"
130
+
131
  try:
132
+ # Capture output
133
+ result = subprocess.run(
134
+ command,
135
+ shell=True,
136
+ cwd=os.getcwd(),
137
+ stdout=subprocess.PIPE,
138
+ stderr=subprocess.PIPE,
139
+ text=True,
140
+ timeout=10
141
+ )
142
+ output = f"$ {command}\n{result.stdout}\n{result.stderr}"
143
+ except Exception as e:
144
+ output = f"$ {command}\nError: {str(e)}"
145
+
146
+ self.history.append(command)
147
+ return output
148
+
149
+ def get_history_choices(self):
150
+ return list(set(self.history)) # Unique history
151
+
152
+ def cleanup(self):
153
+ pass
154
+
155
+ class GUIManager:
156
+ """Manages running the user's Python script as a subprocess."""
157
+ def __init__(self):
158
+ self.process = None
159
 
160
+ def launch(self, code):
161
+ self.stop() # Stop any existing process
162
+
163
+ # Write code to a temp file to run it
164
+ fd, path = tempfile.mkstemp(suffix=".py")
165
+ with os.fdopen(fd, 'w') as f:
166
+ f.write(code)
167
+
168
  try:
169
+ # Run asynchronously
170
+ self.process = subprocess.Popen(
171
+ [sys.executable, path],
172
+ stdout=subprocess.PIPE,
173
+ stderr=subprocess.PIPE,
174
+ text=True,
175
+ cwd=os.getcwd()
176
+ )
177
+ return f"Started process PID: {self.process.pid}"
178
+ except Exception as e:
179
+ return f"Failed to start: {e}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
 
181
+ def stop(self):
182
+ if self.process:
183
+ self.process.terminate()
184
+ self.process = None
185
+ return "Process stopped."
186
+ return "No process running."
187
 
 
 
 
188
  class CodeAnalyzer:
189
+ """Parses Python code to extract structure (Classes/Functions)."""
190
+ def analyze(self, code):
191
+ if not code: return "No code."
 
 
192
  try:
193
  tree = ast.parse(code)
194
+ lines = []
195
+ for node in ast.walk(tree):
196
+ if isinstance(node, ast.ClassDef):
197
+ lines.append(f"📦 Class: {node.name}")
198
+ for item in node.body:
199
+ if isinstance(item, ast.FunctionDef):
200
+ lines.append(f" 🔹 def {item.name}(...)")
201
+ elif isinstance(node, ast.FunctionDef):
202
+ # Only list top-level functions here (those not in a class)
203
+ # This logic is simplified; ast.walk flattens everything.
204
+ # A proper visitor is better, but for a simple view:
205
+ pass
206
+
207
+ # Simple Regex fallback for cleaner hierarchy if AST is too messy
208
+ summary = []
209
+ for line in code.splitlines():
210
+ if line.strip().startswith("class "):
211
+ summary.append(f"📦 {line.split(':')[0]}")
212
+ elif line.strip().startswith("def "):
213
+ indent = " " if line.startswith(" ") else ""
214
+ summary.append(f"{indent}🔹 {line.split(':')[0]}")
215
+
216
+ return "\n".join(summary) if summary else "No classes/functions found."
217
+ except SyntaxError:
218
+ return "❌ Syntax Error"
219
+
220
+ class SnippetManager:
221
+ """Manages code snippets."""
222
+ def __init__(self):
223
+ self.snippets = {
224
+ "Hello World": "print('Hello World')",
225
+ "Tkinter Basic": "import tkinter as tk\nroot = tk.Tk()\nroot.mainloop()",
226
+ "Gradio Demo": "import gradio as gr\ndef greet(n):\n return 'Hi '+n\ngr.Interface(fn=greet, inputs='text', outputs='text').launch()"
227
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
 
229
+ def get_names(self):
230
+ return list(self.snippets.keys())
231
+
232
+ def get(self, name):
233
+ return self.snippets.get(name, "")
234
+
235
+ def add(self, name, code, tags):
236
+ self.snippets[name] = code
237
+ return "Snippet saved."
238
 
 
 
 
239
  class FindReplace:
240
+ """Handles Text Finding and Replacing."""
241
+ def find_in_file(self, code, query, match_case):
242
+ if not query: return "Empty query."
243
+ flags = 0 if match_case else re.IGNORECASE
244
+ matches = list(re.finditer(re.escape(query), code, flags))
245
+ return f"Found {len(matches)} occurrences."
246
+
247
+ def find_in_all_files(self, file_list, query):
248
  results = []
249
+ for filename in file_list:
250
+ try:
251
+ with open(filename, 'r') as f:
252
+ content = f.read()
253
+ if query in content:
254
+ results.append(filename)
255
+ except: pass
256
+ return "\n".join(results) if results else "No matches in other files."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
257
 
258
+ def replace_in_file(self, code, find, replace, match_case):
259
+ if not find: return code, "Empty search."
260
+ flags = 0 if match_case else re.IGNORECASE
261
+ new_code, count = re.subn(re.escape(find), replace, code, flags=flags)
262
+ return new_code, f"Replaced {count} occurrences."
263
 
264
  # ═══════════════════════════════════════
265
+ # 2. INSTANTIATE MANAGERS
266
  # ═══════════════════════════════════════
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
267
 
268
+ vdisplay = VirtualDisplayManager()
269
+ vdisplay.start()
270
 
271
+ fs = FileSystem()
272
+ terminal = TerminalManager()
273
+ gui_mgr = GUIManager()
274
+ analyzer = CodeAnalyzer()
275
+ snippets = SnippetManager()
276
+ finder = FindReplace()
 
 
 
277
 
278
  # ═══════════════════════════════════════
279
+ # 3. UI THEME & CSS
280
  # ═══════════════════════════════════════
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
 
282
+ theme = gr.themes.Soft(
283
+ primary_hue="orange",
284
+ secondary_hue="slate",
285
+ neutral_hue="slate",
 
 
 
 
 
 
 
 
 
 
 
286
  )
287
 
288
  css = """
289
+ .gradio-container { min-height: 100vh; }
290
+ textarea { font-family: 'Consolas', 'Monaco', monospace !important; }
291
+ .cm-scroller { overflow: auto !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
  """
293
 
294
  # ═══════════════════════════════════════
295
+ # 4. UI CONSTRUCTION
296
  # ═══════════════════════════════════════
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
297
 
298
+ with gr.Blocks(theme=theme, css=css, title="Python IDE") as demo:
299
+
300
+ # State variables
301
+ # (Gradio 4.x usually handles state via component values, but we can use State objects if needed)
302
+
303
+ with gr.Row():
304
+ # ─── LEFT SIDEBAR ───
305
+ with gr.Column(scale=1, min_width=250):
306
+ gr.Markdown("### 📂 Explorer")
307
+
308
+ with gr.Row():
309
+ new_file_name = gr.Textbox(placeholder="NewFile.py", show_label=False, scale=3)
310
+ new_file_btn = gr.Button("➕", scale=1, min_width=10)
311
+
312
+ file_list = gr.Dropdown(choices=fs.get_all_files(), label="Files", value=fs.current_file, interactive=True)
313
+
314
+ with gr.Row():
315
+ refresh_btn = gr.Button("🔄 Refresh")
316
+ del_file_btn = gr.Button("🗑 Delete", variant="stop")
317
 
318
+ gr.Markdown("### 🌳 Structure")
319
+ structure_view = gr.Code(language="markdown", label="AST Outline", interactive=False, lines=15)
320
+
321
+ # ─── MAIN AREA ───
322
+ with gr.Column(scale=4):
323
+
324
+ # Toolbar
325
  with gr.Row():
326
+ run_btn = gr.Button("▶ RUN (Console)", variant="primary")
327
+ run_gui_btn = gr.Button("🖼 RUN (GUI)", variant="secondary")
328
+ stop_gui_btn = gr.Button(" STOP", variant="stop")
329
+ save_btn = gr.Button("💾 SAVE")
330
+
331
+ # Editor
332
+ editor = gr.Code(
333
+ value=fs.get_current_file_content(),
334
+ language="python",
335
+ label=f"Editor - {fs.current_file}",
336
+ lines=20,
337
+ interactive=True
338
+ )
339
+
340
+ # AI/Tools Row (Placeholder for future expansion)
341
+ with gr.Row():
342
+ complete_btn = gr.Button("✨ Auto-Complete")
343
+ explain_btn = gr.Button("🧐 Explain")
344
+ refactor_btn = gr.Button("🛠 Refactor")
345
+
346
+ # Bottom Tabs
347
+ with gr.Tabs():
348
+
349
+ # ─ Terminal Tab ─
350
+ with gr.Tab("💻 TERMINAL"):
351
+ terminal_output = gr.Textbox(label="Output", lines=10, max_lines=20, elem_classes="term-out")
352
  with gr.Row():
353
+ terminal_input = gr.Textbox(show_label=False, placeholder="Type command here...", scale=4)
354
+ terminal_run_btn = gr.Button("Run", scale=1)
 
 
355
  with gr.Row():
356
+ clear_btn = gr.Button("Clear Output")
357
+ history_refresh = gr.Button("History")
358
+ history_dd = gr.Dropdown(label="Command History", choices=[])
 
 
359
 
360
+ # Find/Replace Tab ─
361
+ with gr.Tab("🔍 FIND"):
362
+ with gr.Row():
363
+ find_query = gr.Textbox(label="Find", placeholder="Text to find...")
364
+ replace_query = gr.Textbox(label="Replace", placeholder="Replacement text...")
365
+
366
+ with gr.Row():
367
+ find_case = gr.Checkbox(label="Match Case")
368
+
369
  with gr.Row():
370
+ find_btn = gr.Button("FIND", variant="primary")
371
+ find_all_btn = gr.Button("FIND ALL")
372
+ replace_btn = gr.Button("REPLACE")
373
+
374
+ find_results = gr.Textbox(label="Results", lines=5)
375
+
376
+ # ─ Snippets Tab ─
377
+ with gr.Tab("🧩 SNIPPETS"):
378
  with gr.Row():
379
+ snippet_list = gr.Dropdown(choices=snippets.get_names(), label="Load Snippet")
380
+ snippet_insert_btn = gr.Button("Insert into Editor")
381
+
382
+ gr.Markdown("---")
 
 
 
 
383
  with gr.Row():
384
+ snippet_name = gr.Textbox(label="Snippet Name")
385
+ snippet_tags = gr.Textbox(label="Tags (comma sep)")
386
+ snippet_save_btn = gr.Button("Save Selection as Snippet")
387
+ snippet_status = gr.Markdown("")
388
 
389
+ # Display Tab (Xvfb)
390
+ with gr.Tab("🖥 DISPLAY"):
391
+ display_image = gr.Image(label="Xvfb Capture", type="filepath", interactive=False, height=400)
392
  with gr.Row():
393
+ capture_btn = gr.Button("📸 CAPTURE SCREEN", variant="primary")
394
+ auto_capture = gr.Checkbox(label="Auto-refresh (2s)")
395
+
396
+ auto_timer = gr.Timer(value=2.0, active=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
397
 
398
  # ═══════════════════════════════════════
399
+ # 5. LOGIC FUNCTIONS
400
  # ═══════════════════════════════════════
401
+
402
+ # --- File System Wrappers ---
403
+ def load_file_content(f):
404
+ fs.set_current_file(f)
405
+ content = fs.get_current_file_content()
406
+ return content, f"Editor - {f}", analyzer.analyze(content)
407
 
408
+ def refresh_file_list():
409
+ files = fs.refresh()
410
+ return gr.update(choices=files, value=fs.current_file)
411
 
412
+ def save_file_content(code):
413
+ fs.save_file(code)
414
+ return "File saved."
 
415
 
416
+ def create_new_file(name):
417
+ if not name: return gr.update(), "Name required", ""
418
+ fs.create_file(name)
419
+ content = fs.get_current_file_content()
420
+ return gr.update(choices=fs.get_all_files(), value=name), f"Editor - {name}", analyzer.analyze(content)
 
 
421
 
422
+ def delete_current_file(name):
 
423
  if fs.delete_file(name):
424
+ next_file = fs.current_file
425
  content = fs.get_current_file_content()
426
+ return (
427
+ gr.update(choices=fs.get_all_files(), value=next_file),
428
+ content,
429
+ f"Editor - {next_file}",
430
+ analyzer.analyze(content)
431
+ )
432
+ return gr.update(), gr.update(), gr.update(), gr.update()
433
+
434
+ # --- Execution Wrappers ---
435
+ def run_console_code(code):
436
+ # Simply run the code and return stdout
437
+ # Note: This runs in the same environment, simplistic.
438
+ fd, path = tempfile.mkstemp(suffix=".py")
439
+ with os.fdopen(fd, 'w') as f:
440
+ f.write(code)
 
 
 
 
 
 
 
 
 
441
  try:
442
+ res = subprocess.run([sys.executable, path], capture_output=True, text=True, timeout=5)
443
+ return f"OUTPUT:\n{res.stdout}\nERRORS:\n{res.stderr}"
444
+ except Exception as e:
445
+ return str(e)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
446
 
447
+ def run_gui_code(code):
448
+ return gui_mgr.launch(code)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
449
 
450
+ def stop_gui_code():
451
+ return gui_mgr.stop()
 
 
 
452
 
453
+ def terminal_submit(cmd):
454
+ return terminal.run_command(cmd), gr.update(value="")
 
 
 
 
 
 
 
 
 
 
 
 
 
455
 
456
+ # --- Bridge Functions (Find/Display) ---
 
 
 
 
457
  def do_find(code, query, case):
458
  return finder.find_in_file(code, query, case)
459
 
 
462
 
463
  def do_replace(code, find, replace, case):
464
  new_code, msg = finder.replace_in_file(code, find, replace, case)
465
+ # Update editor, results box, AND structure view
466
+ fs.save_file(new_code) # Auto-save on replace
467
  new_structure = analyzer.analyze(new_code)
468
  return new_code, msg, new_structure
469
 
 
470
  def capture_display():
471
  path = vdisplay.capture()
472
+ return path if path else None
 
 
473
 
474
  def toggle_auto(checkbox_state):
475
  return gr.Timer(active=checkbox_state)
 
477
  def auto_capture_tick():
478
  return capture_display()
479
 
480
+ # --- Snippet Wrappers ---
481
+ def insert_snippet(code, snippet_name):
482
+ s_code = snippets.get(snippet_name)
483
+ if not s_code: return code
484
+ return (code + "\n" + s_code) if code else s_code
485
+
486
+ def save_snippet_ui(name, code, tags):
487
+ return snippets.add(name, code, tags), gr.update(choices=snippets.get_names())
488
+
489
+ # --- AI Mock functions ---
490
+ def mock_ai_action(code, action):
491
+ return f"# {action} applied to code (AI Mock)\n" + code
492
+
493
  # ═══════════════════════════════════════
494
+ # 6. EVENT WIRING
495
  # ═══════════════════════════════════════
496
 
497
+ # File Explorer
498
+ file_list.change(fn=load_file_content, inputs=file_list, outputs=[editor, editor.label, structure_view])
499
+ refresh_btn.click(fn=refresh_file_list, inputs=None, outputs=file_list)
500
+ new_file_btn.click(fn=create_new_file, inputs=new_file_name, outputs=[file_list, editor.label, structure_view])
501
+ save_btn.click(fn=save_file_content, inputs=editor, outputs=None)
502
+ del_file_btn.click(fn=delete_current_file, inputs=file_list, outputs=[file_list, editor, editor.label, structure_view])
503
+
504
+ # Editor Actions
505
+ run_btn.click(fn=run_console_code, inputs=editor, outputs=terminal_output)
506
+ run_gui_btn.click(fn=run_gui_code, inputs=editor, outputs=terminal_output)
507
+ stop_gui_btn.click(fn=stop_gui_code, inputs=None, outputs=terminal_output)
508
+
509
+ # AI Actions
510
+ complete_btn.click(fn=lambda c: mock_ai_action(c, "Completion"), inputs=editor, outputs=editor)
511
+ explain_btn.click(fn=lambda c: mock_ai_action(c, "Explanation"), inputs=editor, outputs=terminal_output)
512
+ refactor_btn.click(fn=lambda c: mock_ai_action(c, "Refactor"), inputs=editor, outputs=editor)
513
+
514
+ # Terminal
515
+ terminal_run_btn.click(fn=terminal_submit, inputs=terminal_input, outputs=[terminal_output, terminal_input])
516
+ terminal_input.submit(fn=terminal_submit, inputs=terminal_input, outputs=[terminal_output, terminal_input])
517
+ clear_btn.click(fn=lambda: terminal.run_command("clear"), inputs=None, outputs=terminal_output)
518
+ history_refresh.click(fn=lambda: gr.update(choices=terminal.get_history_choices()), inputs=None, outputs=history_dd)
519
+ history_dd.change(fn=lambda x: x, inputs=history_dd, outputs=terminal_input)
520
+
521
+ # Find & Replace
522
  find_btn.click(fn=do_find, inputs=[editor, find_query, find_case], outputs=find_results)
523
  find_all_btn.click(fn=do_find_all, inputs=[find_query], outputs=find_results)
 
524
  replace_btn.click(fn=do_replace,
525
  inputs=[editor, find_query, replace_query, find_case],
526
  outputs=[editor, find_results, structure_view])
527
 
528
+ # Display / Capture
529
  capture_btn.click(fn=capture_display, inputs=None, outputs=display_image)
530
  auto_capture.change(fn=toggle_auto, inputs=auto_capture, outputs=auto_timer)
531
  auto_timer.tick(fn=auto_capture_tick, inputs=None, outputs=display_image)
532
 
533
+ # Snippets
534
+ snippet_insert_btn.click(fn=insert_snippet, inputs=[editor, snippet_list], outputs=editor)
535
+ snippet_save_btn.click(fn=save_snippet_ui, inputs=[snippet_name, editor, snippet_tags], outputs=[snippet_status, snippet_list])
536
 
537
  if __name__ == "__main__":
538
+ # Register cleanup handlers
539
  atexit.register(terminal.cleanup)
540
  atexit.register(gui_mgr.stop)
541
  atexit.register(vdisplay.cleanup)
542
 
543
+ # Launch
544
+ demo.queue().launch(
545
+ server_name="0.0.0.0",
546
+ server_port=7860,
547
+ ssr_mode=False,
548
+ allowed_paths=["/tmp"]
549
+ )