Files
bhushanct c1eef4c6aa Initial commit: Screen Leads app
Screen-capture lead tool: capture agent, Claude vision extractor with
pluggable site recipes (LinkedIn), canonical funnel model, SQLite storage,
FastAPI backend, dashboard, and setup/run scripts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 07:43:33 +05:30

122 lines
6.5 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Screen Leads</title>
<style>
:root {
--bg: #0f1115; --panel: #171a21; --border: #262b36; --fg: #e6e9ef;
--muted: #8b93a3; --accent: #4f8cff; --ok: #34c759; --warn: #ffb020; --danger: #ff5c5c;
}
@media (prefers-color-scheme: light) {
:root { --bg:#f5f6f8; --panel:#fff; --border:#e2e5ea; --fg:#1b1f27; --muted:#6b7280; }
}
* { box-sizing: border-box; }
body { margin:0; font:14px/1.5 -apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;
background:var(--bg); color:var(--fg); }
header { display:flex; align-items:center; gap:12px; padding:14px 20px;
border-bottom:1px solid var(--border); background:var(--panel); position:sticky; top:0; }
header h1 { font-size:16px; margin:0; font-weight:600; }
header a { color:var(--accent); text-decoration:none; font-size:13px; font-weight:600; }
header a:hover { text-decoration:underline; }
.pill { padding:2px 10px; border-radius:999px; font-size:12px; font-weight:600; border:1px solid var(--border); }
.s-IDLE{color:var(--muted)} .s-RUNNING{color:var(--ok);border-color:var(--ok)}
.s-PAUSED{color:var(--warn);border-color:var(--warn)} .s-STOPPED{color:var(--danger);border-color:var(--danger)}
main { padding:20px; max-width:1200px; margin:0 auto; }
.controls { display:flex; gap:8px; flex-wrap:wrap; margin-left:auto; }
button { font:inherit; font-weight:600; padding:7px 14px; border-radius:8px; cursor:pointer;
border:1px solid var(--border); background:var(--panel); color:var(--fg); }
button:hover { border-color:var(--accent); }
button:disabled { opacity:.4; cursor:not-allowed; }
button.primary { background:var(--accent); border-color:var(--accent); color:#fff; }
.event { color:var(--muted); font-size:12px; padding:8px 20px; border-bottom:1px solid var(--border);
background:var(--panel); }
.stages { display:flex; gap:10px; flex-wrap:wrap; margin-bottom:16px; }
.stage { background:var(--panel); border:1px solid var(--border); border-radius:10px; padding:10px 14px; min-width:120px; }
.stage .n { font-size:22px; font-weight:700; }
.stage .l { font-size:11px; color:var(--muted); letter-spacing:.04em; }
table { width:100%; border-collapse:collapse; background:var(--panel);
border:1px solid var(--border); border-radius:10px; overflow:hidden; }
th,td { text-align:left; padding:9px 12px; border-bottom:1px solid var(--border); vertical-align:top; }
th { font-size:11px; letter-spacing:.05em; color:var(--muted); text-transform:uppercase; }
tr:last-child td { border-bottom:0; }
.tag { font-size:11px; padding:1px 8px; border-radius:6px; border:1px solid var(--border); white-space:nowrap; }
.tag-NEW{color:var(--muted)} .tag-ENRICHED{color:var(--accent);border-color:var(--accent)}
.tag-CONTACT_FOUND{color:var(--ok);border-color:var(--ok)} .tag-EXPORTED{color:var(--warn);border-color:var(--warn)}
a { color:var(--accent); text-decoration:none; }
.muted { color:var(--muted); }
.empty { text-align:center; color:var(--muted); padding:40px; }
</style>
</head>
<body>
<header>
<h1>Screen&nbsp;Leads</h1>
<span id="state" class="pill s-IDLE">IDLE</span>
<div class="controls">
<button id="btn-start" class="primary" onclick="act('start')">Start</button>
<button id="btn-pause" onclick="act('pause')">Pause</button>
<button id="btn-resume" onclick="act('resume')">Resume</button>
<button id="btn-stop" onclick="act('stop')">Stop</button>
<a href="/support">Help</a>
</div>
</header>
<div id="event" class="event"></div>
<main>
<div id="stages" class="stages"></div>
<table>
<thead><tr>
<th>Name</th><th>Title / Company</th><th>Location</th><th>Contact</th>
<th>Stage</th><th>Conf.</th><th>Captured</th><th>Source</th>
</tr></thead>
<tbody id="rows"><tr><td colspan="8" class="empty">No leads yet.</td></tr></tbody>
</table>
</main>
<script>
const STAGES = ["NEW","ENRICHED","CONTACT_FOUND","EXPORTED"];
async function api(path, method="GET"){ const r=await fetch(path,{method}); return r.json(); }
async function act(name){ await api("/api/"+name,"POST"); refresh(); }
function esc(s){ return (s||"").replace(/[&<>]/g,c=>({"&":"&amp;","<":"&lt;",">":"&gt;"}[c])); }
function fmtDate(ts){ if(!ts) return '<span class="muted">—</span>';
const d=new Date(ts*1000);
return d.toLocaleDateString(undefined,{year:'numeric',month:'short',day:'numeric'})
+'<br><span class="muted">'+d.toLocaleTimeString(undefined,{hour:'2-digit',minute:'2-digit'})+'</span>'; }
async function refresh(){
const st = await api("/api/status");
const el = document.getElementById("state");
el.textContent = st.state; el.className = "pill s-"+st.state;
document.getElementById("event").textContent =
(st.last_event||"—") + (st.reason && st.state==="STOPPED" ? " · "+st.reason : "");
document.getElementById("btn-start").disabled = st.state==="RUNNING"||st.state==="PAUSED";
document.getElementById("btn-pause").disabled = st.state!=="RUNNING";
document.getElementById("btn-resume").disabled = st.state!=="PAUSED";
document.getElementById("btn-stop").disabled = st.state!=="RUNNING"&&st.state!=="PAUSED";
const counts = st.stage_counts||{};
document.getElementById("stages").innerHTML = STAGES.map(s=>
`<div class="stage"><div class="n">${counts[s]||0}</div><div class="l">${s.replace("_"," ")}</div></div>`).join("");
const leads = await api("/api/leads");
const rows = document.getElementById("rows");
if(!leads.length){ rows.innerHTML='<tr><td colspan="7" class="empty">No leads yet.</td></tr>'; return; }
rows.innerHTML = leads.map(l=>{
const contact=[l.email,l.phone,l.website].filter(Boolean).map(esc).join("<br>")||'<span class="muted">—</span>';
const src = l.source_url?`<a href="${esc(l.source_url)}" target="_blank" rel="noopener">${esc(l.source_site||"link")}</a>`:esc(l.source_site);
return `<tr>
<td><strong>${esc(l.full_name)||'<span class="muted">?</span>'}</strong><br><span class="muted">${esc(l.headline)}</span></td>
<td>${esc(l.title)}${l.company?' · '+esc(l.company):''}</td>
<td>${esc(l.location)}</td>
<td>${contact}</td>
<td><span class="tag tag-${l.stage}">${l.stage.replace("_"," ")}</span></td>
<td>${l.confidence?Math.round(l.confidence*100)+'%':'—'}</td>
<td>${fmtDate(l.captured_at)}</td>
<td>${src}</td>
</tr>`;
}).join("");
}
refresh(); setInterval(refresh, 2000);
</script>
</body>
</html>