File size: 8,265 Bytes
1f5d867
 
 
 
 
4579385
1f5d867
22ec060
1f5d867
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
319630b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1f5d867
 
319630b
 
 
 
850e779
 
 
271a11b
319630b
 
 
850e779
 
271a11b
850e779
4abd5b3
319630b
22ec060
319630b
 
 
271a11b
319630b
 
 
850e779
 
 
 
1f5d867
4abd5b3
1f5d867
 
850e779
 
 
 
 
 
271a11b
1f5d867
4abd5b3
88e811f
 
 
 
 
 
 
 
 
319630b
88e811f
319630b
88e811f
850e779
1f5d867
5fdb053
 
 
319630b
 
 
 
 
 
 
 
 
 
 
 
 
 
5fdb053
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22ec060
271a11b
1f5d867
 
 
 
 
271a11b
1f5d867
8de28b1
 
271a11b
4abd5b3
 
1f5d867
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
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()