Spaces:
Sleeping
Sleeping
import requests | |
import pandas as pd | |
import gradio as gr | |
import time | |
import re | |
import os | |
from dotenv import load_dotenv | |
load_dotenv() | |
# API_BASE_URL = os.getenv("API_BASE_URL") | |
API_BASE_URL = "https://sale-agent-m179.onrender.com" | |
def clear_chat(): | |
"""Clear all conversation history and reset all states via API""" | |
try: | |
requests.post(f"{API_BASE_URL}/clear_memory", | |
json={"reset_cache": True, "reset_model": False}) | |
except Exception as e: | |
print(f"Warning: clear_memory failed: {e}") | |
# Return empty values for all components that need to be reset | |
# chatbot, state, raw_documents, chatbot_state, textbox | |
return [], {}, {}, [], "" | |
def respond_to_chat(message, history, state, selected_model, debug_mode): | |
"""Handle chat responses with image support""" | |
start = time.perf_counter() | |
# Handle multimodal input - message is a dict with 'text' and 'files' keys | |
if isinstance(message, dict): | |
text_message = message.get('text', '') | |
files = message.get('files', []) | |
# Process any uploaded images | |
image_path = None | |
if files: | |
image_path = files[0] | |
print(image_path) | |
user_query = text_message | |
else: | |
user_query = message | |
image_path = None | |
# call API | |
try: | |
if image_path: | |
with open(image_path, 'rb') as f: | |
files = {"image": f} | |
data = {"message": user_query, "debug": debug_mode} | |
resp = requests.post( | |
f"{API_BASE_URL}/chat_with_image", files=files, data=data, timeout=120) | |
else: | |
payload = {"message": user_query, "debug": debug_mode} | |
resp = requests.post(f"{API_BASE_URL}/chat", | |
json=payload, timeout=120) | |
if resp.status_code == 200: | |
j = resp.json() | |
response = j.get("response", "") | |
specs_advantages = j.get("specs_advantages") | |
solution_packages = j.get("solution_packages") | |
raw_documents = j.get("raw_documents") | |
outputs = j.get("outputs") | |
else: | |
response = f"Error: API status {resp.status_code}" | |
specs_advantages, solution_packages, raw_documents, outputs = None, None, None, None | |
except Exception as e: | |
response = f"Error calling API: {e}" | |
specs_advantages, solution_packages, raw_documents, outputs = None, None, None, None | |
end = time.perf_counter() | |
if state is None: | |
state = {} | |
# specs_advantages is now always a dict for both products and solutions | |
if isinstance(specs_advantages, dict): | |
state["specs_advantages"] = specs_advantages | |
if solution_packages: | |
state["solution_packages"] = solution_packages | |
state["raw_documents"] = raw_documents | |
state["outputs"] = outputs | |
return response | |
def show_specs(state, history=None): | |
specs_map = state.get("specs_advantages", {}) | |
columns = ["Thông số"] | |
raw_data = [] | |
if history is None: | |
history = [] | |
if not specs_map: | |
df = pd.DataFrame({}, columns=columns) | |
markdown_table = df.to_markdown(index=False) | |
# Fix: Use proper ChatInterface message format | |
history.append( | |
{"role": "assistant", "content": f"📄 Thông số kỹ thuật\n{markdown_table}"}) | |
return history | |
# print(specs_map) | |
for prod_id, data in specs_map.items(): | |
spec = data.get("specification", None) | |
model = data.get("model", "") | |
url = data.get("url", "") | |
# Handle both products and solution packages | |
if url: | |
full_name = f"**[{data['name']} {model}]({url})**" | |
else: | |
full_name = f"**[{data['name']} {model}]**" | |
if full_name not in columns: | |
columns.append(full_name) | |
if spec: | |
# Check if this is a solution package (contains markdown table) | |
if "### 📦" in spec: | |
# For solution packages, parse the markdown table properly | |
lines = spec.split('\n') | |
in_table = False | |
headers = [] | |
for line in lines: | |
line = line.strip() | |
if '|' in line and '---' not in line and line.startswith('|') and line.endswith('|'): | |
cells = [cell.strip() | |
for cell in line.split('|')[1:-1]] | |
if not in_table: | |
# This is the header row | |
headers = cells | |
in_table = True | |
continue | |
# This is a data row | |
if len(cells) >= len(headers): | |
for i, header in enumerate(headers): | |
if i < len(cells): | |
param_name = header | |
param_value = cells[i] | |
existing_row = None | |
for row in raw_data: | |
if row["Thông số"] == param_name: | |
existing_row = row | |
break | |
if existing_row: | |
existing_row[full_name] = param_value | |
else: | |
new_row = {"Thông số": param_name} | |
for col in columns[1:]: | |
new_row[col] = "" | |
new_row[full_name] = param_value | |
raw_data.append(new_row) | |
elif in_table and (not line or not line.startswith('|')): | |
in_table = False | |
else: | |
# For products, parse specification items | |
items = re.split(r';|\n', spec) | |
for item in items: | |
if ":" in item: | |
key, value = item.split(':', 1) | |
spec_key = key.strip().capitalize() | |
if spec_key == "Vậtl iệu": | |
spec_key = "Vật liệu" | |
existing_row = None | |
for row in raw_data: | |
if row["Thông số"] == spec_key: | |
existing_row = row | |
break | |
if existing_row: | |
existing_row[full_name] = value.strip( | |
) if value else "" | |
else: | |
new_row = {"Thông số": spec_key} | |
for col in columns[1:]: | |
new_row[col] = "" | |
new_row[full_name] = value.strip() if value else "" | |
raw_data.append(new_row) | |
if raw_data: | |
df = pd.DataFrame(raw_data, columns=columns) | |
df = df.fillna("").replace("None", "").replace("nan", "") | |
else: | |
df = pd.DataFrame( | |
[["Không có thông số kỹ thuật", "", ""]], columns=columns) | |
markdown_table = df.to_markdown(index=False) | |
# Fix: Use proper ChatInterface message format | |
history.append( | |
{"role": "assistant", "content": f"📄 Thông số kỹ thuật\n{markdown_table}"}) | |
return history | |
def show_advantages(state, history=None): | |
specs_map = state.get("specs_advantages", {}) | |
columns = ["Tên", "Ưu điểm nổi trội"] | |
table_data = [] | |
if history is None: | |
history = [] | |
if not specs_map: | |
df = pd.DataFrame([["Không có ưu điểm", ""]], columns=columns) | |
markdown_table = df.to_markdown(index=False) | |
# Fix: Use proper ChatInterface message format | |
history.append( | |
{"role": "assistant", "content": f"💡 Ưu điểm nổi trội\n{markdown_table}"}) | |
return history | |
for prod_id, data in specs_map.items(): | |
adv = data.get("advantages", "Không có ưu điểm") | |
model = data.get("model", "") | |
url = data.get("url", "") | |
# Handle both products and solution packages | |
if url: | |
full_name = f"**[{data['name']} {model}]({url})**" | |
else: | |
full_name = f"**[{data['name']} {model}]**" | |
if adv not in ["Không có ưu điểm", "", None]: | |
table_data.append([full_name, adv]) | |
if table_data: | |
df = pd.DataFrame(table_data, columns=columns) | |
else: | |
df = pd.DataFrame([["Không có ưu điểm", ""]], columns=columns) | |
markdown_table = df.to_markdown(index=False) | |
# Fix: Use proper ChatInterface message format | |
history.append( | |
{"role": "assistant", "content": f"💡 Ưu điểm nổi trội\n{markdown_table}"}) | |
return history | |
def show_solution_packages(state, history=None): | |
"""Show solution packages in a structured format""" | |
solution_packages = state.get("solution_packages", []) | |
if history is None: | |
history = [] | |
if not solution_packages: | |
# Fix: Use proper ChatInterface message format | |
history.append( | |
{"role": "assistant", "content": "📦 Không có gói sản phẩm nào"}) | |
return history | |
# Build markdown content for each package | |
markdown_content = "## 📦 Gói sản phẩm\n\n" | |
# solution_packages is a list | |
for package in solution_packages: | |
markdown_content += f"{package}\n\n" | |
# Fix: Use proper ChatInterface message format | |
history.append({"role": "assistant", "content": markdown_content}) | |
return history | |
css = """ | |
.icon-button-wrapper.top-panel.hide-top-corner, | |
.icon-button-wrapper.top-panel button[aria-label="Clear"], | |
.icon-button-wrapper.top-panel button[title="Clear"], | |
.chat-interface .clear-btn, | |
.chat-interface button[aria-label="Clear"], | |
.chat-interface button[title="Clear"], | |
.chat-interface .svelte-1aq8tno button:last-child, | |
.chat-interface .action-buttons button:last-child { | |
display: none !important; | |
visibility: hidden !important; | |
} | |
.svelte-vuh1yp .prose.svelte-lag733 .md.svelte-7ddecg.prose h1, | |
.svelte-vuh1yp h1, | |
.prose.svelte-lag733 h1 { | |
visibility: hidden !important; | |
} | |
.top-right-clear { | |
position: absolute; | |
top: 10px; | |
right: 10px; | |
z-index: 1000; | |
} | |
.custom-button-row { | |
margin-top: -10px; | |
gap: 8px; | |
justify-content: flex-start; | |
} | |
.custom-clear-btn { | |
background: linear-gradient(135deg, #ff6b6b, #ee5a24) !important; | |
color: white !important; | |
border: none !important; | |
border-radius: 6px !important; | |
font-weight: 500 !important; | |
transition: all 0.3s ease !important; | |
min-width: 80px !important; | |
} | |
.custom-clear-btn:hover { | |
background: linear-gradient(135deg, #ee5a24, #ff6b6b) !important; | |
transform: translateY(-1px) !important; | |
box-shadow: 0 4px 12px rgba(238, 90, 36, 0.3) !important; | |
} | |
.spec-adv-btn { | |
background: linear-gradient(135deg, #4834d4, #686de0) !important; | |
color: white !important; | |
border: none !important; | |
border-radius: 6px !important; | |
font-weight: 500 !important; | |
transition: all 0.3s ease !important; | |
} | |
.spec-adv-btn:hover { | |
background: linear-gradient(135deg, #686de0, #4834d4) !important; | |
transform: translateY(-1px) !important; | |
box-shadow: 0 4px 12px rgba(72, 52, 212, 0.3) !important; | |
} | |
.gradio-container { | |
position: relative; | |
} | |
.model-dropdown, .debug-dropdown { | |
background: linear-gradient(135deg, #667eea, #764ba2) !important; | |
color: white !important; | |
border: none !important; | |
border-radius: 6px !important; | |
font-weight: 500 !important; | |
font-size: 13px !important; | |
transition: all 0.3s ease !important; | |
min-height: 32px !important; | |
box-shadow: 0 2px 4px rgba(0,0,0,0.15) !important; | |
} | |
.model-dropdown select, .debug-dropdown select { | |
color: white !important; | |
background: transparent !important; | |
cursor: pointer !important; | |
} | |
.model-dropdown select:focus, .debug-dropdown select:focus { | |
outline: none; | |
} | |
.model-dropdown select:not(:focus), .debug-dropdown select:not(:focus) { | |
pointer-events: auto; | |
} | |
""" | |
with gr.Blocks(fill_height=True, css=css) as demo: | |
state = gr.State(value={}) | |
raw_documents = gr.State(value={}) | |
with gr.Row(elem_classes="top-right-clear"): | |
with gr.Column(scale=20): | |
gr.Markdown("### 🛍️ RangDong Sales Agent") | |
with gr.Column(scale=1, min_width=180): | |
model_dropdown = gr.Dropdown( | |
choices=["Gemini 2.0 Flash", | |
"Gemini 2.5 Flash Lite", | |
"Gemini 2.0 Flash Lite"], | |
value="Gemini 2.0 Flash", | |
label="Model", | |
show_label=False, | |
elem_classes="model-dropdown", | |
interactive=True, | |
container=False | |
) | |
with gr.Column(scale=1, min_width=120): | |
debug_dropdown = gr.Dropdown( | |
choices=["Normal", "Debug"], | |
value="Normal", | |
label="Mode", | |
show_label=False, | |
elem_classes="debug-dropdown", | |
interactive=True, | |
container=False | |
) | |
with gr.Column(scale=1, min_width=30): | |
clear_btn = gr.Button( | |
"✨ Xoá", variant="secondary", size="sm", elem_classes="custom-clear-btn") | |
chatbot = gr.Chatbot( | |
avatar_images=[ | |
"https://cdn-icons-png.flaticon.com/512/219/219983.png", | |
"assets/agent.png" | |
], | |
type="messages", | |
height=800 | |
) | |
chat = gr.ChatInterface( | |
fn=respond_to_chat, | |
multimodal=True, | |
type="messages", | |
examples=[ | |
["Tìm sản phẩm bình giữ nhiệt dung tích dưới 2 lít."], | |
["Tìm sản phẩm ổ cắm thông minh"], | |
["Tư vấn cho tôi đèn học chống cận cho con gái của tôi học lớp 6"] | |
], | |
title="_", | |
additional_inputs=[state, model_dropdown, debug_dropdown], | |
chatbot=chatbot | |
) | |
with gr.Row(elem_classes="custom-button-row") as button_row: | |
btn_spec = gr.Button("📄 Thông số kỹ thuật", | |
variant="secondary", elem_classes="spec-adv-btn") | |
btn_adv = gr.Button("💡 Ưu điểm nổi trội", | |
variant="secondary", elem_classes="spec-adv-btn") | |
btn_pckg = gr.Button( | |
"📦 Gói sản phẩm", variant="secondary", elem_classes="spec-adv-btn") | |
# Event handlers | |
btn_spec.click(fn=show_specs, inputs=[state, chatbot], outputs=[chatbot]) | |
btn_adv.click(fn=show_advantages, | |
inputs=[state, chatbot], | |
outputs=[chatbot]) | |
# Gói sản phẩm button shows solution packages in structured format | |
btn_pckg.click(fn=show_solution_packages, | |
inputs=[state, chatbot], | |
outputs=[chatbot]) | |
# Model change handler -> call API | |
def _on_model_change(model_name): | |
try: | |
requests.post(f"{API_BASE_URL}/set_model", | |
json={"model_name": model_name}) | |
except Exception as e: | |
print(f"Warning: set_model failed: {e}") | |
model_dropdown.change(fn=_on_model_change, | |
inputs=[model_dropdown], | |
outputs=[] | |
) # type: ignore[attr-defined] | |
# Clear button - clear all ChatInterface internal components | |
clear_btn.click( | |
fn=clear_chat, | |
inputs=[], | |
outputs=[chatbot, state, raw_documents, | |
chat.chatbot_state, chat.textbox] | |
) | |
if __name__ == "__main__": | |
demo.launch() | |