Ferdinann's picture
Update app.py
ddd8f05 verified
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
# NLTK
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
# TensorFlow & Keras
import tensorflow as tf
from tensorflow.keras.models import load_model
from tensorflow.keras.preprocessing.sequence import pad_sequences
# Set seed
np.random.seed(42)
tf.random.set_seed(42)
random.seed(42)
# =====================================================
# SMART CHATBOT CLASS
# =====================================================
class SmartBencanaChatbot:
def __init__(self):
"""Initialize the chatbot with pre-trained model"""
print("πŸ”„ Loading model...")
# Load model
self.model = load_model('chatbot_model.keras')
# Load tokenizer
with open('tokenizer.pickle', 'rb') as f:
self.tokenizer = pickle.load(f)
# Load label encoder
with open('label_encoder.pickle', 'rb') as f:
self.label_encoder = pickle.load(f)
# Load responses
with open('responses.pickle', 'rb') as f:
self.responses = pickle.load(f)
# Get max_len from tokenizer or set default
self.max_len = 50
# Initialize conversation tracking
self.conversation_history = []
self.current_context = None
self.last_intent = None
# Initialize lemmatizer
self.lemmatizer = WordNetLemmatizer()
# Emergency keywords
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']
}
# Politeness patterns
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'
]
# Politeness responses
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):
# Determine type
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."
# Filter by emergency level
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"""
# Check for politeness first
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
}
# Check for unclear/too short input
if len(text.strip()) < 3:
return {
'intent': 'Unknown',
'confidence': 0.0,
'response': "Maaf, pertanyaan terlalu pendek. Bisa diperjelas?",
'emergency_level': 'none',
'is_unclear': True
}
# Detect emergency level
emergency_level = self.detect_emergency_level(text)
# Preprocess
processed_text = self.preprocess_text(text)
# Tokenize and predict
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
# =====================================================
# BMKG INFO FUNCTION
# =====================================================
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)
# Validasi konten sebelum parsing
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"
}
# Cari area pertama yang tersedia
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:
# Ambil nilai terbaru (value pertama)
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)}"
# =====================================================
# INITIALIZE CHATBOT
# =====================================================
print("Initializing chatbot...")
chatbot = SmartBencanaChatbot()
print("Chatbot ready!")
# =====================================================
# GRADIO INTERFACE FUNCTIONS
# =====================================================
def chat_response(message, history):
"""Process chat message and return response"""
result = chatbot.predict(message)
# Format response with metadata
emoji_map = {
'critical': '🚨',
'high': '⚠️',
'medium': 'πŸ’‘',
'low': 'ℹ️',
'none': 'πŸ’¬'
}
emoji = emoji_map.get(result['emergency_level'], 'πŸ’¬')
response = result['response']
# Add metadata if not politeness
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!"
# =====================================================
# GRADIO UI
# =====================================================
# =====================================================
# GRADIO UI
# =====================================================
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():
# Kolom Kiri: Chatbot
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,
)
# Kolom Kanan: Info BMKG & Cuaca
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>
"""
)
# =====================================================
# LAUNCH
# =====================================================
if __name__ == "__main__":
demo.launch(server_name="0.0.0.0", server_port=7860)