Spaces:
Sleeping
Sleeping
Create app.py
Browse files
app.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Install dependensi yang dibutuhkan oleh Hugging Face Spaces
|
| 2 |
+
#!pip install -q gradio google-generativeai pymupdf
|
| 3 |
+
|
| 4 |
+
import gradio as gr
|
| 5 |
+
import google.generativeai as genai
|
| 6 |
+
import fitz # PyMuPDF
|
| 7 |
+
import json
|
| 8 |
+
import os
|
| 9 |
+
|
| 10 |
+
# --- KONFIGURASI API KEY DARI HUGGING FACE SECRETS ---
|
| 11 |
+
# Pastikan Anda sudah mengatur Secret bernama 'GEMINI_API_KEY' di repository Space Anda
|
| 12 |
+
try:
|
| 13 |
+
api_key = os.environ.get('GEMINI_API_KEY')
|
| 14 |
+
genai.configure(api_key=api_key)
|
| 15 |
+
print("✅ Konfigurasi API berhasil!")
|
| 16 |
+
model = genai.GenerativeModel('gemini-1.5-flash-latest')
|
| 17 |
+
print(f"✅ Model '{model.model_name}' berhasil diinisialisasi.")
|
| 18 |
+
API_CONFIGURED = True
|
| 19 |
+
except (TypeError, ValueError) as e:
|
| 20 |
+
print(f"🛑 Error Konfigurasi API: Pastikan GEMINI_API_KEY sudah di-set di Secrets. Detail: {e}")
|
| 21 |
+
API_CONFIGURED = False
|
| 22 |
+
except Exception as e:
|
| 23 |
+
print(f"🛑 Terjadi error tak terduga saat inisialisasi: {e}")
|
| 24 |
+
API_CONFIGURED = False
|
| 25 |
+
|
| 26 |
+
# --- FUNGSI-FUNGSI UTAMA ---
|
| 27 |
+
|
| 28 |
+
def ekstrak_teks_dari_pdf(path_file_pdf):
|
| 29 |
+
"""Membuka file PDF dari path temporer dan mengekstrak teksnya."""
|
| 30 |
+
try:
|
| 31 |
+
dokumen = fitz.open(path_file_pdf)
|
| 32 |
+
teks_lengkap = "".join(halaman.get_text() for halaman in dokumen)
|
| 33 |
+
dokumen.close()
|
| 34 |
+
return teks_lengkap
|
| 35 |
+
except Exception as e:
|
| 36 |
+
raise gr.Error(f"Gagal membaca file PDF: {e}")
|
| 37 |
+
|
| 38 |
+
def format_output_markdown(parsed_json, hasil_analisis):
|
| 39 |
+
"""Menyusun hasil akhir menjadi string Markdown yang rapi."""
|
| 40 |
+
nama = parsed_json.get('nama_lengkap', 'N/A')
|
| 41 |
+
skor = hasil_analisis.get('skor_kecocokan', 'N/A')
|
| 42 |
+
ringkasan = hasil_analisis.get('ringkasan_analisis', 'N/A')
|
| 43 |
+
kekuatan = "\n".join([f"- {poin}" for poin in hasil_analisis.get('poin_kekuatan', [])])
|
| 44 |
+
kelemahan = "\n".join([f"- {poin}" for poin in hasil_analisis.get('potensi_kelemahan', [])])
|
| 45 |
+
|
| 46 |
+
markdown_output = f"""
|
| 47 |
+
### HASIL ANALISIS KECOCOKAN KANDIDAT
|
| 48 |
+
**👤 Nama Kandidat:** {nama}
|
| 49 |
+
***
|
| 50 |
+
**⭐ Skor Kecocokan:** {skor} / 10
|
| 51 |
+
|
| 52 |
+
**📝 Ringkasan Analisis:**
|
| 53 |
+
{ringkasan}
|
| 54 |
+
|
| 55 |
+
**✅ Poin Kekuatan Utama:**
|
| 56 |
+
{kekuatan}
|
| 57 |
+
|
| 58 |
+
**⚠️ Potensi Kelemahan / Pertanyaan Lanjutan:**
|
| 59 |
+
{kelemahan}
|
| 60 |
+
"""
|
| 61 |
+
return markdown_output
|
| 62 |
+
|
| 63 |
+
def analyze_cv(cv_file, deskripsi_pekerjaan):
|
| 64 |
+
"""Fungsi utama yang dijalankan oleh Gradio saat tombol ditekan."""
|
| 65 |
+
if not API_CONFIGURED:
|
| 66 |
+
raise gr.Error("API Key Gemini belum terkonfigurasi. Hubungi pemilik aplikasi.")
|
| 67 |
+
if cv_file is None or not deskripsi_pekerjaan.strip():
|
| 68 |
+
raise gr.Error("Mohon upload file CV (PDF) dan isi deskripsi pekerjaan.")
|
| 69 |
+
|
| 70 |
+
print("1. Memulai ekstraksi teks dari PDF...")
|
| 71 |
+
teks_cv = ekstrak_teks_dari_pdf(cv_file.name) # cv_file.name adalah path temporer
|
| 72 |
+
if not teks_cv:
|
| 73 |
+
raise gr.Error("PDF kosong atau tidak dapat dibaca.")
|
| 74 |
+
|
| 75 |
+
print("2. Memulai ekstraksi CV menjadi JSON...")
|
| 76 |
+
prompt_ekstraksi = f"Ekstrak informasi dari teks CV berikut dan sajikan dalam format JSON. Pastikan JSON memiliki struktur: nama_lengkap, kontak, ringkasan, keterampilan, pengalaman_kerja, dan pendidikan. Teks CV:\n---\n{teks_cv}\n---\nOutput harus berupa JSON saja."
|
| 77 |
+
response_ekstraksi = model.generate_content(prompt_ekstraksi)
|
| 78 |
+
try:
|
| 79 |
+
parsed_json = json.loads(response_ekstraksi.text.strip().replace('```json', '').replace('```', ''))
|
| 80 |
+
except (json.JSONDecodeError, AttributeError) as e:
|
| 81 |
+
raise gr.Error(f"Gagal mem-parsing CV menjadi JSON. Error: {e}\n\nRespons Mentah:\n{response_ekstraksi.text}")
|
| 82 |
+
|
| 83 |
+
print("3. Memulai analisis kecocokan...")
|
| 84 |
+
prompt_analisis = f"""
|
| 85 |
+
Anda adalah seorang Manajer Perekrutan senior. Analisis data kandidat (JSON) dan bandingkan dengan deskripsi pekerjaan.
|
| 86 |
+
Deskripsi Pekerjaan: {deskripsi_pekerjaan}
|
| 87 |
+
Data Kandidat (JSON): {json.dumps(parsed_json, indent=2)}
|
| 88 |
+
Instruksi: Berikan output JSON dengan struktur: skor_kecocokan, ringkasan_analisis, poin_kekuatan, potensi_kelemahan. Output harus JSON saja.
|
| 89 |
+
"""
|
| 90 |
+
response_analisis = model.generate_content(prompt_analisis)
|
| 91 |
+
try:
|
| 92 |
+
hasil_analisis = json.loads(response_analisis.text.strip().replace('```json', '').replace('```', ''))
|
| 93 |
+
except (json.JSONDecodeError, AttributeError) as e:
|
| 94 |
+
raise gr.Error(f"Gagal menganalisis kecocokan. Error: {e}\n\nRespons Mentah:\n{response_analisis.text}")
|
| 95 |
+
|
| 96 |
+
print("4. Menyusun output...")
|
| 97 |
+
return format_output_markdown(parsed_json, hasil_analisis)
|
| 98 |
+
|
| 99 |
+
# --- MEMBUAT INTERFACE GRADIO ---
|
| 100 |
+
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
| 101 |
+
gr.Markdown("# 🤖 Aplikasi Analisis CV Otomatis")
|
| 102 |
+
gr.Markdown("Upload CV dalam format PDF dan masukkan deskripsi pekerjaan untuk mendapatkan analisis kecocokan secara instan.")
|
| 103 |
+
|
| 104 |
+
with gr.Row():
|
| 105 |
+
with gr.Column(scale=1):
|
| 106 |
+
cv_pdf = gr.File(label="1. Upload CV Anda (PDF)", file_types=[".pdf"])
|
| 107 |
+
job_desc = gr.Textbox(
|
| 108 |
+
lines=10,
|
| 109 |
+
label="2. Masukkan Deskripsi Pekerjaan",
|
| 110 |
+
placeholder="Contoh:\nPosisi: Senior Backend Engineer\n\nKami mencari seorang Senior Backend Engineer dengan pengalaman minimal 4 tahun..."
|
| 111 |
+
)
|
| 112 |
+
analyze_button = gr.Button("✨ Analisis Sekarang", variant="primary")
|
| 113 |
+
|
| 114 |
+
with gr.Column(scale=2):
|
| 115 |
+
output_analysis = gr.Markdown(label="Hasil Analisis")
|
| 116 |
+
|
| 117 |
+
analyze_button.click(
|
| 118 |
+
fn=analyze_cv,
|
| 119 |
+
inputs=[cv_pdf, job_desc],
|
| 120 |
+
outputs=[output_analysis]
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
if __name__ == "__main__":
|
| 124 |
+
demo.launch()
|