feat: deep site analysis engine + fix AI assess for any domain
site_analyzer.py (new):
- Fresh scrape with timing, page size, server, CMS detection
- Lorem ipsum detection (16 phrases incl. user's example)
- Placeholder content detection (hello world, sample page, etc.)
- Analytics: GA4, GTM, Facebook Pixel, Hotjar, Clarity
- Webmaster: Google Search Console, Bing, Yandex verification tags
- sitemap.xml and robots.txt check + Googlebot block detection
- Mobile viewport check, word count, image/script count
- Full contact extraction: emails, phones, WhatsApp, social links
- Kit Digital signal detection
AI worker fix:
- No longer requires pre-enrichment — works on ANY selected domain
- Does fresh site_analyzer scrape then calls Gemini with full context
- Stores site_analysis JSON alongside AI assessment
- Upserts into enriched_domains even if domain was never enriched
Gemini prompt now includes:
- Complete technical snapshot (load time, size, server, SSL)
- Full SEO signals (sitemap, robots, analytics, webmaster verified)
- Content quality (lorem ipsum matches, placeholder matches)
- Kit Digital signals
- All extracted contacts
- 500-word page text sample
- Outputs: summary, site_quality_score/10, content_issues[],
urgency_signals[], performance_notes, seo_status,
best_contact_channel+value, all_contacts, ES pitch,
services_needed, outreach_notes
UI: rich AI modal with summary banner, quality grid, content issues,
urgency signals, full contact list, technical snapshot
Fixes: correct Replicate token, ai_queue status='running' bug
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -136,11 +136,10 @@ tr:hover td{background:rgba(255,255,255,.025)}
|
||||
|
||||
/* AI detail modal */
|
||||
.modal-bg{position:fixed;inset:0;background:#000a;z-index:300;display:flex;align-items:center;justify-content:center}
|
||||
.modal{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);padding:20px;max-width:500px;width:90%;max-height:80vh;overflow-y:auto}
|
||||
.modal h2{font-size:16px;font-weight:800;margin-bottom:12px}
|
||||
.modal .row{display:flex;gap:8px;margin-bottom:8px;font-size:13px}
|
||||
.modal .label{color:var(--muted);min-width:110px;font-size:12px}
|
||||
.modal .val{color:var(--text)}
|
||||
.modal{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);padding:18px;max-width:560px;width:95%;max-height:88vh;overflow-y:auto}
|
||||
.modal h2{font-size:15px;font-weight:800}
|
||||
.mrow{display:flex;gap:8px;margin-bottom:6px;font-size:12px;line-height:1.4}
|
||||
.mlabel{color:var(--muted);min-width:90px;font-size:11px;padding-top:1px;flex-shrink:0}
|
||||
|
||||
@media(max-width:700px){.pipeline{grid-template-columns:1fr}.sg{grid-template-columns:1fr 1fr}}
|
||||
</style>
|
||||
@@ -153,15 +152,99 @@ tr:hover td{background:rgba(255,255,255,.025)}
|
||||
<!-- AI Detail Modal -->
|
||||
<div class="modal-bg" x-show="modal.open" @click.self="modal.open=false" x-cloak>
|
||||
<div class="modal" @click.stop>
|
||||
<h2>AI Assessment — <span style="color:var(--accent2)" x-text="modal.domain"></span></h2>
|
||||
<div class="row"><span class="label">Lead quality</span><span class="val"><span class="pill" :class="aiPillClass(modal.data.lead_quality)" x-text="modal.data.lead_quality || '—'"></span></span></div>
|
||||
<div class="row"><span class="label">Kit Digital</span><span class="val" x-text="modal.data.kit_digital_confirmed ? '✅ Confirmed' : '❌ Not confirmed'"></span></div>
|
||||
<div class="row"><span class="label">KD reasoning</span><span class="val" x-text="modal.data.kit_digital_reasoning || '—'"></span></div>
|
||||
<div class="row"><span class="label">Lead reasoning</span><span class="val" x-text="modal.data.lead_reasoning || '—'"></span></div>
|
||||
<div class="row"><span class="label">Best channel</span><span class="val" x-text="(modal.data.best_contact_channel || '—') + (modal.data.best_contact_value ? ': ' + modal.data.best_contact_value : '')"></span></div>
|
||||
<div class="row"><span class="label">Pitch</span><span class="val" style="font-style:italic;color:var(--accent2)" x-text="modal.data.pitch_angle || '—'"></span></div>
|
||||
<div class="row"><span class="label">Services needed</span><span class="val" x-text="(modal.data.services_likely_needed || []).join(', ') || '—'"></span></div>
|
||||
<div class="row"><span class="label">Outreach notes</span><span class="val" x-text="modal.data.outreach_notes || '—'"></span></div>
|
||||
<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:12px">
|
||||
<h2>AI Report — <span style="color:var(--accent2)" x-text="modal.domain"></span></h2>
|
||||
<button class="btn bg sm" @click="modal.open=false">✕</button>
|
||||
</div>
|
||||
|
||||
<!-- Summary banner -->
|
||||
<div x-show="modal.ai.summary" style="background:var(--surface2);border-radius:6px;padding:10px 12px;margin-bottom:12px;font-size:12px;line-height:1.5;color:var(--text)" x-text="modal.ai.summary"></div>
|
||||
|
||||
<!-- Lead + quality -->
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;margin-bottom:12px">
|
||||
<div style="background:var(--surface2);border-radius:6px;padding:8px;text-align:center">
|
||||
<div style="font-size:10px;color:var(--muted);margin-bottom:3px">LEAD</div>
|
||||
<span class="pill" :class="aiPillClass(modal.ai.lead_quality)" x-text="modal.ai.lead_quality||'—'"></span>
|
||||
</div>
|
||||
<div style="background:var(--surface2);border-radius:6px;padding:8px;text-align:center">
|
||||
<div style="font-size:10px;color:var(--muted);margin-bottom:3px">SITE QUALITY</div>
|
||||
<span class="score" :style="qualityBg(modal.ai.site_quality_score)" x-text="(modal.ai.site_quality_score??'—')+'/10'"></span>
|
||||
</div>
|
||||
<div style="background:var(--surface2);border-radius:6px;padding:8px;text-align:center">
|
||||
<div style="font-size:10px;color:var(--muted);margin-bottom:3px">KIT DIGITAL</div>
|
||||
<span x-text="modal.ai.kit_digital_confirmed ? '✅ Yes' : '❌ No'" style="font-size:13px;font-weight:700"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mrow"><span class="mlabel">Reasoning</span><span x-text="modal.ai.lead_reasoning||'—'"></span></div>
|
||||
<div class="mrow"><span class="mlabel">KD notes</span><span x-text="modal.ai.kit_digital_reasoning||'—'"></span></div>
|
||||
<div class="mrow"><span class="mlabel">Performance</span><span x-text="modal.ai.performance_notes||'—'"></span></div>
|
||||
<div class="mrow"><span class="mlabel">SEO status</span><span x-text="modal.ai.seo_status||'—'"></span></div>
|
||||
|
||||
<!-- Content issues -->
|
||||
<div x-show="(modal.ai.content_issues||[]).length>0" style="margin:8px 0">
|
||||
<div style="font-size:10px;color:var(--muted);text-transform:uppercase;margin-bottom:4px">Content Issues</div>
|
||||
<template x-for="issue in (modal.ai.content_issues||[])">
|
||||
<div style="font-size:12px;color:var(--danger);padding:2px 0">⚠ <span x-text="issue"></span></div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Urgency signals -->
|
||||
<div x-show="(modal.ai.urgency_signals||[]).length>0" style="margin:8px 0">
|
||||
<div style="font-size:10px;color:var(--muted);text-transform:uppercase;margin-bottom:4px">Urgency Signals</div>
|
||||
<template x-for="sig in (modal.ai.urgency_signals||[])">
|
||||
<div style="font-size:12px;color:var(--warn);padding:2px 0">🔴 <span x-text="sig"></span></div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Contact -->
|
||||
<div style="background:var(--surface2);border-radius:6px;padding:10px;margin:8px 0">
|
||||
<div style="font-size:10px;color:var(--muted);text-transform:uppercase;margin-bottom:6px">Best Contact</div>
|
||||
<div style="font-size:13px;font-weight:700;color:var(--accent2)" x-text="(modal.ai.best_contact_channel||'unknown').toUpperCase()"></div>
|
||||
<div style="font-size:12px;color:var(--text);margin-top:2px;word-break:break-all" x-text="modal.ai.best_contact_value||'—'"></div>
|
||||
<!-- All contacts from site_analysis -->
|
||||
<div x-show="modal.sa" style="margin-top:8px;display:flex;flex-wrap:wrap;gap:4px">
|
||||
<template x-for="em in (modal.sa?.emails||[])">
|
||||
<a :href="'mailto:'+em" class="chip email" x-text="em"></a>
|
||||
</template>
|
||||
<template x-for="ph in (modal.sa?.phones||[])">
|
||||
<a :href="'tel:'+ph" class="chip phone" x-text="ph"></a>
|
||||
</template>
|
||||
<template x-for="wa in (modal.sa?.whatsapp||[])">
|
||||
<a :href="wa" target="_blank" class="chip wa">💬 WhatsApp</a>
|
||||
</template>
|
||||
<template x-for="s in (modal.sa?.social_links||[]).slice(0,3)">
|
||||
<a :href="s" target="_blank" class="chip social" x-text="s.replace('https://','').split('/')[0]"></a>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pitch -->
|
||||
<div style="background:#6c63ff15;border:1px solid #6c63ff33;border-radius:6px;padding:10px;margin:8px 0">
|
||||
<div style="font-size:10px;color:var(--muted);text-transform:uppercase;margin-bottom:4px">Cold Pitch (ES)</div>
|
||||
<div style="font-size:13px;font-style:italic;color:var(--accent2)" x-text="modal.ai.pitch_angle||'—'"></div>
|
||||
</div>
|
||||
|
||||
<div class="mrow"><span class="mlabel">Services</span><span x-text="(modal.ai.services_needed||[]).join(', ')||'—'"></span></div>
|
||||
<div class="mrow"><span class="mlabel">Notes</span><span x-text="modal.ai.outreach_notes||'—'"></span></div>
|
||||
|
||||
<!-- Site analysis tech snapshot -->
|
||||
<div x-show="modal.sa" style="margin-top:10px;padding-top:10px;border-top:1px solid var(--border)">
|
||||
<div style="font-size:10px;color:var(--muted);text-transform:uppercase;margin-bottom:6px">Technical Snapshot</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:4px;font-size:11px">
|
||||
<div>Load time: <b x-text="(modal.sa?.load_time_ms||'—')+'ms'"></b></div>
|
||||
<div>Page size: <b x-text="(modal.sa?.page_size_kb||'—')+'KB'"></b></div>
|
||||
<div>CMS: <b x-text="modal.sa?.cms||'unknown'"></b></div>
|
||||
<div>Server: <b x-text="modal.sa?.server||'—'"></b></div>
|
||||
<div>Sitemap: <b x-text="modal.sa?.has_sitemap?'✅':'❌'"></b></div>
|
||||
<div>Robots: <b x-text="modal.sa?.has_robots?'✅':'❌'"></b></div>
|
||||
<div>Analytics: <b x-text="(modal.sa?.analytics_present||[]).join(', ')||'none'"></b></div>
|
||||
<div>Mobile: <b x-text="modal.sa?.has_mobile_viewport?'✅':'❌'"></b></div>
|
||||
<div>Lorem ipsum: <b :style="modal.sa?.has_lorem_ipsum?'color:var(--danger)':''" x-text="modal.sa?.has_lorem_ipsum?'⚠ YES':'No'"></b></div>
|
||||
<div>Words: <b x-text="modal.sa?.word_count||'—'"></b></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn bg" style="margin-top:14px;width:100%" @click="modal.open=false">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -436,7 +519,7 @@ function app() {
|
||||
qst: {}, customDomains: '',
|
||||
pipeline: {hot:{count:0,samples:[]},warm:{count:0,samples:[]},cold:{count:0,samples:[]}},
|
||||
toast: {show:false,msg:'',type:'success'},
|
||||
modal: {open:false,domain:'',data:{}},
|
||||
modal: {open:false, domain:'', ai:{}, sa:null},
|
||||
_chart: null, _poll: null, _toastTimer: null,
|
||||
|
||||
async init() {
|
||||
@@ -556,11 +639,20 @@ function app() {
|
||||
|
||||
openModal(row) {
|
||||
this.modal.domain = row.domain;
|
||||
try { this.modal.data = row.ai_assessment ? JSON.parse(row.ai_assessment) : {}; }
|
||||
catch(e) { this.modal.data = {}; }
|
||||
try { this.modal.ai = row.ai_assessment ? JSON.parse(row.ai_assessment) : {}; }
|
||||
catch(e) { this.modal.ai = {}; }
|
||||
try { this.modal.sa = row.site_analysis ? JSON.parse(row.site_analysis) : null; }
|
||||
catch(e) { this.modal.sa = null; }
|
||||
this.modal.open = true;
|
||||
},
|
||||
|
||||
qualityBg(s) {
|
||||
if(s==null) return 'background:#333;color:#888';
|
||||
if(s>=8) return 'background:#00d4aa22;color:var(--accent2)';
|
||||
if(s>=5) return 'background:#ffb34722;color:var(--warn)';
|
||||
return 'background:#ff4f6d22;color:var(--danger)';
|
||||
},
|
||||
|
||||
scoreBg(s) {
|
||||
if(s==null) return 'background:#333;color:#888';
|
||||
if(s>=80) return 'background:#ff4f6d22;color:#ff4f6d';
|
||||
|
||||
Reference in New Issue
Block a user