|
|
import gradio as gr |
|
|
import re |
|
|
from datetime import datetime |
|
|
from transformers import pipeline |
|
|
|
|
|
|
|
|
model_name = "microsoft/DialoGPT-small" |
|
|
chatbot = pipeline("text-generation", model=model_name) |
|
|
|
|
|
|
|
|
PRODUCT_CATALOG = { |
|
|
"Cinco Phone": ["phone", "mobile", "cellphone", "smartphone"], |
|
|
"Cinco Tablet": ["tablet", "pad"], |
|
|
"Cinco Laptop": ["laptop", "notebook", "computer"], |
|
|
"Cinco Earbuds": ["earbuds", "earphones", "headphones"] |
|
|
} |
|
|
|
|
|
RECEIPT_DATABASE = { |
|
|
"RC-1001": {"product": "Cinco Phone", "date": "2023-05-15", "email": "customer1@example.com"}, |
|
|
"RC-1002": {"product": "Cinco Tablet", "date": "2023-06-20", "email": "customer2@example.com"}, |
|
|
"RC-1003": {"product": "Cinco Laptop", "date": "2023-07-10", "email": "customer3@example.com"}, |
|
|
} |
|
|
|
|
|
|
|
|
RETURN_POLICY = { |
|
|
"window": "30 days", |
|
|
"process_time": "5-7 business days", |
|
|
"conditions": "Product must be in original condition with packaging", |
|
|
"contact": "1-800-CINCO-SUPPORT", |
|
|
"email": "returns@cinco.com" |
|
|
} |
|
|
|
|
|
|
|
|
class ChatState: |
|
|
NORMAL = 0 |
|
|
RETURN_DETECTED = 1 |
|
|
PRODUCT_CONFIRMED = 2 |
|
|
RECEIPT_HANDLING = 3 |
|
|
ALT_INFO_HANDLING = 4 |
|
|
RETURN_PROCESSING = 5 |
|
|
DISENGAGED = 6 |
|
|
POLICY_QUESTION = 7 |
|
|
|
|
|
def initialize_state(): |
|
|
return { |
|
|
"stage": ChatState.NORMAL, |
|
|
"return_product": None, |
|
|
"receipt": None, |
|
|
"name": None, |
|
|
"purchase_date": None, |
|
|
"email": None, |
|
|
"error_count": 0, |
|
|
"conversation_history": [] |
|
|
} |
|
|
|
|
|
|
|
|
def process_return(product): |
|
|
return f"β
Return processed for your {product}!\nConfirmation sent to your email." |
|
|
|
|
|
def is_return_request(text): |
|
|
return bool(re.search(r'\b(return|refund|exchange|send back)\b', text.lower())) |
|
|
|
|
|
def is_policy_question(text): |
|
|
policy_keywords = ["policy", "how long", "can i", "after", "before", "days", |
|
|
"time", "window", "condition", "return policy", "refund policy"] |
|
|
return any(keyword in text.lower() for keyword in policy_keywords) |
|
|
|
|
|
def is_general_question(text): |
|
|
general_keywords = ["what", "who", "where", "when", "why", "how", "you", "your", "cinco"] |
|
|
return any(keyword in text.lower() for keyword in general_keywords) |
|
|
|
|
|
def format_conversation(history, new_input): |
|
|
"""Format conversation history with context about Cinco and return policy""" |
|
|
prompt = ( |
|
|
"You are CincoBot, a friendly customer service assistant for Cinco Electronics. " |
|
|
"You specialize in helping customers with product returns. " |
|
|
f"Cinco's return policy: Returns accepted within {RETURN_POLICY['window']} of purchase. " |
|
|
f"Conditions: {RETURN_POLICY['conditions']}. " |
|
|
f"Processing time: {RETURN_POLICY['process_time']}. " |
|
|
"Be helpful, polite, and professional. Answer questions about returns and Cinco products.\n\n" |
|
|
) |
|
|
|
|
|
for message in history: |
|
|
if message["role"] == "user": |
|
|
prompt += f"Customer: {message['content']}\n" |
|
|
else: |
|
|
prompt += f"Assistant: {message['content']}\n" |
|
|
|
|
|
prompt += f"Customer: {new_input}\nAssistant:" |
|
|
return prompt |
|
|
|
|
|
def recognize_product(user_input): |
|
|
"""Match product using keywords and synonyms""" |
|
|
user_input = user_input.lower() |
|
|
for product, aliases in PRODUCT_CATALOG.items(): |
|
|
if any(alias in user_input for alias in aliases): |
|
|
return product |
|
|
if product.lower() in user_input: |
|
|
return product |
|
|
return None |
|
|
|
|
|
def handle_return_flow(user_input, state): |
|
|
"""Natural conversation handling for return process""" |
|
|
response = "" |
|
|
|
|
|
|
|
|
if is_policy_question(user_input): |
|
|
state["stage"] = ChatState.POLICY_QUESTION |
|
|
return None, state |
|
|
|
|
|
|
|
|
if state["stage"] == ChatState.NORMAL: |
|
|
if is_return_request(user_input): |
|
|
state["stage"] = ChatState.RETURN_DETECTED |
|
|
response = ( |
|
|
"I'd be happy to help with your return! Cinco accepts returns within 30 days of purchase. " |
|
|
"Could you tell me which product you'd like to return?" |
|
|
) |
|
|
else: |
|
|
|
|
|
return None, state |
|
|
|
|
|
|
|
|
elif state["stage"] == ChatState.RETURN_DETECTED: |
|
|
product = recognize_product(user_input) |
|
|
if product: |
|
|
state["return_product"] = product |
|
|
state["stage"] = ChatState.PRODUCT_CONFIRMED |
|
|
response = ( |
|
|
f"Got it, you want to return the {product}. " |
|
|
"Could you please share your receipt number? It usually looks like RC-1234. " |
|
|
"If you don't have it, just say so." |
|
|
) |
|
|
else: |
|
|
state["error_count"] += 1 |
|
|
if state["error_count"] >= 2: |
|
|
response = "I'm having trouble identifying the product. Let me connect you to a human agent." |
|
|
state["stage"] = ChatState.DISENGAGED |
|
|
else: |
|
|
response = "I understand you want to return something. Could you specify the product name? We have phones, tablets, laptops, and earbuds." |
|
|
|
|
|
|
|
|
elif state["stage"] == ChatState.PRODUCT_CONFIRMED: |
|
|
receipt_match = re.search(r'RC-\d{4}', user_input.upper()) |
|
|
if receipt_match: |
|
|
receipt_id = receipt_match.group(0) |
|
|
if receipt_id in RECEIPT_DATABASE: |
|
|
state["receipt"] = receipt_id |
|
|
state["email"] = RECEIPT_DATABASE[receipt_id]["email"] |
|
|
response = "Thanks! I've verified your receipt. One moment while I process your return..." |
|
|
state["stage"] = ChatState.RETURN_PROCESSING |
|
|
else: |
|
|
response = "I couldn't find that receipt in our system. Could you double-check the number?" |
|
|
state["error_count"] += 1 |
|
|
else: |
|
|
if "don't" in user_input.lower() or "no" in user_input.lower() or "not" in user_input.lower(): |
|
|
response = ( |
|
|
"No problem! To process your return without a receipt, I'll need:\n" |
|
|
"1. Your full name\n" |
|
|
"2. Purchase date (YYYY-MM-DD)\n" |
|
|
"3. Email address\n" |
|
|
"You can provide them in any order." |
|
|
) |
|
|
state["stage"] = ChatState.ALT_INFO_HANDLING |
|
|
else: |
|
|
response = "Could you share your receipt number? If you don't have it, just say so." |
|
|
state["error_count"] += 1 |
|
|
|
|
|
if state["error_count"] >= 2: |
|
|
response = "I'm having trouble with your receipt. Let me connect you with a human agent." |
|
|
state["stage"] = ChatState.DISENGAGED |
|
|
|
|
|
|
|
|
elif state["stage"] == ChatState.ALT_INFO_HANDLING: |
|
|
|
|
|
name_match = re.search(r'[A-Za-z]+ [A-Za-z]+', user_input) |
|
|
date_match = re.search(r'\d{4}-\d{2}-\d{2}', user_input) |
|
|
email_match = re.search(r'[\w\.-]+@[\w\.-]+', user_input) |
|
|
|
|
|
if name_match: |
|
|
state["name"] = name_match.group(0) |
|
|
if date_match: |
|
|
state["purchase_date"] = date_match.group(0) |
|
|
if email_match: |
|
|
state["email"] = email_match.group(0) |
|
|
|
|
|
|
|
|
if state["name"] and state["purchase_date"] and state["email"]: |
|
|
response = "Thanks! I have all I need to process your return." |
|
|
state["stage"] = ChatState.RETURN_PROCESSING |
|
|
else: |
|
|
missing = [] |
|
|
if not state["name"]: |
|
|
missing.append("full name") |
|
|
if not state["purchase_date"]: |
|
|
missing.append("purchase date (YYYY-MM-DD)") |
|
|
if not state["email"]: |
|
|
missing.append("email address") |
|
|
|
|
|
response = f"I still need your {', '.join(missing)}. Could you provide that?" |
|
|
|
|
|
|
|
|
elif state["stage"] == ChatState.RETURN_PROCESSING: |
|
|
response = process_return(state["return_product"]) |
|
|
response += "\n\nIs there anything else I can help you with today?" |
|
|
state = initialize_state() |
|
|
|
|
|
|
|
|
elif state["stage"] == ChatState.DISENGAGED: |
|
|
response = ( |
|
|
"I'm transferring you to one of our human support agents. " |
|
|
f"Please contact us at {RETURN_POLICY['contact']} or {RETURN_POLICY['email']}." |
|
|
) |
|
|
|
|
|
return response, state |
|
|
|
|
|
def handle_policy_question(question): |
|
|
"""Handle return policy questions with detailed responses""" |
|
|
question = question.lower() |
|
|
|
|
|
if "policy" in question or "return policy" in question: |
|
|
return ( |
|
|
"π Cinco Return Policy:\n" |
|
|
f"- Return Window: {RETURN_POLICY['window']} from purchase date\n" |
|
|
f"- Conditions: {RETURN_POLICY['conditions']}\n" |
|
|
f"- Processing Time: {RETURN_POLICY['process_time']}\n" |
|
|
f"- Contact: {RETURN_POLICY['contact']} or {RETURN_POLICY['email']}" |
|
|
) |
|
|
|
|
|
if "after" in question and "days" in question: |
|
|
return ( |
|
|
f"Our return window is {RETURN_POLICY['window']} from purchase date. " |
|
|
"After this period, returns are generally not accepted, but you can contact " |
|
|
f"our support team at {RETURN_POLICY['contact']} for special cases." |
|
|
) |
|
|
|
|
|
if "how long" in question or "process" in question: |
|
|
return f"Returns take {RETURN_POLICY['process_time']} to process once received." |
|
|
|
|
|
if "condition" in question or "package" in question: |
|
|
return f"To be eligible for return: {RETURN_POLICY['conditions']}" |
|
|
|
|
|
if "contact" in question or "help" in question or "support" in question: |
|
|
return ( |
|
|
"You can contact our support team:\n" |
|
|
f"- Phone: {RETURN_POLICY['contact']}\n" |
|
|
f"- Email: {RETURN_POLICY['email']}\n" |
|
|
"We're available Monday-Friday, 9AM-5PM EST." |
|
|
) |
|
|
|
|
|
return ( |
|
|
"Our standard return policy:\n" |
|
|
f"- {RETURN_POLICY['window']} return window\n" |
|
|
f"- {RETURN_POLICY['conditions']}\n" |
|
|
f"- Refunds processed in {RETURN_POLICY['process_time']}\n" |
|
|
f"Contact us at {RETURN_POLICY['contact']} for more details." |
|
|
) |
|
|
|
|
|
def chat_fn(user_input, chat_history, state): |
|
|
"""Main chat function with natural conversation handling""" |
|
|
|
|
|
return_response, state = handle_return_flow(user_input, state) |
|
|
|
|
|
if return_response is not None: |
|
|
response = return_response |
|
|
elif state["stage"] == ChatState.POLICY_QUESTION: |
|
|
response = handle_policy_question(user_input) |
|
|
state["stage"] = ChatState.NORMAL |
|
|
else: |
|
|
|
|
|
prompt = format_conversation(state["conversation_history"], user_input) |
|
|
result = chatbot( |
|
|
prompt, |
|
|
max_length=100, |
|
|
num_return_sequences=1, |
|
|
pad_token_id=50256, |
|
|
temperature=0.8, |
|
|
do_sample=True |
|
|
) |
|
|
|
|
|
|
|
|
generated = result[0]['generated_text'] |
|
|
if "Assistant:" in generated: |
|
|
response = generated.split("Assistant:")[-1].split("\n")[0].strip() |
|
|
else: |
|
|
response = generated.strip() |
|
|
|
|
|
|
|
|
state["conversation_history"].append({"role": "user", "content": user_input}) |
|
|
state["conversation_history"].append({"role": "assistant", "content": response}) |
|
|
|
|
|
|
|
|
chat_history.append((user_input, response)) |
|
|
|
|
|
return "", chat_history, state |
|
|
|
|
|
|
|
|
with gr.Blocks(title="Cinco Returns Assistant", theme=gr.themes.Soft()) as demo: |
|
|
gr.Markdown("# ποΈ Cinco Returns Assistant") |
|
|
gr.Markdown( |
|
|
"### How can I help with your Cinco return today?\n" |
|
|
f"**Return Policy**: {RETURN_POLICY['window']} return window β’ {RETURN_POLICY['conditions']}" |
|
|
) |
|
|
|
|
|
chatbot_ui = gr.Chatbot(height=400, bubble_full_width=False) |
|
|
|
|
|
with gr.Row(): |
|
|
user_input = gr.Textbox( |
|
|
placeholder="Ask about returns, policies, or Cinco products...", |
|
|
show_label=False, |
|
|
container=False, |
|
|
autofocus=True |
|
|
) |
|
|
submit_btn = gr.Button("Send", variant="primary") |
|
|
|
|
|
state = gr.State(initialize_state()) |
|
|
|
|
|
with gr.Row(): |
|
|
clear_btn = gr.Button("Start New Conversation") |
|
|
policy_btn = gr.Button("View Return Policy") |
|
|
|
|
|
def clear_history(): |
|
|
return [], initialize_state() |
|
|
|
|
|
def show_policy(): |
|
|
return [(handle_policy_question("policy"), "")] |
|
|
|
|
|
def submit_message(user_input, chat_history, state): |
|
|
if not user_input.strip(): |
|
|
return "", chat_history, state |
|
|
return chat_fn(user_input, chat_history, state) |
|
|
|
|
|
submit_btn.click( |
|
|
submit_message, |
|
|
[user_input, chatbot_ui, state], |
|
|
[user_input, chatbot_ui, state] |
|
|
) |
|
|
|
|
|
user_input.submit( |
|
|
submit_message, |
|
|
[user_input, chatbot_ui, state], |
|
|
[user_input, chatbot_ui, state] |
|
|
) |
|
|
|
|
|
clear_btn.click( |
|
|
clear_history, |
|
|
outputs=[chatbot_ui, state] |
|
|
) |
|
|
|
|
|
policy_btn.click( |
|
|
show_policy, |
|
|
inputs=[], |
|
|
outputs=[chatbot_ui] |
|
|
) |
|
|
|
|
|
if __name__ == "__main__": |
|
|
demo.queue().launch() |