Spaces:
Running
Running
pebaryan commited on
Commit Β·
00ce06b
1
Parent(s): 475ac07
feat: add ethics guardrails, verifier, beranda + kredit tabs
Browse files- Added Nilai-nilai Demokrasi & HAM to all 4 agent system prompts
- Created ethics.py verifier (post-processing guardrail using SMALL LLM)
- Added ethics_verify call to all 4 Gradio agent wrappers
- Added Beranda tab with welcome, quick guide, and values framework
- Added Kredit tab with Taufik Basari quote and credentials
- Inpsired by feedback from @taufikbasari (DPR RI 2019-2024)
- .gitignore +1 -0
- app.py +69 -3
- src/legawa/agents/analis_ruu.py +7 -0
- src/legawa/agents/peneliti.py +7 -0
- src/legawa/agents/penyusun.py +6 -0
- src/legawa/agents/surat.py +6 -0
- src/legawa/tools/ethics.py +73 -0
- video/plan.md +54 -0
- video/script.py +321 -0
.gitignore
CHANGED
|
@@ -19,3 +19,4 @@ output/
|
|
| 19 |
|
| 20 |
# Claude Code per-user settings (project-level settings.json is fine to commit)
|
| 21 |
.claude/settings.local.json
|
|
|
|
|
|
| 19 |
|
| 20 |
# Claude Code per-user settings (project-level settings.json is fine to commit)
|
| 21 |
.claude/settings.local.json
|
| 22 |
+
video/
|
app.py
CHANGED
|
@@ -22,6 +22,7 @@ import gradio as gr
|
|
| 22 |
from legawa.agents import analis_ruu, peneliti, penyusun, surat
|
| 23 |
from legawa.tools.cache import CachingPasalClient
|
| 24 |
from legawa.tools.pasal import PasalClient
|
|
|
|
| 25 |
|
| 26 |
# ββ Default HF Inference API config (zero-config demo) ββββββββββββββββββ
|
| 27 |
# Uses huggingface_hub's InferenceClient (works reliably on HF Spaces).
|
|
@@ -166,8 +167,10 @@ def agent_analyze(
|
|
| 166 |
try:
|
| 167 |
progress(0.3, desc="Menganalisis RUU...")
|
| 168 |
result = analis_ruu.analyze(pool, pasal, source)
|
|
|
|
|
|
|
| 169 |
progress(1.0, desc="Selesai!")
|
| 170 |
-
return
|
| 171 |
except Exception as e:
|
| 172 |
return f"**Error:** {e}"
|
| 173 |
finally:
|
|
@@ -195,6 +198,8 @@ def agent_research(
|
|
| 195 |
progress(0.2, desc="Ekspansi query...")
|
| 196 |
progress(0.5, desc="Mencari peraturan...")
|
| 197 |
output = peneliti.research(pool, pasal, topic)
|
|
|
|
|
|
|
| 198 |
progress(1.0, desc="Selesai!")
|
| 199 |
return output
|
| 200 |
except Exception as e:
|
|
@@ -230,6 +235,8 @@ def agent_draft(
|
|
| 230 |
with_research=with_research,
|
| 231 |
extra_instructions=extra_instructions or None,
|
| 232 |
)
|
|
|
|
|
|
|
| 233 |
progress(1.0, desc="Selesai!")
|
| 234 |
return output
|
| 235 |
except Exception as e:
|
|
@@ -262,8 +269,11 @@ def agent_surat(
|
|
| 262 |
pool, pasal, surat_text,
|
| 263 |
verify_law=verify_law,
|
| 264 |
)
|
|
|
|
|
|
|
|
|
|
| 265 |
progress(1.0, desc="Selesai!")
|
| 266 |
-
return
|
| 267 |
except Exception as e:
|
| 268 |
return f"**Error:** {e}"
|
| 269 |
finally:
|
|
@@ -368,7 +378,37 @@ def build_app() -> gr.Blocks:
|
|
| 368 |
)
|
| 369 |
|
| 370 |
with gr.Tabs():
|
| 371 |
-
# βββ Tab 1:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 372 |
with gr.TabItem("π Analisis RUU"):
|
| 373 |
gr.Markdown(
|
| 374 |
"Upload atau tempel teks RUU untuk dianalisis pasal-per-pasal."
|
|
@@ -586,6 +626,32 @@ def build_app() -> gr.Blocks:
|
|
| 586 |
outputs=[big_url, big_key, small_url, small_key, pasal_token, health_out],
|
| 587 |
)
|
| 588 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 589 |
gr.Markdown(
|
| 590 |
f"---\n"
|
| 591 |
f"**Legawa** β *small models, big adventure* ποΈ | "
|
|
|
|
| 22 |
from legawa.agents import analis_ruu, peneliti, penyusun, surat
|
| 23 |
from legawa.tools.cache import CachingPasalClient
|
| 24 |
from legawa.tools.pasal import PasalClient
|
| 25 |
+
from legawa.tools.ethics import ethics_verify
|
| 26 |
|
| 27 |
# ββ Default HF Inference API config (zero-config demo) ββββββββββββββββββ
|
| 28 |
# Uses huggingface_hub's InferenceClient (works reliably on HF Spaces).
|
|
|
|
| 167 |
try:
|
| 168 |
progress(0.3, desc="Menganalisis RUU...")
|
| 169 |
result = analis_ruu.analyze(pool, pasal, source)
|
| 170 |
+
progress(0.8, desc="Verifikasi etika & HAM...")
|
| 171 |
+
output = ethics_verify(result.output, pool.small)
|
| 172 |
progress(1.0, desc="Selesai!")
|
| 173 |
+
return output
|
| 174 |
except Exception as e:
|
| 175 |
return f"**Error:** {e}"
|
| 176 |
finally:
|
|
|
|
| 198 |
progress(0.2, desc="Ekspansi query...")
|
| 199 |
progress(0.5, desc="Mencari peraturan...")
|
| 200 |
output = peneliti.research(pool, pasal, topic)
|
| 201 |
+
progress(0.8, desc="Verifikasi etika & HAM...")
|
| 202 |
+
output = ethics_verify(output, pool.small)
|
| 203 |
progress(1.0, desc="Selesai!")
|
| 204 |
return output
|
| 205 |
except Exception as e:
|
|
|
|
| 235 |
with_research=with_research,
|
| 236 |
extra_instructions=extra_instructions or None,
|
| 237 |
)
|
| 238 |
+
progress(0.8, desc="Verifikasi etika & HAM...")
|
| 239 |
+
output = ethics_verify(output, pool.small)
|
| 240 |
progress(1.0, desc="Selesai!")
|
| 241 |
return output
|
| 242 |
except Exception as e:
|
|
|
|
| 269 |
pool, pasal, surat_text,
|
| 270 |
verify_law=verify_law,
|
| 271 |
)
|
| 272 |
+
output = surat.format_report(result)
|
| 273 |
+
progress(0.8, desc="Verifikasi etika & HAM...")
|
| 274 |
+
output = ethics_verify(output, pool.small)
|
| 275 |
progress(1.0, desc="Selesai!")
|
| 276 |
+
return output
|
| 277 |
except Exception as e:
|
| 278 |
return f"**Error:** {e}"
|
| 279 |
finally:
|
|
|
|
| 378 |
)
|
| 379 |
|
| 380 |
with gr.Tabs():
|
| 381 |
+
# βββ Tab 1: Beranda β Welcome + Quick Guide ββββββββββββββββ
|
| 382 |
+
with gr.TabItem("π Beranda"):
|
| 383 |
+
gr.Markdown(
|
| 384 |
+
"# ποΈ Selamat Datang di Legawa\n\n"
|
| 385 |
+
"**Asisten multi-agen untuk legislator Indonesia (DPR/DPRD).**\n\n"
|
| 386 |
+
"Legawa membantu Anda menganalisis RUU, mencari peraturan terkait, "
|
| 387 |
+
"menyusun naskah, dan membalas surat konstituen β semuanya dalam "
|
| 388 |
+
"hitungan menit.\n\n"
|
| 389 |
+
"---\n"
|
| 390 |
+
)
|
| 391 |
+
gr.Markdown(
|
| 392 |
+
"### π Panduan Cepat\n\n"
|
| 393 |
+
"1. **π Analisis RUU** β Tempel teks RUU atau upload PDF, klik Analisis\n"
|
| 394 |
+
"2. **π Riset Hukum** β Cari peraturan Indonesia berdasarkan topik\n"
|
| 395 |
+
"3. **βοΈ Draf Dokumen** β Buat pidato, naskah akademik, atau memo kebijakan\n"
|
| 396 |
+
"4. **π¬ Surat Konstituen** β Triase dan balas surat/email konstituen\n"
|
| 397 |
+
"5. **βοΈ Pengaturan** β Atur koneksi LLM dan token API\n\n"
|
| 398 |
+
"---\n"
|
| 399 |
+
)
|
| 400 |
+
gr.Markdown(
|
| 401 |
+
"### βοΈ Nilai-nilai Demokrasi & HAM\n\n"
|
| 402 |
+
"Setiap output Legawa diperiksa terhadap 4 pilar:\n"
|
| 403 |
+
"- **Kedaulatan Rakyat** β apakah keputusan berpihak pada rakyat?\n"
|
| 404 |
+
"- **Prinsip Demokrasi** β apakah checks and balances terjaga?\n"
|
| 405 |
+
"- **Hak Asasi Manusia** β apakah HAM dilindungi?\n"
|
| 406 |
+
"- **Etika Politik** β apakah ada do's and don'ts untuk legislator?\n\n"
|
| 407 |
+
"*Inisiatif ini terinspirasi dari masukan Taufik Basari, S.H., S.Hum., LL.M., "
|
| 408 |
+
"anggota DPR RI 2019β2024.*\n"
|
| 409 |
+
)
|
| 410 |
+
|
| 411 |
+
# βββ Tab 2: Analisis RUU ββββββββββββββββββββββββββββββββββ
|
| 412 |
with gr.TabItem("π Analisis RUU"):
|
| 413 |
gr.Markdown(
|
| 414 |
"Upload atau tempel teks RUU untuk dianalisis pasal-per-pasal."
|
|
|
|
| 626 |
outputs=[big_url, big_key, small_url, small_key, pasal_token, health_out],
|
| 627 |
)
|
| 628 |
|
| 629 |
+
# βββ Tab 6: Kredit β Attribution ββββββββββββββββββββββββββ
|
| 630 |
+
with gr.TabItem("π€ Kredit"):
|
| 631 |
+
gr.Markdown(
|
| 632 |
+
"### π£οΈ Masukan dari Legislator\n\n"
|
| 633 |
+
"Fitur **Nilai-nilai Demokrasi & HAM** dikembangkan berdasarkan "
|
| 634 |
+
"masukan dari:\n\n"
|
| 635 |
+
"**Taufik Basari, S.H., S.Hum., LL.M.**\n"
|
| 636 |
+
"*Anggota Dewan Perwakilan Rakyat Republik Indonesia*\n"
|
| 637 |
+
"*Masa jabatan: 1 Oktober 2019 β 30 September 2024*\n\n"
|
| 638 |
+
"> *\"AI agent nya mesti dilatih utk kasih do's and don'ts, "
|
| 639 |
+
"konsep kedaulatan rakyat, prinsip demokrasi dan HAM serta "
|
| 640 |
+
"mengingatkan pentingnya political ethics di setiap jawaban "
|
| 641 |
+
"yg diberikan. Jd kalau mau pake bahan dari AI, legislator "
|
| 642 |
+
"tsb harus sertakan jg nilai2 itu.\"\n"
|
| 643 |
+
"> β Taufik Basari, 29 Mei 2026*\n\n"
|
| 644 |
+
"---\n"
|
| 645 |
+
"[π X/Twitter](https://x.com/taufikbasari) | "
|
| 646 |
+
"[Wikipedia](https://id.wikipedia.org/wiki/Taufik_Basari)\n\n"
|
| 647 |
+
"---\n"
|
| 648 |
+
"### ποΈ Legawa\n\n"
|
| 649 |
+
"*Small models, big adventure* ποΈ\n\n"
|
| 650 |
+
"Dibangun untuk [Build Small Hackathon](https://huggingface.co/build-small-hackathon) "
|
| 651 |
+
"oleh [@pebaryan](https://x.com/pebaryan).\n\n"
|
| 652 |
+
"Kode terbuka di [GitHub](https://github.com/pebaryan/Legawa).\n\n"
|
| 653 |
+
)
|
| 654 |
+
|
| 655 |
gr.Markdown(
|
| 656 |
f"---\n"
|
| 657 |
f"**Legawa** β *small models, big adventure* ποΈ | "
|
src/legawa/agents/analis_ruu.py
CHANGED
|
@@ -48,6 +48,13 @@ Aturan:
|
|
| 48 |
- Gunakan tool secara agresif. Cari dahulu, sintesis kemudian. Lakukan setidaknya 3 pencarian
|
| 49 |
untuk RUU bertema substansial.
|
| 50 |
- Output akhir dalam format Markdown dengan heading yang jelas.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
"""
|
| 52 |
|
| 53 |
|
|
|
|
| 48 |
- Gunakan tool secara agresif. Cari dahulu, sintesis kemudian. Lakukan setidaknya 3 pencarian
|
| 49 |
untuk RUU bertema substansial.
|
| 50 |
- Output akhir dalam format Markdown dengan heading yang jelas.
|
| 51 |
+
|
| 52 |
+
Nilai-nilai Demokrasi & HAM β WAJIB dipertimbangkan dalam setiap analisis:
|
| 53 |
+
1. **Kedaulatan Rakyat** β apakah RUU ini memperkuat atau melemahkan partisipasi publik?
|
| 54 |
+
2. **Prinsip Demokrasi** β apakah mekanisme checks and balances terjaga?
|
| 55 |
+
3. **Hak Asasi Manusia** β apakah ada pasal yang berpotensi melanggar HAM?
|
| 56 |
+
4. **Etika Politik** β apa do's and don'ts yang perlu diingatkan ke legislator?
|
| 57 |
+
Sertakan poin-poin ini di bagian **Catatan untuk Legislator**.
|
| 58 |
"""
|
| 59 |
|
| 60 |
|
src/legawa/agents/peneliti.py
CHANGED
|
@@ -57,6 +57,13 @@ Aturan:
|
|
| 57 |
- Setiap klaim tentang isi peraturan harus berasal dari hasil pencarian; cantumkan frbr_uri.
|
| 58 |
- Jangan mengarang nomor UU atau pasal yang tidak ada di hasil.
|
| 59 |
- Bila hasil tidak cukup, katakan secara eksplisit di bagian "Celah".
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
"""
|
| 61 |
|
| 62 |
|
|
|
|
| 57 |
- Setiap klaim tentang isi peraturan harus berasal dari hasil pencarian; cantumkan frbr_uri.
|
| 58 |
- Jangan mengarang nomor UU atau pasal yang tidak ada di hasil.
|
| 59 |
- Bila hasil tidak cukup, katakan secara eksplisit di bagian "Celah".
|
| 60 |
+
|
| 61 |
+
Nilai-nilai Demokrasi & HAM β WAJIB dipertimbangkan dalam setiap rekomendasi:
|
| 62 |
+
1. **Kedaulatan Rakyat** β apakah peraturan ini berpihak pada kepentingan publik?
|
| 63 |
+
2. **Prinsip Demokrasi** β apakah ada potensi abuse of power?
|
| 64 |
+
3. **Hak Asasi Manusia** β identifikasi dampak HAM dari peraturan terkait.
|
| 65 |
+
4. **Etika Politik** β berikan do's and don'ts untuk legislator.
|
| 66 |
+
Sertakan di bagian **Rekomendasi Tindak Lanjut**.
|
| 67 |
"""
|
| 68 |
|
| 69 |
|
src/legawa/agents/penyusun.py
CHANGED
|
@@ -90,6 +90,12 @@ Aturan umum:
|
|
| 90 |
- Jangan mengarang fakta hukum atau angka statistik. Jika tidak yakin, gunakan placeholder
|
| 91 |
bersudut kurung siku (mis. [DATA BPS DAPIL]).
|
| 92 |
- Output dalam Markdown.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
"""
|
| 94 |
|
| 95 |
|
|
|
|
| 90 |
- Jangan mengarang fakta hukum atau angka statistik. Jika tidak yakin, gunakan placeholder
|
| 91 |
bersudut kurung siku (mis. [DATA BPS DAPIL]).
|
| 92 |
- Output dalam Markdown.
|
| 93 |
+
|
| 94 |
+
Nilai-nilai Demokrasi & HAM β pastikan naskah mencerminkan:
|
| 95 |
+
1. **Kedaulatan Rakyat** β apakah bahasa dan substansi berpihak pada kepentingan publik?
|
| 96 |
+
2. **Prinsip Demokrasi** β hindari narasi yang melemahkan checks and balances.
|
| 97 |
+
3. **Hak Asasi Manusia** β identifikasi dan lindungi hak-hak yang terdampak.
|
| 98 |
+
4. **Etika Politik** β berikan do's and don'ts dalam rekomendasi kebijakan.
|
| 99 |
"""
|
| 100 |
|
| 101 |
|
src/legawa/agents/surat.py
CHANGED
|
@@ -104,6 +104,12 @@ Pedoman:
|
|
| 104 |
- Tanda tangan: "Hormat kami,\\n[NAMA ANGGOTA LEGISLATIF]\\n[FRAKSI/DAERAH PEMILIHAN]"
|
| 105 |
- Panjang 250β450 kata. Output Markdown.
|
| 106 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
{KOMISI_DPR_2024_2029}
|
| 108 |
"""
|
| 109 |
|
|
|
|
| 104 |
- Tanda tangan: "Hormat kami,\\n[NAMA ANGGOTA LEGISLATIF]\\n[FRAKSI/DAERAH PEMILIHAN]"
|
| 105 |
- Panjang 250β450 kata. Output Markdown.
|
| 106 |
|
| 107 |
+
Nilai-nilai Demokrasi & HAM β pastikan balasan mencerminkan:
|
| 108 |
+
1. **Kedaulatan Rakyat** β hormati konstituen sebagai pemilik kedaulatan.
|
| 109 |
+
2. **Prinsip Demokrasi** β tegaskan peran legislator sebagai wakil rakyat.
|
| 110 |
+
3. **Hak Asasi Manusia** β tanggapi keluhan HAM dengan serius dan spesifik.
|
| 111 |
+
4. **Etika Politik** β jangan buat janji berlebihan; beri ekspektasi realistis.
|
| 112 |
+
|
| 113 |
{KOMISI_DPR_2024_2029}
|
| 114 |
"""
|
| 115 |
|
src/legawa/tools/ethics.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Ethics verifier β post-processing guardrails for democratic values & HAM.
|
| 2 |
+
|
| 3 |
+
Checks agent output against 4 democratic values (people's sovereignty, democracy,
|
| 4 |
+
human rights, political ethics) and appends a considerations section if missing.
|
| 5 |
+
|
| 6 |
+
Inspired by feedback from Taufik Basari, S.H., S.Hum., LL.M.
|
| 7 |
+
(DPR RI 2019β2024).
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from __future__ import annotations
|
| 11 |
+
|
| 12 |
+
from typing import Any
|
| 13 |
+
|
| 14 |
+
VERIFIER_PROMPT = """\
|
| 15 |
+
Periksa apakah analisis berikut sudah mempertimbangkan 4 nilai demokrasi & HAM ini:
|
| 16 |
+
1. **Kedaulatan Rakyat** β partisipasi publik, kepentingan rakyat
|
| 17 |
+
2. **Prinsip Demokrasi** β checks and balances, abuse of power
|
| 18 |
+
3. **Hak Asasi Manusia** β dampak HAM, hak-hak terdampak
|
| 19 |
+
4. **Etika Politik** β do's and don'ts, transparansi, akuntabilitas
|
| 20 |
+
|
| 21 |
+
Jika KEEMPAT nilai sudah dibahas di dalam teks, balas hanya dengan: OK
|
| 22 |
+
Jika ADA yang belum dibahas, hasilkan bagian "### βοΈ Pertimbangan Etika & HAM"
|
| 23 |
+
dalam Bahasa Indonesia yang mencakup nilai-nilai yang kurang. Output markdown."""
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def ethics_verify(
|
| 27 |
+
output: str,
|
| 28 |
+
llm: Any,
|
| 29 |
+
*,
|
| 30 |
+
temperature: float = 0.2,
|
| 31 |
+
max_tokens: int = 512,
|
| 32 |
+
) -> str:
|
| 33 |
+
"""Post-process agent output: append ethics considerations if missing.
|
| 34 |
+
|
| 35 |
+
Args:
|
| 36 |
+
output: The agent's raw markdown output.
|
| 37 |
+
llm: An object with a .chat() method (HFLLM or legawa LLM).
|
| 38 |
+
|
| 39 |
+
Returns:
|
| 40 |
+
Original output, possibly with an appended ethics section.
|
| 41 |
+
"""
|
| 42 |
+
if not output.strip():
|
| 43 |
+
return output
|
| 44 |
+
|
| 45 |
+
try:
|
| 46 |
+
resp = llm.chat(
|
| 47 |
+
messages=[
|
| 48 |
+
{
|
| 49 |
+
"role": "system",
|
| 50 |
+
"content": VERIFIER_PROMPT,
|
| 51 |
+
},
|
| 52 |
+
{
|
| 53 |
+
"role": "user",
|
| 54 |
+
"content": (
|
| 55 |
+
"Berikut adalah output analisis yang sudah dihasilkan.\n"
|
| 56 |
+
"Periksa nilai-nilai demokrasi & HAM:\n\n"
|
| 57 |
+
f"{output[:4000]}"
|
| 58 |
+
),
|
| 59 |
+
},
|
| 60 |
+
],
|
| 61 |
+
temperature=temperature,
|
| 62 |
+
max_tokens=max_tokens,
|
| 63 |
+
)
|
| 64 |
+
text = (resp or "").strip()
|
| 65 |
+
if not text or text.upper().startswith("OK"):
|
| 66 |
+
return output # all values already covered
|
| 67 |
+
|
| 68 |
+
# Append the ethics section
|
| 69 |
+
return output.rstrip() + "\n\n---\n" + text
|
| 70 |
+
|
| 71 |
+
except Exception:
|
| 72 |
+
# Fail-safe: return original output on any error
|
| 73 |
+
return output
|
video/plan.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Legawa Explainer Video β Plan
|
| 2 |
+
|
| 3 |
+
## Narrative Arc
|
| 4 |
+
Problem β Solution β Architecture β Value
|
| 5 |
+
|
| 6 |
+
## Scenes
|
| 7 |
+
|
| 8 |
+
### Scene 1: The Problem (15s)
|
| 9 |
+
- Tall stack of papers labeled "RUU" piles up
|
| 10 |
+
- A stressed legislator figure with question mark
|
| 11 |
+
- Text: "Ratusan RUU setiap tahun... berhari-hari riset per dokumen"
|
| 12 |
+
- Exit: papers fade, revealing Legawa logo
|
| 13 |
+
|
| 14 |
+
### Scene 2: What Legawa Does (25s)
|
| 15 |
+
- 4 agent cards appear one by one:
|
| 16 |
+
- π Analisis RUU β pasal-per-pasal analysis
|
| 17 |
+
- π Riset Hukum β search pasal.id
|
| 18 |
+
- βοΈ Draf Dokumen β draft speeches/policy memos
|
| 19 |
+
- π¬ Surat Konstituen β triage constituent letters
|
| 20 |
+
- Each card slides in with a brief label
|
| 21 |
+
- Final title: "Multi-Agent AI untuk Legislator"
|
| 22 |
+
|
| 23 |
+
### Scene 3: How It Works (30s)
|
| 24 |
+
- Flow diagram from left to right:
|
| 25 |
+
1. Input (RUU text / PDF)
|
| 26 |
+
2. BIG LLM (Qwen3.5-27B) β synthesis & analysis
|
| 27 |
+
3. SMALL LLM (Qwen3.5-9B) β classification & extraction
|
| 28 |
+
4. pasal.id β legal database search
|
| 29 |
+
5. Output β analysis with citations
|
| 30 |
+
- Animated arrows connect each box
|
| 31 |
+
- Final text appears: "Hasil lengkap dalam hitungan menit"
|
| 32 |
+
|
| 33 |
+
### Scene 4: Free & Open (15s)
|
| 34 |
+
- Two tokens float in: HF Token + pasal.id Token
|
| 35 |
+
- Text: "Bawa token sendiri. Buka di GitHub."
|
| 36 |
+
- GitHub link + HF Space link at bottom
|
| 37 |
+
- Closing: "Legawa β small models, big adventure ποΈ"
|
| 38 |
+
|
| 39 |
+
## Color Palette (Warm Academic)
|
| 40 |
+
| Role | Color | Hex |
|
| 41 |
+
|------|-------|-----|
|
| 42 |
+
| Background | Dark navy | `#1a1a2e` |
|
| 43 |
+
| Title text | Warm white | `#eaeaea` |
|
| 44 |
+
| Primary accent | Coral | `#ff6b6b` |
|
| 45 |
+
| Secondary | Gold | `#ffd93d` |
|
| 46 |
+
| Accent | Mint | `#6bcb77` |
|
| 47 |
+
| Muted | Grey | `#888888` |
|
| 48 |
+
|
| 49 |
+
## Technical Notes
|
| 50 |
+
- No LaTeX available β use Text() with monospace font
|
| 51 |
+
- Resolution: 1280x720 (manim -qm)
|
| 52 |
+
- FPS: 30
|
| 53 |
+
- No audio/voiceover β text-based explainer with captions
|
| 54 |
+
- Each scene independently renderable
|
video/script.py
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Legawa Explainer Video β Manim CE script.
|
| 2 |
+
|
| 3 |
+
Scenes:
|
| 4 |
+
1. The Problem β stack of RUU documents
|
| 5 |
+
2. What Legawa Does β 4 agent cards
|
| 6 |
+
3. How It Works β flow diagram
|
| 7 |
+
4. Free & Open β tokens + links
|
| 8 |
+
|
| 9 |
+
Rendered with: manim -qm script.py Scene1_Problem Scene2_Agents Scene3_Flow Scene4_Closing
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
from manim import *
|
| 13 |
+
|
| 14 |
+
# ββ Palette (Warm Academic) ββββββββββββββββββββββββββββββββββββββββββββββ
|
| 15 |
+
BG = "#1a1a2e"
|
| 16 |
+
TITLE = "#eaeaea"
|
| 17 |
+
BODY = "#cccccc"
|
| 18 |
+
PRIMARY = "#ff6b6b"
|
| 19 |
+
SECONDARY = "#ffd93d"
|
| 20 |
+
ACCENT = "#6bcb77"
|
| 21 |
+
MUTED = "#666666"
|
| 22 |
+
MONO = "monospace"
|
| 23 |
+
|
| 24 |
+
# ββ Helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 25 |
+
|
| 26 |
+
def T(text, size=36, color=TITLE, weight=NORMAL):
|
| 27 |
+
"""Shorthand for monospace Text."""
|
| 28 |
+
return Text(text, font_size=size, font=MONO, color=color, weight=weight)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def card_box(width=2.2, height=1.2, color=PRIMARY):
|
| 32 |
+
"""Rounded rect for agent cards."""
|
| 33 |
+
return RoundedRectangle(
|
| 34 |
+
width=width, height=height,
|
| 35 |
+
corner_radius=0.15,
|
| 36 |
+
stroke_color=color, stroke_width=2.5,
|
| 37 |
+
fill_color=color, fill_opacity=0.12,
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 42 |
+
# Scene 1 β The Problem
|
| 43 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 44 |
+
|
| 45 |
+
class Scene1_Problem(Scene):
|
| 46 |
+
def construct(self):
|
| 47 |
+
self.camera.background_color = BG
|
| 48 |
+
|
| 49 |
+
# Papers piling up
|
| 50 |
+
papers = VGroup()
|
| 51 |
+
labels = ["RUU", "RUU", "RUU", "RUU", "RUU", "RUU"]
|
| 52 |
+
colors = [PRIMARY, SECONDARY, ACCENT, PRIMARY, SECONDARY, ACCENT]
|
| 53 |
+
for i, (label, c) in enumerate(zip(labels[:6], colors[:6])):
|
| 54 |
+
w, h = 2.6 - i * 0.15, 3.2 - i * 0.15
|
| 55 |
+
x_shift = i * 0.06
|
| 56 |
+
y_shift = i * 0.08 - 1.0
|
| 57 |
+
paper = Rectangle(
|
| 58 |
+
width=w, height=h,
|
| 59 |
+
stroke_color=c, stroke_width=2,
|
| 60 |
+
fill_color=c, fill_opacity=0.08,
|
| 61 |
+
).shift(RIGHT * x_shift + UP * y_shift)
|
| 62 |
+
text = T(label, size=18, color=c).move_to(paper)
|
| 63 |
+
papers.add(paper, text)
|
| 64 |
+
|
| 65 |
+
self.play(
|
| 66 |
+
LaggedStart(*[FadeIn(p, shift=UP * 0.3, scale=0.9) for p in papers],
|
| 67 |
+
lag_ratio=0.12),
|
| 68 |
+
run_time=2.5,
|
| 69 |
+
)
|
| 70 |
+
self.wait(0.5)
|
| 71 |
+
|
| 72 |
+
# Stressed legislator text
|
| 73 |
+
stress = T("?", size=72, color=PRIMARY, weight=BOLD).shift(RIGHT * 3.2 + UP * 1.0)
|
| 74 |
+
self.play(Write(stress), run_time=0.5)
|
| 75 |
+
self.wait(0.3)
|
| 76 |
+
|
| 77 |
+
# Problem text above the stack
|
| 78 |
+
problem_text = T(
|
| 79 |
+
"Ratusan RUU setiap tahun...\nberhari-hari riset per dokumen",
|
| 80 |
+
size=26, color=BODY,
|
| 81 |
+
).to_edge(UP, buff=0.5)
|
| 82 |
+
self.play(Write(problem_text), run_time=1.2)
|
| 83 |
+
self.wait(1.5)
|
| 84 |
+
|
| 85 |
+
# Fade out to Legawa logo
|
| 86 |
+
logo = T("ποΈ Legawa", size=56, color=SECONDARY, weight=BOLD)
|
| 87 |
+
subtitle = T("Multi-Agent AI untuk Legislator", size=24, color=MUTED).next_to(logo, DOWN)
|
| 88 |
+
|
| 89 |
+
self.play(
|
| 90 |
+
FadeOut(VGroup(*papers, stress, problem_text)),
|
| 91 |
+
run_time=0.8,
|
| 92 |
+
)
|
| 93 |
+
self.play(Write(logo), run_time=1.0)
|
| 94 |
+
self.play(Write(subtitle), run_time=0.8)
|
| 95 |
+
self.wait(1.5)
|
| 96 |
+
self.play(FadeOut(VGroup(logo, subtitle)), run_time=0.5)
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 100 |
+
# Scene 2 β What Legawa Does
|
| 101 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 102 |
+
|
| 103 |
+
class Scene2_Agents(Scene):
|
| 104 |
+
def construct(self):
|
| 105 |
+
self.camera.background_color = BG
|
| 106 |
+
|
| 107 |
+
title = T("Empat Agen dalam Satu Asisten", size=40, color=TITLE, weight=BOLD)
|
| 108 |
+
title.to_edge(UP, buff=0.5)
|
| 109 |
+
self.play(Write(title), run_time=0.8)
|
| 110 |
+
self.wait(0.3)
|
| 111 |
+
|
| 112 |
+
# 4 agent cards in a 2x2 grid
|
| 113 |
+
agents = [
|
| 114 |
+
("π Analisis\nRUU", PRIMARY, "Analisis pasal-per-pasal\nlengkap sitasi"),
|
| 115 |
+
("π Riset\nHukum", ACCENT, "Cari peraturan di\npasal.id"),
|
| 116 |
+
("βοΈ Draf\nDokumen", SECONDARY, "Pidato, naskah akademik,\nmemo kebijakan"),
|
| 117 |
+
("π¬ Surat\nKonstituen", PRIMARY, "Triase & draft\nbalasan"),
|
| 118 |
+
]
|
| 119 |
+
|
| 120 |
+
positions = [
|
| 121 |
+
LEFT * 3 + UP * 0.5,
|
| 122 |
+
RIGHT * 3 + UP * 0.5,
|
| 123 |
+
LEFT * 3 + DOWN * 1.8,
|
| 124 |
+
RIGHT * 3 + DOWN * 1.8,
|
| 125 |
+
]
|
| 126 |
+
|
| 127 |
+
cards = VGroup()
|
| 128 |
+
for (emoji_name, color, desc), pos in zip(agents, positions):
|
| 129 |
+
box = RoundedRectangle(
|
| 130 |
+
width=4.5, height=2.2,
|
| 131 |
+
corner_radius=0.15,
|
| 132 |
+
stroke_color=color, stroke_width=2.5,
|
| 133 |
+
fill_color=color, fill_opacity=0.1,
|
| 134 |
+
).move_to(pos)
|
| 135 |
+
|
| 136 |
+
name_text = T(emoji_name, size=30, color=color, weight=BOLD)
|
| 137 |
+
name_text.move_to(box).shift(UP * 0.3)
|
| 138 |
+
|
| 139 |
+
desc_text = T(desc, size=18, color=BODY)
|
| 140 |
+
desc_text.next_to(name_text, DOWN, buff=0.3)
|
| 141 |
+
|
| 142 |
+
cards.add(VGroup(box, name_text, desc_text))
|
| 143 |
+
|
| 144 |
+
# Animate in one by one
|
| 145 |
+
for card in cards:
|
| 146 |
+
self.play(FadeIn(card, shift=UP * 0.3, scale=0.95), run_time=0.6)
|
| 147 |
+
self.wait(0.15)
|
| 148 |
+
|
| 149 |
+
self.wait(1.0)
|
| 150 |
+
|
| 151 |
+
# Tagline
|
| 152 |
+
tag = T("Small models, big adventure ποΈ", size=24, color=SECONDARY)
|
| 153 |
+
tag.to_edge(DOWN, buff=0.4)
|
| 154 |
+
self.play(Write(tag), run_time=0.8)
|
| 155 |
+
self.wait(1.5)
|
| 156 |
+
|
| 157 |
+
self.play(FadeOut(VGroup(title, cards, tag)), run_time=0.5)
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 161 |
+
# Scene 3 β How It Works (Flow Diagram)
|
| 162 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 163 |
+
|
| 164 |
+
class Scene3_Flow(Scene):
|
| 165 |
+
def construct(self):
|
| 166 |
+
self.camera.background_color = BG
|
| 167 |
+
|
| 168 |
+
title = T("Bagaimana Cara Kerjanya?", size=36, color=TITLE, weight=BOLD)
|
| 169 |
+
title.to_edge(UP, buff=0.4)
|
| 170 |
+
self.play(Write(title), run_time=0.6)
|
| 171 |
+
self.wait(0.2)
|
| 172 |
+
|
| 173 |
+
def make_box(text_lines, color, w=2.2, h=1.0):
|
| 174 |
+
"""Helper: box with centered text."""
|
| 175 |
+
box = RoundedRectangle(
|
| 176 |
+
width=w, height=h,
|
| 177 |
+
corner_radius=0.12,
|
| 178 |
+
stroke_color=color, stroke_width=2,
|
| 179 |
+
fill_color=color, fill_opacity=0.1,
|
| 180 |
+
)
|
| 181 |
+
txt = T(text_lines, size=18, color=TITLE)
|
| 182 |
+
txt.move_to(box)
|
| 183 |
+
return VGroup(box, txt)
|
| 184 |
+
|
| 185 |
+
def arrow_between(left_obj, right_obj, color=MUTED):
|
| 186 |
+
"""Arrow from right edge of left_obj to left edge of right_obj."""
|
| 187 |
+
start = left_obj.get_right() + RIGHT * 0.15
|
| 188 |
+
end = right_obj.get_left() + LEFT * 0.15
|
| 189 |
+
arr = Arrow(
|
| 190 |
+
start, end,
|
| 191 |
+
stroke_width=2.5,
|
| 192 |
+
stroke_color=color,
|
| 193 |
+
max_tip_length_to_length_ratio=0.08,
|
| 194 |
+
)
|
| 195 |
+
return arr
|
| 196 |
+
|
| 197 |
+
# Row 1: Input
|
| 198 |
+
input_box = make_box("π\nInput RUU", ACCENT, w=2.0, h=1.2)
|
| 199 |
+
input_box.shift(LEFT * 5.5 + UP * 0.5)
|
| 200 |
+
|
| 201 |
+
# Row 1: BIG LLM
|
| 202 |
+
big_box = make_box("π§ BIG\nQwen3.5-27B", PRIMARY, w=2.2, h=1.2)
|
| 203 |
+
big_box.shift(LEFT * 2.0 + UP * 0.5)
|
| 204 |
+
|
| 205 |
+
# Row 1: pasal.id
|
| 206 |
+
pasal_box = make_box("π\npasal.id", SECONDARY, w=2.0, h=1.2)
|
| 207 |
+
pasal_box.shift(RIGHT * 1.2 + UP * 0.5)
|
| 208 |
+
|
| 209 |
+
# Row 2: SMALL LLM
|
| 210 |
+
small_box = make_box("π§ SMALL\nQwen3.5-9B", PRIMARY, w=2.2, h=1.2)
|
| 211 |
+
small_box.shift(LEFT * 1.0 + DOWN * 1.3)
|
| 212 |
+
|
| 213 |
+
# Row 2: Output
|
| 214 |
+
output_box = make_box("β
\nHasil Analisis", ACCENT, w=2.0, h=1.2)
|
| 215 |
+
output_box.shift(RIGHT * 3.5 + DOWN * 1.3)
|
| 216 |
+
|
| 217 |
+
# Animate in
|
| 218 |
+
self.play(FadeIn(input_box, shift=RIGHT * 0.5), run_time=0.6)
|
| 219 |
+
self.wait(0.15)
|
| 220 |
+
self.play(FadeIn(big_box, shift=RIGHT * 0.5), run_time=0.6)
|
| 221 |
+
self.wait(0.15)
|
| 222 |
+
|
| 223 |
+
# Arrow: input β BIG
|
| 224 |
+
arr1 = arrow_between(input_box, big_box)
|
| 225 |
+
self.play(Create(arr1), run_time=0.3)
|
| 226 |
+
|
| 227 |
+
self.play(FadeIn(pasal_box, shift=RIGHT * 0.5), run_time=0.6)
|
| 228 |
+
arr2 = arrow_between(big_box, pasal_box)
|
| 229 |
+
self.play(Create(arr2), run_time=0.3)
|
| 230 |
+
self.wait(0.3)
|
| 231 |
+
|
| 232 |
+
self.play(FadeIn(small_box, shift=UP * 0.5), run_time=0.6)
|
| 233 |
+
# Arrow: BIG β SMALL (down-left)
|
| 234 |
+
arr3 = Arrow(
|
| 235 |
+
big_box.get_bottom() + DOWN * 0.15,
|
| 236 |
+
small_box.get_top() + UP * 0.15,
|
| 237 |
+
stroke_width=2.5, stroke_color=MUTED,
|
| 238 |
+
max_tip_length_to_length_ratio=0.08,
|
| 239 |
+
)
|
| 240 |
+
self.play(Create(arr3), run_time=0.3)
|
| 241 |
+
|
| 242 |
+
# Arrow: SMALL β Output (right)
|
| 243 |
+
arr4 = Arrow(
|
| 244 |
+
small_box.get_right() + RIGHT * 0.15,
|
| 245 |
+
output_box.get_left() + LEFT * 0.15,
|
| 246 |
+
stroke_width=2.5, stroke_color=MUTED,
|
| 247 |
+
max_tip_length_to_length_ratio=0.08,
|
| 248 |
+
)
|
| 249 |
+
self.play(FadeIn(output_box, shift=RIGHT * 0.5), run_time=0.6)
|
| 250 |
+
self.play(Create(arr4), run_time=0.3)
|
| 251 |
+
|
| 252 |
+
self.wait(0.5)
|
| 253 |
+
|
| 254 |
+
# Arrow: pasal.id β SMALL (feed back)
|
| 255 |
+
arr5 = Arrow(
|
| 256 |
+
pasal_box.get_bottom() + DOWN * 0.15,
|
| 257 |
+
small_box.get_right() + RIGHT * 0.5,
|
| 258 |
+
stroke_width=2, stroke_color=SECONDARY,
|
| 259 |
+
max_tip_length_to_length_ratio=0.08,
|
| 260 |
+
)
|
| 261 |
+
# Curve it by adding a path
|
| 262 |
+
self.play(Create(arr5), run_time=0.4)
|
| 263 |
+
|
| 264 |
+
# Value text
|
| 265 |
+
value = T("Hasil lengkap dalam hitungan menit β‘", size=28, color=SECONDARY, weight=BOLD)
|
| 266 |
+
value.to_edge(DOWN, buff=0.5)
|
| 267 |
+
self.play(Write(value), run_time=0.8)
|
| 268 |
+
self.wait(2.0)
|
| 269 |
+
|
| 270 |
+
self.play(FadeOut(VGroup(
|
| 271 |
+
title, input_box, big_box, pasal_box, small_box, output_box,
|
| 272 |
+
arr1, arr2, arr3, arr4, arr5, value,
|
| 273 |
+
)), run_time=0.5)
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 277 |
+
# Scene 4 β Free & Open (Closing)
|
| 278 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 279 |
+
|
| 280 |
+
class Scene4_Closing(Scene):
|
| 281 |
+
def construct(self):
|
| 282 |
+
self.camera.background_color = BG
|
| 283 |
+
|
| 284 |
+
# Token icons β spread wider
|
| 285 |
+
hf_token = T("π HF Token", size=32, color=PRIMARY, weight=BOLD)
|
| 286 |
+
pasal_token = T("π pasal.id Token", size=32, color=SECONDARY, weight=BOLD)
|
| 287 |
+
|
| 288 |
+
hf_token.shift(LEFT * 3.5 + UP * 0.8)
|
| 289 |
+
pasal_token.shift(RIGHT * 3.5 + UP * 0.8)
|
| 290 |
+
|
| 291 |
+
self.play(FadeIn(hf_token, shift=UP * 0.5), run_time=0.6)
|
| 292 |
+
self.wait(0.2)
|
| 293 |
+
self.play(FadeIn(pasal_token, shift=UP * 0.5), run_time=0.6)
|
| 294 |
+
self.wait(0.3)
|
| 295 |
+
|
| 296 |
+
# Tagline β centered below both tokens
|
| 297 |
+
byo = T("Bawa token sendiri. Buka di GitHub.", size=26, color=BODY)
|
| 298 |
+
byo.shift(DOWN * 0.5)
|
| 299 |
+
self.play(Write(byo), run_time=0.8)
|
| 300 |
+
self.wait(0.3)
|
| 301 |
+
|
| 302 |
+
# Links
|
| 303 |
+
gh = T("github.com/pebaryan/Legawa", size=20, color=ACCENT)
|
| 304 |
+
hf = T("build-small-hackathon/legawa", size=20, color=ACCENT)
|
| 305 |
+
gh.to_edge(DOWN, buff=0.8)
|
| 306 |
+
hf.next_to(gh, DOWN, buff=0.2)
|
| 307 |
+
|
| 308 |
+
self.play(
|
| 309 |
+
Write(gh), Write(hf),
|
| 310 |
+
run_time=0.6,
|
| 311 |
+
)
|
| 312 |
+
self.wait(0.5)
|
| 313 |
+
|
| 314 |
+
# Final brand
|
| 315 |
+
final = T("ποΈ Legawa β small models, big adventure ποΈ", size=34, color=SECONDARY, weight=BOLD)
|
| 316 |
+
final.to_edge(UP, buff=1.5)
|
| 317 |
+
self.play(Write(final), run_time=1.0)
|
| 318 |
+
self.wait(2.0)
|
| 319 |
+
|
| 320 |
+
# Clean exit
|
| 321 |
+
self.play(*[FadeOut(mob) for mob in self.mobjects], run_time=0.5)
|