| import gradio as gr |
| import pandas as pd |
| import numpy as np |
| import faiss |
| import os |
| import re |
| import random |
| from openai import OpenAI |
|
|
| |
| client = OpenAI(api_key=os.environ["OPENAI_API_KEY"]) |
|
|
| |
| 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')}" |
| ) |
|
|
| |
| def get_embedding(text): |
| response = client.embeddings.create( |
| input=[text], |
| model="text-embedding-3-small" |
| ) |
| return response.data[0].embedding |
|
|
| |
| 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]] |
|
|
| |
| 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" |
|
|
| |
| 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." |
|
|
| |
| |
| 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 |
|
|
| |
| 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) |
|
|
| |
| 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""" |
| <div style='margin-top:20px;padding:10px;font-size:13px;color:gray; |
| border-top:1px solid #ddd;'> |
| 📌 Target: <b>{account_name}</b> (Region: {row.get('Region', 'N/A')}, |
| Focus: {row.get('Recent_Interaction_Focus', 'N/A')}) |
| </div> |
| """ |
|
|
| html = f""" |
| <div style='font-family:Arial,sans-serif;max-width:700px;margin:auto;padding:24px;background:#ffffff; |
| border-radius:12px;box-shadow:0 3px 10px rgba(0,0,0,0.1);'> |
| <img src="{image_url}" style="width:100%;border-radius:8px;margin-bottom:20px;"> |
| {output_text} |
| {clinic_note} |
| </div> |
| """ |
|
|
| return html |
|
|
| |
| 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: "<p style='color:gray;'>⏳ Processing... please wait 30–60 seconds.</p>", |
| inputs=None, |
| outputs=output, |
| queue=False |
| ) |
|
|
| app.launch() |
|
|