saitejatirunagari Claude Sonnet 4.6 commited on
Commit
2726556
Β·
1 Parent(s): 106786d

feat: LaTeX paste text area on resume step (primary input)

Browse files

Replace file upload for LaTeX with a text area where user can paste
their .tex source directly. Tectonic compiles on button click.
PDF upload kept below as secondary option. Saved .tex restored into
the text area on next visit so the user can edit without re-pasting.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Files changed (1) hide show
  1. ui.py +94 -40
ui.py CHANGED
@@ -1296,58 +1296,113 @@ if show_config:
1296
  <div class="step-number {"done" if has_resume else ""}">{"βœ“" if has_resume else "1"}</div>
1297
  <p class="step-title-text">Upload your resume</p>
1298
  </div>
1299
- <p class="step-helper">Upload your LaTeX source for the best ATS results, or a PDF if you don't have one.</p>
1300
- </div>
1301
 
1302
- <div style="display:flex;gap:12px;margin:12px 0 8px">
1303
- <div style="flex:1;padding:14px 16px;background:#EEF2FF;border:2px solid #6366F1;border-radius:12px">
1304
- <div style="font-size:1rem;font-weight:700;color:#4338CA">πŸ“„ LaTeX (.tex)</div>
1305
- <div style="font-size:0.78rem;color:#6366F1;margin-top:2px">Recommended β€” highest ATS accuracy</div>
1306
- </div>
1307
- <div style="flex:1;padding:14px 16px;background:#F8FAFC;border:1px solid #E2E8F0;border-radius:12px">
1308
- <div style="font-size:1rem;font-weight:500;color:#475569">πŸ“‹ PDF</div>
1309
- <div style="font-size:0.78rem;color:#94A3B8;margin-top:2px">Also supported</div>
1310
  </div>
1311
  </div>""")
1312
 
1313
- resume_file = st.file_uploader(
1314
- "Drop your .tex or PDF resume here",
1315
- type=["tex", "pdf"], key="resume_upload",
1316
- label_visibility="visible",
1317
- help="LaTeX (.tex) gives the best ATS scores. PDF is also accepted. Max 10MB.",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1318
  )
1319
- # Save on EVERY new/changed upload (detected by name+size) so a
1320
- # re-upload actually REPLACES the old resume. The previous guard
1321
- # (`not has_resume`) ignored new uploads when an old resume.pdf
1322
- # existed β†’ profile showed the stale resume.
1323
- if resume_file is not None:
1324
- _sig = f"{resume_file.name}:{getattr(resume_file, 'size', 0)}"
1325
- if st.session_state.get("_uploaded_sig") != _sig:
1326
- os.makedirs("data/resume", exist_ok=True)
1327
- if resume_file.name.lower().endswith(".tex"):
1328
- import tempfile as _tmpfile, subprocess as _sp, shutil as _sh
 
 
1329
  with _tmpfile.TemporaryDirectory() as _td:
1330
  _tex = os.path.join(_td, "resume.tex")
1331
- with open(_tex, "wb") as _f:
1332
- _f.write(resume_file.getvalue())
1333
  _r = _sp.run(
1334
  ["tectonic", "--outdir", _td, _tex],
1335
  capture_output=True, text=True, timeout=60,
1336
  )
1337
  _pdf = os.path.join(_td, "resume.pdf")
1338
  if _r.returncode == 0 and os.path.exists(_pdf):
 
1339
  _sh.copy(_pdf, "data/resume/resume.pdf")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1340
  else:
1341
  st.error(
1342
- "LaTeX compilation failed. Check your .tex file.\n\n"
1343
- + (_r.stderr[-600:] or _r.stdout[-600:])
1344
  )
1345
- st.stop()
1346
- else:
1347
- with open("data/resume/resume.pdf", "wb") as f:
1348
- f.write(resume_file.getvalue())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1349
  st.session_state["_uploaded_sig"] = _sig
1350
- # Persist to Supabase Storage so it survives HF Space restarts
1351
  try:
1352
  from src.supabase_client import get_service_client, is_configured, get_owner_user_id
1353
  if is_configured():
@@ -1360,8 +1415,6 @@ if show_config:
1360
  )
1361
  except Exception:
1362
  pass
1363
- # Invalidate the parsed-profile cache so it re-parses the
1364
- # NEW file (cache is keyed by mtime+size, but delete to be safe)
1365
  try:
1366
  if os.path.exists("data/resume/_parsed.json"):
1367
  os.remove("data/resume/_parsed.json")
@@ -1370,17 +1423,18 @@ if show_config:
1370
  has_resume = True
1371
  st.rerun()
1372
 
 
1373
  if has_resume:
1374
  fsize = os.path.getsize("data/resume/resume.pdf") // 1024
1375
- _uploaded_name = st.session_state.get("_uploaded_sig", "resume").split(":")[0] or "resume.pdf"
 
1376
  st.html(f"""
1377
  <div class="micro-success">
1378
- βœ… <strong>{_uploaded_name}</strong> β†’ resume.pdf ({fsize} KB) β€” Ready for AI matching
1379
  </div>""")
1380
- # ── Show the profile we built from THIS resume ──
1381
  _render_uploaded_profile()
1382
  else:
1383
- st.caption("Upload your resume to unlock AI matching.")
1384
 
1385
  # ────────────────────────────────────────────────────────────────────
1386
  # STEP 2: Target Roles
 
1296
  <div class="step-number {"done" if has_resume else ""}">{"βœ“" if has_resume else "1"}</div>
1297
  <p class="step-title-text">Upload your resume</p>
1298
  </div>
1299
+ <p class="step-helper">Paste your LaTeX source for the best ATS results, or upload a PDF below.</p>
1300
+ </div>""")
1301
 
1302
+ # ── PRIMARY: LaTeX paste ──────────────────────────────────────────
1303
+ st.html("""
1304
+ <div style="background:#EEF2FF;border:2px solid #6366F1;border-radius:12px;
1305
+ padding:14px 16px;margin:12px 0 8px">
1306
+ <div style="font-size:1rem;font-weight:700;color:#4338CA">πŸ“„ LaTeX source</div>
1307
+ <div style="font-size:0.78rem;color:#6366F1;margin-top:2px">
1308
+ Recommended β€” paste your full .tex code below for highest ATS accuracy
 
1309
  </div>
1310
  </div>""")
1311
 
1312
+ _saved_latex = ""
1313
+ if os.path.exists("data/resume/resume.tex"):
1314
+ try:
1315
+ with open("data/resume/resume.tex", "r", encoding="utf-8") as _lf:
1316
+ _saved_latex = _lf.read()
1317
+ except Exception:
1318
+ pass
1319
+
1320
+ latex_input = st.text_area(
1321
+ "LaTeX code",
1322
+ value=_saved_latex,
1323
+ height=260,
1324
+ placeholder=r"""\documentclass[11pt]{article}
1325
+ % Paste your full resume LaTeX code here
1326
+ \begin{document}
1327
+ ...
1328
+ \end{document}""",
1329
+ key="latex_paste_input",
1330
+ label_visibility="collapsed",
1331
  )
1332
+
1333
+ _latex_changed = latex_input.strip() and latex_input.strip() != _saved_latex.strip()
1334
+ if st.button(
1335
+ "⚑ Compile LaTeX & Use as Resume",
1336
+ type="primary",
1337
+ use_container_width=True,
1338
+ disabled=not bool(latex_input and latex_input.strip()),
1339
+ key="compile_latex_btn",
1340
+ ):
1341
+ if latex_input and latex_input.strip():
1342
+ import tempfile as _tmpfile, subprocess as _sp, shutil as _sh
1343
+ with st.spinner("Compiling with Tectonic…"):
1344
  with _tmpfile.TemporaryDirectory() as _td:
1345
  _tex = os.path.join(_td, "resume.tex")
1346
+ with open(_tex, "w", encoding="utf-8") as _f:
1347
+ _f.write(latex_input)
1348
  _r = _sp.run(
1349
  ["tectonic", "--outdir", _td, _tex],
1350
  capture_output=True, text=True, timeout=60,
1351
  )
1352
  _pdf = os.path.join(_td, "resume.pdf")
1353
  if _r.returncode == 0 and os.path.exists(_pdf):
1354
+ os.makedirs("data/resume", exist_ok=True)
1355
  _sh.copy(_pdf, "data/resume/resume.pdf")
1356
+ with open("data/resume/resume.tex", "w", encoding="utf-8") as _f:
1357
+ _f.write(latex_input)
1358
+ _sig = f"latex_paste:{len(latex_input)}"
1359
+ st.session_state["_uploaded_sig"] = _sig
1360
+ try:
1361
+ from src.supabase_client import get_service_client, is_configured, get_owner_user_id
1362
+ if is_configured():
1363
+ _uid = st.session_state.get("user_id") or get_owner_user_id()
1364
+ if _uid:
1365
+ with open("data/resume/resume.pdf", "rb") as _rf:
1366
+ get_service_client().storage.from_("resumes").upload(
1367
+ f"{_uid}/resume.pdf", _rf.read(),
1368
+ {"upsert": "true", "content-type": "application/pdf"},
1369
+ )
1370
+ except Exception:
1371
+ pass
1372
+ try:
1373
+ if os.path.exists("data/resume/_parsed.json"):
1374
+ os.remove("data/resume/_parsed.json")
1375
+ except Exception:
1376
+ pass
1377
+ has_resume = True
1378
+ st.rerun()
1379
  else:
1380
  st.error(
1381
+ "LaTeX compilation failed. Check your code.\n\n"
1382
+ + (_r.stderr[-800:] or _r.stdout[-800:])
1383
  )
1384
+
1385
+ # ── SECONDARY: PDF upload ─────────────────────────────────────────
1386
+ st.html("""
1387
+ <div style="display:flex;align-items:center;gap:10px;margin:18px 0 10px">
1388
+ <div style="flex:1;height:1px;background:#E2E8F0"></div>
1389
+ <span style="font-size:0.8rem;color:#94A3B8;white-space:nowrap">or upload a PDF</span>
1390
+ <div style="flex:1;height:1px;background:#E2E8F0"></div>
1391
+ </div>""")
1392
+
1393
+ resume_file = st.file_uploader(
1394
+ "Upload PDF resume",
1395
+ type=["pdf"], key="resume_upload",
1396
+ label_visibility="collapsed",
1397
+ help="Upload a PDF if you don't have a LaTeX source. Max 10MB.",
1398
+ )
1399
+ if resume_file is not None:
1400
+ _sig = f"{resume_file.name}:{getattr(resume_file, 'size', 0)}"
1401
+ if st.session_state.get("_uploaded_sig") != _sig:
1402
+ os.makedirs("data/resume", exist_ok=True)
1403
+ with open("data/resume/resume.pdf", "wb") as f:
1404
+ f.write(resume_file.getvalue())
1405
  st.session_state["_uploaded_sig"] = _sig
 
1406
  try:
1407
  from src.supabase_client import get_service_client, is_configured, get_owner_user_id
1408
  if is_configured():
 
1415
  )
1416
  except Exception:
1417
  pass
 
 
1418
  try:
1419
  if os.path.exists("data/resume/_parsed.json"):
1420
  os.remove("data/resume/_parsed.json")
 
1423
  has_resume = True
1424
  st.rerun()
1425
 
1426
+ # ── Success / profile ─────────────────────────────────────────────
1427
  if has_resume:
1428
  fsize = os.path.getsize("data/resume/resume.pdf") // 1024
1429
+ _sig_raw = st.session_state.get("_uploaded_sig", "")
1430
+ _src_label = "LaTeX source" if _sig_raw.startswith("latex_paste:") else (_sig_raw.split(":")[0] or "resume.pdf")
1431
  st.html(f"""
1432
  <div class="micro-success">
1433
+ βœ… <strong>{_src_label}</strong> compiled β†’ resume.pdf ({fsize} KB) β€” Ready for AI matching
1434
  </div>""")
 
1435
  _render_uploaded_profile()
1436
  else:
1437
+ st.caption("Paste your LaTeX or upload a PDF to unlock AI matching.")
1438
 
1439
  # ────────────────────────────────────────────────────────────────────
1440
  # STEP 2: Target Roles