""" ShazaTwin - AI Assistant A Gradio-based chatbot that acts as a digital twin for Shaza Aly, answering questions about skills, experience, and background. """ import os import gradio as gr from openai import OpenAI from dotenv import load_dotenv from pypdf import PdfReader # --- 1. Load Environment Variables and API Keys --- load_dotenv(override=True) api_key = os.getenv("OPENROUTER_API_KEY") cv_link = os.getenv("CV_LINK") name = os.getenv("NAME") if not api_key: raise ValueError("OPENROUTER_API_KEY not found in .env file. Please set it.") # --- 2. Initialize OpenAI Client --- client = OpenAI( base_url="https://openrouter.ai/api/v1", api_key=api_key ) # --- 3. THEME CUSTOMIZATION --- # Using a cheerful theme with vibrant colors cheerful_theme = gr.themes.Soft( primary_hue=gr.themes.colors.purple, secondary_hue=gr.themes.colors.pink, font=gr.themes.GoogleFont("Poppins") ) # --- 4. Prepare Context for the Chatbot --- summary = ( "I am a 3 years experienced Backend Software Engineer with a strong focus on building scalable, well-architected systems " "using NestJS, GraphQL, and PostgreSQL. I have proven experience in developing high-performance APIs, " "applying Domain-Driven Design (DDD), and leading AI-powered solutions such as RAG-based chatbots " "integrated with vector databases like Qdrant and Pinecone. I am skilled in clean architecture, " "microservices, and production-grade e-commerce systems, and I'm adept at bridging business needs with " "technical implementation through both code and technical writing. I have contributed to real-time systems " "using gRPC, Redis, and message queues (e.g., RabbitMQ). I'm passionate about automation, developer " "tools (e.g., n8n, Copilot, Cursor), and mentoring through technical content. As an ALX alumna, I have a " "hands-on mindset and a continuous learning attitude. I am also the author of a technical blog on " "software engineering and AI systems: shazaali.substack.com. " "I worked on projects like: Mediconsult, E-commerce, and more. So proud of RAG chatbot I built for suplyd.app" ) linkedin_text = "My LinkedIn Profile: https://www.linkedin.com/in/shazaali/\n\n" try: reader = PdfReader("linkedin.pdf") for page in reader.pages: linkedin_text += page.extract_text() + "\n" except FileNotFoundError: print("Warning: 'linkedin.pdf' not found. The bot will rely only on the summary.") linkedin_text = "LinkedIn profile data is not available." system_prompt = ( f"You are acting as {name}. You are answering questions on my personal website, " f"particularly questions related to my career, background, skills, and experience. " f"Your responsibility is to represent me as faithfully as possible. " f"You are given a summary of my background and my LinkedIn profile to use for answering questions. " f"Be professional, friendly, and engaging, as if you are talking to a potential client or future employer. " f"If you don't know the answer based on the provided context, it's better to say so than to invent information. " f"Always stay in character as {name}. Answer in the same language as the user's question without disclosing any private information." f"\n\n## My Summary:\n{summary}\n\n## My LinkedIn Profile Text:\n{linkedin_text}\n\n## My CV:\n{cv_link}" ) # --- 5. Define the Chat Function --- def chat(message, history): formatted_history = [] for user_msg, assistant_msg in history: formatted_history.append({"role": "user", "content": user_msg}) formatted_history.append({"role": "assistant", "content": assistant_msg}) messages = [ {"role": "system", "content": system_prompt}, *formatted_history, {"role": "user", "content": message} ] try: response = client.chat.completions.create( model="openai/gpt-3.5-turbo", max_tokens=300, messages=messages, temperature=0.7 ) return response.choices[0].message.content except Exception as e: print(f"An error occurred: {e}") return "Sorry, I encountered an error while processing your request. Please try again." # --- 6. Create and Launch the Gradio App with Your Custom CSS --- # The magic happens here: fill_height=True and your refined CSS. with gr.Blocks( theme=cheerful_theme, fill_height=True, # This is crucial for making the app fill the HF Space vertically css=""" /* --- Global Layout & Background --- */ #root, .gradio-container { background: linear-gradient(135deg, #f5f7fa 0%, #e4e8ff 100%) !important; } /* --- Titles & Headers --- */ h1 { font-size: 42px !important; font-weight: 700 !important; background: linear-gradient(90deg, #9333ea, #e879f9); -webkit-background-clip: text; -webkit-text-fill-color: transparent; text-shadow: 0px 2px 4px rgba(0,0,0,0.1); text-align: center !important; padding-top: 20px !important; } /* --- Example Prompts Section --- */ .examples-panel { padding: 20px !important; background-color: rgba(255, 255, 255, 0.7) !important; border-radius: 20px !important; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05) !important; } .examples-panel .prose h2 { font-size: 28px !important; color: #7e22ce !important; text-align: center !important; font-weight: 700 !important; } .examples-panel .prose h2::before { content: "✨ "; } .examples-panel .prose h2::after { content: " ✨"; } .examples-panel .gr-button { font-size: 18px !important; background-color: #f0e6ff !important; border: 2px solid #d8b4fe !important; color: #7e22ce !important; transition: all 0.3s ease !important; border-radius: 16px !important; font-weight: 600 !important; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05) !important; } .examples-panel .gr-button:hover { background-color: #e9d5ff !important; transform: scale(1.03) !important; box-shadow: 0 8px 15px rgba(0, 0, 0, 0.1) !important; } /* --- Chat Area & Messages --- */ .message-wrap { font-size: 18px !important; } /* Base message font size */ .message { margin-bottom: 12px !important; } /* Space between messages */ .user > .message { background-color: #4b0082 !important; /* or keep your original color */ border-radius: 18px !important; color: #ffffff !important; /* bright text for dark background */ font-weight: 500 !important; } .bot > .message { background-color: #6a0dad !important; /* or keep your original color */ border-radius: 18px !important; color: #ffffff !important; /* bright text for dark background */ font-weight: 500 !important; } /* --- Input Textbox & Submit Button --- */ textarea { font-size: 20px !important; line-height: 1.5 !important; padding: 16px !important; border-radius: 16px !important; border: 2px solid #d8b4fe !important; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1) !important; } textarea:focus { border-color: #9333ea !important; box-shadow: 0 0 0 3px rgba(147, 51, 234, 0.3) !important; } .send-button { /* More specific selector for the submit button */ background: linear-gradient(90deg, #9333ea, #d946ef) !important; color: white !important; font-weight: bold !important; font-size: 20px !important; } .send-button:hover { background: linear-gradient(90deg, #7e22ce, #c026d3) !important; transform: translateY(-2px) !important; } """) as interface: gr.ChatInterface( fn=chat, # We don't need the title and description here because your CSS h1 is now the main title. chatbot=gr.Chatbot( label="Talk to me: Shaza Aly", ), examples=[ ["What are your main technical skills?"], ["Tell me about your experience with AI and RAG chatbots."], ["¿Hablas español?"] ] ) # Now, launch the interface defined within the Blocks context. if __name__ == "__main__": interface.launch()