From 7579c78f3a1382c22c7452ad30a9b3f9bb4d104f Mon Sep 17 00:00:00 2001 From: Floke Date: Mon, 2 Mar 2026 13:35:18 +0000 Subject: [PATCH] [31388f42] Restructure Lead Engine UI: vertical layout with 2-column top section for source data --- lead-engine/app.py | 191 +++++++++++++++++++++------------------------ 1 file changed, 88 insertions(+), 103 deletions(-) diff --git a/lead-engine/app.py b/lead-engine/app.py index 7edd37fb..85904bf6 100644 --- a/lead-engine/app.py +++ b/lead-engine/app.py @@ -129,120 +129,105 @@ if not df.empty: except: pass - with st.expander(f"{date_str} | {row['company_name']} ({row['status']})"): - # Quality Warning + with st.expander(f"{date_str} | {row['company_name']}"): + # Metadata Parsing meta = {} if row.get('lead_metadata'): try: meta = json.loads(row['lead_metadata']) except: pass + # --- TOP SECTION: QUALITY WARNING --- if meta.get('is_low_quality'): - st.warning("⚠️ Low Quality Lead detected (Free-mail or missing company). Use for reclamation if applicable.") + st.warning("⚠️ **Low Quality Lead detected** (Free-mail or missing company).") + # --- SECTION 1: LEAD INFO (2 Columns) --- + st.markdown("### 📋 Lead Data") c1, c2 = st.columns(2) - # --- Left Column: Lead Data --- - c1.write(f"**Contact:** {row['contact_name']}") - c1.write(f"**Email:** {row['email']}") - - # Metadata Display - meta = {} - if row.get('lead_metadata'): - try: - meta = json.loads(row['lead_metadata']) - except: - pass - - role = meta.get('role') - if role: - c1.info(f"**Role:** {role}") - else: - if c1.button("🔍 Find Role (SerpAPI)", key=f"role_{row['id']}"): - from enrich import enrich_contact_role - with st.spinner("Searching LinkedIn via Google..."): - found_role = enrich_contact_role(row) - if found_role: - st.success(f"Found: {found_role}") - st.rerun() - else: - st.error("No role found.") - - if meta: - c1.write("---") - c1.write(f"**Area:** {meta.get('area', '-')}") - c1.write(f"**Purpose:** {meta.get('purpose', '-')}") - c1.write(f"**Location:** {meta.get('zip', '')} {meta.get('city', '')}") - - # Manual Sync Button for individual lead - if row['status'] == 'new': - if c1.button("🚀 Sync to Company Explorer", key=f"sync_single_{row['id']}"): - with st.spinner("Processing lead (Role + CE Sync)..."): - res = sync_single_lead(row['id']) - if res.get('status') != 'error': - st.success("Lead synced successfully!") - st.rerun() - else: - st.error(f"Sync failed: {res.get('message')}") - else: - if c1.button("🔄 Reset Status to New", key=f"reset_{row['id']}"): - reset_lead(row['id']) - st.toast("Lead status reset.") - st.rerun() - - with c1.expander("Show Original Email Content"): - st.text(clean_html_to_text(row['raw_body'])) - if st.checkbox("Show Raw HTML", key=f"raw_{row['id']}"): - st.code(row['raw_body'], language="html") - - # --- Right Column: Enrichment & Response --- - enrichment = json.loads(row['enrichment_data']) if row['enrichment_data'] else {} - - if enrichment: - c2.write("--- Integration Status ---") - ce_id = enrichment.get('ce_id') + with c1: + st.write(f"**Contact:** {row['contact_name']}") + st.write(f"**Email:** {row['email']}") - if ce_id: - c2.success(f"✅ Linked to Company Explorer (ID: {ce_id})") - - # CE Data Display - ce_data = enrichment.get('ce_data', {}) - # Support both names (CE uses industry_ai internally) - vertical = ce_data.get('industry_ai') or ce_data.get('vertical') - summary = ce_data.get('research_dossier') or ce_data.get('summary') - - if vertical and vertical != 'None': - c2.info(f"**Industry:** {vertical}") - else: - c2.warning("Industry Analysis pending...") - - if summary: - with c2.expander("Show Analysis Summary"): - st.write(summary) - - # Refresh Button - if c2.button("🔄 Refresh Analysis Data", key=f"refresh_{row['id']}"): - with st.spinner("Fetching latest data from Company Explorer..."): - new_data = refresh_ce_data(row['id'], ce_id) - st.toast("Data refreshed!") - st.rerun() - - # Generate Reply Button (only if we have data) - c2.write("--- Response Generation ---") - if c2.button("✨ Generate Expert Reply", key=f"gen_{row['id']}"): - with st.spinner("Generating draft with Gemini..."): - # Prepare lead data dict from row - lead_dict = row.to_dict() - draft = generate_email_draft(lead_dict, ce_data) - # Store draft in session state to persist - st.session_state[f"draft_{row['id']}"] = draft - - # Display Draft - if f"draft_{row['id']}" in st.session_state: - c2.text_area("Draft Email", value=st.session_state[f"draft_{row['id']}"], height=300) - + role = meta.get('role') + if role: + st.info(f"**Role:** {role}") else: - c2.warning("⚠️ Not yet synced or failed") - c2.info(f"Log: {enrichment.get('message')}") + if st.button("🔍 Find Role", key=f"role_{row['id']}"): + from enrich import enrich_contact_role + with st.spinner("Searching..."): + found_role = enrich_contact_role(row) + if found_role: st.success(f"Found: {found_role}"); st.rerun() + else: st.error("No role found.") + + with c2: + st.write(f"**Area:** {meta.get('area', '-')}") + st.write(f"**Purpose:** {meta.get('purpose', '-')}") + st.write(f"**Location:** {meta.get('zip', '')} {meta.get('city', '')}") + + with st.expander("Original Body Preview"): + st.text(clean_html_to_text(row['raw_body'])) + if st.checkbox("Show HTML", key=f"raw_{row['id']}"): + st.code(row['raw_body'], language="html") + + st.divider() + + # --- SECTION 2: INTELLIGENCE (CE) --- + st.markdown("### 🔍 Intelligence (CE)") + enrichment = json.loads(row['enrichment_data']) if row['enrichment_data'] else {} + ce_id = enrichment.get('ce_id') + + if ce_id: + st.success(f"✅ Linked to Company Explorer (ID: {ce_id})") + ce_data = enrichment.get('ce_data', {}) + + vertical = ce_data.get('industry_ai') or ce_data.get('vertical') + summary = ce_data.get('research_dossier') or ce_data.get('summary') + + intel_col1, intel_col2 = st.columns([1, 2]) + with intel_col1: + if vertical and vertical != 'None': + st.info(f"**Industry:** {vertical}") + else: + st.warning("Industry Analysis pending...") + + if st.button("🔄 Refresh CE Data", key=f"refresh_{row['id']}"): + with st.spinner("Fetching..."): + refresh_ce_data(row['id'], ce_id) + st.rerun() + + with intel_col2: + if summary: + with st.expander("Show AI Research Dossier", expanded=True): + st.write(summary) + else: + st.warning("⚠️ Not synced with Company Explorer yet") + if st.button("🚀 Sync to Company Explorer", key=f"sync_single_{row['id']}"): + with st.spinner("Syncing..."): + sync_single_lead(row['id']) + st.rerun() + + st.divider() + + # --- SECTION 3: RESPONSE DRAFT --- + st.markdown("### ✉️ Response Draft") + if row['status'] != 'new' and ce_id: + if st.button("✨ Generate Expert Reply", key=f"gen_{row['id']}", type="primary"): + with st.spinner("Writing email..."): + ce_data = enrichment.get('ce_data', {}) + draft = generate_email_draft(row.to_dict(), ce_data) + st.session_state[f"draft_{row['id']}"] = draft + + if f"draft_{row['id']}" in st.session_state: + st.text_area("Email Entwurf", value=st.session_state[f"draft_{row['id']}"], height=400) + st.button("📋 Copy to Clipboard", key=f"copy_{row['id']}", on_click=lambda: st.write("Copy functionality simulated")) + else: + st.info("Sync with Company Explorer first to generate a response.") + + if row['status'] != 'new': + st.markdown("---") + if st.button("🔄 Reset Lead Status", key=f"reset_{row['id']}", help="Back to 'new' status"): + reset_lead(row['id']) + st.rerun() else: st.info("No leads found. Click 'Ingest Emails' in the sidebar.")