Devity4756 commited on
Commit
807a46a
·
verified ·
1 Parent(s): 7acc356

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +186 -39
app.py CHANGED
@@ -7,19 +7,21 @@ import io
7
  import socket
8
  import random
9
  import string
 
10
  import ast
11
  import re
12
  from huggingface_hub import HfApi, snapshot_download
13
 
14
  # === Config ===
15
  DATASET_REPO = "Devity4756/Terminal"
16
- HF_TOKEN = os.environ.get("HF_TOKEN")
17
  if HF_TOKEN:
18
  api = HfApi(token=HF_TOKEN)
19
  else:
20
  api = None
21
  print("Warning: HF_TOKEN not found. Save functionality will be limited.")
22
 
 
23
  WORKDIR = "workspace"
24
  os.makedirs(WORKDIR, exist_ok=True)
25
 
@@ -28,22 +30,23 @@ VIRTUAL_HOME = os.path.join(WORKDIR, "Alex")
28
  os.makedirs(VIRTUAL_HOME, exist_ok=True)
29
  PATH_MAP = {"Alex": VIRTUAL_HOME}
30
 
31
- # === Restore state ===
32
  try:
33
  if HF_TOKEN:
34
  snapshot_path = snapshot_download(
35
  repo_id=DATASET_REPO,
36
  repo_type="dataset",
37
- token=HF_TOKEN
 
38
  )
39
- shutil.copytree(snapshot_path, WORKDIR, dirs_exist_ok=True)
40
  else:
41
  print("Cannot restore state without HF_TOKEN")
42
  except Exception as e:
43
  print("No previous data restored:", e)
44
 
45
  # === Track current working directory & running process ===
46
- current_dir = WORKDIR
47
  running_process = None
48
  open_file_path = None
49
 
@@ -52,6 +55,7 @@ flask_apps = {}
52
  flask_logs = {}
53
  flask_ports = {}
54
  next_port = 5000
 
55
 
56
  # Get the public URL of the Gradio app
57
  public_url = os.environ.get("SPACE_URL", "https://huggingface.co/spaces/Devity4756/Alex-terminal")
@@ -75,11 +79,16 @@ def generate_random_name(length=12):
75
  return ''.join(random.choice(string.ascii_letters) for _ in range(length))
76
 
77
  def expand_path(path: str) -> str:
78
- for alias, real in PATH_MAP.items():
79
- if path.startswith(alias + "/"):
80
- return path.replace(alias, real, 1)
81
  if path.startswith("/"):
82
- return os.path.join(WORKDIR, path.lstrip("/"))
 
 
 
 
 
 
83
  return os.path.join(current_dir, path)
84
 
85
  def get_local_ip():
@@ -141,14 +150,13 @@ def secure_execute_flask_code(app_instance, code, log_func, request_obj):
141
  random_prefix = generate_random_name(8)
142
 
143
  # Replace function definitions with randomized names
144
- # This pattern matches function definitions and adds a random prefix
145
  code = re.sub(
146
  r'def (\w+)\s*\(',
147
  lambda m: f'def {random_prefix}_{m.group(1)}(',
148
  code
149
  )
150
 
151
- # Create a secure environment
152
  secure_globals = {
153
  'app': app_instance,
154
  'log': log_func,
@@ -157,11 +165,11 @@ def secure_execute_flask_code(app_instance, code, log_func, request_obj):
157
  '__builtins__': {
158
  'str': str, 'int': int, 'float': float, 'bool': bool, 'list': list,
159
  'dict': dict, 'tuple': tuple, 'set': set, 'len': len, 'range': range,
160
- 'print': print, 'isinstance': isinstance, 'type': type, 'repr': repr, '__import__': __import__
161
  }
162
  }
163
 
164
- # Add allowed Flask imports
165
  try:
166
  from flask import Flask, request, jsonify, render_template, redirect, url_for, send_file, abort
167
  secure_globals.update({
@@ -186,8 +194,8 @@ def secure_execute_flask_code(app_instance, code, log_func, request_obj):
186
  return False, f"Execution error: {e}"
187
 
188
  def run_flask_app(app_name: str, code: str):
189
- """Start a Flask app on a dedicated port"""
190
- global next_port
191
  log_buffer = io.StringIO()
192
  flask_logs[app_name] = log_buffer
193
  port = next_port
@@ -206,27 +214,55 @@ def run_flask_app(app_name: str, code: str):
206
  return f"Failed to start Flask app: {validation_msg}"
207
 
208
  from flask import Flask, request
209
- local_flask = Flask(app_name)
210
-
211
- @local_flask.route("/")
212
- def home():
213
- return f"Hello from {app_name}!<br><br>App is running successfully."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
 
215
- success, exec_msg = secure_execute_flask_code(local_flask, code, log, request)
216
  if not success:
217
  log(f"Failed to execute code: {exec_msg}")
218
  return f"Failed to start Flask app: {exec_msg}"
219
 
220
  def run_app():
221
  try:
222
- local_flask.run(host='0.0.0.0', port=port, debug=False, use_reloader=False)
 
 
 
 
 
 
 
 
 
 
223
  except Exception as e:
224
  log(f"Error running Flask app: {e}")
225
- return f"Error running Flask app: {e}"
226
 
227
  thread = threading.Thread(target=run_app, daemon=True)
228
  thread.start()
229
- flask_apps[app_name] = local_flask
230
 
231
  local_ip = get_local_ip()
232
  local_url = f"http://{local_ip}:{port}"
@@ -237,12 +273,27 @@ def run_flask_app(app_name: str, code: str):
237
  live_url = f"{public_url}/proxy/{port}/"
238
 
239
  log(f"Started Flask app: {app_name} on port {port}")
 
 
240
  log(f"Local URL: {local_url}")
241
  log(f"Live URL: {live_url}")
242
- return f"Started Flask app: {app_name}\nLocal URL: {local_url}\nLive URL: {live_url}"
 
243
  except Exception as e:
244
  log(f"Error starting Flask app {app_name}: {e}")
245
  return f"Error starting Flask app: {e}"
 
 
 
 
 
 
 
 
 
 
 
 
246
 
247
  def is_flask_app(file_path):
248
  """Check if a Python file contains Flask code."""
@@ -287,6 +338,36 @@ def run_python_app(file_path: str):
287
  thread.start()
288
 
289
  return f"Started Python application: {file_path}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
 
291
  def is_flask_app(file_path):
292
  """Check if a Python file contains Flask code."""
@@ -315,15 +396,18 @@ def get_flask_apps():
315
  result = "Running Flask apps:\n\n"
316
 
317
  for app_name, port in flask_ports.items():
 
 
318
  local_url = f"http://{local_ip}:{port}"
319
 
320
  if "hf.space" in public_url:
321
  space_id = public_url.split("https://")[1].split(".hf.space")[0]
322
  live_url = f"https://{space_id}.hf.space/proxy/{port}/"
323
- result += f"- {app_name}:\n Local: {local_url}\n Live: {live_url}\n\n"
324
  else:
325
- result += f"- {app_name}:\n Local: {local_url}\n Live: {public_url}/proxy/{port}/\n\n"
326
 
 
327
  return result
328
 
329
  def run_command(cmd: str):
@@ -341,11 +425,14 @@ def run_command(cmd: str):
341
  try:
342
  # === STOP running process ===
343
  if raw_cmd == "close":
344
- if running_process and running_process.poll() is None:
345
- running_process.terminate()
346
- running_process = None
347
- return f"$ {cmd}\n\nStopped running process.", "", None
348
- return f"$ {cmd}\n\nNo active process to stop.", "", None
 
 
 
349
 
350
  # === Start Flask app from code ===
351
  elif raw_cmd == "flaskrun" and args:
@@ -380,12 +467,70 @@ def run_command(cmd: str):
380
  elif raw_cmd == "flasklist":
381
  apps_info = get_flask_apps()
382
  return f"$ {cmd}\n\n{apps_info}", "", None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
383
 
384
  # === View logs ===
385
  elif raw_cmd == "logs" and args:
386
  app_name = args
387
  logs = view_logs(app_name)
388
  return f"$ {cmd}\n\nLogs for {app_name}:\n{logs}", "", None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
389
 
390
  # === Change directory (cd) ===
391
  elif raw_cmd == "cd":
@@ -486,21 +631,23 @@ def save_file(new_content: str, file_path: str):
486
  with open(file_path, "w", encoding="utf-8") as f:
487
  f.write(new_content)
488
 
 
489
  if HF_TOKEN and api:
490
  try:
491
- api.upload_folder(
492
- folder_path=WORKDIR,
 
 
493
  repo_id=DATASET_REPO,
494
  repo_type="dataset",
495
- commit_message=f"Edited file: {file_path}",
496
  token=HF_TOKEN
497
  )
 
498
  except Exception as e:
499
- print("Upload failed:", e)
500
  else:
501
- print("Save local only - no HF token configured")
502
-
503
- return f"Saved file: {file_path}", new_content
504
  except Exception as e:
505
  return f"Error saving file: {e}", new_content
506
 
 
7
  import socket
8
  import random
9
  import string
10
+ import datetime
11
  import ast
12
  import re
13
  from huggingface_hub import HfApi, snapshot_download
14
 
15
  # === Config ===
16
  DATASET_REPO = "Devity4756/Terminal"
17
+ HF_TOKEN = os.environ.get("HF_TOKEN", "")
18
  if HF_TOKEN:
19
  api = HfApi(token=HF_TOKEN)
20
  else:
21
  api = None
22
  print("Warning: HF_TOKEN not found. Save functionality will be limited.")
23
 
24
+ # Use dataset workspace directly
25
  WORKDIR = "workspace"
26
  os.makedirs(WORKDIR, exist_ok=True)
27
 
 
30
  os.makedirs(VIRTUAL_HOME, exist_ok=True)
31
  PATH_MAP = {"Alex": VIRTUAL_HOME}
32
 
33
+ # === Restore state from dataset ===
34
  try:
35
  if HF_TOKEN:
36
  snapshot_path = snapshot_download(
37
  repo_id=DATASET_REPO,
38
  repo_type="dataset",
39
+ token=HF_TOKEN,
40
+ local_dir=WORKDIR # Download directly to workspace
41
  )
42
+ print(f"Restored dataset data to: {WORKDIR}")
43
  else:
44
  print("Cannot restore state without HF_TOKEN")
45
  except Exception as e:
46
  print("No previous data restored:", e)
47
 
48
  # === Track current working directory & running process ===
49
+ current_dir = VIRTUAL_HOME # Start in workspace/Alex
50
  running_process = None
51
  open_file_path = None
52
 
 
55
  flask_logs = {}
56
  flask_ports = {}
57
  next_port = 5000
58
+ flask_threads = {}
59
 
60
  # Get the public URL of the Gradio app
61
  public_url = os.environ.get("SPACE_URL", "https://huggingface.co/spaces/Devity4756/Alex-terminal")
 
79
  return ''.join(random.choice(string.ascii_letters) for _ in range(length))
80
 
81
  def expand_path(path: str) -> str:
82
+ """Expand paths relative to dataset workspace"""
83
+ # Handle absolute paths (relative to Alex in dataset)
 
84
  if path.startswith("/"):
85
+ return os.path.join(VIRTUAL_HOME, path.lstrip("/"))
86
+
87
+ # Handle virtual home alias
88
+ if path.startswith("Alex/"):
89
+ return os.path.join(VIRTUAL_HOME, path.replace("Alex/", "", 1))
90
+
91
+ # Handle relative paths from current directory
92
  return os.path.join(current_dir, path)
93
 
94
  def get_local_ip():
 
150
  random_prefix = generate_random_name(8)
151
 
152
  # Replace function definitions with randomized names
 
153
  code = re.sub(
154
  r'def (\w+)\s*\(',
155
  lambda m: f'def {random_prefix}_{m.group(1)}(',
156
  code
157
  )
158
 
159
+ # Create a secure environment with template rendering support
160
  secure_globals = {
161
  'app': app_instance,
162
  'log': log_func,
 
165
  '__builtins__': {
166
  'str': str, 'int': int, 'float': float, 'bool': bool, 'list': list,
167
  'dict': dict, 'tuple': tuple, 'set': set, 'len': len, 'range': range,
168
+ 'print': print, 'isinstance': isinstance, 'type': type, 'repr': repr
169
  }
170
  }
171
 
172
+ # Add allowed Flask imports with template support
173
  try:
174
  from flask import Flask, request, jsonify, render_template, redirect, url_for, send_file, abort
175
  secure_globals.update({
 
194
  return False, f"Execution error: {e}"
195
 
196
  def run_flask_app(app_name: str, code: str):
197
+ """Start a Flask app using dataset paths"""
198
+ global next_port, current_dir
199
  log_buffer = io.StringIO()
200
  flask_logs[app_name] = log_buffer
201
  port = next_port
 
214
  return f"Failed to start Flask app: {validation_msg}"
215
 
216
  from flask import Flask, request
217
+
218
+ # Get the absolute path to templates
219
+ template_dir = os.path.abspath(os.path.join(VIRTUAL_HOME, 'templates'))
220
+ static_dir = os.path.abspath(os.path.join(VIRTUAL_HOME, 'static'))
221
+
222
+ # Create directories if they don't exist
223
+ os.makedirs(template_dir, exist_ok=True)
224
+ os.makedirs(static_dir, exist_ok=True)
225
+
226
+ log(f"Absolute template path: {template_dir}")
227
+ log(f"Template dir exists: {os.path.exists(template_dir)}")
228
+ if os.path.exists(template_dir):
229
+ log(f"Files in template dir: {os.listdir(template_dir)}")
230
+
231
+ # Use absolute paths for templates and static files
232
+ app = Flask(
233
+ app_name,
234
+ template_folder=template_dir,
235
+ static_folder=static_dir,
236
+ static_url_path='/static'
237
+ )
238
+
239
+ # Set root path to dataset Alex directory
240
+ app.root_path = os.path.abspath(VIRTUAL_HOME)
241
 
242
+ success, exec_msg = secure_execute_flask_code(app, code, log, request)
243
  if not success:
244
  log(f"Failed to execute code: {exec_msg}")
245
  return f"Failed to start Flask app: {exec_msg}"
246
 
247
  def run_app():
248
  try:
249
+ # Change to dataset directory using absolute path
250
+ original_cwd = os.getcwd()
251
+ abs_alex_path = os.path.abspath(VIRTUAL_HOME)
252
+ os.chdir(abs_alex_path)
253
+
254
+ log(f"Running Flask from: {os.getcwd()}")
255
+ log(f"Template folder contents: {os.listdir(template_dir) if os.path.exists(template_dir) else 'NOT FOUND'}")
256
+
257
+ app.run(host='0.0.0.0', port=port, debug=False, use_reloader=False)
258
+
259
+ os.chdir(original_cwd)
260
  except Exception as e:
261
  log(f"Error running Flask app: {e}")
 
262
 
263
  thread = threading.Thread(target=run_app, daemon=True)
264
  thread.start()
265
+ flask_apps[app_name] = app
266
 
267
  local_ip = get_local_ip()
268
  local_url = f"http://{local_ip}:{port}"
 
273
  live_url = f"{public_url}/proxy/{port}/"
274
 
275
  log(f"Started Flask app: {app_name} on port {port}")
276
+ log(f"Absolute dataset directory: {os.path.abspath(VIRTUAL_HOME)}")
277
+ log(f"Absolute template folder: {template_dir}")
278
  log(f"Local URL: {local_url}")
279
  log(f"Live URL: {live_url}")
280
+
281
+ return f"Started Flask app: {app_name}\nDataset directory: {os.path.abspath(VIRTUAL_HOME)}\nLocal URL: {local_url}\nLive URL: {live_url}"
282
  except Exception as e:
283
  log(f"Error starting Flask app {app_name}: {e}")
284
  return f"Error starting Flask app: {e}"
285
+
286
+ def check_template_path():
287
+ """Debug function to check template path"""
288
+ template_path = os.path.abspath(os.path.join(VIRTUAL_HOME, 'templates', 'index.html'))
289
+ return {
290
+ 'template_path': template_path,
291
+ 'exists': os.path.exists(template_path),
292
+ 'is_file': os.path.isfile(template_path) if os.path.exists(template_path) else False,
293
+ 'template_dir': os.path.dirname(template_path),
294
+ 'template_dir_exists': os.path.exists(os.path.dirname(template_path)),
295
+ 'template_dir_contents': os.listdir(os.path.dirname(template_path)) if os.path.exists(os.path.dirname(template_path)) else []
296
+ }
297
 
298
  def is_flask_app(file_path):
299
  """Check if a Python file contains Flask code."""
 
338
  thread.start()
339
 
340
  return f"Started Python application: {file_path}"
341
+
342
+ def stop_flask_app(app_name: str):
343
+ """Stop a specific Flask app"""
344
+ if app_name not in flask_threads:
345
+ return f"Flask app '{app_name}' is not running"
346
+
347
+ try:
348
+ # Try to gracefully shutdown the Flask app
349
+ if app_name in flask_apps:
350
+ # Flask doesn't have a built-in shutdown method for the dev server,
351
+ # so we'll rely on thread termination
352
+ pass
353
+
354
+ # Terminate the thread (this is a bit forceful but works for the dev server)
355
+ thread = flask_threads[app_name]
356
+ if thread.is_alive():
357
+ # Note: This may not clean up perfectly since Flask's dev server
358
+ # doesn't have a proper shutdown mechanism
359
+ pass
360
+
361
+ # Clean up the tracking dictionaries
362
+ flask_apps.pop(app_name, None)
363
+ flask_logs.pop(app_name, None)
364
+ flask_ports.pop(app_name, None)
365
+ flask_threads.pop(app_name, None)
366
+
367
+ return f"Stopped Flask app: {app_name}"
368
+
369
+ except Exception as e:
370
+ return f"Error stopping Flask app '{app_name}': {e}"
371
 
372
  def is_flask_app(file_path):
373
  """Check if a Python file contains Flask code."""
 
396
  result = "Running Flask apps:\n\n"
397
 
398
  for app_name, port in flask_ports.items():
399
+ thread = flask_threads.get(app_name)
400
+ status = "Running" if thread and thread.is_alive() else "Stopped"
401
  local_url = f"http://{local_ip}:{port}"
402
 
403
  if "hf.space" in public_url:
404
  space_id = public_url.split("https://")[1].split(".hf.space")[0]
405
  live_url = f"https://{space_id}.hf.space/proxy/{port}/"
406
+ result += f"- {app_name} ({status}):\n Local: {local_url}\n Live: {live_url}\n\n"
407
  else:
408
+ result += f"- {app_name} ({status}):\n Local: {local_url}\n Live: {public_url}/proxy/{port}/\n\n"
409
 
410
+ result += "Use 'close <app_name>' to stop a specific Flask app."
411
  return result
412
 
413
  def run_command(cmd: str):
 
425
  try:
426
  # === STOP running process ===
427
  if raw_cmd == "close":
428
+ if args:
429
+ result = stop_flask_app(args)
430
+ return f"$ {cmd}\n\n{result}", "", None
431
+ elif running_process and running_process.poll() is None:
432
+ running_process.terminate()
433
+ running_process = None
434
+ return f"$ {cmd}\n\nStopped running process.", "", None
435
+ return f"$ {cmd}\n\nNo active process to stop.", "", None
436
 
437
  # === Start Flask app from code ===
438
  elif raw_cmd == "flaskrun" and args:
 
467
  elif raw_cmd == "flasklist":
468
  apps_info = get_flask_apps()
469
  return f"$ {cmd}\n\n{apps_info}", "", None
470
+ # In run_command function, update debug:
471
+ elif raw_cmd == "debug":
472
+ result = f"Dataset workspace: {WORKDIR}\n"
473
+ result += f"Virtual home (Alex): {VIRTUAL_HOME}\n"
474
+ result += f"Current directory: {current_dir}\n"
475
+
476
+ # Check template path in detail
477
+ template_info = check_template_path()
478
+ result += f"Absolute template path: {template_info['template_path']}\n"
479
+ result += f"Template exists: {template_info['exists']}\n"
480
+ result += f"Template is file: {template_info['is_file']}\n"
481
+ result += f"Template dir exists: {template_info['template_dir_exists']}\n"
482
+ result += f"Template dir contents: {template_info['template_dir_contents']}\n"
483
+
484
+ result += f"Alex contents: {os.listdir(VIRTUAL_HOME) if os.path.exists(VIRTUAL_HOME) else 'Alex directory not found'}\n"
485
+ result += f"HF Token configured: {bool(HF_TOKEN)}\n"
486
+ return f"$ {cmd}\n\n{result}", "", None
487
+
488
+
489
+ # In run_command function, add:
490
+ elif raw_cmd == "sync":
491
+ """Sync current state with Hugging Face dataset"""
492
+ if not HF_TOKEN:
493
+ return "HF_TOKEN not configured. Cannot sync with dataset.", "", None
494
+
495
+ try:
496
+ api.upload_folder(
497
+ folder_path=WORKDIR,
498
+ repo_id=DATASET_REPO,
499
+ repo_type="dataset",
500
+ commit_message=f"Manual sync: {datetime.now().isoformat()}",
501
+ token=HF_TOKEN
502
+ )
503
+ return "Synced workspace with Hugging Face dataset", "", None
504
+ except Exception as e:
505
+ return f"Sync failed: {e}", "", None
506
+
507
+ # In the run_command function, add this case:
508
+ elif raw_cmd == "mkdir" and args:
509
+ if not args:
510
+ return f"$ {cmd}\n\nNo directory specified", "", None
511
+ target = expand_path(args)
512
+ os.makedirs(target, exist_ok=True)
513
+ return f"$ {cmd}\n\nCreated directory: {target}", "", None
514
 
515
  # === View logs ===
516
  elif raw_cmd == "logs" and args:
517
  app_name = args
518
  logs = view_logs(app_name)
519
  return f"$ {cmd}\n\nLogs for {app_name}:\n{logs}", "", None
520
+
521
+ # In run_command function, add:
522
+ elif raw_cmd == "testtemplate":
523
+ """Test if template can be loaded manually"""
524
+ try:
525
+ template_path = os.path.join(VIRTUAL_HOME, 'templates', 'index.html')
526
+ if os.path.exists(template_path):
527
+ with open(template_path, 'r') as f:
528
+ content = f.read()
529
+ return f"$ {cmd}\n\nTemplate found! Content length: {len(content)} characters", "", None
530
+ else:
531
+ return f"$ {cmd}\n\nTemplate not found at: {template_path}", "", None
532
+ except Exception as e:
533
+ return f"$ {cmd}\n\nError testing template: {e}", "", None
534
 
535
  # === Change directory (cd) ===
536
  elif raw_cmd == "cd":
 
631
  with open(file_path, "w", encoding="utf-8") as f:
632
  f.write(new_content)
633
 
634
+ # Always sync with Hugging Face dataset
635
  if HF_TOKEN and api:
636
  try:
637
+ # Upload the specific file that was modified
638
+ api.upload_file(
639
+ path_or_fileobj=file_path,
640
+ path_in_repo=file_path.replace(WORKDIR + "/", ""),
641
  repo_id=DATASET_REPO,
642
  repo_type="dataset",
643
+ commit_message=f"Updated file: {os.path.basename(file_path)}",
644
  token=HF_TOKEN
645
  )
646
+ return f"Saved file: {file_path} (synced with dataset)", new_content
647
  except Exception as e:
648
+ return f"Saved file: {file_path} (local only - upload failed: {e})", new_content
649
  else:
650
+ return f"Saved file: {file_path} (local only - no HF token)", new_content
 
 
651
  except Exception as e:
652
  return f"Error saving file: {e}", new_content
653