Spaces:
Sleeping
Sleeping
Upload 7 files
Browse files- agent.py +28 -0
- app.py +13 -0
- chat_ui.py +139 -0
- gibberish.py +12 -0
- info_extract.py +29 -0
- requirements.txt +0 -0
- settings.py +0 -0
agent.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# chatbot/agent.py
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
import google.generativeai as genai
|
| 5 |
+
from dotenv import load_dotenv
|
| 6 |
+
|
| 7 |
+
# Load environment variables from .env
|
| 8 |
+
load_dotenv()
|
| 9 |
+
|
| 10 |
+
# Configure Gemini API
|
| 11 |
+
genai.configure(api_key=os.getenv("AGENT_API_KEY"))
|
| 12 |
+
|
| 13 |
+
# Initialize Gemini model
|
| 14 |
+
model = genai.GenerativeModel("models/gemini-1.5-flash-latest")
|
| 15 |
+
|
| 16 |
+
def generate_response(conversation):
|
| 17 |
+
"""
|
| 18 |
+
Generate a response from Gemini.
|
| 19 |
+
conversation: list of (role, message) tuples.
|
| 20 |
+
Only 'user' role is supported.
|
| 21 |
+
"""
|
| 22 |
+
# Convert conversation to Gemini-compatible format
|
| 23 |
+
messages = [{"role": "user", "parts": msg} for role, msg in conversation if role == "user"]
|
| 24 |
+
|
| 25 |
+
# Call Gemini directly (no system/model roles, no start_chat)
|
| 26 |
+
response = model.generate_content(messages)
|
| 27 |
+
|
| 28 |
+
return response.text.strip()
|
app.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# app.py
|
| 2 |
+
|
| 3 |
+
import streamlit as st
|
| 4 |
+
from ui.chat_ui import render_chat_interface # <── fixed import
|
| 5 |
+
|
| 6 |
+
def main():
|
| 7 |
+
"""
|
| 8 |
+
Entry point for TalentScout Hiring Assistant.
|
| 9 |
+
"""
|
| 10 |
+
render_chat_interface()
|
| 11 |
+
|
| 12 |
+
if __name__ == "__main__":
|
| 13 |
+
main()
|
chat_ui.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ui/chat_ui.py
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
import json
|
| 5 |
+
import streamlit as st
|
| 6 |
+
from chatbot.agent import generate_response
|
| 7 |
+
from prompts.base_prompts import (
|
| 8 |
+
greeting_prompt,
|
| 9 |
+
info_collection_prompt,
|
| 10 |
+
tech_stack_prompt,
|
| 11 |
+
dynamic_generation_prompt,
|
| 12 |
+
exit_keywords,
|
| 13 |
+
exit_message,
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
# Save candidate info to JSON
|
| 17 |
+
def save_candidate_data(candidate):
|
| 18 |
+
os.makedirs("data", exist_ok=True)
|
| 19 |
+
file_path = os.path.join("data", "candidates.json")
|
| 20 |
+
|
| 21 |
+
try:
|
| 22 |
+
with open(file_path, "r") as f:
|
| 23 |
+
data = json.load(f)
|
| 24 |
+
except:
|
| 25 |
+
data = []
|
| 26 |
+
|
| 27 |
+
data.append(candidate)
|
| 28 |
+
|
| 29 |
+
with open(file_path, "w") as f:
|
| 30 |
+
json.dump(data, f, indent=2)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def render_chat_interface():
|
| 34 |
+
st.set_page_config(page_title="TalentScout Hiring Assistant", layout="centered")
|
| 35 |
+
|
| 36 |
+
st.markdown(
|
| 37 |
+
"<h2 style='text-align: center;'>🤖 TalentScout Hiring Assistant</h2>",
|
| 38 |
+
unsafe_allow_html=True,
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
# Initialize states
|
| 42 |
+
if "conversation" not in st.session_state:
|
| 43 |
+
st.session_state.conversation = []
|
| 44 |
+
if "ended" not in st.session_state:
|
| 45 |
+
st.session_state.ended = False
|
| 46 |
+
if "started" not in st.session_state:
|
| 47 |
+
st.session_state.started = False
|
| 48 |
+
if "user_info" not in st.session_state:
|
| 49 |
+
st.session_state.user_info = {}
|
| 50 |
+
if "tech_stack_collected" not in st.session_state:
|
| 51 |
+
st.session_state.tech_stack_collected = False
|
| 52 |
+
|
| 53 |
+
# Greeting
|
| 54 |
+
if not st.session_state.started:
|
| 55 |
+
st.markdown(
|
| 56 |
+
f"<div class='chat-bubble bot'><strong>Assistant:</strong><br>{greeting_prompt}</div>",
|
| 57 |
+
unsafe_allow_html=True,
|
| 58 |
+
)
|
| 59 |
+
if st.button("Begin"):
|
| 60 |
+
st.session_state.started = True
|
| 61 |
+
st.rerun()
|
| 62 |
+
return
|
| 63 |
+
|
| 64 |
+
# Collect user info
|
| 65 |
+
if not st.session_state.user_info:
|
| 66 |
+
st.markdown(info_collection_prompt, unsafe_allow_html=True)
|
| 67 |
+
|
| 68 |
+
with st.form("candidate_info"):
|
| 69 |
+
full_name = st.text_input("Full Name")
|
| 70 |
+
email = st.text_input("Email Address")
|
| 71 |
+
phone = st.text_input("Phone Number")
|
| 72 |
+
years_exp = st.text_input("Years of Experience")
|
| 73 |
+
location = st.text_input("Current Location")
|
| 74 |
+
desired_pos = st.text_input("Desired Position")
|
| 75 |
+
submitted = st.form_submit_button("Save & Continue")
|
| 76 |
+
|
| 77 |
+
if submitted:
|
| 78 |
+
st.session_state.user_info = {
|
| 79 |
+
"Full Name": full_name,
|
| 80 |
+
"Email": email,
|
| 81 |
+
"Phone": phone,
|
| 82 |
+
"Experience": years_exp,
|
| 83 |
+
"Location": location,
|
| 84 |
+
"Desired Position": desired_pos,
|
| 85 |
+
}
|
| 86 |
+
save_candidate_data(st.session_state.user_info)
|
| 87 |
+
st.rerun()
|
| 88 |
+
return
|
| 89 |
+
|
| 90 |
+
# Collect tech stack
|
| 91 |
+
if not st.session_state.tech_stack_collected:
|
| 92 |
+
st.markdown(tech_stack_prompt, unsafe_allow_html=True)
|
| 93 |
+
tech_stack = st.text_input("Tech Stack (comma separated)")
|
| 94 |
+
|
| 95 |
+
if st.button("Submit Tech Stack"):
|
| 96 |
+
st.session_state.user_info["Tech Stack"] = tech_stack
|
| 97 |
+
st.session_state.tech_stack_collected = True
|
| 98 |
+
|
| 99 |
+
bot_msg = (
|
| 100 |
+
f"Thanks, {st.session_state.user_info['Full Name']}! "
|
| 101 |
+
f"Based on your tech stack, here are some questions:"
|
| 102 |
+
)
|
| 103 |
+
st.session_state.conversation.append(("bot", bot_msg))
|
| 104 |
+
|
| 105 |
+
# ✅ Fixed: no system role, just user prompt
|
| 106 |
+
prompt = dynamic_generation_prompt(tech_stack)
|
| 107 |
+
questions = generate_response([("user", prompt)])
|
| 108 |
+
|
| 109 |
+
st.session_state.conversation.append(("bot", questions))
|
| 110 |
+
st.rerun()
|
| 111 |
+
return
|
| 112 |
+
|
| 113 |
+
# Main conversation loop
|
| 114 |
+
if not st.session_state.ended:
|
| 115 |
+
for role, msg in st.session_state.conversation:
|
| 116 |
+
role_class = "bot" if role == "bot" else "user"
|
| 117 |
+
label = "Assistant" if role == "bot" else "You"
|
| 118 |
+
st.markdown(
|
| 119 |
+
f"<div class='chat-bubble {role_class}'><strong>{label}:</strong><br>{msg}</div>",
|
| 120 |
+
unsafe_allow_html=True,
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
with st.form("chat_form", clear_on_submit=True):
|
| 124 |
+
user_input = st.text_input("Your reply", placeholder="Type here...")
|
| 125 |
+
send = st.form_submit_button("Send")
|
| 126 |
+
|
| 127 |
+
if send and user_input.strip():
|
| 128 |
+
st.session_state.conversation.append(("user", user_input.strip()))
|
| 129 |
+
|
| 130 |
+
if any(exit_word in user_input.lower() for exit_word in exit_keywords):
|
| 131 |
+
st.session_state.conversation.append(("bot", exit_message))
|
| 132 |
+
st.session_state.ended = True
|
| 133 |
+
else:
|
| 134 |
+
bot_reply = generate_response([("user", user_input.strip())])
|
| 135 |
+
st.session_state.conversation.append(("bot", bot_reply))
|
| 136 |
+
|
| 137 |
+
st.rerun()
|
| 138 |
+
else:
|
| 139 |
+
st.success("✅ Session ended. Refresh the page to start again.")
|
gibberish.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# utils/gibberish.py
|
| 2 |
+
|
| 3 |
+
def is_gibberish(text: str) -> bool:
|
| 4 |
+
"""
|
| 5 |
+
Detects whether a candidate's input text is likely to be gibberish.
|
| 6 |
+
A simple heuristic: if less than 50% of characters are alphabetic.
|
| 7 |
+
"""
|
| 8 |
+
if not text:
|
| 9 |
+
return True
|
| 10 |
+
letters = sum(c.isalpha() for c in text)
|
| 11 |
+
ratio = letters / max(len(text), 1)
|
| 12 |
+
return ratio < 0.5
|
info_extract.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# utils/info_extract.py
|
| 2 |
+
|
| 3 |
+
def extract_user_info(text: str):
|
| 4 |
+
"""
|
| 5 |
+
Extract candidate details if provided in a single text input string.
|
| 6 |
+
Example input: "Full Name: John Doe, Email Address: john@example.com"
|
| 7 |
+
Returns a dictionary with expected fields.
|
| 8 |
+
"""
|
| 9 |
+
fields = [
|
| 10 |
+
"Full Name",
|
| 11 |
+
"Email Address",
|
| 12 |
+
"Phone Number",
|
| 13 |
+
"Years of Experience",
|
| 14 |
+
"Desired Position"
|
| 15 |
+
]
|
| 16 |
+
|
| 17 |
+
info = {field: "" for field in fields}
|
| 18 |
+
parts = text.split(",")
|
| 19 |
+
|
| 20 |
+
for part in parts:
|
| 21 |
+
if ":" in part:
|
| 22 |
+
key, val = part.split(":", 1)
|
| 23 |
+
key = key.strip()
|
| 24 |
+
val = val.strip()
|
| 25 |
+
for field in fields:
|
| 26 |
+
if field.lower() == key.lower():
|
| 27 |
+
info[field] = val
|
| 28 |
+
break
|
| 29 |
+
return info
|
requirements.txt
CHANGED
|
Binary files a/requirements.txt and b/requirements.txt differ
|
|
|
settings.py
ADDED
|
File without changes
|