Spaces:
Sleeping
Sleeping
| from dotenv import load_dotenv | |
| import streamlit as st | |
| from openai import OpenAI | |
| import json | |
| import os | |
| import csv | |
| from datetime import datetime | |
| from pypdf import PdfReader | |
| from chatbot_data.system_promt import system_prompt | |
| from css_input import STREAMLIT_CSS | |
| from email_utils import send_user_welcome_email, send_unknown_question_email | |
| # Load environment variables | |
| load_dotenv() | |
| # App version for deployment tracking | |
| __version__ = "1.0.5" | |
| # Page configuration with light theme enforcement | |
| st.set_page_config( | |
| page_title="Blue Bean Data - AI Assistant", | |
| page_icon="☕", | |
| layout="centered", | |
| initial_sidebar_state="collapsed" | |
| ) | |
| st.markdown(STREAMLIT_CSS, unsafe_allow_html=True) | |
| def load_knowledge_base(): | |
| """Load and combine all knowledge base files""" | |
| knowledge_base = "" | |
| # Load FAQ data | |
| try: | |
| with open('src/chatbot_data/faq_blue_bean_data.md', 'r', encoding='utf-8') as file: | |
| faq_content = file.read() | |
| knowledge_base += faq_content + "\n\n" | |
| except FileNotFoundError: | |
| st.error("FAQ file not found.") | |
| knowledge_base += "FAQ data not available.\n\n" | |
| # Load PDF CV | |
| try: | |
| reader = PdfReader('src/chatbot_data/kristof_linkedin_cv.pdf') | |
| kristof_cv = "" | |
| for page in reader.pages: | |
| kristof_cv += page.extract_text() | |
| knowledge_base += f"# Kristof's Professional Background\n{kristof_cv}\n\n" | |
| except FileNotFoundError: | |
| st.error("Kristof's CV file not found.") | |
| except Exception as e: | |
| st.error(f"Error reading PDF: {str(e)}") | |
| return knowledge_base | |
| def generate_conversation_summary(messages): | |
| """Generate an AI summary of the conversation for context""" | |
| try: | |
| # Filter out system messages and get only user/assistant exchanges | |
| conversation_messages = [msg for msg in messages if msg["role"] in ["user", "assistant"]] | |
| if len(conversation_messages) <= 2: # Just welcome message and one exchange | |
| return "Brief initial inquiry about Blue Bean Data services." | |
| # Create a prompt for summarization | |
| conversation_text = "" | |
| for msg in conversation_messages: | |
| role = "Customer" if msg["role"] == "user" else "Assistant" | |
| conversation_text += f"{role}: {msg['content']}\n\n" | |
| client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) | |
| summary_response = client.chat.completions.create( | |
| model="gpt-4o-mini", | |
| messages=[ | |
| { | |
| "role": "system", | |
| "content": ( | |
| "You are an assistant tasked with summarizing a conversation between a customer and Blue Bean Data's AI assistant. " | |
| "Write a clear, professional summary (3-4 sentences) that covers: " | |
| "1) What the customer is interested in, " | |
| "2) Their main questions or challenges, " | |
| "3) Any relevant business context they mentioned. " | |
| "The summary should be actionable for a sales or support follow-up. " | |
| "Only include information explicitly stated in the conversation, and avoid assumptions or internal notes. " | |
| "Do not include anything that a user cannot see or that was not visible to the user in the conversation." | |
| ) | |
| }, | |
| { | |
| "role": "user", | |
| "content": f"Summarize this conversation:\n\n{conversation_text}" | |
| } | |
| ], | |
| max_tokens=150 | |
| ) | |
| return summary_response.choices[0].message.content.strip() | |
| except Exception as e: | |
| print(f"Error generating conversation summary: {str(e)}") | |
| # Fallback: create a simple summary from user messages | |
| user_messages = [msg["content"] for msg in messages if msg["role"] == "user"] | |
| if user_messages: | |
| return f"Customer inquired about: {'; '.join(user_messages[-2:])}" # Last 2 user messages | |
| return "Customer expressed interest in Blue Bean Data services." | |
| def record_user_details(email, name="Name not provided", notes="not provided"): | |
| """Send welcome email to user and notification to company""" | |
| try: | |
| # Send emails using SMTP | |
| email_sent = send_user_welcome_email(email, name, notes) | |
| if email_sent: | |
| # Show success message to user - handle name display properly | |
| if name and name.strip() and name.lower() not in ['name not provided', 'not provided', '']: | |
| st.success(f"✅ Thank you {name}! I've sent a welcome email to {email}. Someone from our team will reach out to you soon.") | |
| else: | |
| st.success(f"✅ Thank you! I've sent a welcome email to {email}. Someone from our team will reach out to you soon.") | |
| return {"recorded": "ok"} | |
| else: | |
| st.error("There was an issue sending the email. Please try again or contact us directly at info@bluebeandata.com") | |
| return {"recorded": "error", "message": "Email sending failed"} | |
| except Exception as e: | |
| st.error(f"Error processing your request: {str(e)}") | |
| return {"recorded": "error", "message": str(e)} | |
| def record_unknown_question(question): | |
| """Send unknown question to company email""" | |
| try: | |
| # Send email to company | |
| email_sent = send_unknown_question_email(question) | |
| if email_sent: | |
| return {"recorded": "ok"} | |
| else: | |
| return {"recorded": "error", "message": "Email sending failed"} | |
| except Exception as e: | |
| st.error(f"Error recording unknown question: {str(e)}") | |
| return {"recorded": "error", "message": str(e)} | |
| # Tool definitions for OpenAI function calling | |
| record_user_details_json = { | |
| "name": "record_user_details", | |
| "description": "Use this tool to record that a user is interested in being in touch and provided an email address. Always provide a meaningful summary of the conversation context.", | |
| "parameters": { | |
| "type": "object", | |
| "properties": { | |
| "email": { | |
| "type": "string", | |
| "description": "The email address of this user" | |
| }, | |
| "name": { | |
| "type": "string", | |
| "description": "The user's name, if they provided it" | |
| }, | |
| "notes": { | |
| "type": "string", | |
| "description": "A brief summary of what the user is interested in, their main questions, and business context from the conversation" | |
| } | |
| }, | |
| "required": ["email"] | |
| } | |
| } | |
| record_unknown_question_json = { | |
| "name": "record_unknown_question", | |
| "description": "Use this to record questions that cannot be answered based on the knowledge base", | |
| "parameters": { | |
| "type": "object", | |
| "properties": { | |
| "question": { | |
| "type": "string", | |
| "description": "The question that couldn't be answered" | |
| } | |
| }, | |
| "required": ["question"] | |
| } | |
| } | |
| def get_chatbot_response(messages, knowledge_base): | |
| """Get response from OpenAI with function calling capabilities""" | |
| try: | |
| # Initialize OpenAI client | |
| client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) | |
| # Create session ID for tracking (could be user's session) | |
| session_id = st.session_state.get('session_id', f"session_{datetime.now().strftime('%Y%m%d_%H%M%S')}") | |
| if 'session_id' not in st.session_state: | |
| st.session_state.session_id = session_id | |
| # Add system message with knowledge base | |
| system_message = { | |
| "role": "system", | |
| "content": f"{system_prompt}\n\nKNOWLEDGE BASE:\n{knowledge_base}" | |
| } | |
| full_messages = [system_message] + messages | |
| # Call OpenAI API with function calling | |
| response = client.chat.completions.create( | |
| model="gpt-4o-mini", | |
| messages=full_messages, | |
| tools=[ | |
| {"type": "function", "function": record_user_details_json}, | |
| {"type": "function", "function": record_unknown_question_json} | |
| ], | |
| tool_choice="auto", | |
| user=session_id # Track sessions for OpenAI usage analytics | |
| ) | |
| # Handle function calls and prepare response | |
| assistant_message = response.choices[0].message | |
| response_content = assistant_message.content | |
| if assistant_message.tool_calls: | |
| for tool_call in assistant_message.tool_calls: | |
| function_name = tool_call.function.name | |
| function_args = json.loads(tool_call.function.arguments) | |
| if function_name == "record_user_details": | |
| # Generate conversation summary and enhance notes | |
| conversation_summary = generate_conversation_summary(messages) | |
| # If notes from AI are minimal, use the generated summary | |
| original_notes = function_args.get("notes", "") | |
| if not original_notes or original_notes.lower() in ["not provided", "none", ""]: | |
| function_args["notes"] = conversation_summary | |
| else: | |
| # Combine AI notes with conversation summary | |
| function_args["notes"] = f"{original_notes}\n\nConversation Summary: {conversation_summary}" | |
| record_user_details(**function_args) | |
| elif function_name == "record_unknown_question": | |
| record_unknown_question(**function_args) | |
| # If there's no content but there were function calls, provide a default response | |
| if not response_content: | |
| response_content = "Thank you! I've recorded your information and notified our team. We'll follow up soon and update our knowledge base as needed. Is there anything else I can assist you with?" | |
| # Ensure we always return a string, never None/null | |
| return response_content or "I'm here to help! What would you like to know about Blue Bean Data?" | |
| except Exception as e: | |
| st.error(f"Error getting response: {str(e)}") | |
| return "I'm sorry, I'm having trouble connecting right now. Please try again later." | |
| def main(): | |
| """Main Streamlit application""" | |
| # Header | |
| st.markdown('<div class="main-header">', unsafe_allow_html=True) | |
| st.title("Blue Bean Data AI Assistant ☕") | |
| st.markdown("*We help you grow.*") | |
| st.markdown('</div>', unsafe_allow_html=True) | |
| # Initialize session state | |
| if "messages" not in st.session_state: | |
| st.session_state.messages = [] | |
| if "knowledge_base" not in st.session_state: | |
| with st.spinner("Loading knowledge base..."): | |
| st.session_state.knowledge_base = load_knowledge_base() | |
| # Display welcome message | |
| if not st.session_state.messages: | |
| welcome_msg = ( | |
| "👋 **Welcome to Blue Bean Data!**\n\n" | |
| "I'm your AI assistant. Ask me about:\n" | |
| "- Our data consulting services & expertise \n" | |
| "- The Blue Bean Data team \n" | |
| "- Or leave your contact details for a follow-up\n\n" | |
| "How can I assist you today?" | |
| ) | |
| st.session_state.messages.append({"role": "assistant", "content": welcome_msg}) | |
| # Display chat messages | |
| for message in st.session_state.messages: | |
| with st.chat_message(message["role"]): | |
| st.markdown(message["content"]) | |
| # Chat input | |
| if prompt := st.chat_input("Type your question here..."): | |
| # Add user message to chat history | |
| st.session_state.messages.append({"role": "user", "content": prompt}) | |
| # Display user message | |
| with st.chat_message("user"): | |
| st.markdown(prompt) | |
| # Get and display assistant response | |
| with st.chat_message("assistant"): | |
| with st.spinner("💡 Thinking... (aka. AI at work)"): | |
| response = get_chatbot_response( | |
| st.session_state.messages.copy(), | |
| st.session_state.knowledge_base | |
| ) | |
| st.markdown(response) | |
| # Add assistant response to chat history | |
| st.session_state.messages.append({"role": "assistant", "content": response}) | |
| # Sidebar with information | |
| with st.sidebar: | |
| st.markdown("### About Blue Bean Data") | |
| st.markdown(""" | |
| We're a data solutions company based in the Netherlands, founded by brothers Kristof and Marton. | |
| **Our Services:** | |
| - Data Strategy & Consulting | |
| - BI & Dashboards | |
| - Data Pipeline Development | |
| - AI & Automation | |
| - Predictive Modeling & Analytics | |
| - Database Design & Optimization | |
| **Get Started:** | |
| Contact us for a free consultation! | |
| """) | |
| if st.button("Clear Chat History"): | |
| st.session_state.messages = [] | |
| st.rerun() | |
| if __name__ == "__main__": | |
| main() | |