Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -71,13 +71,21 @@ def adapt_resume(resume_data, keywords, job_description, model, max_retries=3):
|
|
| 71 |
|
| 72 |
for attempt in range(max_retries):
|
| 73 |
try:
|
| 74 |
-
prompt = f"""
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
Keywords: {json.dumps(keywords)}
|
| 80 |
-
Job: {job_description}"""
|
| 81 |
|
| 82 |
response = model.generate_content(prompt)
|
| 83 |
tailored_resume = json.loads(response.text)
|
|
@@ -123,16 +131,76 @@ def calculate_resume_match(resume_data, keywords):
|
|
| 123 |
|
| 124 |
return normalized_score, matches
|
| 125 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
# Page config
|
| 127 |
st.set_page_config(page_title="Resume Tailor", page_icon="π", layout="wide")
|
| 128 |
|
| 129 |
# Header
|
| 130 |
-
st.title("
|
| 131 |
st.markdown("### Transform your resume for your dream job")
|
| 132 |
# Sidebar with API key
|
| 133 |
with st.sidebar:
|
| 134 |
-
st.markdown("###
|
| 135 |
st.markdown("This tool works with Google's Gemini model, which you can use for free. For more information, visit [Google AI Studio](https://ai.google.dev/aistudio).")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
api_key = st.secrets["google_api_key"]
|
| 137 |
if not api_key:
|
| 138 |
st.error("API key not found in secrets. Please add your API key to the secrets.")
|
|
@@ -148,7 +216,7 @@ with col2:
|
|
| 148 |
st.session_state['original_resume'] = json.load(resume_str)
|
| 149 |
|
| 150 |
# Process button
|
| 151 |
-
if st.button("
|
| 152 |
if job_url and api_key and resume_file:
|
| 153 |
try:
|
| 154 |
with st.status("π Processing...") as status:
|
|
@@ -173,7 +241,7 @@ if st.button("π Tailor Resume", type="primary", use_container_width=True):
|
|
| 173 |
|
| 174 |
# Results section
|
| 175 |
st.markdown("---")
|
| 176 |
-
st.markdown("
|
| 177 |
|
| 178 |
# Calculate and display scores
|
| 179 |
original_score, original_matches = calculate_resume_match(
|
|
@@ -185,24 +253,13 @@ if st.button("π Tailor Resume", type="primary", use_container_width=True):
|
|
| 185 |
st.session_state['keywords']
|
| 186 |
)
|
| 187 |
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
# Keyword matches
|
| 196 |
-
with st.expander("π― View Keyword Matches"):
|
| 197 |
-
for priority in ['high', 'medium', 'low']:
|
| 198 |
-
st.subheader(f"{priority.title()} Priority")
|
| 199 |
-
orig_matches = set(original_matches[priority])
|
| 200 |
-
new_matches = set(tailored_matches[priority])
|
| 201 |
-
added = new_matches - orig_matches
|
| 202 |
-
|
| 203 |
-
st.write("β Original:", ", ".join(orig_matches) if orig_matches else "None")
|
| 204 |
-
if added:
|
| 205 |
-
st.write("β Added:", f"<span style='background-color: #d4edda;'>{', '.join(added)}</span>", unsafe_allow_html=True)
|
| 206 |
|
| 207 |
# Download section
|
| 208 |
st.markdown("### π₯ Download")
|
|
@@ -214,7 +271,7 @@ if st.button("π Tailor Resume", type="primary", use_container_width=True):
|
|
| 214 |
use_container_width=True
|
| 215 |
):
|
| 216 |
webbrowser.open_new_tab("https://rxresu.me/")
|
| 217 |
-
st.info("π
|
| 218 |
|
| 219 |
except Exception as e:
|
| 220 |
st.error(f"An error occurred: {str(e)}")
|
|
|
|
| 71 |
|
| 72 |
for attempt in range(max_retries):
|
| 73 |
try:
|
| 74 |
+
prompt = f"""As a CV expert, optimize the provided resume JSON for the target role.
|
| 75 |
+
Enhance sections (summary, experience, volunteer, interests, awards, projects, skills) by incorporating provided keywords:
|
| 76 |
+
- High priority (3x weight)
|
| 77 |
+
- Medium priority (2x weight)
|
| 78 |
+
- Low priority (1x weight)
|
| 79 |
+
|
| 80 |
+
Rules:
|
| 81 |
+
- Keep all original facts and information
|
| 82 |
+
- Maintain exact JSON structure and all existing keys
|
| 83 |
+
- Use natural language from the keywords list
|
| 84 |
+
- Do not add fictional content
|
| 85 |
+
|
| 86 |
+
Base Schema: {json.dumps(original_schema)}
|
| 87 |
Keywords: {json.dumps(keywords)}
|
| 88 |
+
Job Description: {job_description}"""
|
| 89 |
|
| 90 |
response = model.generate_content(prompt)
|
| 91 |
tailored_resume = json.loads(response.text)
|
|
|
|
| 131 |
|
| 132 |
return normalized_score, matches
|
| 133 |
|
| 134 |
+
def create_match_visualization(original_score, tailored_score, keywords, original_matches, tailored_matches):
|
| 135 |
+
"""Create visualization showing resume match comparison"""
|
| 136 |
+
|
| 137 |
+
# Overall score comparison
|
| 138 |
+
st.markdown("### π Resume Match Analysis")
|
| 139 |
+
|
| 140 |
+
# Score metrics side by side
|
| 141 |
+
col1, col2 = st.columns(2)
|
| 142 |
+
with col1:
|
| 143 |
+
st.metric(
|
| 144 |
+
"Original Resume Match Score",
|
| 145 |
+
f"{original_score:.1f}%"
|
| 146 |
+
)
|
| 147 |
+
with col2:
|
| 148 |
+
st.metric(
|
| 149 |
+
"Tailored Resume Match Score",
|
| 150 |
+
f"{tailored_score:.1f}%",
|
| 151 |
+
delta=f"+{tailored_score - original_score:.1f}%"
|
| 152 |
+
)
|
| 153 |
+
|
| 154 |
+
# Keyword analysis by priority
|
| 155 |
+
st.markdown("### π― Keyword Matches")
|
| 156 |
+
tabs = st.tabs(["High Priority π΄", "Medium Priority π‘", "Low Priority π’"])
|
| 157 |
+
|
| 158 |
+
for idx, priority in enumerate(['high', 'medium', 'low']):
|
| 159 |
+
with tabs[idx]:
|
| 160 |
+
col1, col2 = st.columns(2)
|
| 161 |
+
|
| 162 |
+
orig_matches = set(original_matches[priority])
|
| 163 |
+
new_matches = set(tailored_matches[priority])
|
| 164 |
+
added = new_matches - orig_matches
|
| 165 |
+
|
| 166 |
+
# Original matches
|
| 167 |
+
with col1:
|
| 168 |
+
st.markdown("#### Original Matching Keywords")
|
| 169 |
+
if orig_matches:
|
| 170 |
+
for keyword in orig_matches:
|
| 171 |
+
st.markdown(f"β `{keyword}`")
|
| 172 |
+
else:
|
| 173 |
+
st.info("No matches found")
|
| 174 |
+
|
| 175 |
+
# New matches
|
| 176 |
+
with col2:
|
| 177 |
+
st.markdown("#### Added the following Keywords")
|
| 178 |
+
if added:
|
| 179 |
+
for keyword in added:
|
| 180 |
+
st.markdown(f"β `{keyword}`")
|
| 181 |
+
else:
|
| 182 |
+
st.info("No new matches")
|
| 183 |
+
|
| 184 |
# Page config
|
| 185 |
st.set_page_config(page_title="Resume Tailor", page_icon="π", layout="wide")
|
| 186 |
|
| 187 |
# Header
|
| 188 |
+
st.title("π Curriculum Customization Tool")
|
| 189 |
st.markdown("### Transform your resume for your dream job")
|
| 190 |
# Sidebar with API key
|
| 191 |
with st.sidebar:
|
| 192 |
+
st.markdown("### About")
|
| 193 |
st.markdown("This tool works with Google's Gemini model, which you can use for free. For more information, visit [Google AI Studio](https://ai.google.dev/aistudio).")
|
| 194 |
+
|
| 195 |
+
# Disclaimer
|
| 196 |
+
st.warning("""
|
| 197 |
+
β οΈ **Disclaimer**
|
| 198 |
+
|
| 199 |
+
This tool is for educational purposes only.
|
| 200 |
+
AI-based tools can produce unexpected or inaccurate results.
|
| 201 |
+
By using this tool, you accept full responsibility for verifying and using its output.
|
| 202 |
+
""")
|
| 203 |
+
|
| 204 |
api_key = st.secrets["google_api_key"]
|
| 205 |
if not api_key:
|
| 206 |
st.error("API key not found in secrets. Please add your API key to the secrets.")
|
|
|
|
| 216 |
st.session_state['original_resume'] = json.load(resume_str)
|
| 217 |
|
| 218 |
# Process button
|
| 219 |
+
if st.button("π― Tailor Resume", type="primary", use_container_width=True):
|
| 220 |
if job_url and api_key and resume_file:
|
| 221 |
try:
|
| 222 |
with st.status("π Processing...") as status:
|
|
|
|
| 241 |
|
| 242 |
# Results section
|
| 243 |
st.markdown("---")
|
| 244 |
+
st.markdown("## π Results")
|
| 245 |
|
| 246 |
# Calculate and display scores
|
| 247 |
original_score, original_matches = calculate_resume_match(
|
|
|
|
| 253 |
st.session_state['keywords']
|
| 254 |
)
|
| 255 |
|
| 256 |
+
create_match_visualization(
|
| 257 |
+
original_score,
|
| 258 |
+
tailored_score,
|
| 259 |
+
st.session_state['keywords'],
|
| 260 |
+
original_matches,
|
| 261 |
+
tailored_matches
|
| 262 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 263 |
|
| 264 |
# Download section
|
| 265 |
st.markdown("### π₯ Download")
|
|
|
|
| 271 |
use_container_width=True
|
| 272 |
):
|
| 273 |
webbrowser.open_new_tab("https://rxresu.me/")
|
| 274 |
+
st.info("π Resume Builder opened in new tab")
|
| 275 |
|
| 276 |
except Exception as e:
|
| 277 |
st.error(f"An error occurred: {str(e)}")
|