|
|
import json
|
|
|
import gradio as gr
|
|
|
|
|
|
from backend import (
|
|
|
task_queue,
|
|
|
result_queue,
|
|
|
BROWSER_STATE,
|
|
|
BROWSER_LOCK,
|
|
|
start_worker_thread,
|
|
|
stop_worker_thread,
|
|
|
StorageState
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def start_browser(storage_state_file:bytes):
|
|
|
with BROWSER_LOCK:
|
|
|
running = BROWSER_STATE.running
|
|
|
|
|
|
if running:
|
|
|
with BROWSER_LOCK:
|
|
|
tabs = list(BROWSER_STATE.pages.keys())
|
|
|
active = BROWSER_STATE.active_page
|
|
|
return "Browser is already running.", None, gr.update(choices=tabs, value=active)
|
|
|
|
|
|
start_worker_thread(StorageState(json.loads(storage_state_file)) if storage_state_file else None)
|
|
|
return "Browser Started!", None, gr.update(choices=["Tab-1"], value="Tab-1")
|
|
|
|
|
|
|
|
|
def stop_browser():
|
|
|
with BROWSER_LOCK:
|
|
|
running = BROWSER_STATE.running
|
|
|
|
|
|
if not running:
|
|
|
return "Browser is not running.", None, gr.update(choices=[], value=None)
|
|
|
|
|
|
stop_worker_thread()
|
|
|
with BROWSER_LOCK:
|
|
|
BROWSER_STATE.pages.clear()
|
|
|
BROWSER_STATE.active_page = None
|
|
|
|
|
|
return "Browser Closed!", None, gr.update(choices=[], value=None)
|
|
|
|
|
|
|
|
|
def execute_code(code):
|
|
|
with BROWSER_LOCK:
|
|
|
if not BROWSER_STATE.running:
|
|
|
return "Start browser first.", None
|
|
|
task_queue.put({"cmd": "eval", "code": code})
|
|
|
return result_queue.get()
|
|
|
|
|
|
|
|
|
def navigate(url):
|
|
|
with BROWSER_LOCK:
|
|
|
if not BROWSER_STATE.running:
|
|
|
return "Start browser first.", None
|
|
|
task_queue.put({"cmd": "goto", "url": url})
|
|
|
return result_queue.get()
|
|
|
|
|
|
|
|
|
def click(selector):
|
|
|
with BROWSER_LOCK:
|
|
|
if not BROWSER_STATE.running:
|
|
|
return "Start browser first.", None
|
|
|
task_queue.put({"cmd": "click", "selector": selector})
|
|
|
return result_queue.get()
|
|
|
|
|
|
|
|
|
def type_text(selector, text):
|
|
|
with BROWSER_LOCK:
|
|
|
if not BROWSER_STATE.running:
|
|
|
return "Start browser first.", None
|
|
|
task_queue.put({"cmd": "type", "selector": selector, "text": text})
|
|
|
return result_queue.get()
|
|
|
|
|
|
|
|
|
def new_tab():
|
|
|
with BROWSER_LOCK:
|
|
|
if not BROWSER_STATE.running:
|
|
|
return "Start browser first.", None
|
|
|
task_queue.put({"cmd": "new_tab"})
|
|
|
r, screenshot = result_queue.get()
|
|
|
with BROWSER_LOCK:
|
|
|
tabs = list(BROWSER_STATE.pages.keys())
|
|
|
active = BROWSER_STATE.active_page
|
|
|
return r, screenshot, gr.update(choices=tabs, value=active)
|
|
|
|
|
|
|
|
|
def close_tab(tab):
|
|
|
with BROWSER_LOCK:
|
|
|
if not BROWSER_STATE.running:
|
|
|
return "Start browser first.", None
|
|
|
task_queue.put({"cmd": "close_tab", "tab": tab})
|
|
|
r, screenshot = result_queue.get()
|
|
|
with BROWSER_LOCK:
|
|
|
tabs = list(BROWSER_STATE.pages.keys())
|
|
|
active = BROWSER_STATE.active_page
|
|
|
return r, screenshot, gr.update(choices=tabs, value=active)
|
|
|
|
|
|
|
|
|
def switch_tab(tab):
|
|
|
with BROWSER_LOCK:
|
|
|
if not BROWSER_STATE.running:
|
|
|
return "Start browser first.", None
|
|
|
task_queue.put({"cmd": "switch_tab", "tab": tab})
|
|
|
r, screenshot = result_queue.get()
|
|
|
with BROWSER_LOCK:
|
|
|
tabs = list(BROWSER_STATE.pages.keys())
|
|
|
active = BROWSER_STATE.active_page
|
|
|
return r, screenshot, gr.update(choices=tabs, value=active)
|
|
|
|
|
|
|
|
|
def inspect_element(selector):
|
|
|
with BROWSER_LOCK:
|
|
|
if not BROWSER_STATE.running:
|
|
|
return "Start browser first.", None
|
|
|
task_queue.put({"cmd": "inspect", "selector": selector})
|
|
|
return result_queue.get()
|
|
|
|
|
|
|
|
|
def show_network_logs():
|
|
|
with BROWSER_LOCK:
|
|
|
if not BROWSER_STATE.running:
|
|
|
return "Start browser first.", None
|
|
|
task_queue.put({"cmd": "get_network_logs"})
|
|
|
return result_queue.get()
|
|
|
|
|
|
|
|
|
def show_console_logs():
|
|
|
with BROWSER_LOCK:
|
|
|
if not BROWSER_STATE.running:
|
|
|
return "Start browser first.", None
|
|
|
task_queue.put({"cmd": "get_console_logs"})
|
|
|
return result_queue.get()
|
|
|
|
|
|
|
|
|
def clear_logs():
|
|
|
with BROWSER_LOCK:
|
|
|
if not BROWSER_STATE.running:
|
|
|
return "Start browser first.", None
|
|
|
task_queue.put({"cmd": "clear_logs"})
|
|
|
return result_queue.get()
|
|
|
|
|
|
|
|
|
def start_recording():
|
|
|
with BROWSER_LOCK:
|
|
|
if not BROWSER_STATE.running:
|
|
|
return "Start browser first.", None
|
|
|
task_queue.put({"cmd": "start_record"})
|
|
|
return result_queue.get()
|
|
|
|
|
|
|
|
|
def stop_recording():
|
|
|
with BROWSER_LOCK:
|
|
|
if not BROWSER_STATE.running:
|
|
|
return "Start browser first.", None
|
|
|
task_queue.put({"cmd": "stop_record"})
|
|
|
return result_queue.get()
|
|
|
|
|
|
|
|
|
def play_macro():
|
|
|
with BROWSER_LOCK:
|
|
|
if not BROWSER_STATE.running:
|
|
|
return "Start browser first.", None
|
|
|
task_queue.put({"cmd": "play_macro"})
|
|
|
return result_queue.get()
|
|
|
|
|
|
|
|
|
def capture_screenshot():
|
|
|
with BROWSER_LOCK:
|
|
|
if not BROWSER_STATE.running:
|
|
|
return "Start browser first.", None
|
|
|
task_queue.put({"cmd": "take_screenshot"})
|
|
|
return result_queue.get()
|
|
|
|
|
|
|
|
|
def find_template(template_img):
|
|
|
with BROWSER_LOCK:
|
|
|
if not BROWSER_STATE.running:
|
|
|
return "Start browser first.", None
|
|
|
task_queue.put({"cmd": "find_template", "template": template_img})
|
|
|
return result_queue.get()
|
|
|
|
|
|
|
|
|
def handle_click(event: gr.SelectData, click_type, last_image):
|
|
|
x, y = event.index
|
|
|
|
|
|
if last_image is None:
|
|
|
return "No screenshot available. Take screenshot first.", None
|
|
|
|
|
|
img_w, img_h = last_image.size
|
|
|
|
|
|
with BROWSER_LOCK:
|
|
|
if not BROWSER_STATE.running:
|
|
|
return "Start browser first.", None
|
|
|
|
|
|
task_queue.put({
|
|
|
"cmd": "click_xy",
|
|
|
"x": x,
|
|
|
"y": y,
|
|
|
"img_w": img_w,
|
|
|
"img_h": img_h,
|
|
|
"click_type": click_type
|
|
|
})
|
|
|
return result_queue.get()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with gr.Blocks() as app:
|
|
|
gr.Markdown("## π₯ Advanced Playwright Control Panel (DOM + Logs + Macro + Vision RPA)")
|
|
|
state_file = gr.File(label="PlayWright State File",file_types=['.json'],type="binary")
|
|
|
with gr.Row():
|
|
|
start_btn = gr.Button("Open Browser")
|
|
|
stop_btn = gr.Button("Close Browser")
|
|
|
|
|
|
tabs_dropdown = gr.Dropdown(label="Tabs", choices=[], value=None)
|
|
|
|
|
|
with gr.Tab("Browse"):
|
|
|
with gr.Row():
|
|
|
url_box = gr.Textbox(label="URL", scale=4)
|
|
|
nav_btn = gr.Button("Go", scale=1)
|
|
|
|
|
|
with gr.Row():
|
|
|
sel_box = gr.Textbox(label="Selector (CSS)", scale=3)
|
|
|
click_btn = gr.Button("Click", scale=1)
|
|
|
type_box = gr.Textbox(label="Type Text", scale=3)
|
|
|
type_btn = gr.Button("Type", scale=1)
|
|
|
|
|
|
with gr.Tab("Inspect / Code"):
|
|
|
with gr.Row():
|
|
|
inspect_sel = gr.Textbox(label="Inspect Selector (CSS)")
|
|
|
inspect_btn = gr.Button("Inspect + Generate XPath")
|
|
|
code_input = gr.TextArea(label="Python Code (eval in Playwright worker - restricted)")
|
|
|
run_btn = gr.Button("Run Code")
|
|
|
|
|
|
with gr.Tab("Logs"):
|
|
|
with gr.Row():
|
|
|
net_btn = gr.Button("Show Network Logs")
|
|
|
cons_btn = gr.Button("Show Console Logs")
|
|
|
clear_logs_btn = gr.Button("Clear Logs")
|
|
|
|
|
|
with gr.Tab("Tabs & Macros"):
|
|
|
with gr.Row():
|
|
|
new_tab_btn = gr.Button("New Tab")
|
|
|
close_tab_btn = gr.Button("Close Selected Tab")
|
|
|
switch_tab_btn = gr.Button("Switch Tab")
|
|
|
|
|
|
with gr.Row():
|
|
|
start_rec_btn = gr.Button("Start Macro Recording")
|
|
|
stop_rec_btn = gr.Button("Stop Recording")
|
|
|
play_macro_btn = gr.Button("Play Recorded Macro")
|
|
|
|
|
|
with gr.Tab("Vision Tools"):
|
|
|
capture_btn = gr.Button("πΈ Capture Screenshot")
|
|
|
click_type = gr.Radio(
|
|
|
["left", "double", "right", "hover"],
|
|
|
value="left",
|
|
|
label="Click Type"
|
|
|
)
|
|
|
template_img = gr.Image(
|
|
|
label="Template Image (for OpenCV match)",
|
|
|
type="pil"
|
|
|
)
|
|
|
find_template_btn = gr.Button("π Find Template on Page")
|
|
|
|
|
|
output_text = gr.TextArea(label="Output")
|
|
|
output_image = gr.Image(label="Screenshot", type="pil")
|
|
|
|
|
|
|
|
|
output_image.select(
|
|
|
handle_click,
|
|
|
inputs=[click_type, output_image],
|
|
|
outputs=[output_text, output_image]
|
|
|
)
|
|
|
|
|
|
|
|
|
start_btn.click(start_browser, inputs=[state_file], outputs=[output_text, output_image, tabs_dropdown])
|
|
|
stop_btn.click(stop_browser, outputs=[output_text, output_image, tabs_dropdown])
|
|
|
|
|
|
new_tab_btn.click(new_tab, outputs=[output_text, output_image, tabs_dropdown])
|
|
|
close_tab_btn.click(close_tab, inputs=tabs_dropdown, outputs=[output_text, output_image, tabs_dropdown])
|
|
|
switch_tab_btn.click(switch_tab, inputs=tabs_dropdown, outputs=[output_text, output_image, tabs_dropdown])
|
|
|
|
|
|
nav_btn.click(navigate, inputs=url_box, outputs=[output_text, output_image])
|
|
|
click_btn.click(click, inputs=sel_box, outputs=[output_text, output_image])
|
|
|
type_btn.click(type_text, inputs=[sel_box, type_box], outputs=[output_text, output_image])
|
|
|
|
|
|
run_btn.click(execute_code, inputs=code_input, outputs=[output_text, output_image])
|
|
|
inspect_btn.click(inspect_element, inputs=inspect_sel, outputs=[output_text, output_image])
|
|
|
|
|
|
net_btn.click(show_network_logs, outputs=[output_text, output_image])
|
|
|
cons_btn.click(show_console_logs, outputs=[output_text, output_image])
|
|
|
clear_logs_btn.click(clear_logs, outputs=[output_text, output_image])
|
|
|
|
|
|
start_rec_btn.click(start_recording, outputs=[output_text, output_image])
|
|
|
stop_rec_btn.click(stop_recording, outputs=[output_text, output_image])
|
|
|
play_macro_btn.click(play_macro, outputs=[output_text, output_image])
|
|
|
|
|
|
capture_btn.click(capture_screenshot, outputs=[output_text, output_image])
|
|
|
find_template_btn.click(find_template, inputs=[template_img], outputs=[output_text, output_image])
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
app.launch()
|
|
|
|