Miquel Farré commited on
Commit
f0cc2ca
·
1 Parent(s): 55bc851
Files changed (2) hide show
  1. app.py +200 -16
  2. e2bqwen.py +2 -3
app.py CHANGED
@@ -1,15 +1,21 @@
1
  import gradio as gr
2
  import os
 
3
  import shutil
 
4
  from e2b_desktop import Sandbox
5
  from huggingface_hub import upload_folder, login
6
  from smolagents.monitoring import LogLevel
7
  from textwrap import dedent
 
 
8
 
9
  from e2bqwen import QwenVLAPIModel, E2BVisionAgent
10
 
11
  E2B_API_KEY = os.getenv("E2B_API_KEY")
12
  SANDBOXES = {}
 
 
13
  WIDTH = 1920
14
  HEIGHT = 1440
15
  TMP_DIR = './tmp/'
@@ -93,6 +99,17 @@ custom_css = """
93
  background-color: #e74c3c;
94
  }
95
 
 
 
 
 
 
 
 
 
 
 
 
96
  @keyframes blink {
97
  0% { background-color: rgba(46, 204, 113, 1); } /* Green at full opacity */
98
  50% { background-color: rgba(46, 204, 113, 0.4); } /* Green at 40% opacity */
@@ -100,19 +117,109 @@ custom_css = """
100
  }
101
  """
102
 
 
103
  html_template = """
104
  <div class="sandbox-outer-wrapper">
105
  <div class="sandbox-container">
106
  <img src="https://huggingface.co/datasets/lvwerra/admin/resolve/main/desktop_scaled.png" class="sandbox-background" />
107
  <div class="status-text">{status_text}</div>
108
  <div class="status-indicator {status_class}"></div>
109
- <iframe
110
  src="{stream_url}"
111
  class="sandbox-iframe"
 
112
  allowfullscreen>
113
  </iframe>
 
 
 
 
 
114
  </div>
115
- </div>"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
 
117
  def upload_to_hf_and_remove(folder_path):
118
 
@@ -141,17 +248,69 @@ def upload_to_hf_and_remove(folder_path):
141
  print(f"Error during upload or cleanup: {str(e)}")
142
  raise
143
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
 
145
  def get_or_create_sandbox(session_hash):
146
- if session_hash not in SANDBOXES:
147
- print(f"Creating new sandbox for session {session_hash}")
148
- desktop = Sandbox(api_key=E2B_API_KEY, resolution=(WIDTH, HEIGHT), dpi=96)
149
- desktop.stream.start(require_auth=True)
150
- SANDBOXES[session_hash] = desktop
151
- return SANDBOXES[session_hash]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
 
153
  def update_html(interactive_mode, request: gr.Request):
154
- desktop = get_or_create_sandbox(request.session_hash)
 
155
  auth_key = desktop.stream.get_auth_key()
156
 
157
  # Add view_only parameter based on interactive_mode
@@ -162,19 +321,37 @@ def update_html(interactive_mode, request: gr.Request):
162
  status_class = "status-interactive" if interactive_mode else "status-view-only"
163
  status_text = "Interactive" if interactive_mode else "View Only"
164
 
 
 
165
  html_content = html_template.format(
166
  stream_url=stream_url,
167
  status_class=status_class,
168
- status_text=status_text
 
 
169
  )
 
 
 
 
170
  return html_content
171
 
 
 
 
 
 
 
 
 
 
172
  def run_agent_task(task_input, interactive_mode, request: gr.Request):
173
  session_hash = request.session_hash
 
174
  desktop = get_or_create_sandbox(session_hash)
175
 
176
  # Create data directory for this session
177
- data_dir = os.path.join(TMP_DIR, session_hash)
178
  if not os.path.exists(data_dir):
179
  os.makedirs(data_dir)
180
 
@@ -185,7 +362,7 @@ def run_agent_task(task_input, interactive_mode, request: gr.Request):
185
  data_dir=data_dir,
186
  desktop=desktop,
187
  max_steps=200,
188
- verbosity_level=LogLevel.DEBUG,
189
  planning_interval=5,
190
  )
191
 
@@ -206,21 +383,26 @@ def run_agent_task(task_input, interactive_mode, request: gr.Request):
206
 
207
  try:
208
  # Run the agent
 
209
  result = agent.run(full_task)
 
210
 
211
  interactive_mode = True
212
  return f"Task completed: {result}", update_html(interactive_mode, request)
213
  except Exception as e:
214
- error_message = f"Error running agent: {str(e)}"
 
 
215
  print(error_message)
216
  interactive_mode = True
217
- return error_message, update_html(interactive_mode, request)
 
218
  finally:
219
  upload_to_hf_and_remove(data_dir)
220
 
221
 
222
  # Create a Gradio app with Blocks
223
- with gr.Blocks(css=custom_css) as demo:
224
  gr.HTML("""<h1 style="text-align: center">Personal Computer Assistant</h1>""")
225
 
226
  # HTML output with simulated image and iframe
@@ -248,7 +430,8 @@ with gr.Blocks(css=custom_css) as demo:
248
  # Results output
249
  results_output = gr.Textbox(
250
  label="Results",
251
- interactive=False
 
252
  )
253
 
254
  # Update button
@@ -273,4 +456,5 @@ with gr.Blocks(css=custom_css) as demo:
273
 
274
  # Launch the app
275
  if __name__ == "__main__":
 
276
  demo.launch()
 
1
  import gradio as gr
2
  import os
3
+ import json
4
  import shutil
5
+ import traceback
6
  from e2b_desktop import Sandbox
7
  from huggingface_hub import upload_folder, login
8
  from smolagents.monitoring import LogLevel
9
  from textwrap import dedent
10
+ import time
11
+ from threading import Timer
12
 
13
  from e2bqwen import QwenVLAPIModel, E2BVisionAgent
14
 
15
  E2B_API_KEY = os.getenv("E2B_API_KEY")
16
  SANDBOXES = {}
17
+ SANDBOX_METADATA = {}
18
+ SANDBOX_TIMEOUT = 300
19
  WIDTH = 1920
20
  HEIGHT = 1440
21
  TMP_DIR = './tmp/'
 
99
  background-color: #e74c3c;
100
  }
101
 
102
+ .status-error {
103
+ background-color: #e74c3c;
104
+ animation: blink-error 1s infinite;
105
+ }
106
+
107
+ @keyframes blink-error {
108
+ 0% { background-color: rgba(231, 76, 60, 1); }
109
+ 50% { background-color: rgba(231, 76, 60, 0.4); }
110
+ 100% { background-color: rgba(231, 76, 60, 1); }
111
+ }
112
+
113
  @keyframes blink {
114
  0% { background-color: rgba(46, 204, 113, 1); } /* Green at full opacity */
115
  50% { background-color: rgba(46, 204, 113, 0.4); } /* Green at 40% opacity */
 
117
  }
118
  """
119
 
120
+
121
  html_template = """
122
  <div class="sandbox-outer-wrapper">
123
  <div class="sandbox-container">
124
  <img src="https://huggingface.co/datasets/lvwerra/admin/resolve/main/desktop_scaled.png" class="sandbox-background" />
125
  <div class="status-text">{status_text}</div>
126
  <div class="status-indicator {status_class}"></div>
127
+ <iframe id="sandbox-iframe"
128
  src="{stream_url}"
129
  class="sandbox-iframe"
130
+ style="display: block;"
131
  allowfullscreen>
132
  </iframe>
133
+ <img id="bsod-image"
134
+ src="https://huggingface.co/datasets/mfarre/servedfiles/resolve/main/blue_screen_of_death.gif"
135
+ class="bsod-image"
136
+ style="display: none; position: absolute; top: 10%; left: 25%; width: 400px; height: 300px; border: 4px solid #444444;"
137
+ />
138
  </div>
139
+ </div>
140
+ """
141
+
142
+ custom_js = """
143
+ function() {
144
+ // Function to check if sandbox is timing out
145
+ const checkSandboxTimeout = function() {
146
+ const timeElement = document.getElementById('sandbox-creation-time');
147
+
148
+ if (timeElement) {
149
+ const creationTime = parseFloat(timeElement.getAttribute('data-time'));
150
+ const timeoutValue = parseFloat(timeElement.getAttribute('data-timeout'));
151
+ const currentTime = Math.floor(Date.now() / 1000); // Current time in seconds
152
+
153
+ const elapsedTime = currentTime - creationTime;
154
+ console.log("Sandbox running for: " + elapsedTime + " seconds of " + timeoutValue + " seconds");
155
+
156
+ // If we've exceeded the timeout, show BSOD
157
+ if (elapsedTime >= timeoutValue) {
158
+ console.log("Sandbox timeout! Showing BSOD");
159
+ showBSOD('Error');
160
+ // Don't set another timeout, we're done checking
161
+ return;
162
+ }
163
+ }
164
+
165
+ // Continue checking every 5 seconds
166
+ setTimeout(checkSandboxTimeout, 5000);
167
+ };
168
+
169
+ const showBSOD = function(statusText = 'Error') {
170
+ console.log("Showing BSOD with status: " + statusText);
171
+ const iframe = document.getElementById('sandbox-iframe');
172
+ const bsod = document.getElementById('bsod-image');
173
+
174
+ if (iframe && bsod) {
175
+ iframe.style.display = 'none';
176
+ bsod.style.display = 'block';
177
+
178
+ // Update status indicator
179
+ const statusIndicator = document.querySelector('.status-indicator');
180
+ const statusTextElem = document.querySelector('.status-text');
181
+
182
+ if (statusIndicator) {
183
+ statusIndicator.className = 'status-indicator status-error';
184
+ }
185
+
186
+ if (statusTextElem) {
187
+ statusTextElem.innerText = statusText;
188
+ }
189
+ }
190
+ };
191
+
192
+ // Function to monitor for error messages
193
+ const monitorForErrors = function() {
194
+ console.log("Error monitor started");
195
+ const resultsInterval = setInterval(function() {
196
+ const resultsElements = document.querySelectorAll('textarea, .output-text');
197
+ for (let elem of resultsElements) {
198
+ const content = elem.value || elem.innerText || '';
199
+ if (content.includes('Error running agent:')) {
200
+ console.log("Error detected!");
201
+ showBSOD('Error');
202
+ clearInterval(resultsInterval);
203
+ break;
204
+ }
205
+ }
206
+ }, 1000);
207
+ };
208
+
209
+ // Start monitoring for timeouts immediately
210
+ checkSandboxTimeout();
211
+
212
+ // Start monitoring for errors
213
+ setTimeout(monitorForErrors, 3000);
214
+
215
+ // Also monitor for errors after button clicks
216
+ document.addEventListener('click', function(e) {
217
+ if (e.target.tagName === 'BUTTON') {
218
+ setTimeout(monitorForErrors, 3000);
219
+ }
220
+ });
221
+ }
222
+ """
223
 
224
  def upload_to_hf_and_remove(folder_path):
225
 
 
248
  print(f"Error during upload or cleanup: {str(e)}")
249
  raise
250
 
251
+ def cleanup_sandboxes():
252
+ """Remove sandboxes that haven't been accessed for more than 5 minutes"""
253
+ current_time = time.time()
254
+ sandboxes_to_remove = []
255
+
256
+ for session_id, metadata in SANDBOX_METADATA.items():
257
+ if current_time - metadata['last_accessed'] > SANDBOX_TIMEOUT:
258
+ sandboxes_to_remove.append(session_id)
259
+
260
+ for session_id in sandboxes_to_remove:
261
+ if session_id in SANDBOXES:
262
+ try:
263
+ # Upload data before removing if needed
264
+ data_dir = os.path.join(TMP_DIR, session_id)
265
+ if os.path.exists(data_dir):
266
+ upload_to_hf_and_remove(data_dir)
267
+
268
+ # Close the sandbox
269
+ SANDBOXES[session_id].kill()
270
+ del SANDBOXES[session_id]
271
+ del SANDBOX_METADATA[session_id]
272
+ print(f"Cleaned up sandbox for session {session_id}")
273
+ except Exception as e:
274
+ print(f"Error cleaning up sandbox {session_id}: {str(e)}")
275
+
276
 
277
  def get_or_create_sandbox(session_hash):
278
+ current_time = time.time()
279
+
280
+ # Check if sandbox exists and is still valid
281
+ if (session_hash in SANDBOXES and
282
+ session_hash in SANDBOX_METADATA and
283
+ current_time - SANDBOX_METADATA[session_hash]['created_at'] < SANDBOX_TIMEOUT):
284
+
285
+ # Update last accessed time
286
+ SANDBOX_METADATA[session_hash]['last_accessed'] = current_time
287
+ return SANDBOXES[session_hash]
288
+
289
+ # Close existing sandbox if it exists but is too old
290
+ if session_hash in SANDBOXES:
291
+ try:
292
+ print(f"Closing expired sandbox for session {session_hash}")
293
+ SANDBOXES[session_hash].kill()
294
+ except Exception as e:
295
+ print(f"Error closing expired sandbox: {str(e)}")
296
+
297
+ # Create new sandbox
298
+ print(f"Creating new sandbox for session {session_hash}")
299
+ desktop = Sandbox(api_key=E2B_API_KEY, resolution=(WIDTH, HEIGHT), dpi=96, timeout=SANDBOX_TIMEOUT)
300
+ desktop.stream.start(require_auth=True)
301
+
302
+ # Store sandbox with metadata
303
+ SANDBOXES[session_hash] = desktop
304
+ SANDBOX_METADATA[session_hash] = {
305
+ 'created_at': current_time,
306
+ 'last_accessed': current_time
307
+ }
308
+
309
+ return desktop
310
 
311
  def update_html(interactive_mode, request: gr.Request):
312
+ session_hash = request.session_hash
313
+ desktop = get_or_create_sandbox(session_hash)
314
  auth_key = desktop.stream.get_auth_key()
315
 
316
  # Add view_only parameter based on interactive_mode
 
321
  status_class = "status-interactive" if interactive_mode else "status-view-only"
322
  status_text = "Interactive" if interactive_mode else "View Only"
323
 
324
+ creation_time = SANDBOX_METADATA[session_hash]['created_at'] if session_hash in SANDBOX_METADATA else time.time()
325
+
326
  html_content = html_template.format(
327
  stream_url=stream_url,
328
  status_class=status_class,
329
+ status_text=status_text,
330
+ iframe_style="display: block;",
331
+ bsod_style="display: none;"
332
  )
333
+
334
+ # Add hidden field with creation time for JavaScript to use
335
+ html_content += f'<div id="sandbox-creation-time" style="display:none;" data-time="{creation_time}" data-timeout="{SANDBOX_TIMEOUT}"></div>'
336
+
337
  return html_content
338
 
339
+ def generate_interaction_id(request):
340
+ """Generate a unique ID combining session hash and timestamp"""
341
+ return f"{request.session_hash}_{int(time.time())}"
342
+
343
+ def save_final_status(folder, status, details = None):
344
+ a = open(os.path.join(folder,"status.json"),"w")
345
+ a.write(json.dumps({"status":status,"details":str(details)}))
346
+ a.close()
347
+
348
  def run_agent_task(task_input, interactive_mode, request: gr.Request):
349
  session_hash = request.session_hash
350
+ interaction_id = generate_interaction_id(request)
351
  desktop = get_or_create_sandbox(session_hash)
352
 
353
  # Create data directory for this session
354
+ data_dir = os.path.join(TMP_DIR, interaction_id)
355
  if not os.path.exists(data_dir):
356
  os.makedirs(data_dir)
357
 
 
362
  data_dir=data_dir,
363
  desktop=desktop,
364
  max_steps=200,
365
+ verbosity_level=LogLevel.INFO,
366
  planning_interval=5,
367
  )
368
 
 
383
 
384
  try:
385
  # Run the agent
386
+ r
387
  result = agent.run(full_task)
388
+ save_final_status(data_dir, "completed", details = result)
389
 
390
  interactive_mode = True
391
  return f"Task completed: {result}", update_html(interactive_mode, request)
392
  except Exception as e:
393
+ error_message = f"Error running agent: {str(e)} Details {traceback.format_exc()}"
394
+ save_final_status(data_dir, "failed", details = error_message)
395
+
396
  print(error_message)
397
  interactive_mode = True
398
+ # return error_message, update_html(interactive_mode, request)
399
+ return error_message
400
  finally:
401
  upload_to_hf_and_remove(data_dir)
402
 
403
 
404
  # Create a Gradio app with Blocks
405
+ with gr.Blocks(css=custom_css, js=custom_js) as demo:
406
  gr.HTML("""<h1 style="text-align: center">Personal Computer Assistant</h1>""")
407
 
408
  # HTML output with simulated image and iframe
 
430
  # Results output
431
  results_output = gr.Textbox(
432
  label="Results",
433
+ interactive=False,
434
+ elem_id="results-output"
435
  )
436
 
437
  # Update button
 
456
 
457
  # Launch the app
458
  if __name__ == "__main__":
459
+ Timer(60, cleanup_sandboxes).start() # Run every minute
460
  demo.launch()
e2bqwen.py CHANGED
@@ -279,10 +279,9 @@ REMEMBER TO ALWAYS CLICK IN THE MIDDLE OF THE TEXT, NOT ON THE SIDE, NOT UNDER.
279
  def store_metadata_to_file(self, agent) -> None:
280
  metadata_path = os.path.join(self.data_dir, "metadata.json")
281
  output = {}
282
- output['memory'] = self.write_memory_to_messages()
283
- output['total_token_counts'] = agent.monitor.get_total_token_counts()
284
  a = open(metadata_path,"w")
285
- a.write(json.dumps(output))
286
  a.close()
287
 
288
  def write_memory_to_messages(self) -> List[Dict[str, Any]]:
 
279
  def store_metadata_to_file(self, agent) -> None:
280
  metadata_path = os.path.join(self.data_dir, "metadata.json")
281
  output = {}
282
+ output_memory = self.write_memory_to_messages()
 
283
  a = open(metadata_path,"w")
284
+ a.write(json.dumps(output_memory))
285
  a.close()
286
 
287
  def write_memory_to_messages(self) -> List[Dict[str, Any]]: