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('
', unsafe_allow_html=True) st.title("Blue Bean Data AI Assistant ☕") st.markdown("*We help you grow.*") st.markdown('
', 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()