File size: 31,618 Bytes
22c6f4b
 
 
 
 
 
3835087
 
22c6f4b
 
94d2dcd
 
 
 
 
 
 
 
 
 
 
 
22c6f4b
 
 
cb4f64d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0a69c9c
cb4f64d
 
 
0a69c9c
cb4f64d
 
 
0a69c9c
cb4f64d
 
 
 
 
 
 
0a69c9c
cb4f64d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0a69c9c
 
cb4f64d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0a69c9c
 
cb4f64d
 
0a69c9c
 
cb4f64d
 
0a69c9c
 
cb4f64d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0a69c9c
cb4f64d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0a69c9c
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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
# --- Crash-safety header: ต้องอยู่บนสุดก่อน import ไลบรารีอื่นทั้งหมด ---
import os
os.environ["USE_TF"] = "0"                    # ไม่ให้ transformers โหลด TensorFlow ซ้ำ (เราใช้แค่ PyTorch ฝั่งแปลภาษา)
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2"      # ลด log ของ TensorFlow
os.environ["TF_ENABLE_ONEDNN_OPTS"] = "0"     # ปิด oneDNN กัน crash บน CPU บางรุ่น
os.environ["TOKENIZERS_PARALLELISM"] = "false"
import sys
sys.modules["triton"] = None                  # triton segfault ตอน import บน CPU-only Space: ทำให้ import ล้มเหลวแบบปกติ torch จะข้ามไปเอง
import faulthandler
faulthandler.enable()                         # ถ้า crash อีก จะพิมพ์ตำแหน่งที่พังลง log

# --- ZeroGPU: ต้อง import spaces ก่อน torch และต้องมีฟังก์ชัน @spaces.GPU อย่างน้อย 1 ตัว ---
try:
    import spaces
    @spaces.GPU
    def _zerogpu_placeholder():
        """ฟังก์ชันเปล่า ใช้ผ่านเงื่อนไข ZeroGPU เท่านั้น (โมเดล TensorFlow ของเรารันบน CPU ตามปกติ)"""
        return None
except ImportError:
    pass                                      # รันนอก Hugging Face Spaces ไม่ต้องใช้
# ---------------------------------------------------------------------------------------

import torch                                  # import torch ก่อน tensorflow เพื่อเลี่ยง segfault
# ---------------------------------------------------------------------

import os
import cv2
import numpy as np
import tensorflow as tf
import gradio as gr
from gtts import gTTS
import uuid
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
import torch
import re

# ***** นำเข้า MobileNet และ Preprocessing ที่ถูกต้อง *****
from tensorflow.keras.applications import MobileNet
from tensorflow.keras.applications.mobilenet import preprocess_input as mobilenet_preprocess_input

# =======================================================
# 1. CONFIGURATION & MODEL SETUP
# =======================================================

# --- Audio Fix 1: กำหนดและสร้างโฟลเดอร์ Output สำหรับไฟล์เสียง ---
OUTPUT_DIR = "appliance_audio_outputs"
if not os.path.exists(OUTPUT_DIR):
    os.makedirs(OUTPUT_DIR)
# --------------------------------------------------------------------------

# --- ชื่อ Class ภาษาอังกฤษ (Key) ---
class_names_en = [
    "Air Conditioner", "Electric Kettle", "Fan", "Fridge", "Iron", "Microwave", 
    "Toaster", "TV", "Vacuum Cleaner", "Washing Machine", 
    # --- 💡 NEW CLASSES ADDED HERE ---
    "Rice Cooker", "Water Heater", "Water Purifier", "Blender", "Cooktop", 
    "Air Purifier", "Water Dispenser", "Hair Dryer", "Water Pump", "Computer", 
    "Electric Frying Pan"
]
# --- ชื่อ Class ภาษาไทย (Value) ---
class_names_th = [
    "เครื่องปรับอากาศ", "กาต้มน้ำไฟฟ้า", "พัดลม", "ตู้เย็น", "เตารีด", "ไมโครเวฟ", 
    "เครื่องปิ้งขนมปัง", "โทรทัศน์", "เครื่องดูดฝุ่น", "เครื่องซักผ้า", 
    # --- 💡 NEW CLASSES ADDED HERE ---
    "หม้อหุงข้าว", "เครื่องทำน้ำอุ่น", "เครื่องกรองน้ำ", "เครื่องปั่น", "เตาทำอาหาร", 
    "เครื่องฟอกอากาศ", "เครื่องกดน้ำดื่ม", "เครื่องเป่าผม", "ปั๊มน้ำ", "คอมพิวเตอร์", 
    "กระทะไฟฟ้า"
]

# --- สร้าง Dictionary สำหรับการแปลชื่อ Class ---
CLASS_NAME_MAP = dict(zip(class_names_en, class_names_th))

class_names = class_names_en
# --- 💡 อัปเดตจำนวนคลาสทั้งหมด (21 Classes) ---
NUM_CLASSES = len(class_names) 
# ----------------------------------------------
IMG_SIZE = (224, 224)
MODEL_WEIGHTS_PATH = "final_best_MobileNet_weights.h5"

# --- MarianMT Setup (Thai -> English Translation) ---
MARIAN_MODEL_NAME = "Helsinki-NLP/opus-mt-th-en"
MT_tokenizer = None
MT_model = None

try:
    print(f"🔄 Loading MarianMT Model: {MARIAN_MODEL_NAME}...")
    MT_tokenizer = AutoTokenizer.from_pretrained(MARIAN_MODEL_NAME)
    MT_model = AutoModelForSeq2SeqLM.from_pretrained(MARIAN_MODEL_NAME)
    print("✅ MarianMT Model loaded.")
except Exception as e:
    print(f"❌ Error loading MarianMT model (Translation will be disabled): {e}")


# --- ฟังก์ชันสร้างโครงสร้างโมเดล ---
def create_electrical_appliance_model():
    """สร้างโครงสร้างโมเดล MobileNet + Custom Head"""
    base_model = MobileNet(
        weights='imagenet',
        include_top=False,
        input_shape=(IMG_SIZE[0], IMG_SIZE[1], 3)
    )
    for layer in base_model.layers:
        layer.trainable = False
    model = tf.keras.models.Sequential([
        base_model,
        tf.keras.layers.GlobalAveragePooling2D(),
        # 💡 ใช้ NUM_CLASSES ใหม่ที่ถูกอัปเดตแล้ว
        tf.keras.layers.Dense(NUM_CLASSES, activation='softmax') 
    ])
    return model

# 1. โหลดโมเดลด้วยการสร้างโครงสร้างและโหลด Weights
model = create_electrical_appliance_model()
print("🔄 Loading custom MobileNet weights...")
try:
    # ⚠️ โหลด Weights ด้วยโครงสร้างใหม่ (21 Classes) - ต้องมั่นใจว่าไฟล์ Weights นี้เทรนด้วย 21 คลาสแล้ว
    model.load_weights(MODEL_WEIGHTS_PATH) 
    print(f"✅ Model '{MODEL_WEIGHTS_PATH}' loaded successfully.")
except Exception as e:
    print(f"❌ FATAL Model Error: Failed to load custom weights from {MODEL_WEIGHTS_PATH}. Error: {e}")
    # raise SystemExit(1) # คอมเมนต์ออกเพื่อให้โค้ดรันได้ แม้ไม่มีไฟล์
    print("⚠️ Continuing without weights. Prediction will be random.")

# =======================================================
# 2. DATA AND TRANSLATION FUNCTIONS
# =======================================================

# --- Appliance Details Data (Thai Source) ---
# 💡 เพิ่มรายละเอียดสำหรับคลาสใหม่ (ใช้ข้อมูลตัวอย่าง)
APPLIANCE_DETAILS = {
    "Air Conditioner": {
        "power": "1,000 ถึง 3,000 วัตต์",
        "install": "ควรติดตั้งในที่ร่มและสูงจากพื้น 2.5 ม. ขึ้นไปเพื่อประสิทธิภาพที่ดี",
        "care": "ล้างแผ่นกรองทุก 1 เดือน และเรียกช่างล้างใหญ่ปีละ 2 ครั้ง",
        "warning": "ห้ามติดตั้งใกล้แสงแดดหรือความชื้น และควรมีสายดิน"
    },
    "Electric Kettle": {
        "power": "1,500 ถึง 2,200 วัตต์",
        "install": "ใช้งานบนพื้นผิวเรียบและแห้ง ห่างจากมือเด็ก",
        "care": "ทำความสะอาดตะกรันด้านในด้วยน้ำส้มสายชูหรือน้ำมะนาวทุก 1-2 เดือน",
        "warning": "ห้ามเปิดเครื่องโดยไม่มีน้ำ และระวังน้ำร้อนลวก"
    },
    "Fan": {
        "power": "50 ถึง 100 วัตต์",
        "install": "ตั้งในที่อากาศถ่ายเทสะดวก ห่างจากผ้าม่าน",
        "care": "ถอดใบพัดและตะแกรงล้างทำความสะอาดทุก 2-3 สัปดาห์",
        "warning": "ห้ามสอดมือหรือสิ่งของเข้าไปขณะพัดลมทำงาน"
    },
    "Fridge": {
        "power": "100 ถึง 200 วัตต์",
        "install": "ตั้งห่างจากผนัง 10-15 ซม. ห้ามตั้งใกล้แหล่งความร้อน",
        "care": "ตั้งอุณหภูมิ 2-5 องศาเซลเซียส และละลายน้ำแข็งเมื่อเริ่มหนา",
        "warning": "ห้ามใช้ปลั๊กพ่วงร่วมกับเครื่องใช้ไฟฟ้ากำลังสูง"
    },
    "Iron": {
        "power": "1,000 ถึง 2,000 วัตต์",
        "install": "ใช้งานบนโต๊ะที่มั่นคง และควรมีแผ่นรองรีดทนความร้อน",
        "care": "ทำความสะอาดหน้าเตาด้วยผ้าชุบน้ำหมาดๆ เมื่อเครื่องเย็นแล้ว",
        "warning": "ห้ามวางเตารีดคว่ำหน้าเตาในขณะร้อน"
    },
    "Microwave": {
        "power": "800 ถึง 1,500 วัตต์",
        "install": "วางบนพื้นผิวเรียบ ห้ามวางใกล้เตาแก๊ส",
        "care": "เช็ดภายในหลังใช้งานทันที เพื่อป้องกันคราบฝังแน่น",
        "warning": "ห้ามใส่ภาชนะโลหะหรือวัสดุไวไฟ"
    },
    "Toaster": {
        "power": "800 ถึง 1,500 วัตต์",
        "install": "ใช้งานบนพื้นผิวทนความร้อน ห่างจากวัสดุติดไฟ",
        "care": "ถอดถาดรองเศษขนมปังออกมาทิ้งและทำความสะอาดทุกครั้ง",
        "warning": "ห้ามใช้มือหรือโลหะแหย่ขณะเครื่องทำงาน"
    },
    "TV": {
        "power": "50 ถึง 300 วัตต์",
        "install": "วางบนชั้นที่มั่นคง หรือติดตั้งกับผนังที่แข็งแรง",
        "care": "ใช้ผ้านุ่มเช็ดหน้าจอ ห้ามใช้สารเคมีที่มีแอลกอฮอล์",
        "warning": "หลีกเลี่ยงการวางใกล้น้ำหรือความชื้น"
    },
    "Vacuum Cleaner": {
        "power": "1,000 ถึง 2,000 วัตต์",
        "install": "ควรเลือกหัวดูดที่เหมาะสมกับพื้นผิวที่ต้องการทำความสะอาด",
        "care": "ทิ้งฝุ่นและทำความสะอาดตัวกรอง (Filter) ทันทีหลังการใช้งาน",
        "warning": "ห้ามดูดของเหลวหรือของมีคม"
    },
    "Washing Machine": {
        "power": "200 ถึง 500 วัตต์ (ซัก), 2,000 ถึง 3,000 วัตต์ (ทำน้ำร้อน)",
        "install": "ติดตั้งบนพื้นที่ได้ระดับ และมีก๊อกน้ำ/ท่อระบายน้ำเข้าถึง",
        "care": "ทำความสะอาดถังซักด้วยน้ำยาเฉพาะทางเดือนละครั้ง หรือใช้น้ำส้มสายชูช่วยกำจัดเชื้อรา",
        "warning": "ห้ามเปิดฝาเครื่องขณะกำลังปั่น"
    },
    "Rice Cooker": {
        "power": "500 ถึง 1,000 วัตต์",
        "install": "วางบนพื้นผิวเรียบและแห้ง ห้ามวางใกล้ผนังมากเกินไป",
        "care": "เช็ดทำความสะอาดหม้อชั้นในด้วยผ้านุ่ม ห้ามใช้ฝอยขัด",
        "warning": "ห้ามล้างตัวเครื่องด้วยน้ำ"
    },
    "Water Heater": {
        "power": "2,000 ถึง 4,500 วัตต์",
        "install": "ควรติดตั้งสายดิน และควรติดตั้งโดยช่างผู้ชำนาญการ",
        "care": "ตรวจสอบระบบตัดไฟ (ELCB) และทำความสะอาดฝักบัวทุก 6 เดือน",
        "warning": "ห้ามใช้หากระบบตัดไฟทำงานผิดปกติ"
    },
    "Water Purifier": {
        "power": "0 ถึง 100 วัตต์ (ถ้ามีปั๊ม/ทำความเย็น)",
        "install": "ติดตั้งใกล้ก๊อกน้ำประปา และในที่ร่ม",
        "care": "เปลี่ยนไส้กรองตามอายุการใช้งานที่กำหนดอย่างเคร่งครัด",
        "warning": "ห้ามใช้งานเมื่อไส้กรองหมดอายุ"
    },
    "Blender": {
        "power": "300 ถึง 1,000 วัตต์",
        "install": "ใช้บนพื้นผิวที่มั่นคง อย่าให้สายไฟพาดผ่านคมมีด",
        "care": "ทำความสะอาดโถปั่นทันทีหลังใช้งาน และไม่ควรปั่นเกินกำลังเครื่อง",
        "warning": "ห้ามเปิดฝาโถขณะเครื่องทำงาน"
    },
    "Cooktop": {
        "power": "1,000 ถึง 3,000 วัตต์ (ต่อหัว)",
        "install": "ต้องใช้อุปกรณ์ทำครัวที่เหมาะสม (เช่น เตาแม่เหล็กไฟฟ้าต้องใช้ภาชนะโลหะ)",
        "care": "เช็ดทำความสะอาดคราบอาหารทันทีหลังเครื่องเย็น",
        "warning": "ระวังพื้นผิวร้อนหลังใช้งาน"
    },
    "Air Purifier": {
        "power": "30 ถึง 100 วัตต์",
        "install": "ตั้งไว้กลางห้อง หรือในบริเวณที่อากาศถ่ายเทได้ดี",
        "care": "ดูดฝุ่น/เช็ดแผ่นกรองหยาบ และเปลี่ยนแผ่นกรอง HEPA ตามอายุการใช้งาน",
        "warning": "ห้ามบังช่องดูดและช่องระบายอากาศ"
    },
    "Water Dispenser": {
        "power": "100 ถึง 500 วัตต์ (ทำน้ำเย็น), 500 ถึง 1,000 วัตต์ (ทำน้ำร้อน)",
        "install": "ตั้งในที่ร่ม ห่างจากผนัง 10-15 ซม.",
        "care": "ทำความสะอาดถังเก็บน้ำและช่องจ่ายน้ำอย่างสม่ำเสมอ",
        "warning": "ระวังน้ำร้อนอาจทำให้เกิดแผลไหม้"
    },
    "Hair Dryer": {
        "power": "1,000 ถึง 2,000 วัตต์",
        "install": "ใช้งานในที่แห้ง ห้ามใช้ใกล้น้ำ",
        "care": "ทำความสะอาดตะแกรงกรองด้านหลังเครื่องเพื่อป้องกันฝุ่นอุดตัน",
        "warning": "ห้ามใช้งานขณะมือเปียก"
    },
    "Water Pump": {
        "power": "200 ถึง 500 วัตต์",
        "install": "ควรติดตั้งในที่ร่ม มีการระบายอากาศดี และมีถังพักน้ำ",
        "care": "ตรวจสอบการรั่วซึม และไม่ควรปล่อยให้ปั๊มทำงานโดยไม่มีน้ำ",
        "warning": "ห้ามเดินเครื่องขณะไม่มีน้ำในระบบ"
    },
    "Computer": {
        "power": "150 ถึง 500 วัตต์ (เครื่องเดสก์ท็อป)",
        "install": "ตั้งในบริเวณที่อากาศถ่ายเทสะดวก ห้ามวางชิดผนัง",
        "care": "ทำความสะอาดฝุ่นภายในเครื่องและพัดลมอย่างสม่ำเสมอ",
        "warning": "ห้ามปิดกั้นช่องระบายความร้อน"
    },
    "Electric Frying Pan": {
        "power": "1,000 ถึง 1,500 วัตต์",
        "install": "ใช้งานบนพื้นผิวที่ทนความร้อนและมั่นคง",
        "care": "ห้ามนำตัวกระทะลงน้ำทั้งอัน ทำความสะอาดเฉพาะส่วนที่ถอดได้",
        "warning": "ระวังความร้อนสูงขณะใช้งาน"
    }

    # -----------------------------------------------------------
}

# --- MarianMT Translation Function (ใช้โค้ดเดิม) ---
def translate_th_to_en(text_to_translate: str) -> str:
    """ฟังก์ชันสำหรับแปลข้อความจากไทยเป็นอังกฤษโดยใช้ MarianMT Model"""
    if not text_to_translate or MT_model is None:
        return text_to_translate

    try:
        encoded_input = MT_tokenizer(text_to_translate, return_tensors="pt")
        translated_tokens = MT_model.generate(
            **encoded_input,
            max_length=100,
            num_beams=4
        )
        return MT_tokenizer.decode(translated_tokens.squeeze(), skip_special_tokens=True)
    except Exception as e:
        print(f"Translation Error: {e}")
        return text_to_translate

# --- GTTs Text-to-Audio Function (แก้ไขให้ใช้ OUTPUT_DIR) ---
def text_to_audio(text_to_speak, lang='th'):
    """แปลงข้อความเป็นไฟล์เสียง และคืนค่า path ของไฟล์เสียง"""
    # Audio Fix 1: ใช้ os.path.join เพื่อสร้าง Path ในโฟลเดอร์ output
    filename = os.path.join(OUTPUT_DIR, f"output_audio_{uuid.uuid4()}.mp3")
    try:
        tts = gTTS(text=text_to_speak, lang=lang, slow=False)
        tts.save(filename)
        return filename
    except Exception as e:
        print(f"Error generating audio: {e}")
        return None
        
def generate_and_play_audio_btn(text_to_speak, lang_code):
    """สร้างไฟล์เสียงแล้วส่ง Path ไปยัง Audio Component"""
    if not text_to_speak or "Error" in text_to_speak or "กรุณาอัปโหลด" in text_to_speak:
        print("Error: Audio generation skipped due to empty or error text.")
        return None
        
    return text_to_audio(text_to_speak, lang=lang_code)

# ----------------------------------------------------
# 3. CORE LOGIC PIPELINE
# ----------------------------------------------------

def format_details_text(class_name_en, class_name_th, lang, details):
    confidence = f"{details['confidence']:.1f}%"
    class_name_display = class_name_th if lang == "th" else class_name_en

    warning_text = details.get('warning', 'ไม่มีข้อมูลข้อควรระวัง')

    if lang == "th":
        return (
            f"⚡ เครื่องใช้ไฟฟ้าที่ตรวจพบ: {class_name_display} (ความมั่นใจ {confidence})\n"
            f"🔌 กำลังไฟฟ้าโดยประมาณ: {details['power']}\n\n"
            f"🛠️ การติดตั้ง/ตำแหน่งที่เหมาะสม:\n"
            f"- {details['install']}\n\n"
            f"🛁 การดูแลการใช้งาน/เคล็ดลับ:\n"
            f"- {details['care']}\n\n"
            f"⚠️ ข้อควรระวัง:\n"
            f"- {warning_text}"
        )
    else:
        return (
            f"⚡ Detected Appliance: {class_name_display} (Confidence {confidence})\n"
            f"🔌 Estimated Power Consumption: {details['power']}\n\n"
            f"🛠️ Installation / Proper Placement:\n"
            f"- {details['install']}\n\n"
            f"🛁 Maintenance / Usage Tips:\n"
            f"- {details['care']}\n\n"
            f"⚠️ Warnings:\n"
            f"- {warning_text}"
        )


# 🎯 แก้ไข Outputs: ต้องคืนค่า initial_detail_text เพื่อให้ Textbox หลักแสดงผลทันที
def full_classification_pipeline(input_img_path):
    """ฟังก์ชันทำนายผลหลักที่ส่งออกเฉพาะ Class ที่มั่นใจสูงสุดค่าเดียว และเตรียมข้อมูลสำหรับ UI"""
    if input_img_path is None:
        error_msg = "กรุณาอัปโหลดรูปภาพ หรือใช้กล้องเพื่อทำนายผล"
        # คืนค่า 5 Outputs: Top Class, Text TH State, Text EN State, Class TH State, Detail Textbox
        return error_msg, error_msg, error_msg, "N/A", error_msg

    try:
        input_img = cv2.imread(input_img_path)
        
        if input_img is None:
              raise ValueError("Error reading image file (input_img is None).")
              
        img = cv2.resize(input_img, IMG_SIZE)
        img = np.array(img, dtype=np.float32)
        img = np.expand_dims(img, axis=0)
        img = mobilenet_preprocess_input(img)

        # 2. Prediction
        predictions = model.predict(img, verbose=0)
        top_class_id = int(np.argmax(predictions[0]))
        # ⚠️ ตรวจสอบ: class_names ถูกอัปเดตเป็น 21 คลาสแล้ว
        top_class_name_en = class_names[top_class_id] 
        top_class_name_th = CLASS_NAME_MAP.get(top_class_name_en, top_class_name_en)
        top_confidence = float(predictions[0][top_class_id])

        # 3. Get Details and Translate
        # ⚠️ ตรวจสอบ: APPLIANCE_DETAILS ถูกอัปเดตเป็น 21 คลาสแล้ว
        details_th = dict(APPLIANCE_DETAILS.get(
            top_class_name_en,
            {
                "power": "N/A",
                "install": "N/A",
                "care": "N/A",
                "warning": "ไม่มีข้อมูลข้อควรระวัง"
            }
        ))
 

        details_en = {
            "power": translate_th_to_en(details_th["power"]),
            "install": translate_th_to_en(details_th["install"]),
            "care": translate_th_to_en(details_th["care"]),
            "warning": translate_th_to_en(details_th.get("warning", ""))
        }

        # 4. Format Text Output (TH & EN)
        details_th['confidence'] = top_confidence * 100
        details_en['confidence'] = top_confidence * 100

        predicted_class_display = f"Top Class: {top_class_name_th} ({top_class_name_en}) - {top_confidence*100:.1f}%"

        full_text_th = format_details_text(top_class_name_en, top_class_name_th, "th", details_th)
        full_text_en = format_details_text(top_class_name_en, top_class_name_th, "en", details_en)
        
        # 🎯 คืนค่า Text TH ไปยัง detail_textbox เพื่อให้แสดงผลเริ่มต้นทันที
        initial_detail_text = full_text_th

        # คืนค่า 5 Outputs: (1) ชื่อคลาสที่มั่นใจสูงสุด, (2) Text TH รายละเอียด (State), (3) Text EN รายละเอียด (State), (4) ชื่อคลาส TH (State), (5) Detail Textbox (Output UI)
        return predicted_class_display, full_text_th, full_text_en, top_class_name_th, initial_detail_text

    except Exception as e:
        error_msg_display = f"❌ Error processing image data. Details: {e}"
        return error_msg_display, error_msg_display, error_msg_display, "N/A", error_msg_display


# --- Language Switch Function (ใช้โค้ดเดิม) ---
def switch_language_display(lang, text_th, text_en, top_class_str_all, class_name_th_val):
    """สลับ Textbox ที่แสดงผลและชื่อ Class เมื่อผู้ใช้กด Radio Button"""

    if not top_class_str_all or "Error" in top_class_str_all or "กรุณาอัปโหลดรูปภาพ" in top_class_str_all:
        return gr.update(), gr.update()

    # ค้นหาชื่อ Class EN
    class_name_en = next((k for k, v in CLASS_NAME_MAP.items() if v == class_name_th_val), "N/A")

    confidence_match = re.search(r'(- \d+\.\d+%)', top_class_str_all)
    confidence_str = confidence_match.group(1) if confidence_match else ""

    if lang == "Thai":
        new_top_class_display = f"Top Class: {class_name_th_val} ({class_name_en}) {confidence_str}"
        new_detail_text = text_th
    else:
        new_top_class_display = f"Top Class: {class_name_en} ({class_name_th_val}) {confidence_str}"
        new_detail_text = text_en

    return new_detail_text, new_top_class_display

# ----------------------------------------------------
# 4. GRADIO UI DEFINITION
# ----------------------------------------------------

with gr.Blocks(title="MobileNet Electrical Appliance Classifier with Translation") as demo:
    gr.Markdown("## ⚡ MobileNet Electrical Appliance Classifier (Top 1 Class & Details)")

    # Audio Fix 2: เปลี่ยน Textbox ที่ซ่อนไว้เป็น State Components
    text_for_audio_th = gr.State("")
    text_for_audio_en = gr.State("")
    text_class_name_th = gr.State("")

    with gr.Row():
        # Input Section
        with gr.Column(scale=1):
            with gr.Tabs():
                with gr.Tab("🖼️ อัปโหลดไฟล์"):
                    file_input = gr.Image(type="filepath", label="Upload Image Here", sources=["upload", "clipboard"])
                    submit_file_btn = gr.Button("Submit File")

                with gr.Tab("💻 เว็บแคม"):
                    pc_webcam_input = gr.Image(label="Webcam Input (PC/Mac)", type="filepath", sources=["webcam"])
                    submit_pc_webcam_btn = gr.Button("Capture & Predict")

                with gr.Tab("📱 กล้องมือถือ"):
                    smartphone_camera_input = gr.Image(label="Smartphone Camera Input", type="filepath", sources=["webcam"])
                    submit_mobile_camera_btn = gr.Button("Capture & Predict (Mobile)")

        # Output Section
        with gr.Column(scale=2):
            top_class_display = gr.Textbox(label="ผลลัพธ์การจำแนก (Class ที่มั่นใจสูงสุด)", lines=1, max_lines=1)

            detail_textbox = gr.Textbox(label="ข้อมูลเชิงลึกและการดูแลรักษา | Appliance Details & Care", lines=10)

            with gr.Row():
                language_choice = gr.Radio(
                    ["Thai", "English"],
                    value="Thai",
                    label="เลือกภาษา | Select Language",
                    interactive=True
                )

            with gr.Row():
                audio_output = gr.Audio(label="Audio Playback", type="filepath", interactive=False, autoplay=True)

            with gr.Row():
                # 🎯 ตั้งให้เป็น interactive=True ตั้งแต่เริ่มต้น เนื่องจาก Audio Logic แยกออกมาจาก Pipeline หลัก
                audio_th_btn = gr.Button("🔊 ฟังคำอธิบาย (Thai)", interactive=True) 
                audio_en_btn = gr.Button("🔊 Listen to explanation (English)", interactive=True)

    # --- 5. EVENT HANDLERS ---
    
    # 🎯 Output Targets: 5 Outputs จาก Pipeline (4 State, 1 Textbox UI)
    pipeline_outputs = [
        top_class_display,
        text_for_audio_th,
        text_for_audio_en,
        text_class_name_th,
        detail_textbox
    ]

    all_inputs = [file_input, pc_webcam_input, smartphone_camera_input]
    all_submit_btns = [submit_file_btn, submit_pc_webcam_btn, submit_mobile_camera_btn]

    # เชื่อมโยงแต่ละปุ่มเข้ากับ Pipeline (ใช้ .click() เดียว)
    for i, btn in enumerate(all_submit_btns):
        btn.click(
            fn=full_classification_pipeline,
            inputs=[all_inputs[i]],
            # Outputs คือ 4 State Component และ 1 Textbox ที่แสดงผลเริ่มต้น
            outputs=pipeline_outputs
        )

    # เชื่อมโยง Radio Button (เมื่อผู้ใช้เปลี่ยนภาษา)
    language_choice.change(
        fn=switch_language_display,
        inputs=[language_choice, text_for_audio_th, text_for_audio_en, top_class_display, text_class_name_th],
        outputs=[detail_textbox, top_class_display]
    )

    # เชื่อมโยงปุ่มเสียง (ใช้ Logic เดิม)
    audio_th_btn.click(
        fn=generate_and_play_audio_btn,
        inputs=[text_for_audio_th, gr.State("th")],
        outputs=[audio_output]
    )

    audio_en_btn.click(
        fn=generate_and_play_audio_btn,
        inputs=[text_for_audio_en, gr.State("en")],
        outputs=[audio_output]
    )


if __name__ == "__main__":
    demo.launch()