import gradio as gr import pandas as pd import numpy as np import faiss import os import re import random from openai import OpenAI # Initialize OpenAI client securely client = OpenAI(api_key=os.environ["OPENAI_API_KEY"]) # Convert a CRM row into readable text for embeddings def row_to_text(row): return ( f"Clinic: {row.get('Account_Name', 'N/A')}\n" f"Interaction Focus: {row.get('Recent_Interaction_Focus', 'N/A')}\n" f"Interaction Notes: {row.get('Recent_Interaction_Notes', 'N/A')}\n" f"Region: {row.get('Region', 'Unknown')}\n" f"Account Manager: {row.get('Account_Manager', 'Unknown')}\n" f"Revenue: €{row.get('Revenue', 'N/A')}\n" f"Last Purchase: {row.get('Last_Purchase_Date', 'N/A')}\n" f"Churn Risk: {row.get('Churn_Risk', 'N/A')}\n" f"Satisfaction Score: {row.get('Satisfaction_Score', 'N/A')}/10\n" f"Product Interest: {row.get('Product_Interest', 'N/A')}\n" f"Notes: {row.get('Account_Notes', 'N/A')}" ) # Get embedding for a given text def get_embedding(text): response = client.embeddings.create( input=[text], model="text-embedding-3-small" ) return response.data[0].embedding # Core function: generate contextual pitch def contextual_pitch_assistant(csv_file, query, sender_name): df = pd.read_csv(csv_file.name) text_chunks = df.apply(row_to_text, axis=1).tolist() embeddings = [get_embedding(t) for t in text_chunks] dim = len(embeddings[0]) index = faiss.IndexFlatL2(dim) index.add(np.array(embeddings).astype("float32")) q_emb = np.array([get_embedding(query)]).astype("float32") D, I = index.search(q_emb, 3) retrieved = [df.iloc[i] for i in I[0]] # Extract contextual fields row = retrieved[0] account_manager = row.get("Account_Manager", "team") account_name = row.get("Account_Name", "your clinic") focus = str(row.get("Recent_Interaction_Focus", "")).lower() notes_focus = str(row.get("Recent_Interaction_Notes", "")) notes_background = str(row.get("Account_Notes", "")) if not sender_name.strip(): sender_name = "The Sales Team" # Map focus → style instruction if "business" in focus: style_instruction = "Emphasize ROI, efficiency, and competitive advantage." elif "patient" in focus: style_instruction = "Emphasize patient comfort, satisfaction, and outcomes." elif "clinical" in focus: style_instruction = "Emphasize precision, innovation, and clinical quality." else: style_instruction = "Balance clinical, patient, and business value." # Build prompt prompt = f""" You are an expert in B2B sales messaging, inspired by Challenger Sales and Jeb Blount’s prospecting techniques. Your job is to write a short, natural-sounding HTML email pitch to a dental clinic. Rules: - Begin with "Dear {account_manager}," as the greeting. - Anchor immediately in the clinic’s recent concern: "{notes_focus}". - Use background context if relevant: "{notes_background}". - Adapt the pitch style: {style_instruction} - Apply Challenger logic: * Start with the client’s concern in their own terms. * Reframe with an insight (show a broader problem or missed opportunity). * Link features → outcomes → impact (business, patient, or clinical). * Confident but helpful tone. - Keep under 150 words. - End with a strong, specific CTA (e.g. propose a short call or demo, suggest a time). - Close with "Best regards," followed by "{sender_name}". - Return only HTML — no markdown, no code fences. Query: {query} CRM context (for your understanding, do not copy verbatim): {row_to_text(row)} """ response = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": prompt}], temperature=0.7 ) output_text = response.choices[0].message.content # TEMP FIX: use random pre-uploaded header image image_choices = [ "https://huggingface.co/spaces/nmcamacho/RAGdemo/resolve/main/dental_header_1.png", "https://huggingface.co/spaces/nmcamacho/RAGdemo/resolve/main/dental_header_2.png", "https://huggingface.co/spaces/nmcamacho/RAGdemo/resolve/main/dental_header_3.png" ] image_url = random.choice(image_choices) # Clean output text output_text = re.sub(r"(?is)dall[-·]e.*?```", "", output_text) output_text = re.sub(r"###.*?HTML Email Pitch", "", output_text) output_text = re.sub(r"```html|```", "", output_text) output_text = output_text.strip() clinic_note = f"""
📌 Target: {account_name} (Region: {row.get('Region', 'N/A')}, Focus: {row.get('Recent_Interaction_Focus', 'N/A')})
""" html = f"""
{output_text} {clinic_note}
""" return html # Build Gradio app (unchanged, apart from column updates) with gr.Blocks( title="Contextual Pitch Assistant for Dental Sales", css=""" body { background-color: #f7f9f9; font-family: 'Inter', sans-serif; } #output_html { min-height: 450px; } .gradio-container { max-width: 90% !important; margin: auto; } h1, h2, h3, h4, h5 { color: #00857C; font-weight: 600; } .gr-button { background-color: #00857C !important; color: white !important; border: none !important; font-weight: 600; padding: 10px 18px; border-radius: 8px; } .gr-button:hover { background-color: #006e67 !important; } .gr-file, .gr-textbox { border: 1px solid #d1d5db !important; border-radius: 8px !important; } .gr-box { background: white !important; border-radius: 12px !important; box-shadow: 0 2px 8px rgba(0,0,0,0.05); padding: 20px !important; } """ ) as app: gr.Markdown( """ # 🦷 Contextual Pitch Assistant for Dental Sales *Powered by contextual CRM data and generative AI* --- Upload a CRM file and enter a sales question — get a personalized email pitch with a contextual image. """ ) with gr.Row(): csv_file = gr.File(label="📂 Upload CRM CSV (with Recent_Interaction_Focus/Notes)", file_types=[".csv"]) sender_name = gr.Textbox( label="✍️ Who signs the email?", placeholder="e.g. Nuno Camacho, Sales Director", value="Nuno Camacho" ) query = gr.Textbox( label="💬 Sales Query", placeholder="e.g. Which clinic is best for our imaging subscription?", lines=2 ) run_btn = gr.Button("🚀 Generate Pitch", variant="primary") output = gr.HTML(label="✨ Email Pitch Preview", elem_id="output_html") run_btn.click(fn=contextual_pitch_assistant, inputs=[csv_file, query, sender_name], outputs=output) run_btn.click( lambda: "

⏳ Processing... please wait 30–60 seconds.

", inputs=None, outputs=output, queue=False ) app.launch()