| |
| |
| |
| |
|
|
| import os |
| import gradio as gr |
| from langchain_openai import OpenAIEmbeddings, ChatOpenAI |
| from langchain_chroma import Chroma |
| from langchain_core.prompts import ChatPromptTemplate |
| from langchain_core.runnables import RunnablePassthrough |
| from langchain_core.output_parsers import StrOutputParser |
|
|
| |
| OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY") |
|
|
| if not OPENAI_API_KEY: |
| raise ValueError("Please set OPENAI_API_KEY in HuggingFace Secrets") |
|
|
| |
| VECTOR_STORE_PATH = "./chroma_db" |
|
|
| |
| embeddings = OpenAIEmbeddings(model="text-embedding-ada-002", openai_api_key=OPENAI_API_KEY) |
|
|
| try: |
| vector_store = Chroma( |
| persist_directory=VECTOR_STORE_PATH, |
| embedding_function=embeddings |
| ) |
| print(f"✅ Loaded {vector_store._collection.count()} vectors") |
| except Exception as e: |
| print(f"⚠️ Vector store not found: {e}") |
| vector_store = None |
|
|
| if vector_store: |
| retriever = vector_store.as_retriever(search_kwargs={"k": 4}) |
|
|
| prompt_template = ChatPromptTemplate.from_messages([ |
| ("system", """You are an AI assistant answering questions about Stephen Manickam's resume and CV. |
| Use the context below to answer accurately and helpfully. If the information is not in the context, |
| say "I don't see that in the resume" rather than making up information. |
| Be concise, professional, and friendly. |
| |
| Context: |
| {context} |
| """), |
| ("human", "{question}") |
| ]) |
|
|
| llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.2, openai_api_key=OPENAI_API_KEY) |
|
|
| def format_docs(docs): |
| return "\n\n".join(doc.page_content for doc in docs) |
|
|
| rag_chain = ( |
| {"context": retriever | format_docs, "question": RunnablePassthrough()} |
| | prompt_template |
| | llm |
| | StrOutputParser() |
| ) |
|
|
| def respond(message, history): |
| if not message.strip(): |
| return "Please enter a question." |
| try: |
| return rag_chain.invoke(message) |
| except Exception as e: |
| return f"Error: {str(e)}" |
|
|
| else: |
| def respond(message, history): |
| return "Vector store not found. Please upload your resume first." |
|
|
|
|
| |
| |
| |
|
|
| custom_css = """ |
| /* Main container - clean white background */ |
| .gradio-container { |
| background: #f8fafc !important; |
| } |
| |
| /* Header styling */ |
| .header-title { |
| text-align: center; |
| font-size: 2.5rem !important; |
| font-weight: 600 !important; |
| color: #1e293b !important; |
| margin-bottom: 0.5rem !important; |
| } |
| |
| .header-subtitle { |
| text-align: center; |
| color: #64748b !important; |
| font-size: 1.1rem !important; |
| margin-bottom: 2rem !important; |
| } |
| |
| /* Chat bubble styling */ |
| .user-message { |
| background: #3b82f6 !important; |
| color: white !important; |
| border-radius: 20px !important; |
| padding: 12px 18px !important; |
| margin: 8px 0 !important; |
| } |
| |
| .bot-message { |
| background: #ffffff !important; |
| color: #1e293b !important; |
| border: 1px solid #e2e8f0 !important; |
| border-radius: 20px !important; |
| padding: 12px 18px !important; |
| margin: 8px 0 !important; |
| } |
| |
| /* Input box styling */ |
| input[type="text"], textarea { |
| border-radius: 30px !important; |
| border: 1px solid #cbd5e1 !important; |
| padding: 12px 20px !important; |
| font-size: 1rem !important; |
| transition: all 0.2s ease !important; |
| background: white !important; |
| } |
| |
| input[type="text"]:focus, textarea:focus { |
| border-color: #3b82f6 !important; |
| box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1) !important; |
| outline: none !important; |
| } |
| |
| /* Button styling */ |
| button { |
| border-radius: 30px !important; |
| background: #3b82f6 !important; |
| color: white !important; |
| border: none !important; |
| padding: 10px 24px !important; |
| font-weight: 500 !important; |
| transition: background 0.2s ease !important; |
| } |
| |
| button:hover { |
| background: #2563eb !important; |
| } |
| |
| /* Secondary buttons (examples) */ |
| .gr-button-secondary { |
| background: white !important; |
| color: #3b82f6 !important; |
| border: 1px solid #cbd5e1 !important; |
| } |
| |
| .gr-button-secondary:hover { |
| background: #f1f5f9 !important; |
| border-color: #3b82f6 !important; |
| } |
| |
| /* Accordion styling */ |
| .accordion-header { |
| background: white !important; |
| border-radius: 12px !important; |
| border: 1px solid #e2e8f0 !important; |
| } |
| |
| /* Footer styling */ |
| .footer { |
| text-align: center; |
| color: #94a3b8 !important; |
| font-size: 0.85rem !important; |
| margin-top: 2rem !important; |
| padding: 1rem !important; |
| border-top: 1px solid #e2e8f0 !important; |
| } |
| """ |
|
|
| |
| with gr.Blocks(css=custom_css, title="Stephen Manickam - Resume Q&A Assistant") as demo: |
| |
| gr.HTML(""" |
| <div style="text-align: center; padding: 20px 0 10px 0;"> |
| <h1 style="color: #1e293b; font-size: 2.5rem; font-weight: 600; margin-bottom: 0.5rem;"> |
| 📄 Stephen Manickam |
| </h1> |
| <h2 style="color: #475569; font-size: 1.3rem; font-weight: 500; margin-bottom: 0.25rem;"> |
| Resume & CV Q&A Assistant |
| </h2> |
| <p style="color: #64748b; font-size: 1rem; margin-bottom: 1.5rem;"> |
| Ask me anything about Stephen's education, work experience, skills, projects, and achievements |
| </p> |
| </div> |
| """) |
| |
| with gr.Row(): |
| with gr.Column(scale=1, min_width=50): |
| pass |
| |
| with gr.Column(scale=8): |
| chatbot = gr.ChatInterface( |
| fn=respond, |
| examples=[ |
| "What is Stephen's educational background?", |
| "What technical skills does Stephen have?", |
| "Summarize his work experience", |
| "What projects has he worked on?", |
| "What awards has Stephen received?", |
| "Tell me about his volunteer experience", |
| "What leadership roles has he held?", |
| "What is Stephen passionate about?", |
| ], |
| cache_examples=False, |
| ) |
| |
| with gr.Column(scale=1, min_width=50): |
| pass |
| |
| |
| with gr.Row(): |
| with gr.Column(scale=1, min_width=30): |
| pass |
| with gr.Column(scale=10): |
| with gr.Accordion("ℹ️ About This Assistant", open=False): |
| gr.Markdown(""" |
| **Welcome to Stephen Manickam's Resume Assistant!** |
| |
| This AI-powered assistant answers questions about Stephen Manickam's professional background: |
| |
| - 🎓 **Education** - NTU Mechanical Engineering, Singapore Polytechnic |
| - 💼 **Work Experience** - MENCAST, Blue OC, NTU PEAK (Singtel & Thales), AAG |
| - 🚀 **Projects** - EY Urban Heat Detection, Exoplanets, Blue Horizon, Bug Banisher, Solar PV |
| - 🛠️ **Technical Skills** - Python, AI/LLMs, Data Science, Computer Vision, CAD |
| - 🏆 **Awards** - Most Promising Leader Award, SIEMENS Award, Judges Choice Award |
| - ❤️ **Volunteer Work** - IMH Mental Health, OCEP Vietnam, Women's Shelter |
| |
| **How to use:** Type your question in the chat box above. |
| """) |
| with gr.Column(scale=1, min_width=30): |
| pass |
| |
| |
| gr.HTML(""" |
| <div class="footer"> |
| <p>Powered by OpenAI GPT-3.5-Turbo • Chroma Vector Store • LangChain RAG</p> |
| </div> |
| """) |
|
|
| |
| demo.launch(theme=gr.themes.Base()) |