CoolFace
Apppublic

katiue/browser-use-webui

sourceHugging Faceupdated 8mo agoView on Hugging Face
13likes
webui.py466 linesDownload Raw Back to root
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