katiue/browser-use-webui
13
1# -*- coding: utf-8 -*-
2# @Time : 2025/1/1
3# @Author : wenshao
4# @Email : wenshaoguo1026@gmail.com
5# @Project : browser-use-webui
6# @FileName: webui.py
7from dotenv import load_dotenv
8load_dotenv()
9import argparse
10import gradio as gr
11import os
12import asyncio
13from playwright.async_api import async_playwright
14from browser_use.browser.browser import Browser, BrowserConfig
15from browser_use.browser.context import (
16 BrowserContextConfig,
17 BrowserContextWindowSize,
18)
19from browser_use.agent.service import Agent
20from src.browser.custom_browser import CustomBrowser
21from src.controller.custom_controller import CustomController
22from src.agent.custom_agent import CustomAgent
23from src.agent.custom_prompts import CustomSystemPrompt
24
25from src.utils import utils
26from src.utils.file_utils import get_latest_files
27from src.utils.stream_utils import stream_browser_view, capture_screenshot
28
29
30async def run_browser_agent(
31 agent_type,
32 llm_provider,
33 llm_model_name,
34 llm_temperature,
35 llm_base_url,
36 llm_api_key,
37 use_own_browser,
38 headless,
39 disable_security,
40 window_w,
41 window_h,
42 save_recording_path,
43 task,
44 add_infos,
45 max_steps,
46 use_vision,
47 browser_context=None # Added optional argument
48):
49 """
50 Runs the browser agent based on user configurations.
51 """
52
53 llm = utils.get_llm_model(
54 provider=llm_provider,
55 model_name=llm_model_name,
56 temperature=llm_temperature,
57 base_url=llm_base_url,
58 api_key=llm_api_key
59 )
60 if agent_type == "org":
61 return await run_org_agent(
62 llm=llm,
63 headless=headless,
64 disable_security=disable_security,
65 window_w=window_w,
66 window_h=window_h,
67 save_recording_path=save_recording_path,
68 task=task,
69 max_steps=max_steps,
70 use_vision=use_vision,
71 browser_context=browser_context # pass context
72 )
73 elif agent_type == "custom":
74 return await run_custom_agent(
75 llm=llm,
76 use_own_browser=use_own_browser,
77 headless=headless,
78 disable_security=disable_security,
79 window_w=window_w,
80 window_h=window_h,
81 save_recording_path=save_recording_path,
82 task=task,
83 add_infos=add_infos,
84 max_steps=max_steps,
85 use_vision=use_vision,
86 browser_context=browser_context # pass context
87 )
88 else:
89 raise ValueError(f"Invalid agent type: {agent_type}")
90
91
92async def run_org_agent(
93 llm,
94 headless,
95 disable_security,
96 window_w,
97 window_h,
98 save_recording_path,
99 task,
100 max_steps,
101 use_vision,
102 browser_context=None # receive context
103):
104 browser = None
105 if browser_context is None:
106 browser = Browser(
107 config=BrowserConfig(
108 headless=False, # Force non-headless for streaming
109 disable_security=disable_security,
110 extra_chromium_args=[f'--window-size={window_w},{window_h}'],
111 )
112 )
113 async with await browser.new_context(
114 config=BrowserContextConfig(
115 trace_path='./tmp/traces',
116 save_recording_path=save_recording_path if save_recording_path else None,
117 no_viewport=False,
118 browser_window_size=BrowserContextWindowSize(width=window_w, height=window_h),
119 )
120 ) as browser_context_in:
121 agent = Agent(
122 task=task,
123 llm=llm,
124 use_vision=use_vision,
125 browser_context=browser_context_in,
126 )
127 history = await agent.run(max_steps=max_steps)
128
129 final_result = history.final_result()
130 errors = history.errors()
131 model_actions = history.model_actions()
132 model_thoughts = history.model_thoughts()
133
134 recorded_files = get_latest_files(save_recording_path)
135 trace_file = get_latest_files(save_recording_path + "/../traces")
136
137 await browser.close()
138 return final_result, errors, model_actions, model_thoughts, recorded_files.get('.webm'), trace_file.get('.zip')
139 else:
140 # Reuse existing context
141 agent = Agent(
142 task=task,
143 llm=llm,
144 use_vision=use_vision,
145 browser_context=browser_context
146 )
147 history = await agent.run(max_steps=max_steps)
148 final_result = history.final_result()
149 errors = history.errors()
150 model_actions = history.model_actions()
151 model_thoughts = history.model_thoughts()
152 recorded_files = get_latest_files(save_recording_path)
153 trace_file = get_latest_files(save_recording_path + "/../traces")
154 return final_result, errors, model_actions, model_thoughts, recorded_files.get('.webm'), trace_file.get('.zip')
155
156
157async def run_custom_agent(
158 llm,
159 use_own_browser,
160 headless,
161 disable_security,
162 window_w,
163 window_h,
164 save_recording_path,
165 task,
166 add_infos,
167 max_steps,
168 use_vision,
169 browser_context=None # receive context
170):
171 controller = CustomController()
172 playwright = None
173 browser = None
174 try:
175 if use_own_browser:
176 playwright = await async_playwright().start()
177 chrome_exe = os.getenv("CHROME_PATH", "")
178 chrome_use_data = os.getenv("CHROME_USER_DATA", "")
179 browser_context_ = await playwright.chromium.launch_persistent_context(
180 user_data_dir=chrome_use_data,
181 executable_path=chrome_exe,
182 no_viewport=False,
183 headless=headless, # 保持浏览器窗口可见
184 user_agent=(
185 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
186 '(KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36'
187 ),
188 java_script_enabled=True,
189 bypass_csp=disable_security,
190 ignore_https_errors=disable_security,
191 record_video_dir=save_recording_path if save_recording_path else None,
192 record_video_size={'width': window_w, 'height': window_h}
193 )
194 else:
195 browser_context_ = None
196
197 if browser_context is not None:
198 # Reuse context
199 agent = CustomAgent(
200 task=task,
201 add_infos=add_infos,
202 use_vision=use_vision,
203 llm=llm,
204 browser_context=browser_context,
205 controller=controller,
206 system_prompt_class=CustomSystemPrompt
207 )
208 history = await agent.run(max_steps=max_steps)
209 final_result = history.final_result()
210 errors = history.errors()
211 model_actions = history.model_actions()
212 model_thoughts = history.model_thoughts()
213 recorded_files = get_latest_files(save_recording_path)
214 trace_file = get_latest_files(save_recording_path + "/../traces")
215 return final_result, errors, model_actions, model_thoughts, recorded_files.get('.webm'), trace_file.get('.zip')
216 else:
217 browser = CustomBrowser(
218 config=BrowserConfig(
219 headless=headless,
220 disable_security=disable_security,
221 extra_chromium_args=[f'--window-size={window_w},{window_h}'],
222 )
223 )
224 async with await browser.new_context(
225 config=BrowserContextConfig(
226 trace_path='./tmp/result_processing',
227 save_recording_path=save_recording_path if save_recording_path else None,
228 no_viewport=False,
229 browser_window_size=BrowserContextWindowSize(width=window_w, height=window_h),
230 ),
231 context=browser_context_
232 ) as browser_context_in:
233 agent = CustomAgent(
234 task=task,
235 add_infos=add_infos,
236 use_vision=use_vision,
237 llm=llm,
238 browser_context=browser_context_in,
239 controller=controller,
240 system_prompt_class=CustomSystemPrompt
241 )
242 history = await agent.run(max_steps=max_steps)
243
244 final_result = history.final_result()
245 errors = history.errors()
246 model_actions = history.model_actions()
247 model_thoughts = history.model_thoughts()
248
249 recorded_files = get_latest_files(save_recording_path)
250
251 except Exception as e:
252 import traceback
253 traceback.print_exc()
254 final_result = ""
255 errors = str(e) + "\n" + traceback.format_exc()
256 model_actions = ""
257 model_thoughts = ""
258 recorded_files = {}
259 finally:
260 # 显式关闭持久化上下文
261 if browser_context_:
262 await browser_context_.close()
263
264 # 关闭 Playwright 对象
265 if playwright:
266 await playwright.stop()
267 if browser:
268 await browser.close()
269 return final_result, errors, model_actions, model_thoughts, recorded_files.get('.webm'), recorded_files.get('.zip')
270
271
272async def run_with_stream(*args):
273 """Wrapper to run agent and handle streaming"""
274 browser = None
275 try:
276 browser = CustomBrowser(config=BrowserConfig(
277 headless=False,
278 disable_security=args[8],
279 extra_chromium_args=[f'--window-size={args[9]},{args[10]}'],
280 ))
281
282 async with await browser.new_context(
283 config=BrowserContextConfig(
284 trace_path='./tmp/traces',
285 save_recording_path=args[11],
286 no_viewport=False,
287 browser_window_size=BrowserContextWindowSize(width=args[9], height=args[10]),
288 )
289 ) as browser_context:
290 # No need to explicitly create page - context creation handles it
291
292 # Run agent in background
293 agent_task = asyncio.create_task(run_browser_agent(*args, browser_context=browser_context))
294
295 # Initialize values
296 html_content = "<div>Starting browser...</div>"
297 final_result = errors = model_actions = model_thoughts = ""
298 recording = trace = None
299
300 while not agent_task.done():
301 try:
302 html_content = await capture_screenshot(browser_context)
303 except Exception as e:
304 html_content = f"<div class='error'>Screenshot error: {str(e)}</div>"
305
306 yield [html_content, final_result, errors, model_actions, model_thoughts, recording, trace]
307 await asyncio.sleep(0.01)
308
309 # Get agent results when done
310 try:
311 result = await agent_task
312 if isinstance(result, tuple) and len(result) == 6:
313 final_result, errors, model_actions, model_thoughts, recording, trace = result
314 else:
315 errors = "Unexpected result format from agent"
316 except Exception as e:
317 errors = f"Agent error: {str(e)}"
318
319 yield [
320 html_content,
321 final_result,
322 errors,
323 model_actions,
324 model_thoughts,
325 recording,
326 trace
327 ]
328
329 except Exception as e:
330 import traceback
331 yield [
332 f"<div class='error'>Browser error: {str(e)}</div>",
333 "",
334 f"Error: {str(e)}\n{traceback.format_exc()}",
335 "",
336 "",
337 None,
338 None
339 ]
340 finally:
341 if browser:
342 await browser.close()
343
344
345def main():
346 # Gradio UI setup
347 with gr.Blocks(title="Browser Use WebUI", theme=gr.themes.Soft(font=[gr.themes.GoogleFont("Plus Jakarta Sans")])) as demo:
348 gr.Markdown("<center><h1>Browser Use WebUI</h1></center>")
349
350 with gr.Tabs():
351 # Tab for LLM Settings
352 with gr.Tab("LLM Settings"):
353 with gr.Row():
354 llm_provider = gr.Dropdown(
355 ["anthropic", "openai", "gemini", "azure_openai", "deepseek"], label="LLM Provider", value="gemini"
356 )
357 llm_model_name = gr.Textbox(label="LLM Model Name", value="gemini-2.0-flash-exp")
358 llm_temperature = gr.Number(label="LLM Temperature", value=1.0)
359 with gr.Row():
360 llm_base_url = gr.Textbox(label="LLM Base URL")
361 llm_api_key = gr.Textbox(label="LLM API Key", type="password")
362
363 # Tab for Browser Settings
364 with gr.Tab("Browser Settings"):
365 with gr.Accordion("Browser Settings", open=True):
366 use_own_browser = gr.Checkbox(label="Use Own Browser", value=False)
367 headless = gr.Checkbox(label="Headless", value=False)
368 disable_security = gr.Checkbox(label="Disable Security", value=True)
369 with gr.Row():
370 window_w = gr.Number(label="Window Width", value=1920)
371 window_h = gr.Number(label="Window Height", value=1080)
372 save_recording_path = gr.Textbox(label="Save Recording Path", placeholder="e.g. ./tmp/record_videos",
373 value="./tmp/record_videos")
374
375 # Tab for Task Settings
376 with gr.Tab("Task Settings"):
377 with gr.Accordion("Task Settings", open=True):
378 task = gr.Textbox(label="Task", lines=10,
379 value="go to google.com and type 'OpenAI' click search and give me the first url")
380 add_infos = gr.Textbox(label="Additional Infos (Optional): Hints to help LLM complete Task", lines=5)
381 agent_type = gr.Radio(["org", "custom"], label="Agent Type", value="custom")
382 max_steps = gr.Number(label="Max Run Steps", value=100)
383 use_vision = gr.Checkbox(label="Use Vision", value=True)
384
385 # Tab for Stream + File Download and Agent Thoughts
386 with gr.Tab("Results"):
387 with gr.Column():
388 # Add live stream viewer before other components
389 browser_view = gr.HTML(
390 label="Live Browser View",
391 value="<div style='width:100%; height:600px; border:1px solid #ccc; display:flex; align-items:center; justify-content:center;'><p>Waiting for browser session...</p></div>"
392 )
393 final_result_output = gr.Textbox(label="Final Result", lines=5)
394 errors_output = gr.Textbox(label="Errors", lines=5)
395 model_actions_output = gr.Textbox(label="Model Actions", lines=5)
396 model_thoughts_output = gr.Textbox(label="Model Thoughts", lines=5)
397 with gr.Row():
398 recording_file = gr.Video(label="Recording File") # Changed from gr.File to gr.Video
399 trace_file = gr.File(label="Trace File (ZIP)")
400
401 # Add a refresh button
402 refresh_button = gr.Button("Refresh Files")
403
404 def refresh_files():
405 recorded_files = get_latest_files("./tmp/record_videos")
406 trace_file = get_latest_files("./tmp/traces")
407 return (
408 recorded_files.get('.webm') if recorded_files.get('.webm') else None,
409 trace_file.get('.zip') if trace_file.get('.zip') else None
410 )
411
412 refresh_button.click(
413 fn=refresh_files,
414 inputs=[],
415 outputs=[recording_file, trace_file]
416 )
417
418 # Run button outside tabs for global execution
419 run_button = gr.Button("Run Agent", variant="primary")
420 run_button.click(
421 fn=run_with_stream,
422 inputs=[
423 agent_type,
424 llm_provider,
425 llm_model_name,
426 llm_temperature,
427 llm_base_url,
428 llm_api_key,
429 use_own_browser,
430 headless,
431 disable_security,
432 window_w,
433 window_h,
434 save_recording_path,
435 task,
436 add_infos,
437 max_steps,
438 use_vision
439 ],
440 outputs=[
441 browser_view,
442 final_result_output,
443 errors_output,
444 model_actions_output,
445 model_thoughts_output,
446 recording_file,
447 trace_file
448 ],
449 queue=True
450 )
451
452 demo.launch(server_name=args.ip, server_port=args.port, share=True)
453
454if __name__ == "__main__":
455
456 # For local development
457 import argparse
458 parser = argparse.ArgumentParser(description="Gradio UI for Browser Agent")
459 parser.add_argument("--ip", type=str, default="0.0.0.0", help="IP address to bind to")
460 parser.add_argument("--port", type=int, default=7860, help="Port to listen on")
461 args = parser.parse_args()
462 main()
463else:
464 # For Vercel deployment
465 main()
466 