Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -8,33 +8,41 @@ import json
|
|
| 8 |
import os
|
| 9 |
|
| 10 |
# --- KONFIGURASI API KEY DARI HUGGING FACE SECRETS ---
|
| 11 |
-
|
| 12 |
try:
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
except Exception as e:
|
| 23 |
-
print(f"π Terjadi error
|
| 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 |
-
|
| 32 |
-
|
| 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')
|
|
@@ -43,58 +51,58 @@ def format_output_markdown(parsed_json, hasil_analisis):
|
|
| 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 |
-
|
| 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.
|
| 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 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
|
| 99 |
# --- MEMBUAT INTERFACE GRADIO ---
|
| 100 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
@@ -104,20 +112,22 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
| 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 |
-
|
| 109 |
-
|
| 110 |
-
|
| 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__":
|
|
|
|
| 8 |
import os
|
| 9 |
|
| 10 |
# --- KONFIGURASI API KEY DARI HUGGING FACE SECRETS ---
|
| 11 |
+
API_CONFIGURED = False
|
| 12 |
try:
|
| 13 |
+
# PASTIKAN NAMA SECRET ANDA ADALAH 'GEMINI_API_KEY', BUKAN 'GOOGLE_API_KEY'
|
| 14 |
+
api_key = os.environ.get('GEMINI_API_KEY')
|
| 15 |
+
if api_key:
|
| 16 |
+
genai.configure(api_key=api_key)
|
| 17 |
+
model = genai.GenerativeModel('gemini-1.5-flash-latest')
|
| 18 |
+
API_CONFIGURED = True
|
| 19 |
+
print("β
Konfigurasi API dan model berhasil.")
|
| 20 |
+
else:
|
| 21 |
+
print("π Secret 'GEMINI_API_KEY' tidak ditemukan.")
|
| 22 |
except Exception as e:
|
| 23 |
+
print(f"π Terjadi error saat inisialisasi: {e}")
|
|
|
|
| 24 |
|
| 25 |
# --- FUNGSI-FUNGSI UTAMA ---
|
| 26 |
|
| 27 |
def ekstrak_teks_dari_pdf(path_file_pdf):
|
|
|
|
| 28 |
try:
|
| 29 |
+
with fitz.open(path_file_pdf) as dokumen:
|
| 30 |
+
teks_lengkap = "".join(halaman.get_text() for halaman in dokumen)
|
|
|
|
| 31 |
return teks_lengkap
|
| 32 |
except Exception as e:
|
| 33 |
raise gr.Error(f"Gagal membaca file PDF: {e}")
|
| 34 |
|
| 35 |
+
def clean_and_parse_json(raw_text):
|
| 36 |
+
"""Fungsi yang lebih andal untuk membersihkan dan mem-parsing JSON dari teks mentah."""
|
| 37 |
+
# Menemukan awal dan akhir blok JSON
|
| 38 |
+
start_index = raw_text.find('{')
|
| 39 |
+
end_index = raw_text.rfind('}')
|
| 40 |
+
if start_index == -1 or end_index == -1:
|
| 41 |
+
raise json.JSONDecodeError("Tidak ditemukan blok JSON valid dalam respons.", raw_text, 0)
|
| 42 |
+
|
| 43 |
+
json_str = raw_text[start_index:end_index+1]
|
| 44 |
+
return json.loads(json_str)
|
| 45 |
+
|
| 46 |
def format_output_markdown(parsed_json, hasil_analisis):
|
| 47 |
"""Menyusun hasil akhir menjadi string Markdown yang rapi."""
|
| 48 |
nama = parsed_json.get('nama_lengkap', 'N/A')
|
|
|
|
| 51 |
kekuatan = "\n".join([f"- {poin}" for poin in hasil_analisis.get('poin_kekuatan', [])])
|
| 52 |
kelemahan = "\n".join([f"- {poin}" for poin in hasil_analisis.get('potensi_kelemahan', [])])
|
| 53 |
|
| 54 |
+
return f"""
|
| 55 |
### HASIL ANALISIS KECOCOKAN KANDIDAT
|
| 56 |
**π€ Nama Kandidat:** {nama}
|
| 57 |
***
|
| 58 |
**β Skor Kecocokan:** {skor} / 10
|
| 59 |
+
|
| 60 |
**π Ringkasan Analisis:**
|
| 61 |
{ringkasan}
|
| 62 |
+
|
| 63 |
**β
Poin Kekuatan Utama:**
|
| 64 |
{kekuatan}
|
| 65 |
+
|
| 66 |
**β οΈ Potensi Kelemahan / Pertanyaan Lanjutan:**
|
| 67 |
{kelemahan}
|
| 68 |
"""
|
|
|
|
| 69 |
|
| 70 |
def analyze_cv(cv_file, deskripsi_pekerjaan):
|
| 71 |
"""Fungsi utama yang dijalankan oleh Gradio saat tombol ditekan."""
|
| 72 |
if not API_CONFIGURED:
|
| 73 |
+
raise gr.Error("API Key Gemini belum terkonfigurasi. Periksa Logs aplikasi di Hugging Face.")
|
| 74 |
if cv_file is None or not deskripsi_pekerjaan.strip():
|
| 75 |
raise gr.Error("Mohon upload file CV (PDF) dan isi deskripsi pekerjaan.")
|
| 76 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
try:
|
| 78 |
+
print("--- Memulai Proses Analisis ---")
|
| 79 |
+
|
| 80 |
+
print("1. Mengekstrak teks dari PDF...")
|
| 81 |
+
teks_cv = ekstrak_teks_dari_pdf(cv_file.name)
|
| 82 |
+
if not teks_cv:
|
| 83 |
+
raise gr.Error("PDF kosong atau tidak dapat dibaca.")
|
| 84 |
+
print("β
Teks berhasil diekstrak.")
|
| 85 |
+
|
| 86 |
+
print("2. Mengirim permintaan ekstraksi JSON ke Gemini...")
|
| 87 |
+
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."
|
| 88 |
+
response_ekstraksi = model.generate_content(prompt_ekstraksi)
|
| 89 |
+
parsed_json = clean_and_parse_json(response_ekstraksi.text)
|
| 90 |
+
print("β
Ekstraksi JSON berhasil.")
|
| 91 |
+
|
| 92 |
+
print("3. Mengirim permintaan analisis kecocokan ke Gemini...")
|
| 93 |
+
prompt_analisis = f"Anda adalah Manajer Perekrutan. Analisis data kandidat (JSON) dan bandingkan dengan deskripsi pekerjaan. Deskripsi Pekerjaan: {deskripsi_pekerjaan}. Data Kandidat (JSON): {json.dumps(parsed_json, indent=2)}. Instruksi: Berikan output JSON dengan struktur: skor_kecocokan, ringkasan_analisis, poin_kekuatan, potensi_kelemahan. Output harus JSON saja."
|
| 94 |
+
response_analisis = model.generate_content(prompt_analisis)
|
| 95 |
+
hasil_analisis = clean_and_parse_json(response_analisis.text)
|
| 96 |
+
print("β
Analisis kecocokan berhasil.")
|
| 97 |
+
|
| 98 |
+
print("4. Menyusun output akhir...")
|
| 99 |
+
final_output = format_output_markdown(parsed_json, hasil_analisis)
|
| 100 |
+
print("--- Proses Selesai ---")
|
| 101 |
+
return final_output
|
| 102 |
+
|
| 103 |
+
except Exception as e:
|
| 104 |
+
print(f"π ERROR DALAM FUNGSI ANALISIS: {e}")
|
| 105 |
+
raise gr.Error(f"Terjadi kesalahan: {e}")
|
| 106 |
|
| 107 |
# --- MEMBUAT INTERFACE GRADIO ---
|
| 108 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
|
|
| 112 |
with gr.Row():
|
| 113 |
with gr.Column(scale=1):
|
| 114 |
cv_pdf = gr.File(label="1. Upload CV Anda (PDF)", file_types=[".pdf"])
|
| 115 |
+
job_desc = gr.Textbox(lines=10, label="2. Masukkan Deskripsi Pekerjaan", placeholder="Contoh:\nPosisi: Senior Backend Engineer...")
|
| 116 |
+
with gr.Row():
|
| 117 |
+
clear_button = gr.ClearButton(value="Hapus Semua")
|
| 118 |
+
analyze_button = gr.Button("β¨ Analisis Sekarang", variant="primary")
|
|
|
|
|
|
|
| 119 |
|
| 120 |
with gr.Column(scale=2):
|
| 121 |
output_analysis = gr.Markdown(label="Hasil Analisis")
|
| 122 |
+
|
| 123 |
+
# Menghubungkan tombol hapus ke semua komponen input dan output
|
| 124 |
+
clear_button.add([cv_pdf, job_desc, output_analysis])
|
| 125 |
|
| 126 |
analyze_button.click(
|
| 127 |
fn=analyze_cv,
|
| 128 |
inputs=[cv_pdf, job_desc],
|
| 129 |
+
outputs=[output_analysis],
|
| 130 |
+
show_progress='full' # Menampilkan status proses
|
| 131 |
)
|
| 132 |
|
| 133 |
if __name__ == "__main__":
|