Update app.py
Browse files
app.py
CHANGED
|
@@ -1,59 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
import json
|
| 3 |
import numpy as np
|
| 4 |
import torch
|
| 5 |
-
import os
|
| 6 |
import anthropic
|
| 7 |
from transformers import AutoTokenizer, AutoModel, AutoModelForSequenceClassification
|
| 8 |
|
| 9 |
-
MURIL_MODEL = "models/muril_finetuned"
|
| 10 |
-
BERT_MODEL = "models/bert_finetuned"
|
| 11 |
-
|
| 12 |
-
|
| 13 |
class BargainingAgent:
|
| 14 |
def __init__(self):
|
| 15 |
-
self.device = torch.device(
|
| 16 |
-
|
| 17 |
|
| 18 |
print("Loading India model (MuRIL)...")
|
| 19 |
-
self.muril_embeddings = np.load("
|
| 20 |
-
with open("
|
| 21 |
self.muril_metadata = json.load(f)
|
| 22 |
-
with open("
|
| 23 |
lm = json.load(f)
|
| 24 |
self.muril_id_to_label = {v: k for k, v in lm.items()}
|
| 25 |
-
self.muril_tokenizer = AutoTokenizer.from_pretrained(
|
| 26 |
-
self.muril_classifier = AutoModelForSequenceClassification.from_pretrained(
|
| 27 |
-
MURIL_MODEL).to(self.device)
|
| 28 |
self.muril_classifier.eval()
|
| 29 |
-
self.muril_embed = AutoModel.from_pretrained(
|
| 30 |
-
MURIL_MODEL).to(self.device)
|
| 31 |
self.muril_embed.eval()
|
|
|
|
| 32 |
|
| 33 |
print("Loading Global model (BERT)...")
|
| 34 |
-
self.bert_embeddings = np.load("
|
| 35 |
-
with open("
|
| 36 |
self.bert_metadata = json.load(f)
|
| 37 |
-
with open("
|
| 38 |
lm2 = json.load(f)
|
| 39 |
self.bert_id_to_label = {v: k for k, v in lm2.items()}
|
| 40 |
-
self.bert_tokenizer = AutoTokenizer.from_pretrained(
|
| 41 |
-
self.bert_classifier = AutoModelForSequenceClassification.from_pretrained(
|
| 42 |
-
BERT_MODEL).to(self.device)
|
| 43 |
self.bert_classifier.eval()
|
| 44 |
-
self.bert_embed = AutoModel.from_pretrained(
|
| 45 |
self.bert_embed.eval()
|
|
|
|
| 46 |
|
| 47 |
-
self.claude = anthropic.Anthropic(
|
| 48 |
-
api_key=os.environ.get("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"))
|
| 49 |
print("Both models ready!")
|
| 50 |
|
| 51 |
def classify_intent(self, text, market):
|
| 52 |
tokenizer = self.muril_tokenizer if market == "india" else self.bert_tokenizer
|
| 53 |
classifier = self.muril_classifier if market == "india" else self.bert_classifier
|
| 54 |
id_to_label = self.muril_id_to_label if market == "india" else self.bert_id_to_label
|
| 55 |
-
enc = tokenizer(text, truncation=True, padding=True,
|
| 56 |
-
max_length=128, return_tensors="pt").to(self.device)
|
| 57 |
with torch.no_grad():
|
| 58 |
out = classifier(**enc)
|
| 59 |
probs = torch.softmax(out.logits, dim=1)
|
|
@@ -63,8 +80,7 @@ class BargainingAgent:
|
|
| 63 |
def get_embedding(self, text, market):
|
| 64 |
tokenizer = self.muril_tokenizer if market == "india" else self.bert_tokenizer
|
| 65 |
embed_model = self.muril_embed if market == "india" else self.bert_embed
|
| 66 |
-
enc = tokenizer(text, truncation=True, padding=True,
|
| 67 |
-
max_length=128, return_tensors="pt").to(self.device)
|
| 68 |
with torch.no_grad():
|
| 69 |
out = embed_model(**enc)
|
| 70 |
emb = out[0].mean(dim=1)
|
|
@@ -76,8 +92,7 @@ class BargainingAgent:
|
|
| 76 |
qe = self.get_embedding(query, market)
|
| 77 |
scores = np.dot(embeddings, qe.T).flatten()
|
| 78 |
if pillar:
|
| 79 |
-
filtered = [i for i, m in enumerate(
|
| 80 |
-
metadata) if m["pillar"].lower() == pillar.lower()]
|
| 81 |
fs = np.full(len(embeddings), -1.0)
|
| 82 |
for i in filtered:
|
| 83 |
fs[i] = scores[i]
|
|
@@ -101,8 +116,7 @@ class BargainingAgent:
|
|
| 101 |
}.get(intent, "Negotiation")
|
| 102 |
|
| 103 |
def next_price(self, intent, current, floor):
|
| 104 |
-
drops = {"price_objection": 20,
|
| 105 |
-
"walkaway_threat": 30, "ready_to_buy": 0}
|
| 106 |
drop = drops.get(intent, 10)
|
| 107 |
return max(current - drop, floor)
|
| 108 |
|
|
@@ -116,38 +130,29 @@ class BargainingAgent:
|
|
| 116 |
if market == "india":
|
| 117 |
if gender == "Female":
|
| 118 |
address = "Didi"
|
| 119 |
-
tone = "warm, sisterly, lightly playful
|
| 120 |
-
example = "Arre Didi, aapki choice ekdum amazing hai!
|
| 121 |
elif gender == "Male":
|
| 122 |
address = "Bhaiya"
|
| 123 |
-
tone = "friendly, brotherly, light humor
|
| 124 |
-
example = "Bhaiya aapki nazar
|
| 125 |
else:
|
| 126 |
address = "Aap"
|
| 127 |
tone = "warm, respectful, light humor"
|
| 128 |
-
example = "Aapke liye Rs.820 final kar deta hoon
|
| 129 |
|
| 130 |
language_instruction = f"""Respond in natural Hinglish (Hindi + English mix).
|
| 131 |
-
Address
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
- Add light humor or a playful comment naturally — not forced
|
| 136 |
-
- Praise the customer's choice genuinely — make them feel good
|
| 137 |
-
- Use warmth — make them feel special, not just another customer
|
| 138 |
-
- Never sound desperate or robotic or boring
|
| 139 |
-
- Keep it 2-3 sentences maximum
|
| 140 |
- 1-2 emojis only
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
Good example: '{example}'"""
|
| 144 |
|
| 145 |
else:
|
| 146 |
language_instruction = """Respond in natural conversational English.
|
| 147 |
-
Warm,
|
| 148 |
-
|
| 149 |
-
Never sound like a bot. Keep it short — 2-3 sentences max.
|
| 150 |
-
Good example: 'Great taste! I can do Rs.820 for you — honestly that is our best price and you are getting a steal today 😄'"""
|
| 151 |
|
| 152 |
prompt = f"""You are an experienced shopkeeper negotiating on WhatsApp for {product_name}.
|
| 153 |
|
|
@@ -157,13 +162,12 @@ Customer intent: {intent}
|
|
| 157 |
Negotiation tactic from {book} Page {page}:
|
| 158 |
{tactic}
|
| 159 |
|
| 160 |
-
Price: Previous Rs.{current_offer}
|
| 161 |
-
{'This is
|
| 162 |
|
| 163 |
-
Instructions:
|
| 164 |
{language_instruction}
|
| 165 |
|
| 166 |
-
Write only the WhatsApp reply
|
| 167 |
|
| 168 |
message = self.claude.messages.create(
|
| 169 |
model="claude-haiku-4-5-20251001",
|
|
@@ -171,7 +175,7 @@ Write only the WhatsApp reply message. Nothing else."""
|
|
| 171 |
messages=[{"role": "user", "content": prompt}]
|
| 172 |
)
|
| 173 |
response = message.content[0].text.strip()
|
| 174 |
-
info = f"Intent: {intent} ({conf:.0%}) | Source: {book[:35]}... Page {page} | Offer: Rs.{current_offer}
|
| 175 |
history.append({"role": "user", "content": customer_msg})
|
| 176 |
history.append({"role": "assistant", "content": response})
|
| 177 |
return history, info, next_offer, ""
|
|
@@ -183,13 +187,11 @@ print("Agent ready!")
|
|
| 183 |
|
| 184 |
CSS = """
|
| 185 |
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@300;400;500&display=swap');
|
| 186 |
-
|
| 187 |
body, .gradio-container {
|
| 188 |
font-family: 'DM Sans', sans-serif !important;
|
| 189 |
background: #0a1628 !important;
|
| 190 |
color: #e2e8f0 !important;
|
| 191 |
}
|
| 192 |
-
|
| 193 |
.stack-info {
|
| 194 |
background: #0f1f35;
|
| 195 |
border: 0.5px solid #1e3a5f;
|
|
@@ -200,7 +202,6 @@ body, .gradio-container {
|
|
| 200 |
color: #475569;
|
| 201 |
line-height: 2;
|
| 202 |
}
|
| 203 |
-
|
| 204 |
.footer-note {
|
| 205 |
text-align: center;
|
| 206 |
color: #1e3a5f;
|
|
@@ -211,7 +212,6 @@ body, .gradio-container {
|
|
| 211 |
}
|
| 212 |
"""
|
| 213 |
|
| 214 |
-
|
| 215 |
def chat(message, history, current_offer, floor_price, mrp, product_name, market, gender):
|
| 216 |
if not message.strip():
|
| 217 |
return history, "", current_offer, ""
|
|
@@ -222,16 +222,13 @@ def chat(message, history, current_offer, floor_price, mrp, product_name, market
|
|
| 222 |
)
|
| 223 |
return history, info, new_offer, ""
|
| 224 |
|
| 225 |
-
|
| 226 |
def reset_chat(mrp):
|
| 227 |
starting = int(float(mrp) * 0.94)
|
| 228 |
return [], "Configure your product and start negotiating...", starting
|
| 229 |
|
| 230 |
-
|
| 231 |
def update_price(x):
|
| 232 |
return x
|
| 233 |
|
| 234 |
-
|
| 235 |
def set_q1(): return "bahut mehnga hai bhaiya"
|
| 236 |
def set_q2(): return "quality acchi nahi lagti"
|
| 237 |
def set_q3(): return "Amazon pe sasta milega"
|
|
@@ -241,8 +238,7 @@ def set_q6(): return "I can find it cheaper elsewhere"
|
|
| 241 |
def set_q7(): return "Can you do better on price?"
|
| 242 |
def set_q8(): return "Ok I will take it"
|
| 243 |
|
| 244 |
-
|
| 245 |
-
with gr.Blocks(title="BargainAI") as demo:
|
| 246 |
|
| 247 |
gr.HTML("""
|
| 248 |
<div style="text-align:center;padding:2rem 1rem 1.5rem;background:linear-gradient(180deg,#0f2744 0%,#0a1628 100%);border-bottom:0.5px solid #1e3a5f;margin-bottom:1rem;">
|
|
@@ -256,10 +252,8 @@ with gr.Blocks(title="BargainAI") as demo:
|
|
| 256 |
""")
|
| 257 |
|
| 258 |
with gr.Row():
|
| 259 |
-
|
| 260 |
with gr.Column(scale=1):
|
| 261 |
gr.HTML("<div style='color:#60a5fa;font-size:0.82rem;font-weight:600;letter-spacing:1px;margin-bottom:8px;'>PRODUCT CONFIG</div>")
|
| 262 |
-
|
| 263 |
market = gr.Radio(
|
| 264 |
choices=["India (Hinglish)", "Global (English)"],
|
| 265 |
value="India (Hinglish)",
|
|
@@ -270,14 +264,11 @@ with gr.Blocks(title="BargainAI") as demo:
|
|
| 270 |
value="Male",
|
| 271 |
label="Customer Gender"
|
| 272 |
)
|
| 273 |
-
product_name = gr.Textbox(
|
| 274 |
-
value="Premium Cotton Kurti", label="Product Name")
|
| 275 |
mrp = gr.Number(value=899, label="MRP (Rs.)")
|
| 276 |
-
floor_price = gr.Number(
|
| 277 |
-
value=749, label="Floor Price — Never go below")
|
| 278 |
current_offer = gr.State(value=849)
|
| 279 |
-
price_display = gr.Number(
|
| 280 |
-
value=849, label="Live Offer Price (Rs.)", interactive=False)
|
| 281 |
reset_btn = gr.Button("Reset Conversation", variant="secondary")
|
| 282 |
|
| 283 |
gr.HTML("""
|
|
@@ -286,7 +277,6 @@ with gr.Blocks(title="BargainAI") as demo:
|
|
| 286 |
India: MuRIL fine-tuned · 80.5% accuracy<br>
|
| 287 |
Global: BERT fine-tuned · 83.17% accuracy<br>
|
| 288 |
Gender-aware · Bhaiya / Didi / Aap<br>
|
| 289 |
-
Warm humor · Natural Hinglish<br>
|
| 290 |
9 books · 2,383 indexed chunks<br>
|
| 291 |
Claude Haiku · Real-time responses
|
| 292 |
</div>
|
|
@@ -307,16 +297,14 @@ with gr.Blocks(title="BargainAI") as demo:
|
|
| 307 |
interactive=False
|
| 308 |
)
|
| 309 |
|
| 310 |
-
gr.HTML(
|
| 311 |
-
"<div style='color:#475569;font-size:0.78rem;margin:8px 0 4px;'>India quick replies:</div>")
|
| 312 |
with gr.Row():
|
| 313 |
q1 = gr.Button("bahut mehnga hai", size="sm")
|
| 314 |
q2 = gr.Button("quality acchi nahi", size="sm")
|
| 315 |
q3 = gr.Button("Amazon pe sasta", size="sm")
|
| 316 |
q4 = gr.Button("final price kya hai", size="sm")
|
| 317 |
|
| 318 |
-
gr.HTML(
|
| 319 |
-
"<div style='color:#475569;font-size:0.78rem;margin:8px 0 4px;'>Global quick replies:</div>")
|
| 320 |
with gr.Row():
|
| 321 |
q5 = gr.Button("Too expensive", size="sm")
|
| 322 |
q6 = gr.Button("Cheaper elsewhere", size="sm")
|
|
@@ -334,7 +322,7 @@ with gr.Blocks(title="BargainAI") as demo:
|
|
| 334 |
|
| 335 |
gr.HTML("""
|
| 336 |
<div class="footer-note">
|
| 337 |
-
Built by Nitesh Nankani · MuRIL + BERT + Claude Haiku + 9 Negotiation Books
|
| 338 |
</div>
|
| 339 |
""")
|
| 340 |
|
|
@@ -349,15 +337,13 @@ with gr.Blocks(title="BargainAI") as demo:
|
|
| 349 |
|
| 350 |
send_btn.click(
|
| 351 |
fn=chat,
|
| 352 |
-
inputs=[msg_input, chatbot, current_offer,
|
| 353 |
-
floor_price, mrp, product_name, market, gender],
|
| 354 |
outputs=[chatbot, intel_bar, current_offer, msg_input]
|
| 355 |
).then(fn=update_price, inputs=[current_offer], outputs=[price_display])
|
| 356 |
|
| 357 |
msg_input.submit(
|
| 358 |
fn=chat,
|
| 359 |
-
inputs=[msg_input, chatbot, current_offer,
|
| 360 |
-
floor_price, mrp, product_name, market, gender],
|
| 361 |
outputs=[chatbot, intel_bar, current_offer, msg_input]
|
| 362 |
).then(fn=update_price, inputs=[current_offer], outputs=[price_display])
|
| 363 |
|
|
@@ -368,4 +354,4 @@ with gr.Blocks(title="BargainAI") as demo:
|
|
| 368 |
).then(fn=update_price, inputs=[current_offer], outputs=[price_display])
|
| 369 |
|
| 370 |
if __name__ == "__main__":
|
| 371 |
-
demo.launch(
|
|
|
|
| 1 |
+
from huggingface_hub import snapshot_download
|
| 2 |
+
import os
|
| 3 |
+
|
| 4 |
+
HF_TOKEN = os.environ.get("HF_TOKEN")
|
| 5 |
+
|
| 6 |
+
print("Downloading MuRIL model...")
|
| 7 |
+
muril_path = snapshot_download(
|
| 8 |
+
repo_id="nitz0219/bargainai-muril",
|
| 9 |
+
repo_type="model",
|
| 10 |
+
token=HF_TOKEN
|
| 11 |
+
)
|
| 12 |
+
print("MuRIL ready!")
|
| 13 |
+
|
| 14 |
+
print("Downloading BERT model...")
|
| 15 |
+
bert_path = snapshot_download(
|
| 16 |
+
repo_id="nitz0219/bargainai-bert",
|
| 17 |
+
repo_type="model",
|
| 18 |
+
token=HF_TOKEN
|
| 19 |
+
)
|
| 20 |
+
print("BERT ready!")
|
| 21 |
+
|
| 22 |
+
index_path = os.path.join(muril_path, "index")
|
| 23 |
+
print(f"Index path: {index_path}")
|
| 24 |
+
print(f"Index files: {os.listdir(index_path)}")
|
| 25 |
+
|
| 26 |
import gradio as gr
|
| 27 |
import json
|
| 28 |
import numpy as np
|
| 29 |
import torch
|
|
|
|
| 30 |
import anthropic
|
| 31 |
from transformers import AutoTokenizer, AutoModel, AutoModelForSequenceClassification
|
| 32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
class BargainingAgent:
|
| 34 |
def __init__(self):
|
| 35 |
+
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 36 |
+
print(f"Device: {self.device}")
|
| 37 |
|
| 38 |
print("Loading India model (MuRIL)...")
|
| 39 |
+
self.muril_embeddings = np.load(os.path.join(index_path, "muril_finetuned_embeddings.npy"))
|
| 40 |
+
with open(os.path.join(index_path, "muril_finetuned_metadata.json")) as f:
|
| 41 |
self.muril_metadata = json.load(f)
|
| 42 |
+
with open(os.path.join(muril_path, "label_map.json")) as f:
|
| 43 |
lm = json.load(f)
|
| 44 |
self.muril_id_to_label = {v: k for k, v in lm.items()}
|
| 45 |
+
self.muril_tokenizer = AutoTokenizer.from_pretrained(muril_path)
|
| 46 |
+
self.muril_classifier = AutoModelForSequenceClassification.from_pretrained(muril_path).to(self.device)
|
|
|
|
| 47 |
self.muril_classifier.eval()
|
| 48 |
+
self.muril_embed = AutoModel.from_pretrained(muril_path).to(self.device)
|
|
|
|
| 49 |
self.muril_embed.eval()
|
| 50 |
+
print("MuRIL loaded!")
|
| 51 |
|
| 52 |
print("Loading Global model (BERT)...")
|
| 53 |
+
self.bert_embeddings = np.load(os.path.join(index_path, "bert_finetuned_embeddings.npy"))
|
| 54 |
+
with open(os.path.join(index_path, "bert_finetuned_metadata.json")) as f:
|
| 55 |
self.bert_metadata = json.load(f)
|
| 56 |
+
with open(os.path.join(bert_path, "label_map.json")) as f:
|
| 57 |
lm2 = json.load(f)
|
| 58 |
self.bert_id_to_label = {v: k for k, v in lm2.items()}
|
| 59 |
+
self.bert_tokenizer = AutoTokenizer.from_pretrained(bert_path)
|
| 60 |
+
self.bert_classifier = AutoModelForSequenceClassification.from_pretrained(bert_path).to(self.device)
|
|
|
|
| 61 |
self.bert_classifier.eval()
|
| 62 |
+
self.bert_embed = AutoModel.from_pretrained(bert_path).to(self.device)
|
| 63 |
self.bert_embed.eval()
|
| 64 |
+
print("BERT loaded!")
|
| 65 |
|
| 66 |
+
self.claude = anthropic.Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))
|
|
|
|
| 67 |
print("Both models ready!")
|
| 68 |
|
| 69 |
def classify_intent(self, text, market):
|
| 70 |
tokenizer = self.muril_tokenizer if market == "india" else self.bert_tokenizer
|
| 71 |
classifier = self.muril_classifier if market == "india" else self.bert_classifier
|
| 72 |
id_to_label = self.muril_id_to_label if market == "india" else self.bert_id_to_label
|
| 73 |
+
enc = tokenizer(text, truncation=True, padding=True, max_length=128, return_tensors="pt").to(self.device)
|
|
|
|
| 74 |
with torch.no_grad():
|
| 75 |
out = classifier(**enc)
|
| 76 |
probs = torch.softmax(out.logits, dim=1)
|
|
|
|
| 80 |
def get_embedding(self, text, market):
|
| 81 |
tokenizer = self.muril_tokenizer if market == "india" else self.bert_tokenizer
|
| 82 |
embed_model = self.muril_embed if market == "india" else self.bert_embed
|
| 83 |
+
enc = tokenizer(text, truncation=True, padding=True, max_length=128, return_tensors="pt").to(self.device)
|
|
|
|
| 84 |
with torch.no_grad():
|
| 85 |
out = embed_model(**enc)
|
| 86 |
emb = out[0].mean(dim=1)
|
|
|
|
| 92 |
qe = self.get_embedding(query, market)
|
| 93 |
scores = np.dot(embeddings, qe.T).flatten()
|
| 94 |
if pillar:
|
| 95 |
+
filtered = [i for i, m in enumerate(metadata) if m["pillar"].lower() == pillar.lower()]
|
|
|
|
| 96 |
fs = np.full(len(embeddings), -1.0)
|
| 97 |
for i in filtered:
|
| 98 |
fs[i] = scores[i]
|
|
|
|
| 116 |
}.get(intent, "Negotiation")
|
| 117 |
|
| 118 |
def next_price(self, intent, current, floor):
|
| 119 |
+
drops = {"price_objection": 20, "walkaway_threat": 30, "ready_to_buy": 0}
|
|
|
|
| 120 |
drop = drops.get(intent, 10)
|
| 121 |
return max(current - drop, floor)
|
| 122 |
|
|
|
|
| 130 |
if market == "india":
|
| 131 |
if gender == "Female":
|
| 132 |
address = "Didi"
|
| 133 |
+
tone = "warm, sisterly, lightly playful"
|
| 134 |
+
example = "Arre Didi, aapki choice ekdum amazing hai! Rs.820 final kar deti hoon 😊"
|
| 135 |
elif gender == "Male":
|
| 136 |
address = "Bhaiya"
|
| 137 |
+
tone = "friendly, brotherly, light humor"
|
| 138 |
+
example = "Bhaiya aapki nazar sahi jagah padi! Rs.820 mein le jao 😄"
|
| 139 |
else:
|
| 140 |
address = "Aap"
|
| 141 |
tone = "warm, respectful, light humor"
|
| 142 |
+
example = "Aapke liye Rs.820 final kar deta hoon 😊"
|
| 143 |
|
| 144 |
language_instruction = f"""Respond in natural Hinglish (Hindi + English mix).
|
| 145 |
+
Address customer as {address}. Tone: {tone}
|
| 146 |
+
- Add light humor naturally
|
| 147 |
+
- Praise customer choice genuinely
|
| 148 |
+
- 2-3 sentences max
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
- 1-2 emojis only
|
| 150 |
+
Example: '{example}'"""
|
|
|
|
|
|
|
| 151 |
|
| 152 |
else:
|
| 153 |
language_instruction = """Respond in natural conversational English.
|
| 154 |
+
Warm, friendly, slightly playful. Add light compliment.
|
| 155 |
+
2-3 sentences max. Example: 'Great taste! Rs.820 is our best price today 😄'"""
|
|
|
|
|
|
|
| 156 |
|
| 157 |
prompt = f"""You are an experienced shopkeeper negotiating on WhatsApp for {product_name}.
|
| 158 |
|
|
|
|
| 162 |
Negotiation tactic from {book} Page {page}:
|
| 163 |
{tactic}
|
| 164 |
|
| 165 |
+
Price: Previous Rs.{current_offer} to New offer Rs.{next_offer}
|
| 166 |
+
{'This is FINAL price. Do not go lower.' if at_floor else 'Can negotiate slightly more if needed.'}
|
| 167 |
|
|
|
|
| 168 |
{language_instruction}
|
| 169 |
|
| 170 |
+
Write only the WhatsApp reply."""
|
| 171 |
|
| 172 |
message = self.claude.messages.create(
|
| 173 |
model="claude-haiku-4-5-20251001",
|
|
|
|
| 175 |
messages=[{"role": "user", "content": prompt}]
|
| 176 |
)
|
| 177 |
response = message.content[0].text.strip()
|
| 178 |
+
info = f"Intent: {intent} ({conf:.0%}) | Source: {book[:35]}... Page {page} | Offer: Rs.{current_offer} to Rs.{next_offer}"
|
| 179 |
history.append({"role": "user", "content": customer_msg})
|
| 180 |
history.append({"role": "assistant", "content": response})
|
| 181 |
return history, info, next_offer, ""
|
|
|
|
| 187 |
|
| 188 |
CSS = """
|
| 189 |
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@300;400;500&display=swap');
|
|
|
|
| 190 |
body, .gradio-container {
|
| 191 |
font-family: 'DM Sans', sans-serif !important;
|
| 192 |
background: #0a1628 !important;
|
| 193 |
color: #e2e8f0 !important;
|
| 194 |
}
|
|
|
|
| 195 |
.stack-info {
|
| 196 |
background: #0f1f35;
|
| 197 |
border: 0.5px solid #1e3a5f;
|
|
|
|
| 202 |
color: #475569;
|
| 203 |
line-height: 2;
|
| 204 |
}
|
|
|
|
| 205 |
.footer-note {
|
| 206 |
text-align: center;
|
| 207 |
color: #1e3a5f;
|
|
|
|
| 212 |
}
|
| 213 |
"""
|
| 214 |
|
|
|
|
| 215 |
def chat(message, history, current_offer, floor_price, mrp, product_name, market, gender):
|
| 216 |
if not message.strip():
|
| 217 |
return history, "", current_offer, ""
|
|
|
|
| 222 |
)
|
| 223 |
return history, info, new_offer, ""
|
| 224 |
|
|
|
|
| 225 |
def reset_chat(mrp):
|
| 226 |
starting = int(float(mrp) * 0.94)
|
| 227 |
return [], "Configure your product and start negotiating...", starting
|
| 228 |
|
|
|
|
| 229 |
def update_price(x):
|
| 230 |
return x
|
| 231 |
|
|
|
|
| 232 |
def set_q1(): return "bahut mehnga hai bhaiya"
|
| 233 |
def set_q2(): return "quality acchi nahi lagti"
|
| 234 |
def set_q3(): return "Amazon pe sasta milega"
|
|
|
|
| 238 |
def set_q7(): return "Can you do better on price?"
|
| 239 |
def set_q8(): return "Ok I will take it"
|
| 240 |
|
| 241 |
+
with gr.Blocks(title="BargainAI", css=CSS) as demo:
|
|
|
|
| 242 |
|
| 243 |
gr.HTML("""
|
| 244 |
<div style="text-align:center;padding:2rem 1rem 1.5rem;background:linear-gradient(180deg,#0f2744 0%,#0a1628 100%);border-bottom:0.5px solid #1e3a5f;margin-bottom:1rem;">
|
|
|
|
| 252 |
""")
|
| 253 |
|
| 254 |
with gr.Row():
|
|
|
|
| 255 |
with gr.Column(scale=1):
|
| 256 |
gr.HTML("<div style='color:#60a5fa;font-size:0.82rem;font-weight:600;letter-spacing:1px;margin-bottom:8px;'>PRODUCT CONFIG</div>")
|
|
|
|
| 257 |
market = gr.Radio(
|
| 258 |
choices=["India (Hinglish)", "Global (English)"],
|
| 259 |
value="India (Hinglish)",
|
|
|
|
| 264 |
value="Male",
|
| 265 |
label="Customer Gender"
|
| 266 |
)
|
| 267 |
+
product_name = gr.Textbox(value="Premium Cotton Kurti", label="Product Name")
|
|
|
|
| 268 |
mrp = gr.Number(value=899, label="MRP (Rs.)")
|
| 269 |
+
floor_price = gr.Number(value=749, label="Floor Price — Never go below")
|
|
|
|
| 270 |
current_offer = gr.State(value=849)
|
| 271 |
+
price_display = gr.Number(value=849, label="Live Offer Price (Rs.)", interactive=False)
|
|
|
|
| 272 |
reset_btn = gr.Button("Reset Conversation", variant="secondary")
|
| 273 |
|
| 274 |
gr.HTML("""
|
|
|
|
| 277 |
India: MuRIL fine-tuned · 80.5% accuracy<br>
|
| 278 |
Global: BERT fine-tuned · 83.17% accuracy<br>
|
| 279 |
Gender-aware · Bhaiya / Didi / Aap<br>
|
|
|
|
| 280 |
9 books · 2,383 indexed chunks<br>
|
| 281 |
Claude Haiku · Real-time responses
|
| 282 |
</div>
|
|
|
|
| 297 |
interactive=False
|
| 298 |
)
|
| 299 |
|
| 300 |
+
gr.HTML("<div style='color:#475569;font-size:0.78rem;margin:8px 0 4px;'>India quick replies:</div>")
|
|
|
|
| 301 |
with gr.Row():
|
| 302 |
q1 = gr.Button("bahut mehnga hai", size="sm")
|
| 303 |
q2 = gr.Button("quality acchi nahi", size="sm")
|
| 304 |
q3 = gr.Button("Amazon pe sasta", size="sm")
|
| 305 |
q4 = gr.Button("final price kya hai", size="sm")
|
| 306 |
|
| 307 |
+
gr.HTML("<div style='color:#475569;font-size:0.78rem;margin:8px 0 4px;'>Global quick replies:</div>")
|
|
|
|
| 308 |
with gr.Row():
|
| 309 |
q5 = gr.Button("Too expensive", size="sm")
|
| 310 |
q6 = gr.Button("Cheaper elsewhere", size="sm")
|
|
|
|
| 322 |
|
| 323 |
gr.HTML("""
|
| 324 |
<div class="footer-note">
|
| 325 |
+
Built by Nitesh Nankani · MuRIL + BERT + Claude Haiku + 9 Negotiation Books �� HuggingFace · Gradio
|
| 326 |
</div>
|
| 327 |
""")
|
| 328 |
|
|
|
|
| 337 |
|
| 338 |
send_btn.click(
|
| 339 |
fn=chat,
|
| 340 |
+
inputs=[msg_input, chatbot, current_offer, floor_price, mrp, product_name, market, gender],
|
|
|
|
| 341 |
outputs=[chatbot, intel_bar, current_offer, msg_input]
|
| 342 |
).then(fn=update_price, inputs=[current_offer], outputs=[price_display])
|
| 343 |
|
| 344 |
msg_input.submit(
|
| 345 |
fn=chat,
|
| 346 |
+
inputs=[msg_input, chatbot, current_offer, floor_price, mrp, product_name, market, gender],
|
|
|
|
| 347 |
outputs=[chatbot, intel_bar, current_offer, msg_input]
|
| 348 |
).then(fn=update_price, inputs=[current_offer], outputs=[price_display])
|
| 349 |
|
|
|
|
| 354 |
).then(fn=update_price, inputs=[current_offer], outputs=[price_display])
|
| 355 |
|
| 356 |
if __name__ == "__main__":
|
| 357 |
+
demo.launch()
|