File size: 10,532 Bytes
51402aa
cca3f96
 
 
 
51402aa
cca3f96
e404781
cca3f96
e404781
 
51402aa
e404781
cca3f96
 
 
e404781
51402aa
e404781
 
 
cca3f96
 
51402aa
 
e404781
cca3f96
 
 
 
 
 
51402aa
e404781
cca3f96
e404781
cca3f96
 
 
e404781
cca3f96
 
 
e404781
 
 
cca3f96
51402aa
 
e404781
cca3f96
 
e404781
cca3f96
 
 
 
 
 
e404781
cca3f96
 
 
 
e404781
cca3f96
e404781
cca3f96
 
 
 
51402aa
e404781
 
 
cca3f96
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51402aa
cca3f96
 
 
e404781
cca3f96
 
 
e404781
cca3f96
 
 
 
 
 
 
e404781
cca3f96
 
 
 
 
 
 
 
e404781
cca3f96
 
 
e404781
 
 
 
cca3f96
 
 
e404781
 
9542f69
cca3f96
 
 
 
 
e404781
cca3f96
 
 
e404781
cca3f96
 
 
e404781
cca3f96
 
 
 
 
e404781
cca3f96
 
 
 
 
e404781
cca3f96
51402aa
e404781
 
cca3f96
 
 
 
 
 
51402aa
 
 
cca3f96
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
import gradio as gr
from PIL import Image
import numpy as np
import onnxruntime as rt
import os
import spaces
import torch
# Use AutoImageProcessor for robust image preprocessing (ConvNeXt/ViT)
from transformers import AutoImageProcessor 
from scipy.special import softmax # Used for computing probabilities from model output
import requests # Used for making HTTP API calls (Typhoon 2.5)

# 1. ONNX MODEL AND LLM CONFIGURATION
# ----------------------------------------------------
ONNX_MODEL_PATH = "model.onnx"
CLASS_LABELS_FILE = "class_labels.txt"
MODEL_ID = 'facebook/convnext-tiny-224' # The base model ID used for training

# **LLM CONFIGURATION (Actual API Call Setup)**
MODEL_NAME = "scb10x/typhoon2.5-qwen3-4b" # Typhoon 2.5 Model ID
# Fetch Hugging Face Token from Space Secrets
HF_TOKEN = os.getenv("HF_TOKEN") 
API_URL = f"https://api-inference.huggingface.co/models/{MODEL_NAME}"


# Load character classes from the file created during training
try:
    with open(CLASS_LABELS_FILE, 'r', encoding='utf-8') as f:
        CHARACTER_LABELS = [line.strip() for line in f.readlines()]
except FileNotFoundError:
    CHARACTER_LABELS = ['Luffy', 'Zoro', 'Nami', 'Sanji', 'Chopper', 'Franky', 'Brook', 'Usopp', 'Jinbei', 'Robin', 'Ace', 'Law', 'Shanks', 'Kurohige', 'Mihawk', 'Rayleigh']
    print(f"⚠️ WARNING: {CLASS_LABELS_FILE} not found. Using default labels.")

# Load ONNX Runtime Session
try:
    print(f"Attempting to load ONNX model from: {ONNX_MODEL_PATH}") # <-- NEW DEBUG LINE
    sess = rt.InferenceSession(ONNX_MODEL_PATH)
    onnx_input_name = sess.get_inputs()[0].name
    onnx_output_name = sess.get_outputs()[0].name
    # Load Image Processor (essential for correct image preparation)
    processor = AutoImageProcessor.from_pretrained(MODEL_ID)
    print("ONNX model and Image Processor loaded successfully.")
except Exception as e:
    # <--- NEW DEBUG BLOCK: พิมพ์ Error จริงออกมา
    print(f"FATAL ERROR LOADING ONNX MODEL: {e}")
    print("Please ensure model.onnx is tracked by Git LFS and is uploaded correctly.")
    sess = None


# 2. LLM API FUNCTION (Replaces Placeholder)
# ----------------------------------------------------
def query_typhoon_api(payload):
    """Sends prompt to the Hugging Face Inference API of Typhoon 2.5."""
    if not HF_TOKEN:
        return "Error: HF_TOKEN is not set in Hugging Face Space secrets."
    
    headers = {"Authorization": f"Bearer {HF_TOKEN}"}
    response = requests.post(API_URL, headers=headers, json=payload)
    
    # Check for non-successful status codes (e.g., 401 Unauthorized, 503 Service Unavailable)
    if response.status_code != 200:
        return f"Error {response.status_code}: API call failed. {response.text}"
        
    try:
        # Extract the generated text from the response structure
        result = response.json()[0]['generated_text']
        # Remove the input prompt part from the output text
        return result.split(payload['inputs'])[-1].strip()
    except Exception as e:
        return f"Error processing API response: {e}"


# 3. TYPHOON 2.5 LOGIC (Knowledge Base + Prompt Generation)
# ---------------------------------------------------------
# This dictionary serves as the LLM's knowledge base for the character's full name/role
CHARACTER_INFO = {
    "Ace": "โพโทกัส ดี เอส พี่ชายบุญธรรมของลูฟี่ ผู้ใช้พลังผลปีศาจเมระ เมระ",
    "Luffy": "มังกี้ ดี ลูฟี่ กัปตันกลุ่มโจรสลัดหมวกฟาง ผู้ใฝ่ฝันจะเป็นราชาโจรสลัด",
    "Zoro": "โรโรโนอา โซโล นักดาบสามเล่มแห่งกลุ่มหมวกฟาง ผู้มีเป้าหมายเป็นนักดาบอันดับหนึ่งของโลก",
    "Nami": "นามิ นักเดินเรือสาวแห่งกลุ่มหมวกฟาง และเป็นนักทำแผนที่มือฉมัง",
    "Sanji": "ซันจิ กุ๊กแห่งกลุ่มโจรสลัดหมวกฟาง และเป็นสุดยอดนักสู้ที่ใช้เท้าในการต่อสู้",
    "Chopper": "โทนี่ โทนี่ ช็อปเปอร์ หมอประจำเรือ ผู้มีใจรักเพื่อนและอ่อนไหวที่สุดในกลุ่ม",
    "Robin": "นิโค โรบิน นักโบราณคดี ผู้เดียวที่อ่านโพเนกลีฟได้",
    "Usopp": "อุซป พลซุ่มยิงและนักประดิษฐ์ ผู้มีความฝันเป็นนักรบผู้กล้าหาญแห่งท้องทะเล",
    "Franky": "แฟรงกี้ ช่างต่อเรือผู้สร้างเรือเธาซันด์ ซันนี่ มีพลังไซบอร์กสุดแกร่ง",
    "Brook": "บรู๊ค นักดนตรีผู้ใช้ดาบและมีชีวิตเป็นโครงกระดูก ผู้รักการร้องเพลงและมุกตลก",
    "Jinbei": "จินเบ อดีตเจ็ดเทพโจรสลัด และเป็นมนุษย์เงือกผู้เชี่ยวชาญคาราเต้เงือก",
    "Law": "ทราฟาลการ์ ลอว์ กัปตันกลุ่มโจรสลัดฮาร์ท ผู้ใช้พลังผลโอเปะ โอเปะ",
    "Shanks": "แชงค์ส หนึ่งในสี่จักรพรรดิ์ ผู้มอบหมวกฟางให้กับลูฟี่",
    "Mihawk": "จูราคิล มิฮอว์ค สุดยอดนักดาบผู้เป็นที่มาของฉายา 'ตาเหยี่ยว'",
    "Kurohige": "มาร์แชล ดี ทีช หรือหนวดดำ ผู้เป็นหนึ่งในสี่จักรพรรดิ์คนปัจจุบัน",
    "Rayleigh": "ซิลเวอร์ส เรย์ลี่ อดีตมือขวาของราชาโจรสลัด โกลด์ ดี. โรเจอร์",
}


def generate_thai_response(character_name, confidence):
    """
    Constructs a sophisticated prompt and queries the Typhoon 2.5 API.
    """
    info = CHARACTER_INFO.get(character_name, "ตัวละครวันพีซ")
    
    # 1. Build a clear, instructional prompt for the LLM
    prompt = (
        f"จากผลการวิเคราะห์ภาพ (ความมั่นใจ {confidence*100:.2f}%), ตัวละครที่ทำนายคือ '{character_name}'. "
        f"ตัวละครนี้คือ {info}. "
        f"กรุณาสร้างข้อความตอบกลับที่เป็นมิตรและเป็นภาษาไทย โดยขึ้นต้นด้วย 'ยืนยันผลการทำนาย!' "
        f"และรวมข้อมูลทั้งหมดนี้เข้าด้วยกันในประโยคเดียวโดยใช้ Markdown bold สำหรับชื่อตัวละครและความมั่นใจ (XX.XX%)."
    )
    
    # 2. Prepare Payload
    payload = {
        "inputs": prompt,
        "parameters": {
            "max_new_tokens": 100,
            "return_full_text": False
        }
    }
    
    # 3. Call the API and handle potential errors
    llm_response = query_typhoon_api(payload)
    
    if llm_response.startswith("Error"):
        # Fallback to a static, simple response if API fails
        thai_name = info.split(' ')[0] 
        return (f"⚠️ LLM API ไม่ตอบสนอง: ตัวละครคือ **{thai_name}** ({info}) "
                f"[ความมั่นใจ: **{confidence*100:.2f}%**]")

    return llm_response

# 4. ONNX INFERENCE FUNCTION
# ----------------------------------------------------
#@spaces.GPU
def predict_one_piece_character(pil_image):
    if pil_image is None or sess is None:
        return "⚠️ โมเดลไม่พร้อมใช้งาน กรุณาตรวจสอบไฟล์ ONNX และการตั้งค่า"
    
    try:
        # 4.1 Preprocessing (ConvNeXt standard input)
        inputs = processor(images=pil_image, return_tensors="np")
        onnx_input = inputs['pixel_values'].astype(np.float32)

        # 4.2 Run Inference
        onnx_predictions = sess.run([onnx_output_name], {onnx_input_name: onnx_input})
        logits = onnx_predictions[0].squeeze() 

        # 4.3 Post-processing (Softmax and Argmax)
        probabilities = softmax(logits)
        predicted_index = np.argmax(probabilities)
        predicted_character = CHARACTER_LABELS[predicted_index]
        confidence = probabilities[predicted_index].item()
        
        # 4.4 LLM Integration
        final_response = generate_thai_response(predicted_character, confidence)
        
        return final_response

    except Exception as e:
        print(f"RUNTIME ERROR: {e}") 
        return f"เกิดข้อผิดพลาดในการทำนาย: {e}"

# 5. GRADIO INTERFACE
# ----------------------------------------------------
interface = gr.Interface(
    fn=predict_one_piece_character, 
    inputs=gr.Image(type="pil", label="อัปโหลดรูปภาพตัวละครวันพีซ"), 
    outputs=gr.Textbox(label="ผลการทำนายชื่อตัวละคร (Powered by Typhoon 2.5)"), 
    title="🏴‍☠️ One Piece Classifier (ConvNeXt ONNX + Typhoon 2.5)",
    description="อัปโหลดภาพตัวละครวันพีซ เพื่อให้ AI ทำนายชื่อตัวละครพร้อมสร้างข้อความตอบกลับที่น่าประทับใจ"
)

if __name__ == "__main__":
    interface.launch(inbrowser=True)