simoncck commited on
Commit
5c659ee
ยท
verified ยท
1 Parent(s): ffa3383

Update browser_automation_ui.html

Browse files
Files changed (1) hide show
  1. browser_automation_ui.html +90 -38
browser_automation_ui.html CHANGED
@@ -1,21 +1,22 @@
1
  <!-- ========================= Browser Automation UI =========================
2
- Fullโ€‘screen screenshot | overlay logs | collapsible side panel
3
- Added: download screenshot, perโ€‘session close, confirm on mass close,
4
- Inspect overlay listing selectors with hover details
5
  ============================================================================ -->
6
  <!DOCTYPE html>
7
  <html lang="en">
8
  <head>
9
  <meta charset="UTF-8" />
10
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
11
- <title>Headless Browserย โ€“ Console</title>
12
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
 
13
  <link href="https://cdnjs.cloudflare.com/ajax/libs/lucide/0.263.1/lucide.min.css" rel="stylesheet" />
14
  <style>
15
  :root { --panel-w: 280px; --accent: #2563eb; --bg: #f8fafc; --dark: #1e293b; }
16
  *{box-sizing:border-box;font-family:'Inter',sans-serif;margin:0;padding:0}
17
- body{height:100vh;overflow:hidden;background:#000}
18
- /* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Side panel */
19
  .side-panel{position:fixed;left:0;top:0;width:var(--panel-w);height:100%;background:var(--bg);box-shadow:2px 0 6px rgba(0,0,0,.12);transition:transform .3s ease;z-index:900;overflow-y:auto}
20
  .side-panel.closed{transform:translateX(-100%)}
21
  .panel-toggle{position:absolute;right:-24px;top:12px;width:24px;height:24px;border:none;border-radius:4px;background:var(--accent);color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:16px}
@@ -28,20 +29,21 @@
28
  .btn-sm{padding:4px 8px;font-size:11px}
29
  .form-group{display:flex;flex-direction:column;margin-bottom:8px}
30
  .form-group input,.form-group select{padding:6px 8px;border:1px solid #cbd5e1;border-radius:4px;font-size:13px}
31
- /* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Main screenshot area */
 
32
  #mainArea{position:fixed;left:0;top:0;width:100%;height:100%;display:flex;align-items:center;justify-content:center;transition:margin-left .3s ease}
33
  #mainArea.with-panel{margin-left:var(--panel-w)}
34
  #screenshot{max-width:100%;max-height:100%;object-fit:contain;background:#000}
35
- /* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Log overlay */
36
- #logOverlay{position:fixed;left:0;bottom:0;width:100%;height:20vh;background:rgba(30,41,59,0.7);color:#f1f5f9;font-size:12px;overflow-y:auto;padding:6px 10px;z-index:950;backdrop-filter:blur(3px)}
37
  #logOverlay pre{margin:0;white-space:pre-wrap;word-break:break-word}
38
- /* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Session list */
39
  .session-item{display:flex;align-items:center;justify-content:space-between;padding:4px 6px;border-bottom:1px solid #e2e8f0;font-family:"Source Code Pro",monospace;font-size:12px}
40
  .session-id{cursor:pointer;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
41
  .session-item.active{background:#dbeafe}
42
  .session-close{background:none;border:none;color:#ef4444;cursor:pointer;font-size:14px;width:18px;height:18px;display:flex;align-items:center;justify-content:center}
43
- /* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Inspect overlay */
44
- #inspectOverlay{position:fixed;right:0;top:0;width:300px;height:100%;background:rgba(15,23,42,0.9);color:#f8fafc;z-index:940;transform:translateX(100%);transition:transform .3s ease;overflow-y:auto;padding:10px;font-size:12px}
45
  #inspectOverlay.open{transform:translateX(0)}
46
  #inspectList li{padding:4px 6px;border-bottom:1px solid rgba(255,255,255,.08);cursor:pointer}
47
  #inspectList li:hover{background:rgba(255,255,255,.08)}
@@ -49,7 +51,7 @@
49
  </style>
50
  </head>
51
  <body>
52
- <!-- โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Side Panel โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ -->
53
  <aside id="sidePanel" class="side-panel open">
54
  <button id="togglePanel" class="panel-toggle" title="Hide / Show panel">โฎ</button>
55
  <div class="panel-content">
@@ -65,7 +67,7 @@
65
  </div>
66
  <div class="form-group"><button class="btn btn-danger" onclick="closeAllSessions()"><i class="lucide lucide-trash-2"></i>Closeย All</button></div>
67
  </section>
68
- <hr style="margin:12px 0;border:none;border-top:1px solid #e2e8f0"/>
69
  <!-- Element Interaction -->
70
  <section>
71
  <h3>Element</h3>
@@ -79,7 +81,7 @@
79
  <button class="btn btn-primary" onclick="elementAction()"><i class="lucide lucide-mouse-pointer-click"></i>Run</button>
80
  <button class="btn btn-secondary btn-sm" style="margin-left:6px" onclick="inspectPage()"><i class="lucide lucide-list"></i>Inspect</button>
81
  </section>
82
- <hr style="margin:12px 0;border:none;border-top:1px solid #e2e8f0"/>
83
  <!-- Session Manager -->
84
  <section>
85
  <h3>Sessions</h3>
@@ -89,46 +91,96 @@
89
  </div>
90
  </aside>
91
 
92
- <!-- โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Main Area โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ -->
93
  <main id="mainArea" class="with-panel">
94
  <img id="screenshot" alt="Browser Screenshot" />
95
  </main>
96
 
97
- <!-- โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Log Overlay โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ -->
98
  <div id="logOverlay"><pre id="logText"></pre></div>
99
 
100
- <!-- โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Inspect Overlay โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ -->
101
  <aside id="inspectOverlay">
102
  <h3 style="color:#38bdf8;margin-bottom:8px">Selectors</h3>
103
  <ul id="inspectList"></ul>
104
  <div id="inspectDetail"></div>
105
  </aside>
106
 
107
- <!-- โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Scripts โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ -->
108
  <script src="https://cdnjs.cloudflare.com/ajax/libs/lucide/0.263.1/lucide.min.js"></script>
109
  <script>
110
- /* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Utility fetch wrappers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
111
- async function api(path, body){return fetch(path,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)}).then(r=>r.json())}
112
- async function apiGet(path){return fetch(path).then(r=>r.json())}
113
- function log(msg){const pre=document.getElementById('logText');pre.textContent+=`\n${new Date().toLocaleTimeString()} ${msg}`;pre.parentElement.scrollTop=pre.parentElement.scrollHeight}
 
 
 
 
 
114
 
115
- /* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Global state โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
116
- let currentSessionId=null;let lastScreenshotData=null;
 
 
 
117
 
118
- /* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Side panel toggle โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
119
- const panel=document.getElementById('sidePanel');
120
- const mainArea=document.getElementById('mainArea');
121
  document.getElementById('togglePanel').onclick=()=>{
122
- panel.classList.toggle('closed');mainArea.classList.toggle('with-panel');
 
123
  document.getElementById('togglePanel').textContent=panel.classList.contains('closed')?'โฏ':'โฎ';};
124
 
125
- /* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Browser controls โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
126
- async function launchBrowser(){const res=await api('/api/browser/launch',{});if(res.session_id){currentSessionId=res.session_id;log(`Launched session ${res.session_id}`);refreshSessions();captureScreenshot();}}
127
- async function navigate(){if(!currentSessionId)return alert('No session');const url=document.getElementById('navUrl').value.trim();if(!url)return;await api('/api/browser/navigate',{session_id:currentSessionId,url});log(`Navigated to ${url}`);captureScreenshot();}
128
- async function captureScreenshot(){if(!currentSessionId)return;const res=await api('/api/browser/screenshot',{session_id:currentSessionId,full_page:false});if(res.screenshot){lastScreenshotData=res.screenshot;document.getElementById('screenshot').src=`data:image/png;base64,${res.screenshot}`;log('Screenshot updated');}}
129
- function downloadCurrentScreenshot(){if(!lastScreenshotData)return alert('No screenshot');const a=document.createElement('a');a.href=`data:image/png;base64,${lastScreenshotData}`;a.download=`screenshot_${Date.now()}.png`;a.click();}
130
- async function closeAllSessions(){const items=[...document.querySelectorAll('.session-item')];if(items.length===0)return alert('No sessions');if(!confirm(`Close ALL ${items.length} sessions?`))return;for(const li of items){await api(`/api/browser/close/${li.dataset.id}`,{});}currentSessionId=null;refreshSessions();log('Closed all sessions');document.getElementById('screenshot').src='';lastScreenshotData=null;}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
 
132
  /* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Element interaction โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
133
- document.getElementById('action').addEventListener('change',e=>{document.getElementById('typeTextGroup').style.display=e.target.value==='type'?'block':'none';});
134
- async function elementAction(){if(!currentSessionId)return;const selector=document.getElementById('selector').
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  <!-- ========================= Browser Automation UI =========================
2
+ Fullโ€‘screen screenshot โ€ข floating logs โ€ข collapsible sideโ€‘panel controls
3
+ Working version โ€“ buttons fixed, complete JS, download, session manager,
4
+ inspect overlay, confirm dialogs, log accumulation
5
  ============================================================================ -->
6
  <!DOCTYPE html>
7
  <html lang="en">
8
  <head>
9
  <meta charset="UTF-8" />
10
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
11
+ <title>Headless Browserย Console</title>
12
+ <!-- Fonts & Icons -->
13
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Source+Code+Pro:wght@400;500&display=swap" rel="stylesheet" />
14
  <link href="https://cdnjs.cloudflare.com/ajax/libs/lucide/0.263.1/lucide.min.css" rel="stylesheet" />
15
  <style>
16
  :root { --panel-w: 280px; --accent: #2563eb; --bg: #f8fafc; --dark: #1e293b; }
17
  *{box-sizing:border-box;font-family:'Inter',sans-serif;margin:0;padding:0}
18
+ body{height:100vh;overflow:hidden;background:#000;color:#1e293b}
19
+ /* โ”€โ”€โ”€ Side panel โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
20
  .side-panel{position:fixed;left:0;top:0;width:var(--panel-w);height:100%;background:var(--bg);box-shadow:2px 0 6px rgba(0,0,0,.12);transition:transform .3s ease;z-index:900;overflow-y:auto}
21
  .side-panel.closed{transform:translateX(-100%)}
22
  .panel-toggle{position:absolute;right:-24px;top:12px;width:24px;height:24px;border:none;border-radius:4px;background:var(--accent);color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:16px}
 
29
  .btn-sm{padding:4px 8px;font-size:11px}
30
  .form-group{display:flex;flex-direction:column;margin-bottom:8px}
31
  .form-group input,.form-group select{padding:6px 8px;border:1px solid #cbd5e1;border-radius:4px;font-size:13px}
32
+ hr{margin:12px 0;border:none;border-top:1px solid #e2e8f0}
33
+ /* โ”€โ”€โ”€ Main screenshot area โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
34
  #mainArea{position:fixed;left:0;top:0;width:100%;height:100%;display:flex;align-items:center;justify-content:center;transition:margin-left .3s ease}
35
  #mainArea.with-panel{margin-left:var(--panel-w)}
36
  #screenshot{max-width:100%;max-height:100%;object-fit:contain;background:#000}
37
+ /* โ”€โ”€โ”€ Log overlay โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
38
+ #logOverlay{position:fixed;left:0;bottom:0;width:100%;height:20vh;background:rgba(30,41,59,0.75);color:#f1f5f9;font-size:12px;overflow-y:auto;padding:6px 10px;z-index:950;backdrop-filter:blur(3px)}
39
  #logOverlay pre{margin:0;white-space:pre-wrap;word-break:break-word}
40
+ /* โ”€โ”€โ”€ Session list โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
41
  .session-item{display:flex;align-items:center;justify-content:space-between;padding:4px 6px;border-bottom:1px solid #e2e8f0;font-family:"Source Code Pro",monospace;font-size:12px}
42
  .session-id{cursor:pointer;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
43
  .session-item.active{background:#dbeafe}
44
  .session-close{background:none;border:none;color:#ef4444;cursor:pointer;font-size:14px;width:18px;height:18px;display:flex;align-items:center;justify-content:center}
45
+ /* โ”€โ”€โ”€ Inspect overlay โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
46
+ #inspectOverlay{position:fixed;right:0;top:0;width:300px;height:100%;background:rgba(15,23,42,0.95);color:#f8fafc;z-index:940;transform:translateX(100%);transition:transform .3s ease;overflow-y:auto;padding:10px;font-size:12px}
47
  #inspectOverlay.open{transform:translateX(0)}
48
  #inspectList li{padding:4px 6px;border-bottom:1px solid rgba(255,255,255,.08);cursor:pointer}
49
  #inspectList li:hover{background:rgba(255,255,255,.08)}
 
51
  </style>
52
  </head>
53
  <body>
54
+ <!-- โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Side Panel โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ -->
55
  <aside id="sidePanel" class="side-panel open">
56
  <button id="togglePanel" class="panel-toggle" title="Hide / Show panel">โฎ</button>
57
  <div class="panel-content">
 
67
  </div>
68
  <div class="form-group"><button class="btn btn-danger" onclick="closeAllSessions()"><i class="lucide lucide-trash-2"></i>Closeย All</button></div>
69
  </section>
70
+ <hr/>
71
  <!-- Element Interaction -->
72
  <section>
73
  <h3>Element</h3>
 
81
  <button class="btn btn-primary" onclick="elementAction()"><i class="lucide lucide-mouse-pointer-click"></i>Run</button>
82
  <button class="btn btn-secondary btn-sm" style="margin-left:6px" onclick="inspectPage()"><i class="lucide lucide-list"></i>Inspect</button>
83
  </section>
84
+ <hr/>
85
  <!-- Session Manager -->
86
  <section>
87
  <h3>Sessions</h3>
 
91
  </div>
92
  </aside>
93
 
94
+ <!-- โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Main Area โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ -->
95
  <main id="mainArea" class="with-panel">
96
  <img id="screenshot" alt="Browser Screenshot" />
97
  </main>
98
 
99
+ <!-- โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Log Overlay โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ -->
100
  <div id="logOverlay"><pre id="logText"></pre></div>
101
 
102
+ <!-- โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Inspect Overlay โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ -->
103
  <aside id="inspectOverlay">
104
  <h3 style="color:#38bdf8;margin-bottom:8px">Selectors</h3>
105
  <ul id="inspectList"></ul>
106
  <div id="inspectDetail"></div>
107
  </aside>
108
 
109
+ <!-- โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Scripts โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ -->
110
  <script src="https://cdnjs.cloudflare.com/ajax/libs/lucide/0.263.1/lucide.min.js"></script>
111
  <script>
112
+ /* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Fetch helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
113
+ const API = '/api';
114
+ const headers = {'Content-Type':'application/json'};
115
+ async function apiPost(path, body={}){
116
+ const res = await fetch(`${API}${path}`,{method:'POST',headers,body:JSON.stringify(body)});
117
+ if(!res.ok) throw new Error(await res.text());
118
+ return res.json();
119
+ }
120
+ async function apiGet(path){const res=await fetch(`${API}${path}`);if(!res.ok)throw new Error(await res.text());return res.json();}
121
 
122
+ /* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ State & utils โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
123
+ let currentSessionId=null, lastScreenshot=null;
124
+ const logBox=document.getElementById('logText');
125
+ function log(msg){logBox.textContent+=`\n${new Date().toLocaleTimeString()} ${msg}`;logBox.parentElement.scrollTop=logBox.parentElement.scrollHeight;}
126
+ function handleErr(e){console.error(e);log(`โ— ${e.message||e}`);alert(e.message||e);}
127
 
128
+ /* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ UI helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
 
 
129
  document.getElementById('togglePanel').onclick=()=>{
130
+ const panel=document.getElementById('sidePanel');panel.classList.toggle('closed');
131
+ document.getElementById('mainArea').classList.toggle('with-panel');
132
  document.getElementById('togglePanel').textContent=panel.classList.contains('closed')?'โฏ':'โฎ';};
133
 
134
+ document.getElementById('action').addEventListener('change',e=>{
135
+ document.getElementById('typeTextGroup').style.display=e.target.value==='type'?'block':'none';});
136
+
137
+ /* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Browser / session functions โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
138
+ async function launchBrowser(){try{
139
+ const res=await apiPost('/browser/launch',{});currentSessionId=res.session_id;log(`Launched ${currentSessionId}`);await refreshSessions();await captureScreenshot();}catch(e){handleErr(e)}}
140
+
141
+ async function navigate(){if(!currentSessionId)return alert('No session');const url=document.getElementById('navUrl').value.trim();if(!url)return;
142
+ try{await apiPost('/browser/navigate',{session_id:currentSessionId,url});log(`โžก ${url}`);await captureScreenshot();}catch(e){handleErr(e)}}
143
+
144
+ async function captureScreenshot(){if(!currentSessionId)return;try{
145
+ const res=await apiPost('/browser/screenshot',{session_id:currentSessionId,full_page:false});lastScreenshot=res.screenshot;document.getElementById('screenshot').src=`data:image/png;base64,${lastScreenshot}`;log('๐Ÿ“ธ screenshot');}catch(e){handleErr(e)}}
146
+
147
+ function downloadCurrentScreenshot(){if(!lastScreenshot)return alert('No screenshot');const a=document.createElement('a');a.href=`data:image/png;base64,${lastScreenshot}`;a.download=`screenshot_${Date.now()}.png`;a.click();}
148
+
149
+ async function closeSession(id){if(!confirm(`Close session\n${id}?`))return;try{
150
+ await apiPost(`/browser/close/${id}`,{});log(`โœ– closed ${id}`);if(id===currentSessionId){currentSessionId=null;document.getElementById('screenshot').src='';}
151
+ await refreshSessions();}catch(e){handleErr(e)}}
152
+
153
+ async function closeAllSessions(){const list=document.querySelectorAll('.session-item');if(!list.length)return alert('No sessions');
154
+ if(!confirm(`Close ALL ${list.length} sessions?`))return;try{
155
+ for(const li of list){await apiPost(`/browser/close/${li.dataset.id}`,{});}currentSessionId=null;document.getElementById('screenshot').src='';lastScreenshot=null;await refreshSessions();log('โœ– all sessions closed');}catch(e){handleErr(e)}}
156
+
157
+ async function refreshSessions(){try{
158
+ const res=await apiGet('/sessions');const ul=document.getElementById('sessionList');ul.innerHTML='';(res.sessions||[]).forEach(s=>{
159
+ const li=document.createElement('li');li.className='session-item';li.dataset.id=s.session_id;
160
+ li.innerHTML=`<span class='session-id'>${s.session_id}</span><button class='session-close' title='Close'>&times;</button>`;
161
+ if(s.session_id===currentSessionId)li.classList.add('active');
162
+ li.querySelector('.session-id').onclick=()=>{currentSessionId=s.session_id;log(`๐Ÿ”€ switched to ${s.session_id}`);refreshSessions();};
163
+ li.querySelector('.session-close').onclick=()=>closeSession(s.session_id);
164
+ ul.appendChild(li);});
165
+ }catch(e){handleErr(e)}}
166
 
167
  /* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Element interaction โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
168
+ async function elementAction(){if(!currentSessionId)return;const sel=document.getElementById('selector').value.trim();if(!sel)return alert('No selector');
169
+ const action=document.getElementById('action').value;const value=document.getElementById('typeText').value;
170
+ try{const res=await apiPost('/elements/action',{session_id:currentSessionId,selector:sel,action,value});
171
+ log(`โœ… ${action} on ${sel}`);if(action!=='textContent')await captureScreenshot();else alert(`Element text:\n${res.text}`);}catch(e){handleErr(e)}}
172
+
173
+ /* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Inspect overlay โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
174
+ async function inspectPage(){if(!currentSessionId)return;const ov=document.getElementById('inspectOverlay');ov.classList.add('open');ov.querySelector('#inspectList').innerHTML='<li>Loading...</li>';
175
+ try{const res=await apiGet(`/elements/inspect/${currentSessionId}`);const list=ov.querySelector('#inspectList');list.innerHTML='';
176
+ (res.elements||[]).forEach(el=>{const li=document.createElement('li');li.textContent=el.selector;li.onmouseover=()=>{document.getElementById('inspectDetail').textContent=`<${el.tag}> ${el.text}\n`+JSON.stringify(el.attributes,null,2)};
177
+ li.onclick=()=>{document.getElementById('selector').value=el.selector;document.getElementById('inspectOverlay').classList.remove('open');};list.appendChild(li);});
178
+ }catch(e){handleErr(e)}}
179
+
180
+ document.addEventListener('keydown',e=>{if(e.key==='Escape')document.getElementById('inspectOverlay').classList.remove('open');});
181
+
182
+ /* โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Initial UI setup โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
183
+ refreshSessions();log('UI ready');
184
+ </script>
185
+ </body>
186
+ </html>