| import gradio as gr |
| import json |
| import numpy as np |
| import pickle |
| import random |
| import re |
| from datetime import datetime |
| import requests |
| import xml.etree.ElementTree as ET |
|
|
| |
| import nltk |
| nltk.download('punkt', quiet=True) |
| nltk.download('wordnet', quiet=True) |
| nltk.download('omw-1.4', quiet=True) |
|
|
| from nltk.stem import WordNetLemmatizer |
| from nltk.tokenize import word_tokenize |
|
|
| |
| import tensorflow as tf |
| from tensorflow.keras.models import load_model |
| from tensorflow.keras.preprocessing.sequence import pad_sequences |
|
|
| |
| np.random.seed(42) |
| tf.random.set_seed(42) |
| random.seed(42) |
|
|
| |
| |
| |
|
|
| class SmartBencanaChatbot: |
| def __init__(self): |
| """Initialize the chatbot with pre-trained model""" |
| print("π Loading model...") |
| |
| |
| self.model = load_model('chatbot_model.keras') |
| |
| |
| with open('tokenizer.pickle', 'rb') as f: |
| self.tokenizer = pickle.load(f) |
| |
| |
| with open('label_encoder.pickle', 'rb') as f: |
| self.label_encoder = pickle.load(f) |
| |
| |
| with open('responses.pickle', 'rb') as f: |
| self.responses = pickle.load(f) |
| |
| |
| self.max_len = 50 |
| |
| |
| self.conversation_history = [] |
| self.current_context = None |
| self.last_intent = None |
| |
| |
| self.lemmatizer = WordNetLemmatizer() |
| |
| |
| self.emergency_keywords = { |
| 'critical': ['darurat', 'tolong', 'bahaya', 'mati', 'terjebak', 'hancur', |
| 'tenggelam', 'pingsan', 'luka parah', 'pendarahan', 'sesak'], |
| 'high': ['cepat', 'segera', 'urgent', 'evakuasi', 'gempa', 'tsunami', |
| 'banjir besar', 'longsor', 'kebakaran'], |
| 'medium': ['siaga', 'waspada', 'peringatan', 'persiapan'], |
| 'low': ['info', 'informasi', 'apa itu', 'jelaskan', 'bagaimana'] |
| } |
| |
| |
| self.politeness_patterns = [ |
| r'\b(terima\s*kasih|thanks?|makasih|thx)\b', |
| r'\b(halo|hai|hello|hi|hey|hallo)\b', |
| r'\b(selamat\s+(pagi|siang|sore|malam))\b', |
| r'\b(permisi|maaf|excuse\s*me)\b', |
| r'\b(baik|oke?|ok|ya|siap)\b', |
| r'\b(sampai\s*jumpa|bye|dadah)\b' |
| ] |
| |
| |
| self.politeness_responses = { |
| 'greeting': [ |
| "Halo! Saya siap membantu dengan informasi kesiapsiagaan bencana. Ada yang bisa saya bantu?", |
| "Hai! Selamat datang di Chatbot BantuDarurat. Silakan tanyakan tentang bencana atau pertolongan pertama.", |
| "Selamat datang! Saya chatbot kesiapsiagaan bencana. Bagaimana saya bisa membantu Anda hari ini?" |
| ], |
| 'thanks': [ |
| "Sama-sama! Senang bisa membantu. Jangan ragu bertanya lagi jika butuh informasi.", |
| "Terima kasih kembali! Tetap waspada dan siaga ya!", |
| "Dengan senang hati! Semoga informasinya berguna. Stay safe!" |
| ], |
| 'goodbye': [ |
| "Sampai jumpa! Tetap siaga dan waspada!", |
| "Terima kasih sudah menggunakan layanan kami. Stay safe!", |
| "Bye! Jaga diri baik-baik dan selalu siap siaga!" |
| ], |
| 'acknowledge': [ |
| "Baik! Ada yang bisa saya bantu lagi?", |
| "Siap! Silakan tanyakan jika ada yang ingin diketahui.", |
| "Oke! Saya siap membantu kapan saja." |
| ] |
| } |
| |
| print("β
Model loaded successfully!") |
| |
| def preprocess_text(self, text): |
| """Clean and preprocess text""" |
| text = text.lower() |
| text = re.sub(r'[^a-zA-Z0-9\s]', '', text) |
| tokens = word_tokenize(text) |
| tokens = [self.lemmatizer.lemmatize(word) for word in tokens] |
| return ' '.join(tokens) |
| |
| def detect_emergency_level(self, text): |
| """Detect emergency level from user input""" |
| text_lower = text.lower() |
| |
| for level, keywords in self.emergency_keywords.items(): |
| for keyword in keywords: |
| if keyword in text_lower: |
| return level |
| |
| return 'low' |
| |
| def detect_politeness(self, text): |
| """Detect if input is politeness/greeting""" |
| text_lower = text.lower().strip() |
| |
| for pattern in self.politeness_patterns: |
| if re.search(pattern, text_lower): |
| |
| if re.search(r'\b(halo|hai|hello|hi|selamat)\b', text_lower): |
| return 'greeting' |
| elif re.search(r'\b(terima|thanks?|makasih)\b', text_lower): |
| return 'thanks' |
| elif re.search(r'\b(bye|jumpa|dadah)\b', text_lower): |
| return 'goodbye' |
| else: |
| return 'acknowledge' |
| |
| return None |
| |
| def select_best_response(self, intent, user_text, emergency_level): |
| """Select the most appropriate response""" |
| possible_responses = self.responses.get(intent, []) |
| |
| if not possible_responses: |
| return "Maaf, saya tidak memiliki informasi untuk pertanyaan tersebut." |
| |
| |
| if emergency_level in ['critical', 'high']: |
| urgent_responses = [r for r in possible_responses |
| if any(word in r.lower() for word in |
| ['segera', 'langsung', 'darurat', 'cepat', 'evakuasi'])] |
| if urgent_responses: |
| return random.choice(urgent_responses) |
| |
| return random.choice(possible_responses) |
| |
| def predict(self, text, confidence_threshold=0.3): |
| """Predict intent and generate response""" |
| |
| |
| politeness_type = self.detect_politeness(text) |
| if politeness_type: |
| response = random.choice(self.politeness_responses[politeness_type]) |
| return { |
| 'intent': 'Politeness', |
| 'confidence': 1.0, |
| 'response': response, |
| 'emergency_level': 'none', |
| 'is_politeness': True |
| } |
| |
| |
| if len(text.strip()) < 3: |
| return { |
| 'intent': 'Unknown', |
| 'confidence': 0.0, |
| 'response': "Maaf, pertanyaan terlalu pendek. Bisa diperjelas?", |
| 'emergency_level': 'none', |
| 'is_unclear': True |
| } |
| |
| |
| emergency_level = self.detect_emergency_level(text) |
| |
| |
| processed_text = self.preprocess_text(text) |
| |
| |
| sequence = self.tokenizer.texts_to_sequences([processed_text]) |
| padded = pad_sequences(sequence, maxlen=self.max_len, |
| truncating='post', padding='post') |
| |
| prediction = self.model.predict(padded, verbose=0) |
| confidence = np.max(prediction) |
| predicted_class = np.argmax(prediction) |
| |
| intent = self.label_encoder.inverse_transform([predicted_class])[0] |
| |
| if confidence >= confidence_threshold: |
| response = self.select_best_response(intent, text, emergency_level) |
| self.current_context = intent |
| self.last_intent = intent |
| else: |
| response = "Maaf, saya tidak yakin dengan pertanyaan Anda. Bisakah Anda mengulanginya dengan kata yang berbeda?" |
| intent = "Unknown" |
| |
| result = { |
| 'intent': intent, |
| 'confidence': float(confidence), |
| 'response': response, |
| 'emergency_level': emergency_level, |
| 'is_politeness': False, |
| 'is_unclear': False |
| } |
| |
| self.conversation_history.append({ |
| 'user': text, |
| 'bot': result['response'], |
| 'intent': intent, |
| 'emergency': emergency_level, |
| 'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S") |
| }) |
| |
| return result |
|
|
| |
| |
| |
|
|
| def ambil_info_bmkg(): |
| """Fetch latest earthquake info from BMKG""" |
| try: |
| res_gempa = requests.get("https://data.bmkg.go.id/DataMKG/TEWS/autogempa.xml", timeout=10) |
| root_gempa = ET.fromstring(res_gempa.content) |
| gempa = root_gempa.find('gempa') |
| |
| info_gempa = ( |
| f"β οΈ **GEMPA TERKINI (M > 5.0)**\n\n" |
| f"π
Waktu: {gempa.find('Tanggal').text} | {gempa.find('Jam').text}\n" |
| f"π Magnitudo: {gempa.find('Magnitude').text} SR\n" |
| f"π Kedalaman: {gempa.find('Kedalaman').text}\n" |
| f"π Lokasi: {gempa.find('Wilayah').text}\n" |
| f"β‘ Potensi: {gempa.find('Potensi').text}\n\n" |
| f"βΉοΈ Sumber: BMKG Indonesia" |
| ) |
| |
| return info_gempa |
| except Exception as e: |
| return f"β οΈ Gagal memuat data BMKG. Error: {str(e)}" |
|
|
| def ambil_cuaca_bmkg(wilayah): |
| """Mengambil informasi cuaca berdasarkan wilayah yang dipilih dengan penanganan error XML""" |
| wilayah_map = { |
| "DKI Jakarta": "DKIJakarta", |
| "Jawa Barat": "JawaBarat", |
| "Jawa Tengah": "JawaTengah", |
| "Jawa Timur": "JawaTimur", |
| "Sumatera Utara": "SumateraUtara", |
| "Sulawesi Selatan": "SulawesiSelatan" |
| } |
| |
| file_provinsi = wilayah_map.get(wilayah, "Indonesia") |
| |
| try: |
| url = f"https://data.bmkg.go.id/DataMKG/MEWS/DigitalForecast/DigitalForecast-{file_provinsi}.xml" |
| res = requests.get(url, timeout=10) |
| |
| |
| if res.status_code != 200 or not res.content: |
| return f"β οΈ Server BMKG sedang tidak merespon untuk wilayah {wilayah}." |
|
|
| root = ET.fromstring(res.content) |
| |
| weather_codes = { |
| "0": "Cerah", "1": "Cerah Berawan", "2": "Cerah Berawan", |
| "3": "Berawan", "4": "Berawan Tebal", "5": "Udara Kabur", |
| "10": "Asap", "45": "Kabut", "60": "Hujan Ringan", |
| "61": "Hujan Sedang", "63": "Hujan Lebat", "80": "Hujan Lokal", |
| "95": "Hujan Petir", "97": "Hujan Petir" |
| } |
|
|
| |
| area = root.find(".//area") |
| if area is None: |
| return f"β οΈ Data cuaca spesifik untuk {wilayah} belum tersedia di database BMKG." |
|
|
| name = area.get('description') |
| weather_param = area.find(".//parameter[@id='weather']") |
| |
| if weather_param is not None: |
| |
| val_element = weather_param.find(".//value") |
| kondisi = weather_codes.get(val_element.text, "Informasi tidak tersedia") if val_element is not None else "Tidak tersedia" |
| else: |
| kondisi = "Data parameter cuaca tidak ditemukan" |
| |
| return f"π¦οΈ **Perkiraan Cuaca: {name}**\nKondisi saat ini: **{kondisi}**\n\n*Sumber: BMKG Digital Forecast*" |
| |
| except ET.ParseError: |
| return f"β οΈ Gagal memproses data XML dari BMKG untuk {wilayah}. Silakan coba lagi nanti." |
| except Exception as e: |
| return f"β οΈ Terjadi kendala teknis: {str(e)}" |
| |
| |
| |
|
|
| print("Initializing chatbot...") |
| chatbot = SmartBencanaChatbot() |
| print("Chatbot ready!") |
|
|
| |
| |
| |
|
|
| def chat_response(message, history): |
| """Process chat message and return response""" |
| result = chatbot.predict(message) |
| |
| |
| emoji_map = { |
| 'critical': 'π¨', |
| 'high': 'β οΈ', |
| 'medium': 'π‘', |
| 'low': 'βΉοΈ', |
| 'none': 'π¬' |
| } |
| |
| emoji = emoji_map.get(result['emergency_level'], 'π¬') |
| |
| response = result['response'] |
| |
| |
| if not result.get('is_politeness') and not result.get('is_unclear'): |
| metadata = f"\n\n---\n{emoji} **Level Darurat:** {result['emergency_level'].upper()} | " |
| metadata += f"π **Confidence:** {result['confidence']*100:.1f}% | " |
| metadata += f"π·οΈ **Intent:** {result['intent']}" |
| response += metadata |
| |
| return response |
|
|
| def get_bmkg_info(): |
| """Get BMKG earthquake info""" |
| return ambil_info_bmkg() |
|
|
| def reset_conversation(): |
| """Reset conversation history""" |
| chatbot.conversation_history = [] |
| chatbot.current_context = None |
| chatbot.last_intent = None |
| return "β
Riwayat percakapan telah direset!" |
|
|
| |
| |
| |
|
|
| |
| |
| |
| with gr.Blocks(theme=gr.themes.Soft(), title="Chatbot BantuDarurat") as demo: |
| gr.Markdown( |
| """ |
| # π¨ Chatbot BantuDarurat Bencana |
| ### Asisten Virtual untuk Kesiapsiagaan Bencana & Pertolongan Pertama |
| |
| **Fitur Utama:** |
| - π€ AI-Powered dengan Deep Learning |
| - π― Context-Aware Conversation |
| - β‘ Emergency Level Detection |
| - π Info Gempa & π¦οΈ Cuaca Real-time dari BMKG |
| --- |
| """ |
| ) |
| |
| with gr.Row(): |
| |
| with gr.Column(scale=2): |
| gr.ChatInterface( |
| fn=chat_response, |
| description="Tanyakan tentang kesiapsiagaan bencana atau pertolongan pertama.", |
| examples=[ |
| "Apa yang harus dilakukan saat gempa?", |
| "Bagaimana cara mengevakuasi saat tsunami?", |
| "Pertolongan pertama untuk luka bakar", |
| "Tips menghadapi banjir" |
| ], |
| cache_examples=False, |
| ) |
| |
| |
| with gr.Column(scale=1): |
| gr.Markdown("### π Info BMKG Real-time") |
| bmkg_output = gr.Markdown("Klik tombol di bawah untuk cek gempa terbaru") |
| bmkg_btn = gr.Button("π Refresh Info Gempa", variant="primary") |
| bmkg_btn.click(fn=get_bmkg_info, outputs=bmkg_output) |
| |
| gr.Markdown("---") |
| gr.Markdown("### π¦οΈ Perkiraan Cuaca") |
| wilayah_input = gr.Dropdown( |
| choices=["DKI Jakarta", "Jawa Barat", "Jawa Tengah", "Jawa Timur", "Sumatera Utara", "Sulawesi Selatan"], |
| label="Pilih Wilayah", |
| value="Sumatera Utara" |
| ) |
| cuaca_output = gr.Markdown() |
| cuaca_btn = gr.Button("π Cek Cuaca", variant="secondary") |
| cuaca_btn.click(fn=ambil_cuaca_bmkg, inputs=wilayah_input, outputs=cuaca_output) |
| |
| gr.Markdown("---") |
| gr.Markdown("### βοΈ Pengaturan") |
| reset_btn = gr.Button("π Reset Percakapan", variant="stop") |
| reset_output = gr.Markdown() |
| reset_btn.click(fn=reset_conversation, outputs=reset_output) |
|
|
| gr.Markdown( |
| """ |
| --- |
| <div style="text-align: center; color: #666; font-size: 0.9em;"> |
| <p>π‘οΈ Powered by TensorFlow & Deep Learning | π Data from BNPB & BMKG</p> |
| <p>β οΈ <b>Disclaimer:</b> Informasi ini untuk panduan umum. Dalam keadaan darurat nyata, |
| segera hubungi layanan darurat resmi (110 (Polri), 113 (Pemadam Kebakaran), atau 115 (Basarnas)).</p> |
| <p>β οΈ <b>Disclaimer:</b> Fitur cuaca ini mengambil data langsung secara real-time dari API BMKG. |
| Jika muncul pesan error tersebut, itu menandakan server penyedia data sedang mengalami gangguan |
| teknis atau data XML yang dikirimkan tidak lengkap, namun aplikasi tetap berjalan stabil berkat |
| fitur error handling yang saya terapkan.</p> |
| </div> |
| """ |
| ) |
|
|
| |
| |
| |
| if __name__ == "__main__": |
| demo.launch(server_name="0.0.0.0", server_port=7860) |
|
|
|
|
|
|