Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| from supervisor_agent import supervisor_agent | |
| from database import engine | |
| from sqlalchemy import text | |
| import uuid | |
| import pandas as pd | |
| import os | |
| from supabase import create_client | |
| from dotenv import load_dotenv | |
| import base64 | |
| from langchain_groq import ChatGroq | |
| load_dotenv() | |
| supabase = create_client(os.getenv("SUPABASE_URL"), os.getenv("SUPABASE_KEY")) | |
| # Initialize LLM for evidence detection | |
| GROQ_API_KEY = os.getenv("GROQ_API_KEY") | |
| if not GROQ_API_KEY: | |
| raise ValueError( | |
| "GROQ_API_KEY environment variable is not set. " | |
| "Please set it in your environment variables." | |
| ) | |
| evidence_detector_llm = ChatGroq( | |
| model="moonshotai/kimi-k2-instruct-0905", | |
| api_key=GROQ_API_KEY, | |
| temperature=0.3 | |
| ) | |
| # Handle logo loading safely | |
| try: | |
| logo_path = os.path.join(os.getcwd(), "ic_logo.png") | |
| def embed_image_base64(path): | |
| with open(path, "rb") as f: | |
| return "data:image/png;base64," + base64.b64encode(f.read()).decode() | |
| logo_b64 = embed_image_base64(logo_path) | |
| except: | |
| # Fallback if logo file doesn't exist | |
| logo_b64 = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIwIiBoZWlnaHQ9IjEyMCIgdmlld0JveD0iMCAwIDEyMCAxMjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxMjAiIGhlaWdodD0iMTIwIiByeD0iMTAiIGZpbGw9IiM2NjdlZWEiLz4KPHRleHQgeD0iNjAiIHk9IjcwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmaWxsPSJ3aGl0ZSIgZm9udC1zaXplPSIyNCIgZm9udC13ZWlnaHQ9ImJvbGQiPkU8L3RleHQ+Cjwvc3ZnPgo=" | |
| def upload_file_to_supabase_gradio(file_path: str, order_id: str): | |
| """ | |
| Uploads a file to Supabase storage from a file path (Gradio format) | |
| Returns the public URL or None if upload fails | |
| """ | |
| if not file_path: | |
| return None | |
| try: | |
| with open(file_path, 'rb') as f: | |
| file_bytes = f.read() | |
| file_ext = os.path.splitext(file_path)[1].lstrip('.') | |
| if not file_ext: | |
| file_ext = 'bin' | |
| timestamp = str(uuid.uuid4().hex) | |
| storage_path = f"{order_id}_{timestamp}.{file_ext}" | |
| res = supabase.storage.from_("complaints").upload(storage_path, file_bytes) | |
| if isinstance(res, dict) and res.get("error"): | |
| print(f"Supabase upload error: {res.get('error')}") | |
| return None | |
| public_url = supabase.storage.from_("complaints").get_public_url(storage_path) | |
| print(f"File uploaded successfully: {public_url}") | |
| return public_url | |
| except Exception as e: | |
| print(f"Error uploading file to Supabase: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| return None | |
| def check_if_evidence_requested(bot_response: str) -> bool: | |
| """ | |
| Use LLM to intelligently detect if the bot is asking for file/evidence upload. | |
| Returns True if evidence is requested, False otherwise. | |
| """ | |
| try: | |
| prompt = f"""You are an assistant that determines if a customer service response is asking the user to upload files, photos, videos, or any evidence. | |
| Analyze the following bot response and determine if it is EXPLICITLY asking the user to upload or attach files/photos/videos/evidence. | |
| Bot Response: "{bot_response}" | |
| Rules: | |
| - Only respond with "true" if the bot is CLEARLY asking for file uploads, photos, videos, or evidence | |
| - Respond with "false" if the bot is just asking questions, providing information, or not requesting any files | |
| - Keywords to look for: upload, attach, file, photo, video, evidence, picture, image, send, provide (when related to files) | |
| - Do NOT say "true" for general questions about the issue | |
| Respond with ONLY "true" or "false" - nothing else.""" | |
| response = evidence_detector_llm.invoke(prompt) | |
| result = response.content.strip().lower() | |
| print(f"🤖 LLM Evidence Detection - Bot said: '{bot_response[:100]}...'") | |
| print(f"🤖 LLM Response: '{result}'") | |
| # Return True only if LLM explicitly says "true" | |
| return result == "true" | |
| except Exception as e: | |
| print(f"❌ Error in LLM evidence detection: {e}") | |
| # Fallback to False (don't show upload button if LLM fails) | |
| return False | |
| def chat_with_agent(message, file, session_id): | |
| """Main chat function""" | |
| try: | |
| if not session_id: | |
| session_id = int(uuid.uuid4().int % 1000000) | |
| session_id = int(session_id) | |
| file_url = None | |
| if file is not None: | |
| print(f"📎 File received: {file}") | |
| print(f"📎 File type: {type(file)}") | |
| try: | |
| file_url = upload_file_to_supabase_gradio( | |
| file, | |
| order_id=str(session_id) | |
| ) | |
| if file_url: | |
| print(f"✅ File uploaded: {file_url}") | |
| else: | |
| print("⚠️ File upload failed, continuing without file") | |
| except Exception as e: | |
| # File upload should not crash the conversation | |
| print(f"❌ File upload error: {e}") | |
| return ( | |
| "The file could not be uploaded due to a processing error. " | |
| "Your request was received, but the file was not saved.", | |
| session_id, | |
| gr.update(visible=False) | |
| ) | |
| supervisor_input = f"Session ID: {session_id} | User Query: {message}" | |
| if file_url: | |
| supervisor_input += f" | FileURL: {file_url}" | |
| print(f"\n📨 Sending to supervisor: {supervisor_input}\n") | |
| try: | |
| result = supervisor_agent.invoke( | |
| {"messages": [{"role": "user", "content": supervisor_input}]}, | |
| {"configurable": {"thread_id": str(session_id)}}, | |
| ) | |
| bot_response = result["messages"][-1].content | |
| # USE LLM TO INTELLIGENTLY DETECT IF EVIDENCE/FILE UPLOAD IS REQUESTED | |
| should_show_upload = check_if_evidence_requested(bot_response) | |
| print(f"👁 Upload button visibility: {should_show_upload}") | |
| return ( | |
| bot_response, | |
| session_id, | |
| gr.update(visible=should_show_upload) | |
| ) | |
| except Exception as e: | |
| # Dedicated LLM failure response | |
| print(f"❌ LLM processing error: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| return ( | |
| "The current model is not responding reliably at the moment. " | |
| "Please try again shortly or contact support if the issue continues.", | |
| session_id, | |
| gr.update(visible=False) | |
| ) | |
| except Exception as e: | |
| # Catch-all to avoid silent crashes | |
| print(f"❌ Unexpected server error: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| return ( | |
| "An unexpected internal error occurred while processing your request.", | |
| session_id, | |
| gr.update(visible=False) | |
| ) | |
| def check_complaints(order_id): | |
| """View all complaints for a user""" | |
| try: | |
| if not order_id: | |
| return pd.DataFrame({"Message": ["Please enter a valid Order ID"]}) | |
| with engine.connect() as conn: | |
| result = conn.execute( | |
| text(""" | |
| SELECT order_id, product_name, complaint_text, complaint_file_url, created_at | |
| FROM orders | |
| WHERE order_id = :order_id AND is_complaint = 1 | |
| ORDER BY created_at DESC | |
| """), | |
| {"order_id": order_id} | |
| ) | |
| rows = result.fetchall() | |
| if not rows: | |
| return pd.DataFrame({"Message": ["No complaints found for this Order ID"]}) | |
| df = pd.DataFrame(rows, columns=["Order ID", "Product", "Complaint", "Evidence", "Date"]) | |
| return df | |
| except Exception as e: | |
| return pd.DataFrame({"Error": [str(e)]}) | |
| def upload_catalog(file, table_name): | |
| """Upload CSV/Excel to database with validation""" | |
| if not file: | |
| return "❌ Please select a file to upload" | |
| try: | |
| file_path = file if isinstance(file, str) else file.name | |
| file_name = os.path.basename(file_path) | |
| # Check file extension | |
| if not (file_path.lower().endswith('.csv') or file_path.lower().endswith(('.xlsx', '.xls'))): | |
| return f"❌ Invalid file type: {file_name}\n\n⚠️ Please select a CSV or Excel file (.csv, .xlsx, .xls)" | |
| # Read file based on extension | |
| if file_path.lower().endswith('.csv'): | |
| df = pd.read_csv(file_path) | |
| elif file_path.lower().endswith(('.xlsx', '.xls')): | |
| df = pd.read_excel(file_path) | |
| # Upload to database | |
| df.to_sql(table_name, con=engine, if_exists="replace", index=False) | |
| return f"✅ Successfully uploaded {len(df)} rows from {file_name} to table '{table_name}'" | |
| except Exception as e: | |
| return f"❌ Upload failed: {str(e)}" | |
| custom_css = """ | |
| body { | |
| position: relative; | |
| background-image: | |
| linear-gradient(rgba(59, 130, 246, 0.03) 1px, transparent 1px), | |
| linear-gradient(90deg, rgba(59, 130, 246, 0.03) 1px, transparent 1px); | |
| background-size: 60px 60px; | |
| z-index: 1; | |
| transform: unset !important; | |
| background: | |
| radial-gradient(circle at 20% 15%, rgba(59, 130, 246, 0.12), transparent 40%), | |
| radial-gradient(circle at 80% 25%, rgba(139, 92, 246, 0.1), transparent 45%), | |
| radial-gradient(circle at 50% 70%, rgba(16, 185, 129, 0.08), transparent 50%), | |
| radial-gradient(circle at 10% 85%, rgba(236, 72, 153, 0.08), transparent 45%), | |
| linear-gradient(135deg, #0a0e27 0%, #1a1f3a 25%, #0f1629 50%, #1e2139 75%, #0d1425 100%) !important; | |
| } | |
| gradio-app { | |
| background-color: transparent !important; | |
| } | |
| /* Hexagon pattern overlay */ | |
| body::after { | |
| content: ''; | |
| position: absolute; | |
| z-index: -1; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background-image: | |
| repeating-linear-gradient(45deg, transparent, transparent 25px, rgba(174, 246, 59, 0.015) 35px, rgba(93, 205, 18, 0.015) 70px), | |
| repeating-linear-gradient(-45deg, transparent, transparent 25px, rgba(139, 92, 246, 0.015) 35px, rgba(139, 92, 246, 0.015) 70px); | |
| pointer-events: none; | |
| opacity: 0.6; | |
| } | |
| .header { | |
| padding: 40px 30px; | |
| background: | |
| radial-gradient(circle at 20% 10%, rgba(255, 255, 255, 0.25), transparent 55%), | |
| radial-gradient(circle at 85% 20%, rgba(45, 135, 255, 0.22), transparent 70%), | |
| linear-gradient(135deg, #0e1a2b, #0f1032, #03011a); | |
| color: white; | |
| border-bottom-left-radius: 40px; | |
| border-bottom-right-radius: 40px; | |
| box-shadow: 0 50px 90px -20px rgba(0, 0, 0, 0.6); | |
| display: flex; | |
| align-items: center; | |
| width: 100%; | |
| } | |
| .header-logo { | |
| flex: unset; | |
| display: flex; | |
| justify-content: flex-start; | |
| width: 100px; | |
| } | |
| .header h1 { | |
| margin: 0; | |
| flex: 1; | |
| text-align: center; | |
| font-size: 2.5em; | |
| font-weight: 800; | |
| letter-spacing: 1px; | |
| background: linear-gradient(to right, #fff 20%, #dce4ff 40%, #cedcff 60%); | |
| -webkit-background-clip: text; | |
| color: transparent; | |
| } | |
| /* Tab headers with gradient */ | |
| .tabs button { | |
| background: | |
| radial-gradient(circle at 20% 10%, rgba(255, 255, 255, 0.25), transparent 55%), | |
| radial-gradient(circle at 85% 20%, rgba(45, 135, 255, 0.22), transparent 70%), | |
| linear-gradient(35deg, #0e1a2b, #0f1032, #03011a); | |
| color: white !important; | |
| font-weight: 600 !important; | |
| font-size: 1.1em !important; | |
| padding: 8px 24px !important; | |
| border-radius: 8px 8px 0 0 !important; | |
| border: none !important; | |
| transition: all 0.3s ease !important; | |
| } | |
| .tabs button:hover { | |
| background: linear-gradient(135deg, #764ba2 0%, #667eea 100%) !important; | |
| transform: translateY(-2px) !important; | |
| box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4) !important; | |
| } | |
| .tabs button[aria-selected="true"] { | |
| background: linear-gradient(135deg, #764ba2 0%, #667eea 100%) !important; | |
| box-shadow: 0 4px 15px rgba(102, 126, 234, 0.5) !important; | |
| } | |
| .tabs button .label-text { | |
| width: 100%; | |
| max-width: 200px; | |
| } | |
| /* Target all buttons with variant="primary" */ | |
| button[variant="primary"] { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; | |
| border: none !important; | |
| color: white !important; | |
| font-weight: 600 !important; | |
| border-radius: 8px !important; | |
| transition: all 0.3s ease !important; | |
| } | |
| button[variant="primary"]:hover { | |
| background: linear-gradient(135deg, #764ba2 0%, #667eea 100%) !important; | |
| transform: translateY(-2px) !important; | |
| box-shadow: 0 5px 20px rgba(102, 126, 234, 0.5) !important; | |
| } | |
| /* Backup selectors for buttons */ | |
| .primary { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; | |
| border: none !important; | |
| color: white !important; | |
| } | |
| .primary:hover { | |
| background: linear-gradient(135deg, #764ba2 0%, #667eea 100%) !important; | |
| } | |
| /* ========== NEW: CENTERED FILE UPLOAD BUTTON ========== */ | |
| /* Container for centered file button */ | |
| .centered-file-container { | |
| display: flex !important; | |
| justify-content: center !important; | |
| align-items: center !important; | |
| width: 100% !important; | |
| margin: 15px 0 !important; | |
| padding: 0 !important; | |
| } | |
| /* Style the file upload button itself */ | |
| #evidence-upload { | |
| max-width: 200px !important; | |
| min-width: 180px !important; | |
| margin: 0 auto !important; | |
| display: flex !important; | |
| justify-content: center !important; | |
| } | |
| #evidence-upload button { | |
| font-size: 0.9em !important; | |
| padding: 10px 20px !important; | |
| white-space: nowrap !important; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; | |
| color: white !important; | |
| border: none !important; | |
| border-radius: 8px !important; | |
| font-weight: 600 !important; | |
| transition: all 0.3s ease !important; | |
| cursor: pointer !important; | |
| } | |
| #evidence-upload button:hover { | |
| background: linear-gradient(135deg, #764ba2 0%, #667eea 100%) !important; | |
| transform: translateY(-2px) !important; | |
| box-shadow: 0 5px 20px rgba(102, 126, 234, 0.5) !important; | |
| } | |
| #evidence-upload .wrap { | |
| display: flex !important; | |
| justify-content: center !important; | |
| } | |
| /* ========== END CENTERED FILE UPLOAD BUTTON ========== */ | |
| .compact-action-row { | |
| display: flex; | |
| justify-content: center !important; | |
| gap: 12px !important; | |
| margin-top: 10px; | |
| } | |
| .compact-action-row button { | |
| min-width: 120px !important; | |
| height: 42px !important; | |
| font-size: 0.9rem !important; | |
| border-radius: 10px !important; | |
| font-weight: 600 !important; | |
| } | |
| /* Upload button center alignment */ | |
| .upload-section { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center !important; | |
| } | |
| .upload-btn { | |
| min-width: 165px !important; | |
| height: 45px !important; | |
| color: white; | |
| font-size: 0.9rem !important; | |
| border-radius: 12px !important; | |
| } | |
| /* Hover effect */ | |
| .compact-action-row button:hover { | |
| transform: translateY(-2px) !important; | |
| box-shadow: 0 4px 15px rgba(102, 126, 234, 0.5) !important; | |
| } | |
| /* Clear button override */ | |
| button[variant="secondary"] { | |
| background: rgba(255, 255, 255, 0.14) !important; | |
| backdrop-filter: blur(8px); | |
| border: 1px solid rgba(255, 255, 255, 0.25) !important; | |
| } | |
| .upload-section button { | |
| background: linear-gradient(135deg, #56ccf2 0%, #2f80ed 100%) !important; | |
| min-width: 165px !important; | |
| height: 45px !important; | |
| font-size: 0.9rem !important; | |
| border-radius: 12px !important; | |
| color: #ffffff !important; | |
| margin:auto; | |
| margin-bottom: 6px; | |
| } | |
| /* Align Upload button center horizontally */ | |
| .upload-section { | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| .compact-btn button { | |
| padding: 4px 0px !important; | |
| font-size: 0.85em !important; | |
| min-height: 4px !important; | |
| max-height: 14px !important; | |
| } | |
| .how-to-use .gr-markdown, | |
| .how-to-use .gr-markdown *, | |
| .how-to-use .prose, | |
| .how-to-use .prose *, | |
| .how-to-use p, | |
| .how-to-use li, | |
| .how-to-use h3{ | |
| color: white !important; | |
| } | |
| .how-to-use span{ | |
| color:black; | |
| } | |
| .how-to-use label{ | |
| color:black; | |
| } | |
| .how-to-use { | |
| background: rgba(255, 255, 255, 0.08); | |
| backdrop-filter: blur(14px) brightness(1.3); | |
| -webkit-backdrop-filter: blur(14px) brightness(1.3); | |
| border: 1px solid rgba(255, 255, 255, 0.18); | |
| border-radius: 20px; | |
| padding: 25px; | |
| margin: 20px 10px; | |
| box-shadow: 0px 8px 25px rgba(0, 0, 0, 0.3); | |
| } | |
| .how-to-use .gr-markdown h3 { | |
| margin-top: 0; | |
| font-size: 1.4rem; | |
| font-weight: 700; | |
| text-shadow: 0px 0px 8px rgba(255,255,255,0.3); | |
| } | |
| .how-to-use .gr-markdown li { | |
| padding: 4px 0; | |
| } | |
| .how-to-use li::marker { | |
| color: #ffffff !important; | |
| } | |
| .upload-btn{ | |
| padding: 0px ; | |
| background-color: #1E1C4B; | |
| } | |
| .single-btn { | |
| width: 190px; | |
| margin: auto; | |
| } | |
| .label-text label { | |
| color: white !important; | |
| } | |
| /* Dataframe styling */ | |
| .label-text .gr-dataframe, | |
| .label-text [data-testid="dataframe"], | |
| .label-text .dataframe, | |
| div[data-testid="dataframe"], | |
| .gr-dataframe { | |
| min-height: 600px !important; | |
| height: 600px !important; | |
| max-height: 600px !important; | |
| overflow-y: auto !important; | |
| border: 2px solid rgba(255, 255, 255, 0.3) !important; | |
| border-radius: 8px !important; | |
| background: rgba(0, 0, 0, 0.2) !important; | |
| width: 100% !important; | |
| display: block !important; | |
| margin: 10px 0 !important; | |
| } | |
| .label-text .gr-dataframe .wrap, | |
| .label-text [data-testid="dataframe"] .wrap, | |
| div[data-testid="dataframe"] .wrap, | |
| .gr-dataframe .wrap { | |
| min-height: 600px !important; | |
| height: 600px !important; | |
| max-height: 600px !important; | |
| overflow-y: auto !important; | |
| width: 100% !important; | |
| display: block !important; | |
| } | |
| .label-text table, | |
| .gr-dataframe table, | |
| [data-testid="dataframe"] table { | |
| width: 100% !important; | |
| background: rgba(255, 255, 255, 0.05) !important; | |
| color: white !important; | |
| font-size: 14px !important; | |
| } | |
| .label-text th, | |
| .gr-dataframe th, | |
| [data-testid="dataframe"] th { | |
| background: rgba(255, 255, 255, 0.15) !important; | |
| color: white !important; | |
| padding: 12px !important; | |
| font-weight: bold !important; | |
| } | |
| .label-text td, | |
| .gr-dataframe td, | |
| [data-testid="dataframe"] td { | |
| color: white !important; | |
| padding: 10px !important; | |
| border-bottom: 1px solid rgba(255, 255, 255, 0.1) !important; | |
| } | |
| .table-wrap.svelte-1bvc1.p0.no-wrap, | |
| .table-wrap.svelte-1bvc1p0.no-wrap, | |
| .table-wrap { | |
| display: block !important; | |
| visibility: visible !important; | |
| opacity: 1 !important; | |
| min-height: 300px !important; | |
| max-height: 400px !important; | |
| overflow-y: auto !important; | |
| overflow-x: auto !important; | |
| background: rgba(255, 255, 255, 0.05) !important; | |
| border: 1px solid rgba(255, 255, 255, 0.2) !important; | |
| border-radius: 8px !important; | |
| padding: 10px !important; | |
| } | |
| .table-wrap table { | |
| width: 100% !important; | |
| display: table !important; | |
| color: white !important; | |
| } | |
| .table-wrap::-webkit-scrollbar { | |
| width: 8px !important; | |
| height: 8px !important; | |
| } | |
| .table-wrap::-webkit-scrollbar-track { | |
| background: rgba(255, 255, 255, 0.1) !important; | |
| border-radius: 4px !important; | |
| } | |
| .table-wrap::-webkit-scrollbar-thumb { | |
| background: rgba(255, 255, 255, 0.3) !important; | |
| border-radius: 4px !important; | |
| } | |
| .table-wrap::-webkit-scrollbar-thumb:hover { | |
| background: rgba(255, 255, 255, 0.5) !important; | |
| } | |
| .wrap.svelte-1vmd51o{ | |
| color: #ffffff !important; | |
| } | |
| button.svelte-jqnyug { | |
| display: none !important; | |
| visibility: hidden !important; | |
| } | |
| * [data-testid="dataframe"], | |
| * .gr-dataframe, | |
| * .dataframe { | |
| display: block !important; | |
| visibility: visible !important; | |
| opacity: 1 !important; | |
| background: rgba(0, 0, 0, 0.6) !important; | |
| border: 3px solid #ffffff !important; | |
| min-height: 200px !important; | |
| } | |
| * [data-testid="dataframe"] *, | |
| * .gr-dataframe *, | |
| * .dataframe * { | |
| display: inherit !important; | |
| visibility: visible !important; | |
| opacity: 1 !important; | |
| color: white !important; | |
| } | |
| div#component-70.block.svelte-90oupt.hide-container { | |
| display: block !important; | |
| visibility: visible !important; | |
| opacity: 1 !important; | |
| background: rgba(255, 255, 255, 0.05) !important; | |
| border: 1px solid rgba(255, 255, 255, 0.3) !important; | |
| border-radius: 8px !important; | |
| padding: 10px !important; | |
| min-height: 300px !important; | |
| max-height: 400px !important; | |
| overflow-y: auto !important; | |
| } | |
| div#component-70 .gr-dataframe, | |
| div#component-70 [data-testid="dataframe"] { | |
| display: block !important; | |
| visibility: visible !important; | |
| background: transparent !important; | |
| } | |
| div#component-70 table { | |
| width: 100% !important; | |
| color: white !important; | |
| display: table !important; | |
| } | |
| div#component-70 td, | |
| div#component-70 th { | |
| color: white !important; | |
| padding: 8px !important; | |
| border-bottom: 1px solid rgba(255, 255, 255, 0.2) !important; | |
| } | |
| .orders-table, .complaints-table { | |
| width: 100% !important; | |
| color: white !important; | |
| border-collapse: collapse !important; | |
| } | |
| .orders-table th, .complaints-table th { | |
| background: rgba(255, 255, 255, 0.1) !important; | |
| color: white !important; | |
| padding: 10px !important; | |
| border: 1px solid rgba(255, 255, 255, 0.2) !important; | |
| } | |
| .orders-table td, .complaints-table td { | |
| color: white !important; | |
| padding: 8px !important; | |
| border: 1px solid rgba(255, 255, 255, 0.1) !important; | |
| } | |
| [data-testid="dataframe"] { | |
| display: block !important; | |
| visibility: visible !important; | |
| opacity: 1 !important; | |
| background: rgba(0, 0, 0, 0.6) !important; | |
| border: 3px solid #00ff00 !important; | |
| min-height: 250px !important; | |
| padding: 15px !important; | |
| margin: 15px 0 !important; | |
| } | |
| [data-testid="dataframe"] table { | |
| display: table !important; | |
| visibility: visible !important; | |
| width: 100% !important; | |
| color: white !important; | |
| background: transparent !important; | |
| } | |
| [data-testid="dataframe"] th { | |
| background: rgba(255, 255, 255, 0.2) !important; | |
| color: white !important; | |
| padding: 10px !important; | |
| border: 1px solid white !important; | |
| } | |
| [data-testid="dataframe"] td { | |
| color: white !important; | |
| padding: 8px !important; | |
| border: 1px solid rgba(255, 255, 255, 0.3) !important; | |
| background: rgba(0, 0, 0, 0.2) !important; | |
| } | |
| .gradio-container table tbody tr td[colspan="5"] { | |
| color: white !important; | |
| } | |
| .gradio-container .prose span.md.prose > h3 { | |
| color: white !important; | |
| } | |
| .gradio-container table thead tr th { | |
| color: white !important; | |
| } | |
| .gradio-container table tbody tr td { | |
| color: white !important; | |
| } | |
| """ | |
| # with gr.Blocks(css=custom_css) as demo: | |
| # gr.HTML( | |
| # f""" | |
| # <div class='header'> | |
| # <div class='header-logo'> | |
| # <img src="{logo_b64}" width="120"> | |
| # </div> | |
| # <h1>SparkMart</h1> | |
| # </div> | |
| # <div style='text-align: center;'> | |
| # <p style='margin-top:20px; font-size:1.30em; opacity:0.92; color: white;'> | |
| # A conversational AI solution for e-commerce that automates product inquiries, | |
| # order management, and complaint resolution while maintaining customer context, | |
| # history, and secure evidence-based complaint handling. | |
| # </p> | |
| # <p style='margin-top:5px; font-size:1.05em; opacity:0.85; color: white;'> | |
| # Powered by SparkBrains | |
| # </p> | |
| # </div> | |
| # """ | |
| # ) | |
| # initial_session = int(uuid.uuid4().int % 1000000) | |
| # session_state = gr.State(value=initial_session) | |
| # with gr.Row(): | |
| # with gr.Column(scale=1, elem_classes="how-to-use"): | |
| # gr.Markdown(""" | |
| # ### How to Use: | |
| # 1. **Chat**: Talk to AI for product recommendations, place orders, or file complaints | |
| # 2. **My Complaints**: Track complaint status and view evidence | |
| # """) | |
| with gr.Blocks(css=custom_css) as demo: | |
| gr.HTML( | |
| f""" | |
| <div class='header'> | |
| <div class='header-logo'> | |
| <img src='{logo_b64}' width="120"> | |
| </div> | |
| <h1>SparkMart</h1> | |
| </div> | |
| <div style='text-align: center;'> | |
| <p style='margin-top:20px; font-size:1.30em; opacity:0.92; color: white;'> | |
| A conversational AI solution for e-commerce that automates product inquiries, | |
| order management, and complaint resolution while maintaining customer context, | |
| history, and secure evidence-based complaint handling. | |
| </p> | |
| <p style='margin-top:5px; font-size:1.05em; opacity:0.85; color: white;'> | |
| Powered by SparkBrains | |
| </p> | |
| </div> | |
| """ | |
| ) | |
| # MUST BE ALIGNED WITH gr.HTML (same indentation) | |
| initial_session = int(uuid.uuid4().int % 1000000) | |
| session_state = gr.State(value=initial_session) | |
| with gr.Row(): | |
| with gr.Column(scale=1, elem_classes="how-to-use"): | |
| gr.Markdown(""" | |
| ### How to Use: | |
| 1. **Chat**: Talk to AI for product recommendations, place orders, or file complaints | |
| 2. **My Complaints**: Track complaint status and view evidence | |
| """) | |
| with gr.Column(scale=3): | |
| with gr.Tabs(): | |
| with gr.Tab("Chat with Support"): | |
| chatbot = gr.Chatbot(height=550) | |
| # CENTERED FILE UPLOAD BUTTON - placed right below chatbot | |
| with gr.Row(elem_classes="centered-file-container"): | |
| file_input = gr.File( | |
| file_types=["image", "video", ".jpg", ".jpeg", ".png", ".mp4", ".mov"], | |
| file_count="single", | |
| visible=False, | |
| elem_id="evidence-upload" | |
| ) | |
| msg_input = gr.Textbox( | |
| placeholder="Type your message here...", | |
| label="Your Message", | |
| lines=1 | |
| ) | |
| with gr.Row(elem_classes="compact-action-row"): | |
| send_btn = gr.Button("Send", variant="secondary", size="xs", scale=1, elem_classes="compact-btn") | |
| clear_btn = gr.Button("Clear", variant="secondary", size="xs", scale=1, elem_classes="compact-btn") | |
| def respond(message, chat_history, file_path, session_id): | |
| """Handle user message and bot response""" | |
| if not message or not message.strip(): | |
| return chat_history or [], session_id, None, "" | |
| try: | |
| bot_response, updated_session, file_visibility = chat_with_agent(message, file_path, session_id) | |
| # Ensure chat_history is a list | |
| if chat_history is None: | |
| chat_history = [] | |
| # Gradio 6.0 expects messages format with role and content | |
| chat_history.append({"role": "user", "content": message}) | |
| chat_history.append({"role": "assistant", "content": bot_response}) | |
| return chat_history, updated_session, file_visibility, "" | |
| except Exception as e: | |
| print(f"Error in respond function: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| error_msg = f"I apologize, but I encountered an error: {str(e)}" | |
| if chat_history is None: | |
| chat_history = [] | |
| chat_history.append({"role": "user", "content": message}) | |
| chat_history.append({"role": "assistant", "content": error_msg}) | |
| return chat_history, session_id, None, "" | |
| send_btn.click( | |
| respond, | |
| inputs=[msg_input, chatbot, file_input, session_state], | |
| outputs=[chatbot, session_state, file_input, msg_input] | |
| ) | |
| msg_input.submit( | |
| respond, | |
| inputs=[msg_input, chatbot, file_input, session_state], | |
| outputs=[chatbot, session_state, file_input, msg_input] | |
| ) | |
| def clear_and_update(): | |
| new_session = int(uuid.uuid4().int % 1000000) | |
| return [], new_session, gr.update(visible=False) | |
| clear_btn.click( | |
| clear_and_update, | |
| outputs=[chatbot, session_state, file_input] | |
| ) | |
| with gr.Tab("My Complaints", elem_classes="label-text"): | |
| with gr.Column(): | |
| user_id_complaints = gr.Textbox( | |
| label="Enter Your Order ID", | |
| ) | |
| check_complaints_btn = gr.Button("View My Complaints", variant="primary", elem_classes='single-btn') | |
| gr.Markdown("### Your Complaints") | |
| complaints_output = gr.HTML( | |
| value=""" | |
| <div style='background:rgba(0,0,0,0.4); border:2px solid rgba(255,255,255,0.5); border-radius:8px; padding:15px; max-height:350px; overflow-y:auto;'> | |
| <table style='width:100%; border-collapse:collapse; color:white;'> | |
| <thead> | |
| <tr style='background:rgba(255,255,255,0.1);'> | |
| <th style='border:1px solid rgba(255,255,255,0.3); padding:10px; text-align:left;'>Order ID</th> | |
| <th style='border:1px solid rgba(255,255,255,0.3); padding:10px; text-align:left;'>Product</th> | |
| <th style='border:1px solid rgba(255,255,255,0.3); padding:10px; text-align:left;'>Complaint</th> | |
| <th style='border:1px solid rgba(255,255,255,0.3); padding:10px; text-align:left;'>Evidence Files</th> | |
| <th style='border:1px solid rgba(255,255,255,0.3); padding:10px; text-align:left;'>Date</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td style='border:1px solid rgba(255,255,255,0.2); padding:8px;' colspan='5'>Enter Order ID and click 'View My Complaints' to see your complaints</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| """ | |
| ) | |
| def handle_view_complaints(user_id): | |
| print(f"Complaints button clicked with user_id: {user_id}") | |
| result = check_complaints(user_id) | |
| print(f"Complaints function returned: {result}") | |
| if hasattr(result, 'empty') and result.empty: | |
| return """ | |
| <div style='background:rgba(0,0,0,0.4); border:2px solid rgba(255,255,255,0.5); border-radius:8px; padding:15px;'> | |
| <table style='width:100%; border-collapse:collapse; color:white;'> | |
| <thead> | |
| <tr style='background:rgba(255,255,255,0.1);'> | |
| <th style='border:1px solid rgba(255,255,255,0.3); padding:10px; text-align:left;'>Order ID</th> | |
| <th style='border:1px solid rgba(255,255,255,0.3); padding:10px; text-align:left;'>Product</th> | |
| <th style='border:1px solid rgba(255,255,255,0.3); padding:10px; text-align:left;'>Complaint</th> | |
| <th style='border:1px solid rgba(255,255,255,0.3); padding:10px; text-align:left;'>Evidence Files</th> | |
| <th style='border:1px solid rgba(255,255,255,0.3); padding:10px; text-align:left;'>Date</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr><td style='border:1px solid rgba(255,255,255,0.2); padding:8px; text-align:center;' colspan='5'>No complaints found for this Order ID</td></tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| """ | |
| if hasattr(result, 'columns') and 'Message' in result.columns: | |
| message = result['Message'].iloc[0] | |
| return f""" | |
| <div style='background:rgba(0,0,0,0.4); border:2px solid rgba(255,255,255,0.5); border-radius:8px; padding:15px;'> | |
| <table style='width:100%; border-collapse:collapse; color:white;'> | |
| <thead> | |
| <tr style='background:rgba(255,255,255,0.1);'> | |
| <th style='border:1px solid rgba(255,255,255,0.3); padding:10px; text-align:left;'>Order ID</th> | |
| <th style='border:1px solid rgba(255,255,255,0.3); padding:10px; text-align:left;'>Product</th> | |
| <th style='border:1px solid rgba(255,255,255,0.3); padding:10px; text-align:left;'>Complaint</th> | |
| <th style='border:1px solid rgba(255,255,255,0.3); padding:10px; text-align:left;'>Evidence Files</th> | |
| <th style='border:1px solid rgba(255,255,255,0.3); padding:10px; text-align:left;'>Date</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr><td style='border:1px solid rgba(255,255,255,0.2); padding:8px; text-align:center;' colspan='5'>{message}</td></tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| """ | |
| if hasattr(result, 'columns') and 'Error' in result.columns: | |
| error_msg = result['Error'].iloc[0] | |
| return f""" | |
| <div style='background:rgba(139,0,0,0.4); border:2px solid rgba(255,100,100,0.5); border-radius:8px; padding:15px;'> | |
| <table style='width:100%; border-collapse:collapse; color:white;'> | |
| <thead> | |
| <tr style='background:rgba(255,255,255,0.1);'> | |
| <th style='border:1px solid rgba(255,255,255,0.3); padding:10px; text-align:left;'>Order ID</th> | |
| <th style='border:1px solid rgba(255,255,255,0.3); padding:10px; text-align:left;'>Product</th> | |
| <th style='border:1px solid rgba(255,255,255,0.3); padding:10px; text-align:left;'>Complaint</th> | |
| <th style='border:1px solid rgba(255,255,255,0.3); padding:10px; text-align:left;'>Evidence Files</th> | |
| <th style='border:1px solid rgba(255,255,255,0.3); padding:10px; text-align:left;'>Date</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr><td style='border:1px solid rgba(255,255,255,0.2); padding:8px; text-align:center; color:#ffcccc;' colspan='5'>⚠️ Error: {error_msg}</td></tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| """ | |
| if hasattr(result, 'values') and hasattr(result, 'columns') and len(result.columns) >= 5: | |
| rows_html = "" | |
| for _, row in result.iterrows(): | |
| rows_html += f""" | |
| <tr> | |
| <td style='border:1px solid rgba(255,255,255,0.2); padding:8px;'>{row.iloc[0]}</td> | |
| <td style='border:1px solid rgba(255,255,255,0.2); padding:8px;'>{row.iloc[1]}</td> | |
| <td style='border:1px solid rgba(255,255,255,0.2); padding:8px;'>{row.iloc[2]}</td> | |
| <td style='border:1px solid rgba(255,255,255,0.2); padding:8px;'>{row.iloc[3]}</td> | |
| <td style='border:1px solid rgba(255,255,255,0.2); padding:8px;'>{row.iloc[4]}</td> | |
| </tr> | |
| """ | |
| return f""" | |
| <div style='background:rgba(0,0,0,0.4); border:2px solid rgba(255,255,255,0.5); border-radius:8px; padding:15px; max-height:350px; overflow-y:auto;'> | |
| <table style='width:100%; border-collapse:collapse; color:white;'> | |
| <thead> | |
| <tr style='background:rgba(255,255,255,0.1);'> | |
| <th style='border:1px solid rgba(255,255,255,0.3); padding:10px; text-align:left;'>Order ID</th> | |
| <th style='border:1px solid rgba(255,255,255,0.3); padding:10px; text-align:left;'>Product</th> | |
| <th style='border:1px solid rgba(255,255,255,0.3); padding:10px; text-align:left;'>Complaint</th> | |
| <th style='border:1px solid rgba(255,255,255,0.3); padding:10px; text-align:left;'>Evidence Files</th> | |
| <th style='border:1px solid rgba(255,255,255,0.3); padding:10px; text-align:left;'>Date</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| {rows_html} | |
| </tbody> | |
| </table> | |
| </div> | |
| """ | |
| return f"<div style='padding:20px; color:white;'>{str(result)}</div>" | |
| check_complaints_btn.click( | |
| handle_view_complaints, | |
| inputs=[user_id_complaints], | |
| outputs=[complaints_output] | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch( | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| share=False, | |
| debug=True | |
| ) |