Spaces:
No application file
No application file
Update browser_automation_ui.html
Browse files- browser_automation_ui.html +90 -38
browser_automation_ui.html
CHANGED
|
@@ -1,21 +1,22 @@
|
|
| 1 |
<!-- ========================= Browser Automation UI =========================
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 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ย
|
| 12 |
-
|
|
|
|
| 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 |
-
/*
|
| 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 |
-
|
|
|
|
| 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 |
-
/*
|
| 36 |
-
#logOverlay{position:fixed;left:0;bottom:0;width:100%;height:20vh;background:rgba(30,41,59,0.
|
| 37 |
#logOverlay pre{margin:0;white-space:pre-wrap;word-break:break-word}
|
| 38 |
-
/*
|
| 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 |
-
/*
|
| 44 |
-
#inspectOverlay{position:fixed;right:0;top:0;width:300px;height:100%;background:rgba(15,23,42,0.
|
| 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 |
-
<!--
|
| 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
|
| 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
|
| 83 |
<!-- Session Manager -->
|
| 84 |
<section>
|
| 85 |
<h3>Sessions</h3>
|
|
@@ -89,46 +91,96 @@
|
|
| 89 |
</div>
|
| 90 |
</aside>
|
| 91 |
|
| 92 |
-
<!--
|
| 93 |
<main id="mainArea" class="with-panel">
|
| 94 |
<img id="screenshot" alt="Browser Screenshot" />
|
| 95 |
</main>
|
| 96 |
|
| 97 |
-
<!--
|
| 98 |
<div id="logOverlay"><pre id="logText"></pre></div>
|
| 99 |
|
| 100 |
-
<!--
|
| 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 |
-
<!--
|
| 108 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/lucide/0.263.1/lucide.min.js"></script>
|
| 109 |
<script>
|
| 110 |
-
/* โโโโโโโโโโ
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
function
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
|
| 115 |
-
/* โโโโโโโโโโ
|
| 116 |
-
let currentSessionId=null
|
|
|
|
|
|
|
|
|
|
| 117 |
|
| 118 |
-
/* โโโโโโโโโโ
|
| 119 |
-
const panel=document.getElementById('sidePanel');
|
| 120 |
-
const mainArea=document.getElementById('mainArea');
|
| 121 |
document.getElementById('togglePanel').onclick=()=>{
|
| 122 |
-
panel.
|
|
|
|
| 123 |
document.getElementById('togglePanel').textContent=panel.classList.contains('closed')?'โฏ':'โฎ';};
|
| 124 |
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
function
|
| 130 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
|
| 132 |
/* โโโโโโโโโโ Element interaction โโโโโโโโโโ */
|
| 133 |
-
|
| 134 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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'>×</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>
|