RangGaraga commited on
Commit
25444cd
·
verified ·
1 Parent(s): 4da7434

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -479
app.py DELETED
@@ -1,479 +0,0 @@
1
- import gradio as gr
2
- from transformers import pipeline
3
- import fitz
4
- import tempfile
5
- import os
6
- import re
7
- import time
8
-
9
- # Load model
10
- ner = pipeline("ner", model="cahya/NusaBert-ner-v1.3", aggregation_strategy="simple")
11
-
12
- # Label mapping
13
- LABEL_MAP = {
14
- "CRD": "Kardinal", "DAT": "Tanggal", "EVT": "Peristiwa",
15
- "FAC": "Fasilitas", "GPE": "Entitas Geopolitik", "LAW": "Peraturan / Undang-Undang",
16
- "LOC": "Lokasi", "MON": "Uang", "NOR": "Organisasi Politik",
17
- "ORD": "Ordinal", "ORG": "Organisasi", "PER": "Orang",
18
- "PRC": "Persentase", "PRD": "Produk", "QTY": "Kuantitas",
19
- "REG": "Agama", "TIM": "Waktu", "WOA": "Karya Seni", "LAN": "Bahasa",
20
- }
21
-
22
- RTF_COLOR_TABLE = [
23
- (255,191,191),(174,221,255),(141,199,255),(184,255,184),(153,242,204),
24
- (217,255,165),(255,229,140),(255,204,102),(204,255,218),(242,204,255),
25
- (224,191,255),(255,242,178),(199,242,242),(255,186,186),(255,217,165),
26
- (191,223,255),(230,218,255),(255,230,218),(204,255,242),
27
- ]
28
-
29
- LABEL_RTF_COLOR_IDX = {
30
- "PER":1,"ORG":2,"NOR":3,"LOC":4,"GPE":5,"FAC":6,"DAT":7,"TIM":8,
31
- "MON":9,"CRD":10,"ORD":11,"PRC":12,"QTY":13,"LAW":14,"EVT":15,
32
- "PRD":16,"REG":17,"WOA":18,"LAN":19,
33
- }
34
-
35
- LABEL_HEX = {
36
- "PER":"#FFBFBF","ORG":"#AEDDFF","NOR":"#8DC7FF","LOC":"#B8FFB8",
37
- "GPE":"#99F2CC","FAC":"#D9FFA5","DAT":"#FFE58C","TIM":"#FFCC66",
38
- "MON":"#CCFFDA","CRD":"#F2CCFF","ORD":"#E0BFFF","PRC":"#FFF2B2",
39
- "QTY":"#C7F2F2","LAW":"#FFBABA","EVT":"#FFD9A5","PRD":"#BFDFFF",
40
- "REG":"#E6DAFF","WOA":"#FFE6DA","LAN":"#CCFFF2",
41
- }
42
-
43
- MAX_PDF_PAGES = 5
44
- # NusaBERT max 512 token ≈ ~400 kata ≈ ~2500 karakter aman
45
- MAX_CHUNK_CHARS = 2000
46
- OVERLAP_CHARS = 150
47
-
48
- EXAMPLES = [
49
- ("Contoh 1 – RUPS & Dana Cadangan",
50
- "Berdasarkan Rapat Umum Pemegang Saham (RUPS) pada tanggal 24 Juni 2024 yang disahkan "
51
- "oleh notaris Ashoya Ratam, S.H., M.Kn., Risalah No.124/VI/2024, Perusahaan memutuskan "
52
- "antara lain menyisihkan 5% dari laba bersih untuk tahun yang berakhir 31 Desember 2023 "
53
- "atau sebesar Rp5.299.075.507 sebagai dana cadangan jaminan."),
54
- ("Contoh 2 – Akta Jual Beli Saham PEFINDO",
55
- "Berdasarkan Akta Notaris Melinda, S.Sos., S.H., M.Kn dengan No. 17 tanggal 21 Januari "
56
- "2025, Perusahaan dan Dana Pensiun Pertamina telah menandatangani Akta Jual Beli saham "
57
- "dan Perusahaan telah melakukan pembayaran penuh untuk pembelian 5.170 lembar saham "
58
- "PEFINDO yang dimiliki Dana Pensiun Pertamina. Dengan demikian total kepemilikan saham "
59
- "Perusahaan pada tanggal 21 Januari 2025 menjadi sebanyak 37.548 lembar saham atau sama "
60
- "dengan 31,92% kepemilikan di PEFINDO."),
61
- ("Contoh 3 – Fasilitas Kredit Bank Permata",
62
- "Pada tanggal 12 Desember 2022, PEI, entitas anak, dan PT Bank Permata Tbk "
63
- "menandatangani perjanjian fasilitas money market dengan fasilitas kredit maksimum "
64
- "sebesar Rp50.000.000.000. Pinjaman ini digunakan untuk keperluan stand by facility "
65
- "dengan jangka waktu penarikan antara 3 (tiga) hari sampai dengan 3 (tiga) bulan "
66
- "semenjak tanggal penarikan pinjaman dilakukan."),
67
- ("Contoh 4 – Dividen PEFINDO Biro Kredit",
68
- "Berdasarkan Rapat Umum Pemegang Saham Tahunan tanggal 28 Juni 2024, pemegang saham "
69
- "PEFINDO Biro Kredit menyetujui pembagian dividen untuk Perusahaan sebesar Rp6.637.962.683."),
70
- ("Contoh 5 – Regulasi Bursa Karbon",
71
- "Peraturan Presiden RI No. 98 Tahun 2021 tentang Penyelenggaraan Nilai Ekonomi Karbon "
72
- "untuk Pencapaian Target Kontribusi yang Ditetapkan Secara Nasional dan Pengendalian "
73
- "Emisi Gas Rumah Kaca dalam Pembangunan Nasional mengatur mengenai mekanisme pencapaian "
74
- "NDC. Undang-undang RI No. 4 Tahun 2023 tentang Pengembangan dan Penguatan Sektor "
75
- "Keuangan menegaskan bahwa tugas pengaturan dan pengawasan bursa karbon dilakukan oleh "
76
- "Otoritas Jasa Keuangan."),
77
- ]
78
-
79
-
80
- # ── Helpers ──────────────────────────────────────────────────────────────────
81
-
82
- def clean_word(word: str) -> str:
83
- return word.replace("▁", " ").replace("##", "").strip()
84
-
85
- def get_label_id(raw_label: str) -> str:
86
- label_id = raw_label.replace("B-","").replace("I-","").replace("B_","").replace("I_","")
87
- return label_id.split("-")[-1].upper().strip()
88
-
89
- def escape_rtf(text: str) -> str:
90
- out = []
91
- for ch in text:
92
- if ch == '\\': out.append('\\\\')
93
- elif ch == '{': out.append('\\{')
94
- elif ch == '}': out.append('\\}')
95
- elif ord(ch) > 127: out.append(f'\\u{ord(ch)}?')
96
- else: out.append(ch)
97
- return ''.join(out)
98
-
99
- def build_rtf_color_table() -> str:
100
- entries = "\\red0\\green0\\blue0;"
101
- for r,g,b in RTF_COLOR_TABLE:
102
- entries += f"\\red{r}\\green{g}\\blue{b};"
103
- return "{\\colortbl ;" + entries + "}"
104
-
105
- def text_to_rtf_with_highlights(text: str, entity_map: dict) -> str:
106
- sorted_entities = sorted(entity_map.items(), key=lambda x: len(x[0]), reverse=True)
107
- spans = []
108
- used = [False] * len(text)
109
- for entity_lower, label_id in sorted_entities:
110
- if not entity_lower:
111
- continue
112
- pattern = re.compile(re.escape(entity_lower), re.IGNORECASE)
113
- for m in pattern.finditer(text):
114
- s, e = m.start(), m.end()
115
- if any(used[i] for i in range(s, e)):
116
- continue
117
- spans.append((s, e, label_id))
118
- for i in range(s, e):
119
- used[i] = True
120
- spans.sort(key=lambda x: x[0])
121
- rtf_parts = []
122
- cursor = 0
123
- for s, e, label_id in spans:
124
- if cursor < s:
125
- rtf_parts.append(escape_rtf(text[cursor:s]))
126
- color_idx = LABEL_RTF_COLOR_IDX.get(label_id, 1)
127
- highlighted = escape_rtf(text[s:e])
128
- rtf_parts.append(f"{{\\highlight{color_idx} {highlighted}}}")
129
- cursor = e
130
- if cursor < len(text):
131
- rtf_parts.append(escape_rtf(text[cursor:]))
132
- return "".join(rtf_parts)
133
-
134
- def wrap_rtf(content: str) -> str:
135
- color_table = build_rtf_color_table()
136
- content_rtf = content.replace("\r\n","\n").replace("\r","\n").replace("\n","\\par\n")
137
- return (
138
- "{\\rtf1\\ansi\\ansicpg1252\\deff0\n"
139
- "{\\fonttbl{\\f0\\froman\\fcharset0 Times New Roman;}}\n"
140
- f"{color_table}\n"
141
- "\\widowctrl\\wpaper12240\\wpaperh15840\n"
142
- "\\margl1800\\margr1800\\margt1440\\margb1440\n"
143
- "\\f0\\fs24\\sl360\\slmult1\n"
144
- f"{content_rtf}"
145
- "}"
146
- )
147
-
148
- def _build_debug_html(lines: list) -> str:
149
- rows = "".join(
150
- f'<div style="padding:6px 10px; border-bottom:1px solid rgba(255,255,255,0.07); '
151
- f'font-size:13px; color:#cbd5e1; font-family:monospace;">{line}</div>'
152
- for line in lines
153
- )
154
- return f"""
155
- <div style="margin-top:12px; background:rgba(15,23,42,0.85); border:1px solid rgba(99,102,241,0.35);
156
- border-radius:12px; overflow:hidden;">
157
- <div style="background:linear-gradient(135deg,#1e40af,#6d28d9); padding:8px 14px;
158
- font-size:11px; font-weight:700; letter-spacing:0.1em; color:#fff;">
159
- 🔍 DEBUG — WAKTU PER LANGKAH
160
- </div>
161
- {rows}
162
- </div>"""
163
-
164
-
165
- # ── NER Teks ─────────────────────────────────────────────────────────────────
166
-
167
- def run_ner(text: str):
168
- if not text or not text.strip():
169
- return "<p style='color:#94a3b8; font-style:italic; padding:16px;'>Masukkan teks terlebih dahulu.</p>"
170
- results = ner(text.strip())
171
- if not results:
172
- return "<p style='color:#94a3b8; font-style:italic; padding:16px;'>Tidak ada entitas yang ditemukan.</p>"
173
-
174
- rows_html = ""
175
- row_num = 1
176
- seen_words = set()
177
- for ent in results:
178
- raw_label = ent["entity_group"]
179
- label_id = get_label_id(raw_label)
180
- label_idn = LABEL_MAP.get(label_id, raw_label)
181
- word = clean_word(ent["word"])
182
- if not word: continue
183
- word_key = word.lower()
184
- if word_key in seen_words: continue
185
- seen_words.add(word_key)
186
- hex_color = LABEL_HEX.get(label_id, "#e2e8f0")
187
- score = f"{ent['score']:.2%}"
188
- row_bg = "#f8faff" if row_num % 2 == 0 else "#ffffff"
189
- rows_html += f"""
190
- <tr style="background:{row_bg};">
191
- <td style="padding:9px 14px;border-bottom:1px solid #e8edf5;text-align:center;color:#64748b;font-size:12px;">{row_num}</td>
192
- <td style="padding:9px 14px;border-bottom:1px solid #e8edf5;font-weight:600;color:#1e293b;">{word}</td>
193
- <td style="padding:9px 14px;border-bottom:1px solid #e8edf5;">
194
- <span style="background:{hex_color};padding:3px 10px;border-radius:20px;font-size:12px;font-weight:600;color:#1e293b;">{label_idn}</span>
195
- </td>
196
- <td style="padding:9px 14px;border-bottom:1px solid #e8edf5;font-size:12px;color:#64748b;text-align:center;">{score}</td>
197
- </tr>"""
198
- row_num += 1
199
-
200
- if not rows_html:
201
- return "<p style='color:#94a3b8; font-style:italic; padding:16px;'>Tidak ada entitas yang ditemukan.</p>"
202
-
203
- return f"""
204
- <div style="overflow-x:auto;margin-top:4px;border-radius:12px;border:1px solid #e2e8f0;box-shadow:0 2px 12px rgba(0,0,0,0.06);">
205
- <table style="width:100%;border-collapse:collapse;font-size:14px;font-family:'Segoe UI',sans-serif;">
206
- <thead>
207
- <tr style="background:linear-gradient(135deg,#1e40af,#6d28d9);">
208
- <th style="padding:12px 14px;color:#fff;width:55px;font-weight:600;font-size:12px;letter-spacing:0.05em;">NO</th>
209
- <th style="padding:12px 14px;color:#fff;text-align:left;font-weight:600;font-size:12px;letter-spacing:0.05em;">KATA / FRASA</th>
210
- <th style="padding:12px 14px;color:#fff;text-align:left;font-weight:600;font-size:12px;letter-spacing:0.05em;">ENTITAS</th>
211
- <th style="padding:12px 14px;color:#fff;width:90px;font-weight:600;font-size:12px;letter-spacing:0.05em;">SKOR</th>
212
- </tr>
213
- </thead>
214
- <tbody>{rows_html}</tbody>
215
- </table>
216
- </div>"""
217
-
218
-
219
- # ── NER PDF → RTF ─────────────────────────────────────────────────────────────
220
-
221
- def run_ner_pdf(pdf_file):
222
- # return: (out_path, legend_html, warn_html, debug_html)
223
- if pdf_file is None:
224
- return None, "", "<p style='color:#94a3b8;font-style:italic;padding:16px;'>Unggah file PDF terlebih dahulu.</p>", ""
225
-
226
- pdf_path = pdf_file if isinstance(pdf_file, str) else pdf_file.name
227
- doc = fitz.open(pdf_path)
228
- page_count = len(doc)
229
-
230
- if page_count > MAX_PDF_PAGES:
231
- doc.close()
232
- return None, "", f"""
233
- <div style="background:#fff1f2;border:1.5px solid #fda4af;border-radius:12px;
234
- padding:16px 20px;display:flex;align-items:center;gap:12px;">
235
- <span style="font-size:24px;">⚠️</span>
236
- <div>
237
- <p style="margin:0;font-weight:700;color:#be123c;font-size:15px;">PDF Terlalu Banyak Halaman</p>
238
- <p style="margin:4px 0 0;color:#9f1239;font-size:13px;">
239
- File Anda memiliki <strong>{page_count} halaman</strong>.
240
- Maksimal <strong>{MAX_PDF_PAGES} halaman</strong>.
241
- </p>
242
- </div>
243
- </div>""", ""
244
-
245
- debug_lines = []
246
- t_total = time.time()
247
-
248
- # ── LANGKAH 1: Ekstrak teks ──
249
- t0 = time.time()
250
- pages_text = [page.get_text() for page in doc]
251
- doc.close()
252
- full_text = "\n\n".join(pages_text)
253
- char_count = len(full_text)
254
- t1 = time.time()
255
- debug_lines.append(f"✅ <b>Langkah 1</b> — Ekstrak teks PDF: <b>{t1-t0:.3f}s</b> | {char_count:,} karakter | {page_count} halaman")
256
-
257
- # ── LANGKAH 2: Chunking ──
258
- t0 = time.time()
259
- chunks = []
260
- start = 0
261
- while start < len(full_text):
262
- end = min(start + MAX_CHUNK_CHARS, len(full_text))
263
- chunks.append(full_text[start:end])
264
- if end == len(full_text):
265
- break
266
- start = end - OVERLAP_CHARS
267
- t1 = time.time()
268
- debug_lines.append(f"✅ <b>Langkah 2</b> — Chunking teks: <b>{t1-t0:.4f}s</b> | {len(chunks)} chunk @ {MAX_CHUNK_CHARS} karakter")
269
-
270
- # ── LANGKAH 3: NER model (ini yang biasanya paling lama) ──
271
- t0 = time.time()
272
- all_ner_results = []
273
- chunk_times = []
274
- for i, chunk in enumerate(chunks):
275
- tc = time.time()
276
- chunk_results = ner(chunk.strip())
277
- chunk_times.append(time.time() - tc)
278
- all_ner_results.extend(chunk_results)
279
- t1 = time.time()
280
- avg_chunk = sum(chunk_times)/len(chunk_times) if chunk_times else 0
281
- debug_lines.append(
282
- f"✅ <b>Langkah 3</b> — NER model: <b>{t1-t0:.2f}s</b> total | "
283
- f"{len(chunks)} chunk | avg/chunk: {avg_chunk:.2f}s | {len(all_ner_results)} entitas raw"
284
- )
285
- # Detail per chunk
286
- for i, ct in enumerate(chunk_times):
287
- debug_lines.append(f"&nbsp;&nbsp;&nbsp;↳ chunk {i+1}/{len(chunks)}: {ct:.2f}s ({len(chunks[i])} karakter)")
288
-
289
- if not all_ner_results:
290
- dh = _build_debug_html(debug_lines)
291
- return None, "", "<p style='color:#94a3b8;padding:16px;'>Tidak ada entitas ditemukan.</p>", dh
292
-
293
- # ── LANGKAH 4: Bangun entity_map ──
294
- t0 = time.time()
295
- entity_map: dict[str, str] = {}
296
- for ent in all_ner_results:
297
- word = clean_word(ent["word"])
298
- if len(word) < 2: continue
299
- label_id = get_label_id(ent["entity_group"])
300
- w_lower = word.lower()
301
- if w_lower not in entity_map:
302
- entity_map[w_lower] = label_id
303
- t1 = time.time()
304
- debug_lines.append(f"✅ <b>Langkah 4</b> — Bangun entity_map: <b>{t1-t0:.4f}s</b> | {len(entity_map)} entitas unik")
305
-
306
- if not entity_map:
307
- dh = _build_debug_html(debug_lines)
308
- return None, "", "<p style='color:#94a3b8;padding:16px;'>Tidak ada entitas ditemukan.</p>", dh
309
-
310
- # ── LANGKAH 5: Highlight → RTF content ──
311
- t0 = time.time()
312
- rtf_content = text_to_rtf_with_highlights(full_text, entity_map)
313
- t1 = time.time()
314
- debug_lines.append(f"✅ <b>Langkah 5</b> — Highlight & build RTF content: <b>{t1-t0:.4f}s</b>")
315
-
316
- # ── LANGKAH 6: Tulis file .rtf ──
317
- t0 = time.time()
318
- rtf_full = wrap_rtf(rtf_content)
319
- out_fd, out_path = tempfile.mkstemp(suffix=".rtf")
320
- with os.fdopen(out_fd, "w", encoding="ascii", errors="replace") as f:
321
- f.write(rtf_full)
322
- t1 = time.time()
323
- debug_lines.append(f"✅ <b>Langkah 6</b> — Tulis file RTF: <b>{t1-t0:.4f}s</b> | {len(rtf_full):,} bytes")
324
-
325
- t_end = time.time()
326
- debug_lines.append(f"⏱ <b>TOTAL WAKTU: {t_end-t_total:.2f}s</b>")
327
-
328
- debug_html = _build_debug_html(debug_lines)
329
- found_labels: set[str] = set(entity_map.values())
330
-
331
- # ── Legend ──
332
- legend_rows = ""
333
- for label_id in sorted(found_labels):
334
- label_idn = LABEL_MAP.get(label_id, label_id)
335
- hex_color = LABEL_HEX.get(label_id, "#e2e8f0")
336
- legend_rows += f"""
337
- <tr>
338
- <td style="padding:8px 14px;border-bottom:1px solid #e8edf5;">
339
- <span style="display:inline-block;width:24px;height:24px;border-radius:6px;
340
- background:{hex_color};vertical-align:middle;border:1px solid rgba(0,0,0,0.1);"></span>
341
- </td>
342
- <td style="padding:8px 14px;border-bottom:1px solid #e8edf5;font-weight:600;color:#1e293b;font-size:13px;">{label_id}</td>
343
- <td style="padding:8px 14px;border-bottom:1px solid #e8edf5;color:#475569;font-size:13px;">{label_idn}</td>
344
- </tr>"""
345
-
346
- legend_html = f"""
347
- <div style="margin-top:4px;">
348
- <p style="font-size:13px;color:#64748b;margin-bottom:10px;">
349
- Ditemukan <strong style='color:#1e40af;'>{len(found_labels)}</strong> jenis entitas
350
- dari <strong style='color:#1e40af;'>{page_count}</strong> halaman PDF.
351
- Buka file <strong>.rtf</strong> di <strong>Microsoft Word</strong> atau <strong>LibreOffice Writer</strong>.
352
- </p>
353
- <div style="overflow-x:auto;border-radius:12px;border:1px solid #e2e8f0;box-shadow:0 2px 12px rgba(0,0,0,0.06);">
354
- <table style="width:100%;border-collapse:collapse;font-family:'Segoe UI',sans-serif;">
355
- <thead>
356
- <tr style="background:linear-gradient(135deg,#1e40af,#6d28d9);">
357
- <th style="padding:11px 14px;color:#fff;font-size:12px;letter-spacing:0.05em;width:50px;">WARNA</th>
358
- <th style="padding:11px 14px;color:#fff;font-size:12px;letter-spacing:0.05em;text-align:left;">KODE</th>
359
- <th style="padding:11px 14px;color:#fff;font-size:12px;letter-spacing:0.05em;text-align:left;">NAMA ENTITAS</th>
360
- </tr>
361
- </thead>
362
- <tbody>{legend_rows}</tbody>
363
- </table>
364
- </div>
365
- </div>"""
366
-
367
- return out_path, legend_html, "", debug_html
368
-
369
-
370
- # ── CSS ───────────────────────────────────────────────────────────────────────
371
-
372
- CUSTOM_CSS = """
373
- @import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700;900&family=DM+Sans:wght@400;500;600&display=swap');
374
- .gradio-container{max-width:100%!important;padding:0!important;background:linear-gradient(160deg,#0f172a 0%,#1e1b4b 40%,#0f172a 100%)!important;min-height:100vh;}
375
- #hero-header{background:linear-gradient(135deg,#1e40af 0%,#6d28d9 50%,#be185d 100%);padding:40px 48px 36px;margin:0 0 24px;border-radius:16px;text-align:center;position:relative;overflow:hidden;}
376
- #hero-header::before{content:'';position:absolute;inset:0;background:url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.04'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");}
377
- .tab-nav{background:rgba(255,255,255,0.05)!important;border-radius:12px!important;padding:4px!important;border:1px solid rgba(255,255,255,0.1)!important;margin:0!important;}
378
- .tab-nav button{background:transparent!important;color:#94a3b8!important;border-radius:8px!important;padding:10px 24px!important;font-family:'DM Sans',sans-serif!important;font-weight:600!important;font-size:14px!important;transition:all 0.2s!important;}
379
- .tab-nav button.selected{background:linear-gradient(135deg,#1e40af,#6d28d9)!important;color:#ffffff!important;box-shadow:0 4px 12px rgba(109,40,217,0.4)!important;}
380
- label span{color:#cbd5e1!important;font-family:'DM Sans',sans-serif!important;font-weight:600!important;font-size:12px!important;letter-spacing:0.07em!important;text-transform:uppercase!important;}
381
- textarea,.gr-textbox textarea{background:rgba(15,23,42,0.7)!important;border:1.5px solid rgba(255,255,255,0.12)!important;border-radius:10px!important;color:#e2e8f0!important;font-family:'DM Sans',sans-serif!important;font-size:14px!important;padding:14px!important;transition:border 0.2s!important;}
382
- textarea:focus{border-color:#6d28d9!important;outline:none!important;}
383
- .example-btn{display:block!important;width:100%!important;text-align:left!important;padding:12px 16px!important;margin-bottom:8px!important;background:rgba(30,64,175,0.15)!important;border:1px solid rgba(99,102,241,0.30)!important;border-radius:10px!important;cursor:pointer!important;font-size:13px!important;line-height:1.6!important;color:#cbd5e1!important;white-space:normal!important;height:auto!important;font-family:'DM Sans',sans-serif!important;transition:all 0.2s!important;}
384
- .example-btn:hover{background:rgba(109,40,217,0.25)!important;border-color:#6d28d9!important;color:#e2e8f0!important;transform:translateX(3px);}
385
- #analyze-btn,#analyze-pdf-btn{background:linear-gradient(135deg,#1e40af,#6d28d9)!important;color:white!important;font-weight:700!important;font-size:15px!important;border-radius:10px!important;padding:12px 0!important;margin-top:8px!important;font-family:'DM Sans',sans-serif!important;letter-spacing:0.03em!important;box-shadow:0 4px 16px rgba(109,40,217,0.35)!important;transition:all 0.2s!important;border:none!important;}
386
- #analyze-btn:hover,#analyze-pdf-btn:hover{transform:translateY(-2px)!important;box-shadow:0 6px 24px rgba(109,40,217,0.50)!important;}
387
- .section-heading{font-family:'DM Sans',sans-serif;font-weight:700;font-size:11px;letter-spacing:0.12em;text-transform:uppercase;color:#ffffff!important;margin-bottom:12px;display:flex;align-items:center;gap:8px;}
388
- .section-heading::before{content:'';display:inline-block;width:18px;height:2px;background:linear-gradient(90deg,#6d28d9,#be185d);border-radius:2px;}
389
- .gr-file{background:rgba(15,23,42,0.7)!important;border:1.5px dashed rgba(99,102,241,0.40)!important;border-radius:10px!important;color:#94a3b8!important;}
390
- .gr-file-download{background:rgba(30,64,175,0.2)!important;border:1px solid rgba(99,102,241,0.4)!important;border-radius:10px!important;color:#a5b4fc!important;font-family:'DM Sans',sans-serif!important;}
391
- #center-col{max-width:780px!important;margin:0 auto!important;width:100%!important;padding:0 8px!important;}
392
- #footer{text-align:center;padding:20px;color:rgba(148,163,184,0.5);font-family:'DM Sans',sans-serif;font-size:12px;letter-spacing:0.04em;}
393
- .gradio-container h3{color:#e2e8f0!important;}
394
- #center-col p{color:#94a3b8!important;}
395
- """
396
-
397
- HERO_HTML = """
398
- <div id="hero-header">
399
- <p style="font-family:'DM Sans',sans-serif;font-size:12px;font-weight:700;letter-spacing:0.20em;text-transform:uppercase;color:rgba(165,180,252,0.8);margin:0 0 10px;">Tugas Kelompok · NLP &amp; Text Mining</p>
400
- <h1 style="font-family:'Playfair Display',serif;font-size:clamp(32px,5vw,56px);font-weight:900;color:#ffffff;margin:0 0 8px;line-height:1.1;text-shadow:0 2px 20px rgba(109,40,217,0.5);">
401
- NER <span style="color:#a78bfa;">for</span> Financial Statements
402
- </h1>
403
- <p style="font-family:'DM Sans',sans-serif;font-size:clamp(13px,2vw,16px);color:rgba(203,213,225,0.85);margin:0 auto 18px;max-width:640px;line-height:1.6;">
404
- Implementasi Named Entity Recognition pada Kumpulan<br>Laporan-laporan Keuangan Bahasa Indonesia
405
- </p>
406
- <div style="display:inline-flex;align-items:center;gap:8px;background:rgba(0,0,0,0.25);border:1px solid rgba(255,255,255,0.15);border-radius:20px;padding:6px 16px 6px 8px;">
407
- <span style="background:linear-gradient(135deg,#1e40af,#6d28d9);border-radius:12px;padding:3px 10px;font-size:11px;font-weight:700;color:#fff;letter-spacing:0.05em;">MODEL</span>
408
- <span style="font-family:monospace;font-size:13px;color:#a5b4fc;">cahya/NusaBert-ner-v1.3</span>
409
- </div>
410
- </div>
411
- """
412
-
413
-
414
- # ── Gradio UI ─────────────────────────────────────────────────────────────────
415
-
416
- with gr.Blocks(title="NER for Financial Statements", css=CUSTOM_CSS) as demo:
417
-
418
- gr.HTML(HERO_HTML)
419
-
420
- with gr.Tabs(elem_classes="tab-nav"):
421
-
422
- with gr.Tab("Analisis Teks"):
423
- with gr.Column(elem_id="center-col"):
424
- gr.HTML('<div class="section-heading">Contoh Teks</div>')
425
- example_btns = []
426
- for title, body in EXAMPLES:
427
- btn = gr.Button(f"📌 {title}\n\n{body}", elem_classes="example-btn")
428
- example_btns.append((btn, body))
429
-
430
- gr.HTML('<div class="section-heading" style="margin-top:20px;">Input Teks</div>')
431
- text_input = gr.Textbox(lines=9, placeholder="Ketik atau tempel teks laporan keuangan di sini…", label="", show_label=False)
432
- analyze_btn = gr.Button("Lakukan Analisis", elem_id="analyze-btn")
433
-
434
- gr.HTML('<div class="section-heading" style="margin-top:20px;">Hasil Analisis Entitas</div>')
435
- text_output = gr.HTML(value="<p style='color:#64748b;font-size:14px;font-family:DM Sans,sans-serif;padding:20px;text-align:center;'>Masukkan teks lalu klik Lakukan Analisis.</p>")
436
-
437
- with gr.Tab("Analisis PDF"):
438
- with gr.Column(elem_id="center-col"):
439
- gr.HTML(
440
- '<p style="color:#94a3b8;font-family:DM Sans,sans-serif;font-size:13px;margin:0 0 16px;">'
441
- 'Unggah PDF laporan keuangan (maks. <strong style="color:#a78bfa;">5 halaman</strong>). '
442
- 'Output: file <strong style="color:#a78bfa;">.rtf</strong> dengan stabilo warna — '
443
- 'bisa dibuka di <strong>Microsoft Word</strong> atau <strong>LibreOffice</strong>.</p>'
444
- )
445
- gr.HTML('<div class="section-heading">Unggah PDF</div>')
446
- pdf_input = gr.File(label="", file_types=[".pdf"], type="filepath")
447
- analyze_pdf_btn = gr.Button("Highlight Entitas → Download RTF", elem_id="analyze-pdf-btn")
448
- pdf_warning = gr.HTML(value="")
449
-
450
- with gr.Column(visible=False) as pdf_results_col:
451
- gr.HTML('<div class="section-heading" style="margin-top:20px;">Download Hasil RTF</div>')
452
- pdf_output = gr.File(label="", file_types=[".rtf"], interactive=False)
453
- gr.HTML('<div class="section-heading" style="margin-top:20px;">Keterangan Warna Entitas</div>')
454
- pdf_legend = gr.HTML(value="")
455
- # Debug panel — selalu tampil setelah proses
456
- pdf_debug = gr.HTML(value="")
457
-
458
- gr.HTML('<div id="footer">NER for Financial Statements 2026</div>')
459
-
460
- # Wiring
461
- for btn, body in example_btns:
462
- btn.click(fn=lambda b=body: b, inputs=[], outputs=text_input)
463
-
464
- analyze_btn.click(fn=run_ner, inputs=text_input, outputs=text_output)
465
- text_input.submit(fn=run_ner, inputs=text_input, outputs=text_output)
466
-
467
- def handle_pdf(pdf_file):
468
- out_path, legend_html, warn_html, debug_html = run_ner_pdf(pdf_file)
469
- show = gr.Column(visible=out_path is not None)
470
- return out_path, legend_html, warn_html, debug_html, show
471
-
472
- analyze_pdf_btn.click(
473
- fn=handle_pdf,
474
- inputs=pdf_input,
475
- outputs=[pdf_output, pdf_legend, pdf_warning, pdf_debug, pdf_results_col],
476
- )
477
-
478
- if __name__ == "__main__":
479
- demo.launch()