Spaces:
Sleeping
Sleeping
| import boto3 | |
| import gradio as gr | |
| import requests | |
| from huggingface_hub import InferenceClient | |
| import uuid | |
| from datetime import datetime | |
| from urllib.parse import urlparse, parse_qs | |
| import os | |
| # Initialize DeepSeek via HuggingFace | |
| hf_token = os.environ.get("HF_TOKEN", "") | |
| client = InferenceClient(model="deepseek-ai/DeepSeek-V3", token=hf_token) | |
| # Initialize AWS SES | |
| ses = boto3.client("ses", | |
| aws_access_key_id=os.environ.get("access_key"), | |
| aws_secret_access_key=os.environ.get("secret_access_key"), | |
| region_name="us-east-1") | |
| def get_username_from_request(request: gr.Request): | |
| if request and request.url: | |
| parsed = urlparse(str(request.url)) | |
| params = parse_qs(parsed.query) | |
| username = params.get("userName", ["UnknownUser"])[0] | |
| email = params.get("userEmail", ["UnknownUser"])[0] | |
| return username, email | |
| return "UnknownUser", "unknown@email.com" | |
| def fetch_google_doc_text(doc_id: str) -> str: | |
| """Fetch documentation from Google Docs""" | |
| export_url = f"https://docs.google.com/document/d/{doc_id}/export?format=txt" | |
| try: | |
| response = requests.get(export_url, timeout=10) | |
| if response.status_code == 200: | |
| return response.text.strip() | |
| else: | |
| return f"Unable to fetch document. Status code: {response.status_code}" | |
| except Exception as e: | |
| return f"Error fetching document: {e}" | |
| # Google Doc ID | |
| GOOGLE_DOC_ID = "1u7wt-7Gp6ETH1OPh2o9FIgGPgm3dAWsQDM6DT3MCd6Q" | |
| doc_context = fetch_google_doc_text(GOOGLE_DOC_ID) | |
| # Store chat history for email logging | |
| chat_history = [] | |
| def send_email_if_needed(username, message, bot_response, email): | |
| """Send email notification when bot cannot answer""" | |
| trigger_phrases = [ | |
| "I don't have information", | |
| "not found in the documentation" | |
| ] | |
| should_send = ( | |
| any(phrase.lower() in bot_response.lower() for phrase in trigger_phrases) or | |
| len(bot_response) < 50 | |
| ) | |
| if should_send: | |
| sender_email = "process@documents.beiinghuman.com" | |
| recipient_emails = ["rishi@beiinghuman.com", "support@beiinghuman.com", "ubaid@beiinghuman.com"] | |
| session_id = str(uuid.uuid4()) | |
| timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") | |
| log = ( | |
| f"UNANSWERED QUESTION ALERT\n" | |
| f"{'=' * 60}\n" | |
| f"Session ID: {session_id}\n" | |
| f"Timestamp: {timestamp}\n" | |
| f"User: {username}\n" | |
| f"Email: {email}\n" | |
| f"{'=' * 60}\n\n" | |
| f"CONVERSATION HISTORY:\n" | |
| f"{'-' * 60}\n" | |
| ) | |
| for i, (user_msg, bot_msg) in enumerate(chat_history, 1): | |
| log += f"\n[{i}] {user_msg}\n" | |
| log += f"Bot: {bot_msg}\n" | |
| log += f"{'-' * 60}\n" | |
| log += ( | |
| f"\n\nACTION REQUIRED:\n" | |
| f"Please review this conversation and update the documentation.\n" | |
| ) | |
| try: | |
| ses.send_email( | |
| Source=sender_email, | |
| Destination={'ToAddresses': recipient_emails}, | |
| Message={ | |
| 'Subject': {'Data': f"Unanswered FAQ - {username}"}, | |
| 'Body': {'Text': {'Data': log}} | |
| } | |
| ) | |
| print(f"Email notification sent to {recipient_email}") | |
| except Exception as e: | |
| print(f"Error sending email: {e}") | |
| finally: | |
| chat_history.clear() | |
| def respond(message, history, system_message, max_tokens, temperature, top_p, request: gr.Request): | |
| """Main chatbot response function using DeepSeek""" | |
| try: | |
| username, email = get_username_from_request(request) | |
| # Enhanced system prompt with better instructions | |
| system_prompt = f"""You are a knowledgeable support assistant for Beiing Human, an invoice processing and approval platform. | |
| ROLE: Answer user questions accurately using ONLY the documentation provided below. | |
| RESPONSE GUIDELINES: | |
| 1. ACCURACY: Base all answers strictly on the documentation. Never invent features or make assumptions. | |
| 2. CLARITY: | |
| - Write in clear, professional language | |
| - Use numbered steps for procedures | |
| - Break complex topics into digestible sections | |
| - Use bullet points for lists of features or options | |
| 3. RECOGNITION: | |
| - Understand user intent even with different wording (e.g., "recall" = "pull back", "remove" = "delete") | |
| - Match questions to relevant documentation sections | |
| - Recognize abbreviated terms (PO = Purchase Order, DT = Delivery Ticket, HIL = Human-in-the-Loop) | |
| 4. STRUCTURE: | |
| - Start with a direct answer | |
| - Follow with step-by-step instructions if applicable | |
| - Add relevant context or tips at the end | |
| - Keep responses concise but complete | |
| 5. WHEN INFORMATION IS MISSING: | |
| If the documentation doesn't contain the answer, respond with: | |
| "I don't have information about that in the current documentation. Please contact support@beiinghuman.com for assistance with this specific question." | |
| 6. FORMATTING: | |
| - Use **bold** for important terms or actions | |
| - Use numbered lists (1. 2. 3.) for sequential steps | |
| - Use bullet points (•) for non-sequential items | |
| - Include relevant section references when helpful | |
| DOCUMENTATION: | |
| --- | |
| {doc_context} | |
| --- | |
| Remember: Your goal is to help users quickly find accurate information. Be helpful, precise, and professional.""" | |
| # Build messages | |
| messages = [ | |
| {"role": "system", "content": system_prompt} | |
| ] | |
| # Add conversation history | |
| for user_msg, assistant_msg in history: | |
| messages.append({"role": "user", "content": user_msg}) | |
| messages.append({"role": "assistant", "content": assistant_msg}) | |
| # Add current message | |
| messages.append({"role": "user", "content": message}) | |
| # Stream response from DeepSeek | |
| response = "" | |
| try: | |
| for chunk in client.chat_completion( | |
| messages=messages, | |
| max_tokens=max_tokens, | |
| temperature=temperature, | |
| top_p=top_p, | |
| stream=True | |
| ): | |
| # Safe access with multiple checks | |
| if hasattr(chunk, 'choices') and len(chunk.choices) > 0: | |
| choice = chunk.choices[0] | |
| if hasattr(choice, 'delta'): | |
| delta = choice.delta | |
| if hasattr(delta, 'content') and delta.content is not None: | |
| token = delta.content | |
| response += token | |
| yield response | |
| except IndexError as e: | |
| print(f"IndexError in streaming: {e}") | |
| if not response: | |
| response = "I apologize, but I encountered an error processing your request. Please try again." | |
| yield response | |
| except Exception as stream_error: | |
| print(f"Streaming error: {stream_error}") | |
| if not response: | |
| response = "I apologize, but I encountered an error processing your request. Please try again." | |
| yield response | |
| # Save to history and check if email notification needed | |
| if response: # Only save if we got a response | |
| chat_history.append((f"{username}: {message}", response)) | |
| send_email_if_needed(username, message, response, email) | |
| except Exception as e: | |
| print(f"Error in respond function: {str(e)}") | |
| error_message = f"Error: {str(e)}\n\nPlease try again or contact support if the issue persists." | |
| yield error_message | |
| # Gradio UI Setup | |
| demo = gr.ChatInterface( | |
| respond, | |
| additional_inputs=[ | |
| gr.Textbox(value="", label="System message", visible=False), | |
| gr.Slider(minimum=64, maximum=32000, value=2000, step=64, label="Max tokens", visible=False), | |
| gr.Slider(minimum=0.0, maximum=1.5, value=0.3, step=0.1, label="Temperature", visible=False), | |
| gr.Slider(minimum=0.0, maximum=1.0, value=0.95, step=0.05, label="Top-p", visible=False), | |
| ], | |
| title="💬 Beiing Human FAQ Chatbot", | |
| description=( | |
| "Ask questions on how to use Beiing Human App.\n\n" | |
| "I can help you with:\n" | |
| "• Document submission and processing\n" | |
| "• Invoice matching and approval workflows\n" | |
| "• User management and ERP integration\n" | |
| "• Reports and advanced features\n" | |
| "• Troubleshooting common issues\n\n" | |
| ), | |
| examples=[ | |
| ["How do I upload invoices?"], | |
| ["What does the INYA report show?"], | |
| ["How do I match an invoice to a PO?"], | |
| ["How can I invite new users?"], | |
| ["What are the different document statuses?"], | |
| ["How does the Q&A feature work?"], | |
| ["How do I use the pull back feature?"], | |
| ["What is a Sub Admin and what can they do?"], | |
| ["How do I integrate with Foundation ERP?"], | |
| ["What's the difference between Approved and Verified statuses?"], | |
| ["How do I attach a delivery ticket to an invoice?"], | |
| ], | |
| css=""" | |
| /* Override Gradio's CSS variables to force dark theme */ | |
| :root { | |
| --background-fill-primary: #141414 !important; | |
| --background-fill-secondary: #1e1e1e !important; | |
| --body-text-color: #ffffff !important; | |
| --body-text-color-subdued: #cccccc !important; | |
| --color-accent: #ffffff !important; | |
| --color-accent-soft: #333333 !important; | |
| --border-color-primary: #444444 !important; | |
| --border-color-accent: #555555 !important; | |
| --neutral-100: #141414 !important; | |
| --neutral-200: #1e1e1e !important; | |
| --neutral-300: #333333 !important; | |
| --neutral-400: #444444 !important; | |
| --neutral-500: #555555 !important; | |
| --neutral-600: #666666 !important; | |
| --neutral-700: #777777 !important; | |
| --neutral-800: #888888 !important; | |
| --neutral-900: #999999 !important; | |
| } | |
| /* Force dark mode styles */ | |
| body, html, .app, .gradio-container { | |
| background-color: #141414 !important; | |
| color: #ffffff !important; | |
| } | |
| textarea, | |
| input, | |
| .message, | |
| .chatbot { | |
| background-color: unset !important; | |
| } | |
| /* Only force text color on specific elements, not everything */ | |
| body, html, .gradio-container, | |
| .chatbot, .message, | |
| textarea, input, | |
| label, p, span, div.block, | |
| h1, h2, h3, h4, h5, h6 { | |
| color: #ffffff !important; | |
| } | |
| /* Input styling */ | |
| textarea, input { | |
| background-color: #1e1e1e !important; | |
| color: #ffffff !important; | |
| border: 1px solid #444444 !important; | |
| } | |
| """ | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() |