simoncck commited on
Commit
6090064
ยท
verified ยท
1 Parent(s): 9e2c003

Update browser_automation_ui.html

Browse files
Files changed (1) hide show
  1. browser_automation_ui.html +221 -165
browser_automation_ui.html CHANGED
@@ -1,186 +1,242 @@
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}
23
- .panel-content{padding:14px 16px 40px 16px}
24
- h3{font-size:16px;margin-bottom:6px;color:var(--dark)}
25
- .btn{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border:none;border-radius:6px;font-size:13px;cursor:pointer}
26
- .btn-primary{background:var(--accent);color:#fff}
27
- .btn-secondary{background:#e2e8f0;color:#334155}
28
- .btn-danger{background:#ef4444;color:#fff}
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)}
50
- #inspectDetail{margin-top:6px;font-size:11px;color:#cbd5e1;white-space:pre-wrap}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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">
58
- <!-- Browser Controls -->
59
- <section>
60
- <h3>Browser</h3>
61
- <div class="form-group"><button class="btn btn-primary" onclick="launchBrowser()"><i class="lucide lucide-rocket"></i>Launch</button></div>
62
- <div class="form-group"><input id="navUrl" placeholder="https://example.com"/></div>
63
- <div class="form-group"><button class="btn btn-secondary" onclick="navigate()"><i class="lucide lucide-globe"></i>Navigate</button></div>
64
- <div class="form-group" style="display:flex;gap:6px">
65
- <button class="btn btn-secondary" onclick="captureScreenshot()"><i class="lucide lucide-camera"></i>Capture</button>
66
- <button class="btn btn-secondary" onclick="downloadCurrentScreenshot()"><i class="lucide lucide-download"></i>Download</button>
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>
74
- <div class="form-group"><input id="selector" placeholder="CSS selector"/></div>
75
- <div class="form-group"><select id="action">
76
- <option value="click">click</option>
77
- <option value="type">type</option>
78
- <option value="textContent">getText</option>
79
- </select></div>
80
- <div class="form-group" id="typeTextGroup" style="display:none"><input id="typeText" placeholder="text to type"/></div>
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>
88
- <button class="btn btn-secondary btn-sm" style="margin-bottom:6px" onclick="refreshSessions()"><i class="lucide lucide-refresh-ccw"></i>Refresh</button>
89
- <ul id="sessionList"></ul>
90
- </section>
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>
 
1
  <!-- ========================= Browser Automation UI =========================
2
  Fullโ€‘screen screenshot โ€ข floating logs โ€ข collapsible sideโ€‘panel controls
3
+ v2.1 โ€“ fix 405 errors: use correct HTTP verbs (GET /sessions, DELETE /close)
 
4
  ============================================================================ -->
5
  <!DOCTYPE html>
6
  <html lang="en">
7
  <head>
8
  <meta charset="UTF-8" />
9
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
10
+ <title>Headless Browserย โ€“ Console</title>
 
11
  <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" />
12
  <link href="https://cdnjs.cloudflare.com/ajax/libs/lucide/0.263.1/lucide.min.css" rel="stylesheet" />
13
+
14
  <style>
15
+ :root {
16
+ --bg: #f8fafc;
17
+ --fg: #1e293b;
18
+ --primary: #2563eb;
19
+ --danger: #dc2626;
20
+ --surface: #ffffff;
21
+ --radius: 8px;
22
+ }
23
+ * { box-sizing: border-box; }
24
+ body {
25
+ margin: 0; font-family: Inter, sans-serif; background: var(--bg); color: var(--fg);
26
+ height: 100vh; overflow: hidden;
27
+ }
28
+ /* screenshot fills the viewport */
29
+ #mainArea { position: absolute; inset: 0; }
30
+ #screenshot {
31
+ width: 100%; height: 100%; object-fit: contain; background:#0f172a;
32
+ }
33
+ /* floating log overlay */
34
+ #logOverlay {
35
+ position: fixed; left: 0; right: 0; bottom: 0; height: 20vh;
36
+ background: rgba(0,0,0,.6); color:#e2e8f0; font-size:12px; font-family: "Source Code Pro", monospace;
37
+ overflow-y: auto; padding:4px 8px; pointer-events: none;
38
+ }
39
+ #logOverlay .entry { white-space: pre-wrap; }
40
+ /* side panel */
41
+ #sidePanel {
42
+ position: fixed; top:0; left:0; width: 320px; height:100vh; background:var(--surface);
43
+ border-right:1px solid #e2e8f0; box-shadow:2px 0 6px rgba(0,0,0,.05); overflow-y:auto;
44
+ transition: transform .3s ease; padding:12px;
45
+ }
46
+ #sidePanel.closed { transform: translateX(-100%); }
47
+ .panel-toggle { position:fixed; top:12px; left:12px; z-index:9; }
48
+ /* buttons */
49
+ .btn { cursor:pointer; border:none; border-radius:var(--radius); padding:6px 12px; margin:2px 0; font:500 14px Inter; }
50
+ .btn-primary { background:var(--primary); color:#fff; }
51
+ .btn-secondary { background:#cbd5e1; color:#334155; }
52
+ .btn-danger { background:var(--danger); color:#fff; }
53
+ /* session list */
54
+ #sessionList { list-style:none; padding:0; margin:4px 0; max-height:160px; overflow-y:auto; font-family:"Source Code Pro", monospace; font-size:12px; border:1px solid #e2e8f0; border-radius:var(--radius); }
55
+ .session-item { display:flex; align-items:center; justify-content:space-between; padding:4px 6px; cursor:pointer; }
56
+ .session-item.active { background:var(--primary); color:#fff; }
57
+ .session-item:hover { background:#e2e8f0; }
58
+ .session-item .close { color:var(--danger); cursor:pointer; margin-left:6px; }
59
+ /* inspect overlay */
60
+ #inspectOverlay { position:fixed; right:0; top:0; width:320px; height:100vh; background:var(--surface); border-left:1px solid #e2e8f0; box-shadow:-2px 0 6px rgba(0,0,0,.05); overflow-y:auto; transform: translateX(100%); transition:transform .3s ease; padding:12px; }
61
+ #inspectOverlay.open { transform:translateX(0); }
62
+ #selectorList { list-style:none; padding:0; margin:0; max-height:70vh; overflow-y:auto; font-size:12px; font-family:"Source Code Pro", monospace; border:1px solid #e2e8f0; border-radius:var(--radius); }
63
+ #selectorList li { padding:4px 6px; border-bottom:1px solid #f1f5f9; cursor:pointer; }
64
+ #selectorList li:hover { background:#e2e8f0; }
65
  </style>
66
  </head>
67
  <body>
68
+ <!-- panel toggle -->
69
+ <button class="btn btn-secondary panel-toggle" onclick="togglePanel()"><i class="lucide" data-lucide="menu"></i></button>
70
+
71
+ <!-- side panel -->
72
+ <div id="sidePanel">
73
+ <h3>Controls</h3>
74
+ <button class="btn btn-primary" onclick="launchBrowser()">Launch Browser</button>
75
+ <button class="btn btn-secondary" onclick="navigate()">Navigate</button>
76
+ <input id="urlInput" placeholder="https://example.com" style="width:100%;margin-bottom:8px;" />
77
+
78
+ <hr/>
79
+ <h4>Screenshot</h4>
80
+ <button class="btn btn-secondary" onclick="captureScreenshot()">Capture</button>
81
+ <button class="btn btn-secondary" onclick="downloadCurrentScreenshot()">Download PNG</button>
82
+
83
+ <hr/>
84
+ <h4>Element Action</h4>
85
+ <input id="selectorInput" placeholder="CSS selector" style="width:100%;margin-bottom:4px;" />
86
+ <select id="actionSelect" style="width:100%;margin-bottom:4px;">
87
+ <option value="click">click</option>
88
+ <option value="type">type</option>
89
+ <option value="textContent">getText</option>
90
+ </select>
91
+ <input id="valueInput" placeholder="value for type" style="width:100%;margin-bottom:4px;" />
92
+ <button class="btn btn-secondary" onclick="elementAction()">Run</button>
93
+
94
+ <hr/>
95
+ <h4>Session Manager</h4>
96
+ <button class="btn btn-secondary" onclick="refreshSessions()">Refresh Sessions</button>
97
+ <button class="btn btn-danger" onclick="closeAllSessions()">Close All</button>
98
+ <ul id="sessionList"></ul>
99
+
100
+ <hr/>
101
+ <button class="btn btn-secondary" onclick="inspectPage()">Inspect Page</button>
102
+ </div>
103
+
104
+ <!-- inspect overlay -->
105
+ <div id="inspectOverlay">
106
+ <h4>Selectors <button class="btn btn-secondary" onclick="closeInspect()" style="float:right;padding:2px 6px;"><i class="lucide" data-lucide="x"></i></button></h4>
107
+ <ul id="selectorList"></ul>
108
+ <pre id="selectorDetails" style="font-size:12px;background:#f1f5f9;padding:4px;border-radius:var(--radius);"></pre>
109
+ </div>
110
+
111
+ <!-- main area (screenshot) -->
112
+ <div id="mainArea"><img id="screenshot" alt="Screenshot will appear here" /></div>
113
+
114
+ <!-- log overlay -->
115
+ <div id="logOverlay"></div>
116
+
 
 
 
 
 
 
 
117
  <script src="https://cdnjs.cloudflare.com/ajax/libs/lucide/0.263.1/lucide.min.js"></script>
118
  <script>
119
+ // โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
120
+ let sessionId = null; let currentScreenshot = null;
121
+
122
+ function appendLog(msg, level="info") {
123
+ const log = document.createElement("div"); log.className="entry";
124
+ log.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`;
125
+ document.getElementById("logOverlay").append(log);
126
+ document.getElementById("logOverlay").scrollTop = 1e6;
127
+ }
128
+
129
+ async function api(method, path, body) {
130
+ const res = await fetch(`/api${path}`, {
131
+ method,
132
+ headers: {"Content-Type":"application/json"},
133
+ body: body ? JSON.stringify(body) : undefined
134
+ });
135
+ if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
136
  return res.json();
137
  }
138
+ const apiGet = path => api("GET", path);
139
+ const apiPost = (path, body) => api("POST", path, body);
140
+ const apiDelete = path => api("DELETE", path);
141
+
142
+ function togglePanel() {
143
+ document.getElementById("sidePanel").classList.toggle("closed");
144
+ }
145
+
146
+ // โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ browser ops โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
147
+ async function launchBrowser() {
148
+ try {
149
+ const data = await apiPost("/browser/launch", {headless:true,width:1280,height:720});
150
+ sessionId = data.session_id; appendLog(`Launched session ${sessionId}`);
151
+ await refreshSessions();
152
+ } catch(e){ appendLog(`โ— launch failed: ${e.message}`); }
153
+ }
154
+
155
+ async function navigate() {
156
+ if(!sessionId) return alert("No session");
157
+ const url=document.getElementById("urlInput").value.trim(); if(!url) return;
158
+ try { await apiPost("/browser/navigate", {session_id:sessionId, url}); appendLog(`Navigate โ†’ ${url}`); }
159
+ catch(e){ appendLog(`โ— navigate failed: ${e.message}`); }
160
+ }
161
+
162
+ async function captureScreenshot() {
163
+ if(!sessionId) return alert("No session");
164
+ try{
165
+ const data = await apiPost("/browser/screenshot", {session_id:sessionId, full_page:true});
166
+ currentScreenshot = `data:image/png;base64,${data.screenshot}`;
167
+ document.getElementById("screenshot").src = currentScreenshot;
168
+ appendLog("Screenshot captured");
169
+ }catch(e){ appendLog(`โ— screenshot failed: ${e.message}`); }
170
+ }
171
+
172
+ function downloadCurrentScreenshot() {
173
+ if(!currentScreenshot) return alert("Take a screenshot first");
174
+ const a=document.createElement("a"); a.href=currentScreenshot; a.download=`screenshot_${Date.now()}.png`; a.click();
175
+ }
176
+
177
+ async function elementAction() {
178
+ if(!sessionId) return alert("No session");
179
+ const sel=document.getElementById("selectorInput").value.trim(); if(!sel) return;
180
+ const act=document.getElementById("actionSelect").value;
181
+ const val=document.getElementById("valueInput").value;
182
+ try{
183
+ const data = await apiPost("/elements/action", {session_id:sessionId, selector:sel, action:act, value:val});
184
+ appendLog(`Action ${act} on ${sel}`);
185
+ if(data.text) appendLog(`Returned text: ${data.text}`);
186
+ }catch(e){ appendLog(`โ— action failed: ${e.message}`); }
187
+ }
188
+
189
+ // โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ session manager โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
190
+ async function listSessions() {
191
+ try { return (await apiGet("/sessions")).sessions || []; }
192
+ catch(e){ appendLog(`โ— listSessions: ${e.message}`); return []; }
193
+ }
194
+
195
+ async function refreshSessions() {
196
+ const list = await listSessions();
197
+ const ul=document.getElementById("sessionList"); ul.innerHTML="";
198
+ list.forEach(s=>{
199
+ const li=document.createElement("li"); li.className="session-item"; li.textContent=s.session_id;
200
+ if(s.session_id===sessionId) li.classList.add("active");
201
+ li.onclick=()=>{ sessionId=s.session_id; refreshSessions(); };
202
+ const close=document.createElement("span"); close.className="close"; close.innerHTML="&times;";
203
+ close.onclick=ev=>{ ev.stopPropagation(); closeSession(s.session_id); };
204
+ li.append(close); ul.append(li);
205
+ });
206
+ }
207
+
208
+ async function closeSession(id){
209
+ if(!confirm(`Close session ${id}?`)) return;
210
+ try{ await apiDelete(`/browser/close/${id}`); appendLog(`Closed ${id}`); if(id===sessionId) sessionId=null; refreshSessions(); }
211
+ catch(e){ appendLog(`โ— close ${id}: ${e.message}`); }
212
+ }
213
+
214
+ async function closeAllSessions(){
215
+ const list=await listSessions(); if(!list.length) return alert("No sessions");
216
+ if(!confirm("Close ALL sessions? This cannot be undone.")) return;
217
+ for(const s of list){ try{ await apiDelete(`/browser/close/${s.session_id}`);}catch(e){ appendLog(`โ— close ${s.session_id}: ${e.message}`);} }
218
+ sessionId=null; refreshSessions(); appendLog("All sessions closed");
219
+ }
220
+
221
+ // โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ inspect overlay โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
222
+ async function inspectPage(){
223
+ if(!sessionId) return alert("No session");
224
+ try{
225
+ const data=await apiGet(`/elements/inspect/${sessionId}`);
226
+ const ul=document.getElementById("selectorList"); ul.innerHTML="";
227
+ data.elements.forEach(el=>{
228
+ const li=document.createElement("li"); li.textContent=el.selector; li.title=el.text;
229
+ li.onmouseover=()=>{ document.getElementById("selectorDetails").textContent=`${el.selector}\n${JSON.stringify(el.attributes,null,2)}`; };
230
+ li.onclick=()=>{ document.getElementById("selectorInput").value=el.selector; closeInspect(); };
231
+ ul.append(li);
232
+ });
233
+ document.getElementById("inspectOverlay").classList.add("open");
234
+ }catch(e){ appendLog(`โ— inspect failed: ${e.message}`); }
235
+ }
236
+ function closeInspect(){ document.getElementById("inspectOverlay").classList.remove("open"); }
237
+
238
+ // init
239
+ lucide.createIcons(); refreshSessions();
240
  </script>
241
  </body>
242
  </html>