Miquel Farré
commited on
Commit
·
5504eb2
1
Parent(s):
a8daf8a
changing session_hash with browser generated uuid
Browse files
app.py
CHANGED
|
@@ -3,6 +3,7 @@ import os
|
|
| 3 |
import json
|
| 4 |
import shutil
|
| 5 |
import traceback
|
|
|
|
| 6 |
from textwrap import dedent
|
| 7 |
import time
|
| 8 |
from threading import Timer
|
|
@@ -368,72 +369,57 @@ def cleanup_sandboxes():
|
|
| 368 |
except Exception as e:
|
| 369 |
print(f"Error cleaning up sandbox {session_id}: {str(e)}")
|
| 370 |
|
| 371 |
-
|
| 372 |
-
def get_or_create_sandbox(session_hash):
|
| 373 |
current_time = time.time()
|
| 374 |
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
# Close existing sandbox if it exists but is too old
|
| 385 |
-
if session_hash in SANDBOXES:
|
| 386 |
try:
|
| 387 |
-
print(f"Closing expired sandbox for session {
|
| 388 |
-
SANDBOXES[
|
| 389 |
except Exception as e:
|
| 390 |
print(f"Error closing expired sandbox: {str(e)}")
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
print(f"Creating new sandbox for session {session_hash}")
|
| 394 |
desktop = Sandbox(api_key=E2B_API_KEY, resolution=(WIDTH, HEIGHT), dpi=96, timeout=SANDBOX_TIMEOUT)
|
| 395 |
desktop.stream.start(require_auth=True)
|
| 396 |
setup_cmd = """sudo mkdir -p /usr/lib/firefox-esr/distribution && echo '{"policies":{"OverrideFirstRunPage":"","OverridePostUpdatePage":"","DisableProfileImport":true,"DontCheckDefaultBrowser":true}}' | sudo tee /usr/lib/firefox-esr/distribution/policies.json > /dev/null"""
|
| 397 |
desktop.commands.run(setup_cmd)
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
SANDBOX_METADATA[session_hash] = {
|
| 402 |
'created_at': current_time,
|
| 403 |
'last_accessed': current_time
|
| 404 |
}
|
| 405 |
return desktop
|
| 406 |
|
| 407 |
-
def update_html(interactive_mode: bool,
|
| 408 |
-
|
| 409 |
-
desktop = get_or_create_sandbox(session_hash)
|
| 410 |
auth_key = desktop.stream.get_auth_key()
|
| 411 |
-
|
| 412 |
-
# Add view_only parameter based on interactive_mode
|
| 413 |
base_url = desktop.stream.get_url(auth_key=auth_key)
|
| 414 |
stream_url = base_url if interactive_mode else f"{base_url}&view_only=true"
|
| 415 |
|
| 416 |
-
# Set status indicator class and text
|
| 417 |
status_class = "status-interactive" if interactive_mode else "status-view-only"
|
| 418 |
status_text = "Interactive" if interactive_mode else "Agent running..."
|
| 419 |
-
|
| 420 |
-
creation_time = SANDBOX_METADATA[session_hash]['created_at'] if session_hash in SANDBOX_METADATA else time.time()
|
| 421 |
|
| 422 |
sandbox_html_content = sandbox_html_template.format(
|
| 423 |
stream_url=stream_url,
|
| 424 |
status_class=status_class,
|
| 425 |
status_text=status_text,
|
| 426 |
)
|
| 427 |
-
|
| 428 |
-
# Add hidden field with creation time for JavaScript to use
|
| 429 |
sandbox_html_content += f'<div id="sandbox-creation-time" style="display:none;" data-time="{creation_time}" data-timeout="{SANDBOX_TIMEOUT}"></div>'
|
| 430 |
-
|
| 431 |
return sandbox_html_content
|
| 432 |
|
| 433 |
-
def generate_interaction_id(request):
|
| 434 |
-
"""Generate a unique ID combining session hash and timestamp"""
|
| 435 |
-
return f"{request.session_hash}_{int(time.time())}"
|
| 436 |
|
|
|
|
|
|
|
| 437 |
|
| 438 |
def chat_message_to_json(obj):
|
| 439 |
"""Custom JSON serializer for ChatMessage and related objects"""
|
|
@@ -466,10 +452,18 @@ def save_final_status(folder, status: str, summary, error_message = None) -> Non
|
|
| 466 |
output_file.write(json.dumps({"status":status, "summary":summary, "error_message": error_message}, default=chat_message_to_json))
|
| 467 |
output_file.close()
|
| 468 |
|
| 469 |
-
def
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 473 |
|
| 474 |
|
| 475 |
def create_agent(data_dir, desktop):
|
|
@@ -501,22 +495,19 @@ class EnrichedGradioUI(GradioUI):
|
|
| 501 |
gr.Button(interactive=False),
|
| 502 |
)
|
| 503 |
|
| 504 |
-
def interact_with_agent(self, task_input, stored_messages, session_state,
|
| 505 |
-
|
|
|
|
| 506 |
|
| 507 |
-
interaction_id = generate_interaction_id(request)
|
| 508 |
-
desktop = get_or_create_sandbox(session_hash)
|
| 509 |
-
|
| 510 |
-
# Create data directory for this session
|
| 511 |
data_dir = os.path.join(TMP_DIR, interaction_id)
|
| 512 |
if not os.path.exists(data_dir):
|
| 513 |
os.makedirs(data_dir)
|
| 514 |
|
| 515 |
if "agent" in session_state:
|
| 516 |
-
session_state["agent"].data_dir = data_dir
|
| 517 |
else:
|
| 518 |
session_state["agent"] = create_agent(data_dir=data_dir, desktop=desktop)
|
| 519 |
-
|
| 520 |
try:
|
| 521 |
stored_messages.append(gr.ChatMessage(role="user", content=task_input))
|
| 522 |
yield stored_messages
|
|
@@ -553,7 +544,8 @@ theme = gr.themes.Default(font=["Oxanium", "sans-serif"], primary_hue="amber", s
|
|
| 553 |
# Create a Gradio app with Blocks
|
| 554 |
with gr.Blocks(theme=theme, css=custom_css, js=custom_js) as demo:
|
| 555 |
#Storing session hash in a state variable
|
| 556 |
-
|
|
|
|
| 557 |
|
| 558 |
|
| 559 |
with gr.Row():
|
|
@@ -690,12 +682,11 @@ _Please note that we store the task logs by default so **do not write any person
|
|
| 690 |
return f"Guru meditation: {str(e)}"
|
| 691 |
|
| 692 |
# Function to set view-only mode
|
| 693 |
-
def clear_and_set_view_only(task_input,
|
| 694 |
-
|
| 695 |
-
return update_html(False, request)
|
| 696 |
|
| 697 |
-
def set_interactive(
|
| 698 |
-
return update_html(True,
|
| 699 |
|
| 700 |
def reactivate_stop_btn():
|
| 701 |
return gr.Button("Stop the agent!", variant="huggingface")
|
|
@@ -705,14 +696,15 @@ _Please note that we store the task logs by default so **do not write any person
|
|
| 705 |
# Chain the events
|
| 706 |
run_event = run_btn.click(
|
| 707 |
fn=clear_and_set_view_only,
|
| 708 |
-
inputs=[task_input],
|
| 709 |
outputs=[sandbox_html]
|
| 710 |
).then(
|
| 711 |
agent_ui.interact_with_agent,
|
| 712 |
-
inputs=[task_input, stored_messages, session_state,
|
| 713 |
outputs=[chatbot_display]
|
| 714 |
).then(
|
| 715 |
fn=set_interactive,
|
|
|
|
| 716 |
outputs=[sandbox_html]
|
| 717 |
).then(
|
| 718 |
fn=reactivate_stop_btn,
|
|
@@ -749,9 +741,13 @@ _Please note that we store the task logs by default so **do not write any person
|
|
| 749 |
# )
|
| 750 |
|
| 751 |
demo.load(
|
|
|
|
|
|
|
|
|
|
| 752 |
fn=initialize_session,
|
|
|
|
| 753 |
inputs=[is_interactive],
|
| 754 |
-
outputs=[sandbox_html,
|
| 755 |
)
|
| 756 |
|
| 757 |
# Launch the app
|
|
|
|
| 3 |
import json
|
| 4 |
import shutil
|
| 5 |
import traceback
|
| 6 |
+
import uuid
|
| 7 |
from textwrap import dedent
|
| 8 |
import time
|
| 9 |
from threading import Timer
|
|
|
|
| 369 |
except Exception as e:
|
| 370 |
print(f"Error cleaning up sandbox {session_id}: {str(e)}")
|
| 371 |
|
| 372 |
+
def get_or_create_sandbox(session_uuid):
|
|
|
|
| 373 |
current_time = time.time()
|
| 374 |
|
| 375 |
+
if (session_uuid in SANDBOXES and
|
| 376 |
+
session_uuid in SANDBOX_METADATA and
|
| 377 |
+
current_time - SANDBOX_METADATA[session_uuid]['created_at'] < SANDBOX_TIMEOUT):
|
| 378 |
+
print(f"Reusing Sandbox for {session_uuid}")
|
| 379 |
+
SANDBOX_METADATA[session_uuid]['last_accessed'] = current_time
|
| 380 |
+
return SANDBOXES[session_uuid]
|
| 381 |
+
|
| 382 |
+
if session_uuid in SANDBOXES:
|
|
|
|
|
|
|
|
|
|
| 383 |
try:
|
| 384 |
+
print(f"Closing expired sandbox for session {session_uuid}")
|
| 385 |
+
SANDBOXES[session_uuid].kill()
|
| 386 |
except Exception as e:
|
| 387 |
print(f"Error closing expired sandbox: {str(e)}")
|
| 388 |
+
|
| 389 |
+
print(f"Creating new sandbox for session {session_uuid}")
|
|
|
|
| 390 |
desktop = Sandbox(api_key=E2B_API_KEY, resolution=(WIDTH, HEIGHT), dpi=96, timeout=SANDBOX_TIMEOUT)
|
| 391 |
desktop.stream.start(require_auth=True)
|
| 392 |
setup_cmd = """sudo mkdir -p /usr/lib/firefox-esr/distribution && echo '{"policies":{"OverrideFirstRunPage":"","OverridePostUpdatePage":"","DisableProfileImport":true,"DontCheckDefaultBrowser":true}}' | sudo tee /usr/lib/firefox-esr/distribution/policies.json > /dev/null"""
|
| 393 |
desktop.commands.run(setup_cmd)
|
| 394 |
+
|
| 395 |
+
SANDBOXES[session_uuid] = desktop
|
| 396 |
+
SANDBOX_METADATA[session_uuid] = {
|
|
|
|
| 397 |
'created_at': current_time,
|
| 398 |
'last_accessed': current_time
|
| 399 |
}
|
| 400 |
return desktop
|
| 401 |
|
| 402 |
+
def update_html(interactive_mode: bool, session_uuid):
|
| 403 |
+
desktop = get_or_create_sandbox(session_uuid)
|
|
|
|
| 404 |
auth_key = desktop.stream.get_auth_key()
|
|
|
|
|
|
|
| 405 |
base_url = desktop.stream.get_url(auth_key=auth_key)
|
| 406 |
stream_url = base_url if interactive_mode else f"{base_url}&view_only=true"
|
| 407 |
|
|
|
|
| 408 |
status_class = "status-interactive" if interactive_mode else "status-view-only"
|
| 409 |
status_text = "Interactive" if interactive_mode else "Agent running..."
|
| 410 |
+
creation_time = SANDBOX_METADATA[session_uuid]['created_at'] if session_uuid in SANDBOX_METADATA else time.time()
|
|
|
|
| 411 |
|
| 412 |
sandbox_html_content = sandbox_html_template.format(
|
| 413 |
stream_url=stream_url,
|
| 414 |
status_class=status_class,
|
| 415 |
status_text=status_text,
|
| 416 |
)
|
|
|
|
|
|
|
| 417 |
sandbox_html_content += f'<div id="sandbox-creation-time" style="display:none;" data-time="{creation_time}" data-timeout="{SANDBOX_TIMEOUT}"></div>'
|
|
|
|
| 418 |
return sandbox_html_content
|
| 419 |
|
|
|
|
|
|
|
|
|
|
| 420 |
|
| 421 |
+
def generate_interaction_id(session_uuid):
|
| 422 |
+
return f"{session_uuid}_{int(time.time())}"
|
| 423 |
|
| 424 |
def chat_message_to_json(obj):
|
| 425 |
"""Custom JSON serializer for ChatMessage and related objects"""
|
|
|
|
| 452 |
output_file.write(json.dumps({"status":status, "summary":summary, "error_message": error_message}, default=chat_message_to_json))
|
| 453 |
output_file.close()
|
| 454 |
|
| 455 |
+
def extract_browser_uuid(js_uuid):
|
| 456 |
+
print(f"[BROWSER] Got browser UUID from JS: {js_uuid}")
|
| 457 |
+
return js_uuid
|
| 458 |
+
|
| 459 |
+
def initialize_session(request: gr.Request, interactive_mode, browser_uuid):
|
| 460 |
+
if not browser_uuid:
|
| 461 |
+
new_uuid = str(uuid.uuid4())
|
| 462 |
+
print(f"[LOAD] No UUID from browser, generating: {new_uuid}")
|
| 463 |
+
return update_html(interactive_mode, new_uuid), new_uuid
|
| 464 |
+
else:
|
| 465 |
+
print(f"[LOAD] Got UUID from browser: {browser_uuid}")
|
| 466 |
+
return update_html(interactive_mode, browser_uuid), browser_uuid
|
| 467 |
|
| 468 |
|
| 469 |
def create_agent(data_dir, desktop):
|
|
|
|
| 495 |
gr.Button(interactive=False),
|
| 496 |
)
|
| 497 |
|
| 498 |
+
def interact_with_agent(self, task_input, stored_messages, session_state, session_uuid, consent_storage, request: gr.Request):
|
| 499 |
+
interaction_id = generate_interaction_id(session_uuid)
|
| 500 |
+
desktop = get_or_create_sandbox(session_uuid)
|
| 501 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 502 |
data_dir = os.path.join(TMP_DIR, interaction_id)
|
| 503 |
if not os.path.exists(data_dir):
|
| 504 |
os.makedirs(data_dir)
|
| 505 |
|
| 506 |
if "agent" in session_state:
|
| 507 |
+
session_state["agent"].data_dir = data_dir
|
| 508 |
else:
|
| 509 |
session_state["agent"] = create_agent(data_dir=data_dir, desktop=desktop)
|
| 510 |
+
|
| 511 |
try:
|
| 512 |
stored_messages.append(gr.ChatMessage(role="user", content=task_input))
|
| 513 |
yield stored_messages
|
|
|
|
| 544 |
# Create a Gradio app with Blocks
|
| 545 |
with gr.Blocks(theme=theme, css=custom_css, js=custom_js) as demo:
|
| 546 |
#Storing session hash in a state variable
|
| 547 |
+
session_uuid_state = gr.State(None)
|
| 548 |
+
|
| 549 |
|
| 550 |
|
| 551 |
with gr.Row():
|
|
|
|
| 682 |
return f"Guru meditation: {str(e)}"
|
| 683 |
|
| 684 |
# Function to set view-only mode
|
| 685 |
+
def clear_and_set_view_only(task_input, session_uuid):
|
| 686 |
+
return update_html(False, session_uuid)
|
|
|
|
| 687 |
|
| 688 |
+
def set_interactive(session_uuid):
|
| 689 |
+
return update_html(True, session_uuid)
|
| 690 |
|
| 691 |
def reactivate_stop_btn():
|
| 692 |
return gr.Button("Stop the agent!", variant="huggingface")
|
|
|
|
| 696 |
# Chain the events
|
| 697 |
run_event = run_btn.click(
|
| 698 |
fn=clear_and_set_view_only,
|
| 699 |
+
inputs=[task_input, session_uuid_state],
|
| 700 |
outputs=[sandbox_html]
|
| 701 |
).then(
|
| 702 |
agent_ui.interact_with_agent,
|
| 703 |
+
inputs=[task_input, stored_messages, session_state, session_uuid_state, consent_storage],
|
| 704 |
outputs=[chatbot_display]
|
| 705 |
).then(
|
| 706 |
fn=set_interactive,
|
| 707 |
+
inputs=[session_uuid_state],
|
| 708 |
outputs=[sandbox_html]
|
| 709 |
).then(
|
| 710 |
fn=reactivate_stop_btn,
|
|
|
|
| 741 |
# )
|
| 742 |
|
| 743 |
demo.load(
|
| 744 |
+
fn=lambda: True, # dummy to trigger the load
|
| 745 |
+
outputs=[is_interactive],
|
| 746 |
+
).then(
|
| 747 |
fn=initialize_session,
|
| 748 |
+
js="() => localStorage.getItem('gradio-session-uuid') || (() => { const id = self.crypto.randomUUID(); localStorage.setItem('gradio-session-uuid', id); return id })()",
|
| 749 |
inputs=[is_interactive],
|
| 750 |
+
outputs=[sandbox_html, session_uuid_state],
|
| 751 |
)
|
| 752 |
|
| 753 |
# Launch the app
|