Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,158 +1,125 @@
|
|
| 1 |
import streamlit as st
|
| 2 |
import google.generativeai as genai
|
| 3 |
from github import Github
|
|
|
|
| 4 |
import os
|
| 5 |
from dotenv import load_dotenv
|
| 6 |
|
| 7 |
-
# --- CONFIG
|
| 8 |
-
st.set_page_config(page_title="DevResume
|
| 9 |
|
| 10 |
-
# Load Keys
|
| 11 |
api_key = os.getenv("GEMINI_API_KEY")
|
| 12 |
github_token = os.getenv("GITHUB_TOKEN")
|
| 13 |
|
| 14 |
-
# Fallback for local testing
|
| 15 |
if not api_key:
|
| 16 |
load_dotenv()
|
| 17 |
api_key = os.getenv("GEMINI_API_KEY")
|
| 18 |
github_token = os.getenv("GITHUB_TOKEN")
|
| 19 |
|
| 20 |
-
# ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
def fetch_github_data(username):
|
| 22 |
-
"""
|
| 23 |
-
SOTA Scraper: Fetches top repos, stars, languages, and README summaries.
|
| 24 |
-
"""
|
| 25 |
try:
|
| 26 |
-
# Auth guarantees higher rate limits
|
| 27 |
g = Github(github_token) if github_token else Github()
|
| 28 |
user = g.get_user(username)
|
|
|
|
| 29 |
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
"url": user.html_url,
|
| 34 |
-
"repos": []
|
| 35 |
-
}
|
| 36 |
-
|
| 37 |
-
# Sort repos by Stars (Metric of Impact)
|
| 38 |
-
repos = user.get_repos()
|
| 39 |
-
sorted_repos = sorted(repos, key=lambda x: x.stargazers_count, reverse=True)[:6]
|
| 40 |
-
|
| 41 |
-
for repo in sorted_repos:
|
| 42 |
-
# Skip forked repos to focus on original work
|
| 43 |
-
if repo.fork:
|
| 44 |
-
continue
|
| 45 |
-
|
| 46 |
-
# Smart README Fetcher
|
| 47 |
-
readme_text = "No details provided."
|
| 48 |
try:
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
except:
|
| 53 |
-
pass
|
| 54 |
-
|
| 55 |
-
data["repos"].append({
|
| 56 |
-
"name": repo.name,
|
| 57 |
-
"stars": repo.stargazers_count,
|
| 58 |
-
"lang": repo.language,
|
| 59 |
-
"desc": repo.description,
|
| 60 |
-
"readme": readme_text
|
| 61 |
-
})
|
| 62 |
|
| 63 |
-
|
|
|
|
|
|
|
| 64 |
except Exception as e:
|
| 65 |
-
return
|
| 66 |
|
| 67 |
-
# ---
|
| 68 |
-
def
|
| 69 |
genai.configure(api_key=api_key)
|
| 70 |
-
|
| 71 |
-
# Try SOTA Model first
|
| 72 |
try:
|
| 73 |
model = genai.GenerativeModel('gemini-2.5-flash')
|
| 74 |
except:
|
| 75 |
model = genai.GenerativeModel('gemini-1.5-flash')
|
| 76 |
|
| 77 |
-
# Context Construction
|
| 78 |
-
repo_context = ""
|
| 79 |
-
for r in profile['repos']:
|
| 80 |
-
repo_context += f"""
|
| 81 |
-
- PROJECT: {r['name']} ({r['stars']} Stars, {r['lang']})
|
| 82 |
-
DESCRIPTION: {r['desc']}
|
| 83 |
-
TECHNICAL CONTEXT: {r['readme']}
|
| 84 |
-
"""
|
| 85 |
-
|
| 86 |
prompt = f"""
|
| 87 |
-
You are
|
| 88 |
|
| 89 |
-
|
| 90 |
-
|
|
|
|
| 91 |
|
| 92 |
-
|
| 93 |
-
|
| 94 |
|
| 95 |
-
|
| 96 |
-
1. Select the top 3 most technically impressive projects.
|
| 97 |
-
2. Write 3 bullet points for each project.
|
| 98 |
-
3. STRICTLY use the "XYZ Pattern" (Accomplished [X] as measured by [Y], by doing [Z]).
|
| 99 |
-
4. Quantify impact where possible (e.g., "Reduced latency by...", "Processed 10k+ requests...").
|
| 100 |
-
5. Highlight stack: Python, PyTorch, FastAPI, Docker, etc.
|
| 101 |
|
| 102 |
-
|
| 103 |
-
|
| 104 |
|
| 105 |
-
|
| 106 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
|
| 108 |
-
|
| 109 |
-
* [Bullet 1 using XYZ pattern]
|
| 110 |
-
* [Bullet 2 using XYZ pattern]
|
| 111 |
-
* [Bullet 3 using XYZ pattern]
|
| 112 |
"""
|
| 113 |
|
| 114 |
try:
|
| 115 |
response = model.generate_content(prompt)
|
| 116 |
return response.text
|
| 117 |
except Exception as e:
|
| 118 |
-
return f"AI
|
| 119 |
|
| 120 |
-
# --- UI
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
st.markdown("Turns your **GitHub Code** into a **Senior Resume** instantly.")
|
| 124 |
-
st.info("Powered by **Gemini 2.5** & **PyGithub**")
|
| 125 |
-
st.caption("Built by Owadokun Tosin Tobi")
|
| 126 |
-
|
| 127 |
-
st.title("👨💻 GitHub-to-Resume Generator")
|
| 128 |
-
st.markdown("Stop writing generic bullets. Let AI analyze your actual code and write high-impact XYZ statements.")
|
| 129 |
|
| 130 |
col1, col2 = st.columns([1, 2])
|
| 131 |
|
| 132 |
with col1:
|
|
|
|
| 133 |
gh_user = st.text_input("GitHub Username", placeholder="eatosin")
|
| 134 |
role = st.text_input("Target Role", value="Senior AI Engineer")
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
st.warning("Please enter a username.")
|
| 141 |
else:
|
| 142 |
-
with st.
|
| 143 |
-
|
| 144 |
-
|
|
|
|
|
|
|
|
|
|
| 145 |
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
st.
|
| 149 |
else:
|
| 150 |
-
st.
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
st.markdown(resume)
|
| 158 |
-
st.download_button("📥 Download Markdown", resume, file_name="featured_projects.md")
|
|
|
|
| 1 |
import streamlit as st
|
| 2 |
import google.generativeai as genai
|
| 3 |
from github import Github
|
| 4 |
+
from pypdf import PdfReader
|
| 5 |
import os
|
| 6 |
from dotenv import load_dotenv
|
| 7 |
|
| 8 |
+
# --- CONFIG ---
|
| 9 |
+
st.set_page_config(page_title="DevResume Pro", layout="wide", page_icon="⚡")
|
| 10 |
|
| 11 |
+
# Load Keys
|
| 12 |
api_key = os.getenv("GEMINI_API_KEY")
|
| 13 |
github_token = os.getenv("GITHUB_TOKEN")
|
| 14 |
|
|
|
|
| 15 |
if not api_key:
|
| 16 |
load_dotenv()
|
| 17 |
api_key = os.getenv("GEMINI_API_KEY")
|
| 18 |
github_token = os.getenv("GITHUB_TOKEN")
|
| 19 |
|
| 20 |
+
# --- FUNC 1: PDF PARSER ---
|
| 21 |
+
def extract_pdf_text(file):
|
| 22 |
+
try:
|
| 23 |
+
reader = PdfReader(file)
|
| 24 |
+
text = ""
|
| 25 |
+
for page in reader.pages:
|
| 26 |
+
text += page.extract_text()
|
| 27 |
+
return text
|
| 28 |
+
except:
|
| 29 |
+
return None
|
| 30 |
+
|
| 31 |
+
# --- FUNC 2: GITHUB CRAWLER ---
|
| 32 |
def fetch_github_data(username):
|
|
|
|
|
|
|
|
|
|
| 33 |
try:
|
|
|
|
| 34 |
g = Github(github_token) if github_token else Github()
|
| 35 |
user = g.get_user(username)
|
| 36 |
+
repos = sorted(user.get_repos(), key=lambda x: x.stargazers_count, reverse=True)[:4]
|
| 37 |
|
| 38 |
+
repo_data = ""
|
| 39 |
+
for repo in repos:
|
| 40 |
+
readme = "No README"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
try:
|
| 42 |
+
if repo.get_readme().decoded_content:
|
| 43 |
+
readme = repo.get_readme().decoded_content.decode("utf-8")[:1000]
|
| 44 |
+
except: pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
|
| 46 |
+
repo_data += f"Project: {repo.name} ({repo.stargazers_count} Stars, {repo.language})\nDesc: {repo.description}\nContext: {readme}\n\n"
|
| 47 |
+
|
| 48 |
+
return repo_data
|
| 49 |
except Exception as e:
|
| 50 |
+
return f"Error fetching GitHub: {str(e)}"
|
| 51 |
|
| 52 |
+
# --- FUNC 3: THE RESUME ARCHITECT ---
|
| 53 |
+
def generate_full_resume(old_resume_text, github_data, target_role):
|
| 54 |
genai.configure(api_key=api_key)
|
|
|
|
|
|
|
| 55 |
try:
|
| 56 |
model = genai.GenerativeModel('gemini-2.5-flash')
|
| 57 |
except:
|
| 58 |
model = genai.GenerativeModel('gemini-1.5-flash')
|
| 59 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
prompt = f"""
|
| 61 |
+
You are an Elite Tech Resume Writer.
|
| 62 |
|
| 63 |
+
SOURCE MATERIAL:
|
| 64 |
+
1. OLD RESUME TEXT:
|
| 65 |
+
{old_resume_text[:4000]}
|
| 66 |
|
| 67 |
+
2. GITHUB PORTFOLIO ANALYSIS:
|
| 68 |
+
{github_data}
|
| 69 |
|
| 70 |
+
TARGET ROLE: {target_role}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
|
| 72 |
+
TASK:
|
| 73 |
+
Write a complete, SOTA Resume (Markdown Format).
|
| 74 |
|
| 75 |
+
STRUCTURE:
|
| 76 |
+
1. **Header:** Name, Title (Target Role), Links (GitHub/LinkedIn).
|
| 77 |
+
2. **Professional Summary:** Synthesize the old resume + new GitHub achievements into a killer 3-line bio.
|
| 78 |
+
3. **Technical Skills:** Grouped by category (Languages, Frameworks, Tools).
|
| 79 |
+
4. **Featured Projects:** Use the GitHub Data. Write "XYZ Style" impact bullets.
|
| 80 |
+
5. **Experience:** Polish the old resume's experience section. Make it punchy.
|
| 81 |
+
6. **Education:** Keep it simple.
|
| 82 |
|
| 83 |
+
TONE: Senior, High-Impact, Quantified.
|
|
|
|
|
|
|
|
|
|
| 84 |
"""
|
| 85 |
|
| 86 |
try:
|
| 87 |
response = model.generate_content(prompt)
|
| 88 |
return response.text
|
| 89 |
except Exception as e:
|
| 90 |
+
return f"AI Error: {e}"
|
| 91 |
|
| 92 |
+
# --- UI ---
|
| 93 |
+
st.title("⚡ DevResume Pro: The Hybrid Architect")
|
| 94 |
+
st.markdown("Combines your **Old Resume** + **Real GitHub Code** to build the ultimate portfolio.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
|
| 96 |
col1, col2 = st.columns([1, 2])
|
| 97 |
|
| 98 |
with col1:
|
| 99 |
+
st.subheader("1. Your Inputs")
|
| 100 |
gh_user = st.text_input("GitHub Username", placeholder="eatosin")
|
| 101 |
role = st.text_input("Target Role", value="Senior AI Engineer")
|
| 102 |
+
uploaded_file = st.file_uploader("Upload Old Resume (PDF)", type="pdf")
|
| 103 |
+
|
| 104 |
+
if st.button("🚀 Build Resume", type="primary"):
|
| 105 |
+
if not gh_user or not uploaded_file:
|
| 106 |
+
st.error("Please provide both GitHub User and PDF Resume.")
|
|
|
|
| 107 |
else:
|
| 108 |
+
with st.spinner("🕵️♂️ Reading PDF & Scraping GitHub..."):
|
| 109 |
+
# 1. Read PDF
|
| 110 |
+
pdf_text = extract_pdf_text(uploaded_file)
|
| 111 |
+
|
| 112 |
+
# 2. Scrape GitHub
|
| 113 |
+
gh_data = fetch_github_data(gh_user)
|
| 114 |
|
| 115 |
+
# 3. Generate
|
| 116 |
+
if pdf_text and gh_data:
|
| 117 |
+
st.session_state['resume'] = generate_full_resume(pdf_text, gh_data, role)
|
| 118 |
else:
|
| 119 |
+
st.error("Failed to read inputs.")
|
| 120 |
+
|
| 121 |
+
with col2:
|
| 122 |
+
if 'resume' in st.session_state:
|
| 123 |
+
st.subheader("📄 Your New Resume")
|
| 124 |
+
st.markdown(st.session_state['resume'])
|
| 125 |
+
st.download_button("📥 Download Markdown", st.session_state['resume'], file_name="SOTA_Resume.md")
|
|
|
|
|
|