dentoAI / app.py
kadalicious22's picture
Update app.py
8de28b1 verified
import gradio as gr
import os
from mistralai.client import MistralClient
from mistralai.models.chat_completion import ChatMessage
api_key = os.getenv("MISTRAL_API_KEY")
if not api_key:
raise ValueError("MISTRAL_API_KEY environment variable tidak ditemukan! Tambahkan di Settings → Variables and secrets.")
client = MistralClient(api_key=api_key)
DENTAL_SYSTEM_PROMPT = """You are a professional dental AI assistant specialized in dentistry and oral health.
Your role is to provide helpful, accurate information about dental topics including:
- Dental hygiene and preventive care
- Common dental procedures and treatments
- Oral health conditions and symptoms
- Dental anatomy and terminology
- Post-operative care instructions
- Dental emergency guidance
Important guidelines:
- Always emphasize that you're not a substitute for professional dental care
- Encourage users to consult with licensed dentists for specific medical advice
- Provide evidence-based information when possible
- Be clear about when immediate dental care is needed
- Use simple, understandable language for non-medical users
- Focus on educational and supportive information
Please respond in Indonesian language unless the user specifically asks for English."""
NON_MEDICAL_KEYWORDS = [
'masak', 'resep', 'film', 'musik', 'lagu', 'game', 'politik', 'bisnis',
'saham', 'crypto', 'olahraga', 'bola', 'basket', 'travel', 'wisata',
'fashion', 'cuaca', 'berita', 'gosip', 'artis', 'coding', 'programming',
'hukum', 'harga', 'beli', 'jual', 'toko', 'belanja'
]
def extract_text(content):
"""Ekstrak teks dari content yang bisa berupa string, list, atau dict"""
if isinstance(content, str):
return content
elif isinstance(content, list):
texts = []
for item in content:
if isinstance(item, dict):
texts.append(item.get('text', '') or item.get('content', ''))
elif isinstance(item, str):
texts.append(item)
return ' '.join(filter(None, texts))
elif isinstance(content, dict):
return content.get('text', '') or content.get('content', '') or str(content)
return str(content) if content else ''
def is_medical_related(message):
"""Izinkan semua topik medis, blokir hanya yang jelas non-medis"""
msg_lower = message.lower()
# Blokir jika mengandung keyword non-medis yang jelas
non_medical_count = sum(1 for kw in NON_MEDICAL_KEYWORDS if kw in msg_lower)
# Jika mengandung 2+ keyword non-medis dan tidak ada konteks medis, tolak
medical_hints = ['sakit', 'nyeri', 'obat', 'dokter', 'gejala', 'penyakit',
'kesehatan', 'medis', 'klinik', 'rumah sakit', 'operasi',
'terapi', 'diagnosis', 'treatment', 'health', 'pain', 'disease']
has_medical = any(kw in msg_lower for kw in medical_hints)
if non_medical_count >= 2 and not has_medical:
return False
return True
def dental_chat(message, history):
message_text = extract_text(message)
if not message_text.strip():
history.append({"role": "user", "content": message_text})
history.append({"role": "assistant", "content": "Mohon masukkan pertanyaan yang valid."})
yield "", history
return
if not is_medical_related(message_text):
history.append({"role": "user", "content": message_text})
history.append({"role": "assistant", "content": "Maaf, saya hanya bisa membantu dengan pertanyaan seputar kesehatan dan kedokteran gigi. Silakan ajukan pertanyaan medis."})
yield "", history
return
try:
mistral_messages = [ChatMessage(role="system", content=DENTAL_SYSTEM_PROMPT)]
for msg in history:
content_text = extract_text(msg["content"])
if content_text.strip():
mistral_messages.append(ChatMessage(role=msg["role"], content=content_text))
mistral_messages.append(ChatMessage(role="user", content=message_text))
history.append({"role": "user", "content": message_text})
history.append({"role": "assistant", "content": ""})
response_text = ""
for chunk in client.chat_stream(
model="mistral-tiny",
messages=mistral_messages,
max_tokens=500,
temperature=0.7
):
token = chunk.choices[0].delta.content
if token:
response_text += token
history[-1]["content"] = response_text
yield "", history
except Exception as e:
print(f"Error: {str(e)}")
error_str = str(e).lower()
if "429" in error_str or "rate limit" in error_str or "quota" in error_str or "too many" in error_str:
pesan = "⚠️ Layanan sedang sibuk atau kuota habis. Silakan coba beberapa saat lagi."
elif "401" in error_str or "unauthorized" in error_str or "api key" in error_str:
pesan = "🔑 API Key tidak valid atau sudah kadaluarsa. Hubungi administrator."
elif "timeout" in error_str or "connection" in error_str:
pesan = "🌐 Koneksi bermasalah. Periksa internet dan coba lagi."
else:
pesan = "❌ Terjadi kesalahan. Silakan coba lagi."
if history and history[-1]["content"] == "":
history[-1]["content"] = pesan
else:
history.append({"role": "assistant", "content": pesan})
yield "", history
css = """
footer { display: none !important; }
/* Ganti loading dots dengan teks Thinking... */
.progress-text span,
.eta-bar,
.generating span {
display: none !important;
}
.generating::after {
content: 'Thinking...' !important;
color: #94a3b8 !important;
font-style: italic !important;
font-size: 0.9rem !important;
}
body, .gradio-container {
background-color: #0a0e27 !important;
}
.main, .wrap {
background-color: #0a0e27 !important;
}
.chatbot {
background-color: #131729 !important;
border: 1px solid rgba(148, 163, 184, 0.15) !important;
border-radius: 12px !important;
}
.message-wrap {
background-color: #131729 !important;
}
.user .message {
background: linear-gradient(135deg, #00d9ff, #7c3aed) !important;
color: white !important;
border-radius: 12px !important;
}
.bot .message {
background-color: #1a1f3a !important;
color: #e2e8f0 !important;
border-radius: 12px !important;
border: 1px solid rgba(148, 163, 184, 0.1) !important;
}
textarea, input[type="text"] {
background-color: #131729 !important;
color: #e2e8f0 !important;
border: 1px solid rgba(148, 163, 184, 0.2) !important;
border-radius: 10px !important;
}
textarea:focus, input[type="text"]:focus {
border-color: #00d9ff !important;
box-shadow: 0 0 0 2px rgba(0, 217, 255, 0.2) !important;
}
label, .label-wrap span {
color: #94a3b8 !important;
}
.primary {
background: linear-gradient(135deg, #00d9ff, #7c3aed) !important;
border: none !important;
color: white !important;
border-radius: 25px !important;
font-weight: 600 !important;
}
.primary:hover {
opacity: 0.9 !important;
transform: translateY(-1px) !important;
}
.secondary {
background-color: #1a1f3a !important;
border: 1px solid rgba(148, 163, 184, 0.2) !important;
color: #94a3b8 !important;
border-radius: 25px !important;
}
.secondary:hover {
border-color: #00d9ff !important;
color: #00d9ff !important;
}
.panel, .block, .form {
background-color: #131729 !important;
border-color: rgba(148, 163, 184, 0.1) !important;
}
"""
with gr.Blocks(title="DentoAI", css=css) as demo:
chatbot = gr.Chatbot(height=500, label="Chat dengan DentoAI")
msg = gr.Textbox(
label="Ketik pertanyaan tentang kesehatan gigi...",
placeholder="Contoh: Bagaimana cara menyikat gigi yang benar?",
lines=2
)
with gr.Row():
submit_btn = gr.Button("Send", variant="primary")
clear_btn = gr.Button("Clear Chat", variant="secondary")
msg.submit(dental_chat, [msg, chatbot], [msg, chatbot])
submit_btn.click(dental_chat, [msg, chatbot], [msg, chatbot])
clear_btn.click(lambda: [], None, chatbot, queue=False)
if __name__ == "__main__":
demo.launch()