firmanaziz commited on
Commit
df81cdf
Β·
verified Β·
1 Parent(s): fe4f8f6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +100 -54
app.py CHANGED
@@ -4,22 +4,26 @@ import fitz # PyMuPDF
4
  import json
5
  import os
6
  import urllib.parse
7
- import base64 # Diperlukan untuk client-side API call
8
 
9
- # --- KONFIGURASI API KEY (TETAP SAMA) ---
10
  API_CONFIGURED = False
11
  try:
12
- api_key = os.environ.get('GEMINI_API_KEY')
13
  if api_key:
14
  genai.configure(api_key=api_key)
15
- model = genai.GenerativeModel('gemini-pro-latest')
 
16
  API_CONFIGURED = True
17
- print("βœ… Konfigurasi API dan model berhasil.")
18
  else:
19
  print("πŸ›‘ Secret 'GEMINI_API_KEY' tidak ditemukan.")
20
  except Exception as e:
21
  print(f"πŸ›‘ Terjadi error saat inisialisasi: {e}")
22
 
 
 
 
 
23
  # --- FUNGSI-FUNGSI UTAMA ---
24
 
25
  def ekstrak_teks_dari_pdf(path_file_pdf):
@@ -33,19 +37,36 @@ def ekstrak_teks_dari_pdf(path_file_pdf):
33
  def generate_search_links(keywords):
34
  if not keywords:
35
  return {}
36
- keywords_encoded = urllib.parse.quote_plus(keywords)
37
  keywords_hyphenated = keywords.lower().replace(" ", "-").replace("(", "").replace(")", "")
38
- links = {
39
- "LinkedIn": f"https://www.linkedin.com/jobs/search/?keywords={keywords_encoded}&location=Indonesia",
40
- "JobStreet": f"https://www.jobstreet.co.id/id/job-search/{keywords_hyphenated}-jobs/",
41
- "Glints": f"https://glints.com/id/opportunities/jobs/explore?keyword={keywords_encoded}",
42
- "Indeed": f"https://id.indeed.com/jobs?q={keywords_encoded}",
43
  "Google Jobs": f"https://www.google.com/search?q={keywords_encoded}+jobs+in+Indonesia&ibp=htl;jobs"
44
  }
45
- return links
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
  def analyze_career_path(cv_file):
48
- """Fungsi utama pipeline: Analisis CV -> Buat Laporan JSON -> Buat Link -> Gabungkan."""
49
  if not API_CONFIGURED:
50
  raise gr.Error("API Key Gemini belum terkonfigurasi. Periksa Logs aplikasi.")
51
  if cv_file is None:
@@ -53,68 +74,93 @@ def analyze_career_path(cv_file):
53
 
54
  try:
55
  print("--- Memulai Proses Analisis Karir ---")
56
-
 
57
  teks_cv = ekstrak_teks_dari_pdf(cv_file.name)
58
  if not teks_cv:
59
  raise gr.Error("PDF kosong atau tidak dapat dibaca.")
 
 
 
 
 
60
  print("βœ… Teks berhasil diekstrak.")
61
 
62
- print("2. Mengirim permintaan analisis karir ke Gemini...")
 
63
  prompt_analisis_karir = f"""
64
- Anda adalah seorang "Career Analyst AI". Baca teks CV dan buat laporan peluang karir dalam format JSON.
65
- Teks CV: --- {teks_cv} ---
66
- Struktur JSON yang diinginkan:
67
- - "jabatan_ideal": Jabatan paling ideal untuk kandidat.
68
- - "alasan_kecocokan": Array (list) berisi 3-4 poin MENGAPA kandidat cocok.
69
- - "deskripsi_pekerjaan": Array (list) berisi 5 poin deskripsi pekerjaan umum.
70
- - "potensi_karir": Array (list) berisi 3-4 jalur pengembangan karir.
71
- - "kisaran_gaji": Objek JSON berisi estimasi gaji untuk level "junior", "mid_level", dan "senior".
72
- - "kelebihan_tambahan": Array (list) berisi 1-2 poin saran atau kelebihan unik kandidat.
73
- Pastikan output hanya berupa JSON saja.
74
- """
75
-
76
- generation_config = genai.types.GenerationConfig(response_mime_type="application/json")
77
- response = model.generate_content(prompt_analisis_karir, generation_config=generation_config)
78
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  response_json = json.loads(response.text)
80
- print("βœ… Laporan karir komprehensif berhasil diterima.")
81
-
82
- print("3. Membuat tautan pencarian dari hasil analisis...")
83
- keywords_from_analysis = response_json.get("jabatan_ideal", "")
84
- search_links = generate_search_links(keywords_from_analysis)
85
-
86
- # ==================================================================
87
- # PERUBAHAN PENTING: Menambahkan link ke dalam JSON
88
- # ==================================================================
89
- response_json["tautan_pencarian"] = search_links
90
- print("βœ… Tautan pencarian ditambahkan ke JSON.")
91
 
92
  print("--- Proses Selesai ---")
93
- # Mengembalikan dictionary/JSON mentah
94
- return response_json
95
 
 
 
96
  except Exception as e:
97
- print(f"πŸ›‘ ERROR DALAM FUNGSI ANALISIS: {e}")
98
  raise gr.Error(f"Terjadi kesalahan: {e}")
99
 
100
- # --- MEMBUAT INTERFACE GRADIO ---
101
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
102
- gr.Markdown("# πŸš€ API Analis Peluang Karir Personal")
103
- gr.Markdown("Antarmuka ini dapat digunakan untuk pengujian. Endpoint API publik tersedia di `/run/predict` untuk integrasi ke website Anda.")
104
-
 
 
 
105
  with gr.Row():
106
  with gr.Column(scale=1):
107
- cv_pdf = gr.File(label="Upload CV (PDF) untuk Uji Coba", file_types=[".pdf"])
108
  analyze_button = gr.Button("πŸ” Analisis Karir Saya", variant="primary")
109
-
110
  with gr.Column(scale=2):
111
- # Menggunakan gr.JSON untuk menampilkan dan menghasilkan data JSON sebagai output API
112
- output_analysis = gr.JSON(label="Output JSON dari API")
113
-
114
  analyze_button.click(
115
  fn=analyze_career_path,
116
  inputs=[cv_pdf],
117
- outputs=[output_analysis],
118
  show_progress='full'
119
  )
120
 
 
4
  import json
5
  import os
6
  import urllib.parse
 
7
 
8
+ # --- KONFIGURASI API KEY ---
9
  API_CONFIGURED = False
10
  try:
11
+ api_key = os.environ.get('GEMINI_API_KEY')
12
  if api_key:
13
  genai.configure(api_key=api_key)
14
+ # βœ… Model paling murah & tersedia di Free Tier
15
+ model = genai.GenerativeModel('gemini-1.5-flash')
16
  API_CONFIGURED = True
17
+ print("βœ… Konfigurasi API dan model (gemini-1.5-flash) berhasil.")
18
  else:
19
  print("πŸ›‘ Secret 'GEMINI_API_KEY' tidak ditemukan.")
20
  except Exception as e:
21
  print(f"πŸ›‘ Terjadi error saat inisialisasi: {e}")
22
 
23
+ # --- KONSTANTA TOKEN ---
24
+ MAX_OUTPUT_TOKENS = 1024 # Batas token output (hemat kuota)
25
+ MAX_INPUT_CHARS = 12000 # Batas karakter teks CV agar tidak meledak input token
26
+
27
  # --- FUNGSI-FUNGSI UTAMA ---
28
 
29
  def ekstrak_teks_dari_pdf(path_file_pdf):
 
37
  def generate_search_links(keywords):
38
  if not keywords:
39
  return {}
40
+ keywords_encoded = urllib.parse.quote_plus(keywords)
41
  keywords_hyphenated = keywords.lower().replace(" ", "-").replace("(", "").replace(")", "")
42
+ return {
43
+ "LinkedIn" : f"https://www.linkedin.com/jobs/search/?keywords={keywords_encoded}&location=Indonesia",
44
+ "JobStreet" : f"https://www.jobstreet.co.id/id/job-search/{keywords_hyphenated}-jobs/",
45
+ "Glints" : f"https://glints.com/id/opportunities/jobs/explore?keyword={keywords_encoded}",
46
+ "Indeed" : f"https://id.indeed.com/jobs?q={keywords_encoded}",
47
  "Google Jobs": f"https://www.google.com/search?q={keywords_encoded}+jobs+in+Indonesia&ibp=htl;jobs"
48
  }
49
+
50
+ def format_token_info(usage_metadata) -> str:
51
+ """Mengubah usage_metadata Gemini menjadi string ringkasan yang rapi."""
52
+ if usage_metadata is None:
53
+ return "ℹ️ Data penggunaan token tidak tersedia."
54
+
55
+ prompt_tokens = getattr(usage_metadata, 'prompt_token_count', 'N/A')
56
+ candidate_tokens = getattr(usage_metadata, 'candidates_token_count', 'N/A')
57
+ total_tokens = getattr(usage_metadata, 'total_token_count', 'N/A')
58
+
59
+ lines = [
60
+ "πŸ“Š **Penggunaan Token (gemini-1.5-flash)**",
61
+ f"β€’ Input (prompt) : {prompt_tokens:,} token" if isinstance(prompt_tokens, int) else f"β€’ Input (prompt) : {prompt_tokens}",
62
+ f"β€’ Output (response): {candidate_tokens:,} token" if isinstance(candidate_tokens, int) else f"β€’ Output (response): {candidate_tokens}",
63
+ f"β€’ **Total : {total_tokens:,} token**" if isinstance(total_tokens, int) else f"β€’ Total : {total_tokens}",
64
+ f"β€’ Limit output : {MAX_OUTPUT_TOKENS:,} token (konfigurasi saat ini)",
65
+ ]
66
+ return "\n".join(lines)
67
 
68
  def analyze_career_path(cv_file):
69
+ """Pipeline utama: Analisis CV β†’ Laporan JSON β†’ Link β†’ Gabung + Info Token."""
70
  if not API_CONFIGURED:
71
  raise gr.Error("API Key Gemini belum terkonfigurasi. Periksa Logs aplikasi.")
72
  if cv_file is None:
 
74
 
75
  try:
76
  print("--- Memulai Proses Analisis Karir ---")
77
+
78
+ # 1. Ekstrak teks PDF
79
  teks_cv = ekstrak_teks_dari_pdf(cv_file.name)
80
  if not teks_cv:
81
  raise gr.Error("PDF kosong atau tidak dapat dibaca.")
82
+
83
+ # Potong teks CV agar tidak melebihi batas input yang wajar
84
+ if len(teks_cv) > MAX_INPUT_CHARS:
85
+ teks_cv = teks_cv[:MAX_INPUT_CHARS]
86
+ print(f"⚠️ Teks CV dipotong hingga {MAX_INPUT_CHARS} karakter untuk efisiensi token.")
87
  print("βœ… Teks berhasil diekstrak.")
88
 
89
+ # 2. Kirim ke Gemini dengan batas token output
90
+ print("2. Mengirim permintaan ke gemini-1.5-flash...")
91
  prompt_analisis_karir = f"""
92
+ Anda adalah seorang "Career Analyst AI". Baca teks CV berikut dan buat laporan peluang karir dalam format JSON yang ringkas.
93
+
94
+ Teks CV:
95
+ ---
96
+ {teks_cv}
97
+ ---
98
+
99
+ Buat JSON dengan struktur berikut (jawab singkat dan padat untuk menghemat token):
100
+ - "jabatan_ideal": string β€” jabatan paling ideal.
101
+ - "alasan_kecocokan": array 3 poin singkat MENGAPA kandidat cocok.
102
+ - "deskripsi_pekerjaan": array 4 poin deskripsi pekerjaan umum.
103
+ - "potensi_karir": array 3 jalur karir berikutnya.
104
+ - "kisaran_gaji": objek dengan key "junior", "mid_level", "senior" (estimasi IDR/bulan).
105
+ - "kelebihan_tambahan": array 2 saran atau kelebihan unik.
106
+
107
+ Output HANYA berupa JSON. Tidak ada teks lain di luar JSON.
108
+ """
109
+
110
+ generation_config = genai.types.GenerationConfig(
111
+ response_mime_type="application/json",
112
+ max_output_tokens=MAX_OUTPUT_TOKENS, # ← Batas token output
113
+ temperature=0.4, # Lebih deterministik & hemat
114
+ )
115
+
116
+ response = model.generate_content(
117
+ prompt_analisis_karir,
118
+ generation_config=generation_config
119
+ )
120
+
121
+ # 3. Parse JSON
122
  response_json = json.loads(response.text)
123
+ print("βœ… Laporan karir berhasil diterima.")
124
+
125
+ # 4. Tambahkan link pencarian
126
+ keywords = response_json.get("jabatan_ideal", "")
127
+ response_json["tautan_pencarian"] = generate_search_links(keywords)
128
+ print("βœ… Tautan pencarian ditambahkan.")
129
+
130
+ # 5. Ambil info token dari metadata respons
131
+ token_info = format_token_info(getattr(response, 'usage_metadata', None))
132
+ print(f"πŸ“Š Token usage: {getattr(response, 'usage_metadata', 'unavailable')}")
 
133
 
134
  print("--- Proses Selesai ---")
135
+ return response_json, token_info
 
136
 
137
+ except json.JSONDecodeError:
138
+ raise gr.Error("Gagal mem-parse JSON dari Gemini. Coba lagi atau periksa CV Anda.")
139
  except Exception as e:
140
+ print(f"πŸ›‘ ERROR: {e}")
141
  raise gr.Error(f"Terjadi kesalahan: {e}")
142
 
143
+ # --- INTERFACE GRADIO ---
144
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
145
+ gr.Markdown("# πŸš€ Analis Peluang Karir Personal")
146
+ gr.Markdown(
147
+ "Powered by **Gemini 1.5 Flash** β€” model tercepat & termurah di Free Tier. "
148
+ f"Batas output: **{MAX_OUTPUT_TOKENS} token** per analisis."
149
+ )
150
+
151
  with gr.Row():
152
  with gr.Column(scale=1):
153
+ cv_pdf = gr.File(label="πŸ“„ Upload CV (PDF)", file_types=[".pdf"])
154
  analyze_button = gr.Button("πŸ” Analisis Karir Saya", variant="primary")
155
+
156
  with gr.Column(scale=2):
157
+ output_analysis = gr.JSON(label="πŸ“‹ Hasil Analisis (JSON)")
158
+ token_display = gr.Markdown(label="πŸ“Š Info Token", value="*Belum ada analisis.*")
159
+
160
  analyze_button.click(
161
  fn=analyze_career_path,
162
  inputs=[cv_pdf],
163
+ outputs=[output_analysis, token_display],
164
  show_progress='full'
165
  )
166