File size: 7,436 Bytes
7e0e6b2 507a7b9 7c03c77 0884ede 7c03c77 8e37637 0884ede 507a7b9 7c03c77 7e0e6b2 0666861 8e37637 0666861 7e0e6b2 7c03c77 7e0e6b2 7c03c77 071ebb8 7e0e6b2 071ebb8 7e0e6b2 071ebb8 7e0e6b2 8e37637 a63e8a5 071ebb8 8e37637 9380505 7e0e6b2 9380505 071ebb8 9380505 a63e8a5 8e37637 9380505 a63e8a5 9380505 a63e8a5 9380505 071ebb8 7e0e6b2 071ebb8 8e37637 071ebb8 f478807 9380505 7e0e6b2 7c03c77 7987393 adec15b 7c03c77 7e0e6b2 6952a2a 7e0e6b2 071ebb8 7e0e6b2 6952a2a 7e0e6b2 6952a2a 7e0e6b2 8e37637 b153128 071ebb8 f2d20ef 071ebb8 b153128 42b62ad 071ebb8 f2d20ef 42b62ad f2d20ef 8e37637 f2d20ef 071ebb8 0666861 42b62ad 071ebb8 42b62ad 63d328e 071ebb8 63d328e 7e0e6b2 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
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"""
<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
# 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: "<p style='color:gray;'>⏳ Processing... please wait 30–60 seconds.</p>",
inputs=None,
outputs=output,
queue=False
)
app.launch()
|