deankk/CapGen-AI
0
1"""2✍️ CapGen AI — Caption Generator Indonesia (v2)3Versi improved: few-shot prompting + generate 1 per 14"""5 6import gradio as gr7import torch8from transformers import AutoTokenizer, AutoModelForCausalLM9 10MODEL_NAME = "Qwen/Qwen2.5-3B-Instruct"11 12print(f"⏳ Loading {MODEL_NAME}...")13tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True)14model = AutoModelForCausalLM.from_pretrained(15 MODEL_NAME,16 torch_dtype=torch.bfloat16,17 low_cpu_mem_usage=True,18 trust_remote_code=True,19)20model.eval()21print("✅ Model siap!")22 23# ══════════════════════════════════════════════════════24# CONTOH CAPTION PER GAYA (ini kunci kualitasnya)25# ══════════════════════════════════════════════════════26 27CONTOH = {28 "Aesthetic": {29 "Instagram": (30 "Contoh caption bagus:\n"31 '"Di antara decitan kerupuk dan aroma rempah, hidup rasanya lengkap 🌾✨\n'32 '#warungcita #hidupsimple #nungguinmatahari"\n'33 '"Senja kali ini bilang, kamu boleh lelah tapi jangan berhenti 🌅\n'34 '#senja #istirahatsejenak #catatanpetang"'35 ),36 "TikTok": (37 "Contoh caption bagus:\n"38 '"Senja ngomong kamu cantik hari ini 🧡"\n'39 '"Yang penting bahagia, sisanya urusan nanti 🌿"'40 ),41 "default": (42 "Contoh caption bagus:\n"43 '"Hal kecil yang bikin senyum tanpa alasan 🌿✨\n'44 '#simplejoy #catatanhariini"\n'45 '"Beberapa hal indah datang tanpa direncanakan 🌙\n'46 '#unexpected #grateful"'47 ),48 },49 "Lucu": {50 "Instagram": (51 "Contoh caption bagus:\n"52 '"Diet mulai Senin... Senin apa ya yang mana 🤔\n'53 '#dietgagal #makanyangpenting #realita"\n'54 '"Hidupku kayak WiFi kantoran, kadang nyambung kadang nggak 📶😅\n'55 '#hidupini #relatable #galau"'56 ),57 "default": (58 "Contoh caption bagus:\n"59 '"Udah usaha maksimal, hasilnya tetap minimal 🤡"\n'60 '"Mau kaya tapi males, gimana tuh 😭"'61 ),62 },63 "Jualan": {64 "Instagram": (65 "Contoh caption bagus:\n"66 '"PROMO TERBATAS! 🔥 Serum glow yang biasanya 150rb, sekarang CUMA 89rb!\n'67 'Stok terbatas, siapa cepat dia dapat 💕\n'68 'Order via DM / klik link di bio!\n'69 '#skincare #serumglow #promoskincare #diskon"\n'70 '"Best seller kami kembali hadir! ✨ Celana linin premium yang nyaman seharian.\n'71 'Harga normal 199rb → NOW 129rb aja!\n'72 'Tersisa 30 pcs saja, buruan!\n'73 '#celanalinen #fashionpria #diskon"'74 ),75 "TikTok": (76 "Contoh caption bagus:\n"77 '"Harga 89rb aja buat kulit glowing! Link di bio 🔗🔥"'78 ),79 "default": (80 "Contoh caption bagus:\n"81 '"Promo hari ini! Dari 150rb jadi 89rb aja 🔥\n'82 'Order langsung via DM ya!\n'83 '#promohariini #diskon"'84 ),85 },86 "Motivasi": {87 "default": (88 "Contoh caption bagus:\n"89 '"Proses itu berat, tapi hasilnya buat kamu bangun bangun pagi 🔥\n'90 '#motivasipagi #hustle #nevergiveup"\n'91 '"Kamu bukan lambat, kamu lagi build fondasi yang kuat 💪\n'92 '#proses #trusttheprocess #growth"'93 ),94 },95 "Romantis": {96 "default": (97 "Contoh caption bagus:\n"98 '"Kamu tuh kayak kopi pagi, tanpa kamu seharian rasanya hambar ☕💕\n'99 '#pacarsayang #romantis #cintalokal"\n'100 '"Senyum kamu itu autofokus, yang lain jadi blur semua 📸💗\n'101 '#jodoh #romance #lovestory"'102 ),103 },104 "Gaul/Jaksel": {105 "default": (106 "Contoh caption bagus:\n"107 '"Literally this is the best day ever bangeet bestie 🫶✨\n'108 '#bestie #jaksel #vibeskekinian"\n'109 '"Makanannya literally bikin crying aaah parah banget sih 🍜😭\n'110 '#foodie #makanenak #gaul"'111 ),112 },113 "Santai": {114 "default": (115 "Contoh caption bagus:\n"116 '"Hari ini ngga ada rencana spesial, tapi ngga apa-apa kok 🌿\n'117 '#chill #santai #flow"\n'118 '"Nongkrong, ngopi, ngobrol ga jelas. Tapi bahagia ☕😊\n'119 '#weekendvibes #hangout #qualitytime"'120 ),121 },122 "Formal": {123 "default": (124 "Contoh caption bagus:\n"125 '"Kami mengucapkan terima kasih atas kehadiran Bapak/Ibu dalam acara seminar nasional ini.\n'126 'Semoga ilmu yang diperoleh dapat bermanfaat.\n'127 '#seminar #profesional #diknas"\n'128 '"Peluncuran program terbaru kami telah berlangsung dengan baik.\n'129 'Kami berharap program ini dapat memberikan dampak positif bagi masyarakat.\n'130 '#programbaru #layananpublik"'131 ),132 },133}134 135# Fallback kalau gaya/platform ga ada di contoh136def get_contoh(gaya, platform):137 if gaya in CONTOH:138 if platform in CONTOH[gaya]:139 return CONTOH[gaya][platform]140 return CONTOH[gaya]["default"]141 return CONTOH["Aesthetic"]["default"]142 143 144PLATFORMS = {145 "Instagram": "Maks 2200 karakter. Boleh panjang, emoji bebas, hashtag penting.",146 "TikTok": "Maks 150 karakter. SANGAT pendek, catchy.",147 "Twitter/X": "Maks 280 karakter. Padat dan tajam.",148 "LinkedIn": "Maks 3000 karakter. Profesional tapi engaging.",149 "WhatsApp Status": "Maks 100 karakter. Sangat singkat.",150}151 152STYLES = {153 "Aesthetic": "Puitis, minimalis emoji, kata-kata indah",154 "Lucu": "Punchline di akhir, bahasa slang, relatable",155 "Jualan": "Ada harga/CTA, urgency, benefit jelas",156 "Motivasi": "Inspiratif, powerful words, emoji api",157 "Romantis": "Manis, puitis, emoji hati",158 "Gaul/Jaksel": "Slang Jakarta: literally, bestie, bangeet",159 "Santai": "Casual, kayak ngobrol sama temen",160 "Formal": "Bahasa baku, sopan",161}162 163LANGUAGES = {164 "Indonesia": "Bahasa Indonesia standar",165 "Indonesia Gaul": "Slang gaul kekinian",166 "Bahasa Jawa": "Bahasa Jawa (ngoko/krama)",167 "Bahasa Sunda": "Bahasa Sunda gaul",168 "English": "Bahasa Inggris natural",169 "Bilingual": "Campuran Indonesia dan English",170}171 172PRESETS = {173 "🍜 Makanan": "Foto makan nasi goreng spesial di warung pinggir jalan, porsi besar, harga cuma 15rb, ada telur dan kerupuk",174 "🌅 Sunset": "Foto sunset di pantai Bali, langit oranye kemerahan, pegang minuman kelapa, rambut diterpa angin",175 "🛒 Jualan": "Foto produk skincare baru, botol kaca bening, tekstur gel, harga normal 150rb promo jadi 89rb",176 "🏢 Kantor": "Foto bareng tim kantor setelah selesai project besar, semua senang, pakai seragam kompak",177 "🐱 Kucing": "Foto kucing tidur miring di bantal pink, lucu banget, bulunya lembut dan fluffy",178 "🛵 Jalan-jalan": "Foto naik motor melalui jalan Malioboro malam hari, lampu warna-warni, habis belanja batik",179 "🏋️ Gym": "Foto habis workout di gym, badan berkeringat, baju sport, terlihat bugar dan proud",180 "📚 Belajar": "Foto meja belajar yang rapi, ada buku dan kopi, lampu belajar warm light, vibes produktif",181}182 183# ══════════════════════════════════════════════════════184# LOGIC185# ══════════════════════════════════════════════════════186 187def generate_text(messages):188 text = tokenizer.apply_chat_template(189 messages, tokenize=False, add_generation_prompt=True190 )191 inputs = tokenizer(text, return_tensors="pt")192 with torch.no_grad():193 outputs = model.generate(194 **inputs,195 max_new_tokens=300,196 temperature=0.7,197 top_p=0.85,198 top_k=50,199 do_sample=True,200 repetition_penalty=1.2,201 )202 new_tokens = outputs[0][inputs["input_ids"].shape[-1]:]203 return tokenizer.decode(new_tokens, skip_special_tokens=True).strip()204 205 206def build_prompt(konteks, platform, gaya, bahasa):207 contoh = get_contoh(gaya, platform)208 return (209 f"Buat SATU caption untuk:\n\n"210 f"Platform: {platform} ({PLATFORMS[platform]})\n"211 f"Gaya: {gaya} ({STYLES[gaya]})\n"212 f"Bahasa: {bahasa} ({LANGUAGES[bahasa]})\n"213 f"Konteks: {konteks.strip()}\n\n"214 f"{contoh}\n\n"215 f"Sekarang buat SATU caption baru yang BERBEDA dari contoh di atas, "216 f"untuk konteks yang saya berikan. Langsung tulis caption-nya saja, "217 f"sertakan 3-6 hashtag di akhir."218 )219 220 221def clean_output(text):222 """Bersihkan output dari kalimat pembuka yang tidak perlu"""223 lines_to_remove = [224 "Baik, berikut captionnya:", "Berikut captionnya:",225 "Berikut:", "Tentu, berikut:", "Oke, berikut:",226 "Baik,", "Tentu,", "Oke,", "Here is", "Sure, here",227 "Berikut adalah caption yang kamu minta:",228 "Ini captionnya:", "Nih captionnya:",229 ]230 for line in lines_to_remove:231 if text.strip().startswith(line):232 text = text.strip()[len(line):].strip()233 if text.startswith(":"):234 text = text[1:].strip()235 return text.strip()236 237 238def generate_caption(konteks, platform, gaya, bahasa):239 if not konteks or not konteks.strip():240 return "⚠️ Tulis dulu deskripsi foto/konten kamu!"241 242 system_msg = (243 "Kamu adalah penulis caption media sosial profesional bahasa Indonesia. "244 "Kamu HANYA mengeluarkan caption. Tidak ada penjelasan, tidak ada pembuka, "245 "tidak ada kata 'Berikut'. Langsung tulis caption saja."246 )247 user_msg = build_prompt(konteks, platform, gaya, bahasa)248 249 messages = [250 {"role": "system", "content": system_msg},251 {"role": "user", "content": user_msg}252 ]253 254 try:255 result = generate_text(messages)256 except Exception as e:257 return f"❌ Error: {str(e)[:200]}"258 259 return clean_output(result)260 261 262def fill_preset(name):263 return PRESETS.get(name, "")264 265 266# ══════════════════════════════════════════════════════267# UI — DIUBAH: Generate 1 caption, bisa klik berkali-kali268# ══════════════════════════════════════════════════════269 270CUSTOM_CSS = """271#btn-generate {272 font-size: 18px !important;273 font-weight: 600 !important;274 padding: 14px 32px !important;275 border-radius: 12px !important;276}277.output-box textarea {278 font-size: 16px !important;279 line-height: 1.8 !important;280 border-radius: 10px !important;281 min-height: 120px !important;282}283.preset-row button {284 min-width: fit-content !important;285 font-size: 13px !important;286}287.footer-text {288 text-align: center;289 color: #aaa;290 font-size: 12px;291 margin-top: 16px;292}293.hint-text {294 font-size: 13px;295 color: #888;296 margin-top: 4px;297}298"""299 300with gr.Blocks() as demo:301 302 gr.Markdown("""303 <div style="text-align:center; padding:8px 0 4px 0;">304 <h1 style="margin:0;">✍️ CapGen AI</h1>305 <p style="font-size:17px; color:#555; margin:4px 0 0 0;">306 Caption Generator Khusus Bahasa Indonesia307 </p>308 <p style="font-size:13px; color:#999; margin:2px 0 0 0;">309 Gak perlu login · Gak perlu token · Langsung kepakai310 </p>311 </div>312 """)313 314 with gr.Row(equal_height=False):315 316 with gr.Column(scale=1):317 318 gr.Markdown("### 📝 Ceritakan Foto/Konten Kamu")319 konteks = gr.Textbox(320 placeholder=(321 "Contoh: Foto makan nasi goreng di warung pinggir jalan, "322 "porsi besar, harga 15rb, ada telur mata sapi..."323 ),324 lines=4,325 max_lines=8,326 show_label=False,327 )328 329 gr.Markdown("### ⚡ Contoh Cepat (klik = langsung isi)")330 preset_names = list(PRESETS.keys())331 for row_start in range(0, len(preset_names), 4):332 with gr.Row(elem_classes=["preset-row"]):333 for name in preset_names[row_start:row_start + 4]:334 gr.Button(name, size="sm", variant="secondary").click(335 fn=lambda n=name: fill_preset(n),336 outputs=konteks,337 )338 339 gr.Markdown("### ⚙️ Pengaturan")340 with gr.Row():341 platform = gr.Dropdown(342 choices=list(PLATFORMS.keys()),343 value="Instagram",344 label="📱 Platform",345 )346 gaya = gr.Dropdown(347 choices=list(STYLES.keys()),348 value="Aesthetic",349 label="🎨 Gaya",350 )351 352 with gr.Row():353 bahasa = gr.Dropdown(354 choices=list(LANGUAGES.keys()),355 value="Indonesia",356 label="🌐 Bahasa",357 )358 359 btn_generate = gr.Button(360 "✨ Generate Caption",361 variant="primary",362 size="lg",363 elem_id="btn-generate",364 )365 gr.Markdown(366 '<p class="hint-text">💡 Klik berkali-kali untuk hasil berbeda-beda</p>'367 )368 369 with gr.Column(scale=1):370 gr.Markdown("### 📋 Caption Kamu")371 372 output_caption = gr.Textbox(373 label="",374 lines=5,375 max_lines=10,376 interactive=False,377 show_label=False,378 placeholder="Hasil caption akan muncul di sini...",379 elem_classes=["output-box"],380 )381 382 gr.Markdown(383 '<p class="hint-text">Blok teks di atas lalu Ctrl+C / Cmd+C untuk copy</p>'384 )385 386 with gr.Accordion("📜 Riwayat Caption", open=False):387 history = gr.Textbox(388 label="",389 lines=12,390 interactive=False,391 show_label=False,392 placeholder="Caption-caption sebelumnya akan muncul di sini...",393 )394 395 # State untuk riwayat396 history_state = gr.State("")397 398 def generate_with_history(konteks, platform, gaya, bahasa, prev_history):399 caption = generate_caption(konteks, platform, gaya, bahasa)400 401 if not caption.startswith("⚠️") and not caption.startswith("❌"):402 separator = "\n" + "─" * 40 + "\n"403 new_history = f"[{platform} · {gaya} · {bahasa}]\n{caption}{separator}{prev_history}"404 return caption, new_history405 return caption, prev_history406 407 btn_generate.click(408 fn=generate_with_history,409 inputs=[konteks, platform, gaya, bahasa, history_state],410 outputs=[output_caption, history_state],411 )412 btn_generate.click(413 fn=lambda h: h,414 inputs=[history_state],415 outputs=[history],416 )417 418 gr.Markdown(419 '<div class="footer-text">'420 "Dibuat dengan ❤️ untuk komunitas Indonesia · "421 f"Model: {MODEL_NAME}"422 "</div>"423 )424 425demo.launch(426 theme=gr.themes.Soft(427 primary_hue="orange",428 secondary_hue="amber",429 neutral_hue="stone",430 ),431 css=CUSTOM_CSS,432)