Team Ai
Apppublic

AMAMnarin/integer-web-app-demo

sourceHugging Faceupdated 5mo agoView on Hugging Face
0likes
integer_ai.html556 linesDownload Raw Back to root
1<!DOCTYPE html>
2<html lang="th">
3<head>
4    <meta charset="UTF-8">
5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6    <title>🧮 การบวกจำนวนเต็ม | AI-Powered EdTech (Custom API)</title>
7    
8    <!-- M1 & M3: Math Rendering & Sanitization Stack -->
9    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.css">
10    <script src="https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.js"></script>
11    <script src="https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/contrib/auto-render.min.js"></script>
12    <script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.0.6/purify.min.js"></script>
13    <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
14
15    <style>
16        /* --- Original Styles --- */
17        * { box-sizing: border-box; margin: 0; padding: 0; }
18        body {
19            font-family: 'Sarabun', 'Kanit', sans-serif;
20            background: linear-gradient(145deg, #fff9f0 0%, #ffe8d6 100%);
21            min-height: 100vh;
22            padding: 20px;
23            display: flex;
24            flex-direction: column;
25            align-items: center;
26        }
27        .main-wrapper {
28            display: flex;
29            gap: 20px;
30            width: 100%;
31            max-width: 1200px;
32            align-items: flex-start;
33        }
34        .stream-container {
35            flex: 2;
36            background: white;
37            border-radius: 20px;
38            box-shadow: 0 10px 30px rgba(0,0,0,0.1);
39            overflow: hidden;
40            position: relative;
41        }
42        .stream-header { background: #f39c12; color: white; padding: 20px; text-align: center; }
43        .step-indicator { display: flex; justify-content: space-around; background: #fdebd0; padding: 15px 10px; }
44        .step-dot { display: flex; flex-direction: column; align-items: center; cursor: pointer; opacity: 0.6; transition: 0.2s; }
45        .step-dot.active { opacity: 1; font-weight: bold; transform: scale(1.05); }
46        .step-dot span:first-child { background: #e67e22; color: white; width: 32px; height: 32px; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin-bottom: 5px; }
47        .step-dot.active span:first-child { background: #d35400; box-shadow: 0 0 10px #f39c12; }
48        .step-name { font-size: 0.8rem; color: #5d4037; }
49        .content-area { padding: 25px 30px; min-height: 400px; background: #fff; }
50        .step-section { display: none; animation: fadeSlide 0.4s ease; }
51        .step-section.active { display: block; }
52        @keyframes fadeSlide { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } }
53        h2 { color: #d35400; margin-bottom: 15px; font-size: 1.5rem; border-left: 6px solid #f39c12; padding-left: 15px; }
54        .example-box { background: #faf5ef; padding: 15px; border-radius: 15px; margin: 12px 0; }
55        details { margin: 10px 0; background: #fff; border: 1px solid #f1c40f; border-radius: 10px; padding: 10px 15px; cursor: pointer; }
56        
57        /* --- Exercises --- */
58        .exercise-item { background: #fdf2e9; padding: 15px; border-radius: 14px; margin-bottom: 15px; }
59        .exercise-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
60        .exercise-item input { width: 90px; padding: 8px; border: 2px solid #f39c12; border-radius: 8px; text-align: center; }
61        .feedback { font-weight: bold; margin-left: 8px; }
62        .btn-check, .btn-action { background: #27ae60; color: white; border: none; padding: 10px 20px; border-radius: 20px; cursor: pointer; transition: 0.2s; font-family: inherit;}
63        .btn-check:hover { background: #2ecc71; }
64        .btn-action.secondary { background: #3498db; }
65        .btn-explain { background: #e74c3c; color: white; border: none; padding: 5px 12px; border-radius: 15px; cursor: pointer; font-size: 0.85rem; display: none; margin-top: 10px;}
66        .ai-feedback-box { background: #f0f8ff; border-left: 4px solid #3498db; padding: 15px; margin-top: 10px; border-radius: 0 10px 10px 0; display: none; font-size: 0.95rem;}
67        
68        .nav-buttons { display: flex; justify-content: space-between; padding: 15px 30px; background: #f9f3e7; }
69        .nav-btn { background: #f39c12; border: none; color: white; padding: 10px 20px; border-radius: 20px; cursor: pointer; }
70        .nav-btn:disabled { background: #ccc; cursor: not-allowed; }
71
72        /* --- Settings Panel --- */
73        .settings-btn { position: fixed; bottom: 20px; right: 20px; background: #34495e; color: white; border: none; width: 50px; height: 50px; border-radius: 50%; font-size: 1.5rem; cursor: pointer; z-index: 1000; box-shadow: 0 4px 10px rgba(0,0,0,0.2); display: flex; align-items: center; justify-content: center;}
74        .overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); display: none; justify-content: center; align-items: center; z-index: 1001; }
75        .modal { background: white; padding: 25px; border-radius: 20px; width: 90%; max-width: 450px; box-shadow: 0 10px 40px rgba(0,0,0,0.3); }
76        .modal h3 { margin-bottom: 20px; color: #2c3e50; border-bottom: 2px solid #eee; padding-bottom: 10px; }
77        .field-group { margin-bottom: 15px; }
78        .field-group label { display: block; margin-bottom: 5px; font-weight: bold; font-size: 0.9rem; color: #7f8c8d; }
79        .modal input, .modal select { width: 100%; padding: 10px; border: 1px solid #bdc3c7; border-radius: 8px; font-family: inherit; }
80        .modal .btn-save { width: 100%; background: #27ae60; color: white; padding: 12px; border: none; border-radius: 8px; cursor: pointer; font-weight: bold; margin-top: 10px; }
81        .modal .btn-close { width: 100%; background: #95a5a6; color: white; padding: 8px; border: none; border-radius: 8px; cursor: pointer; margin-top: 10px; }
82
83        /* --- Chat --- */
84        .chat-container { flex: 1; min-width: 300px; background: white; border-radius: 20px; box-shadow: 0 10px 30px rgba(0,0,0,0.1); display: flex; flex-direction: column; height: 80vh; max-height: 700px; position: sticky; top: 20px;}
85        .chat-header { background: #2980b9; color: white; padding: 15px; border-radius: 20px 20px 0 0; text-align: center; }
86        .mission-progress { width: 100%; background: #1a5276; height: 8px; border-radius: 4px; margin-top: 10px; overflow: hidden;}
87        .progress-bar { width: 0%; height: 100%; background: #f1c40f; transition: width 0.5s; }
88        .chat-messages { flex: 1; padding: 15px; overflow-y: auto; display: flex; flex-direction: column; gap: 10px; background: #f4f6f7; }
89        .msg { padding: 10px 15px; border-radius: 15px; max-width: 85%; font-size: 0.95rem; line-height: 1.4;}
90        .msg.ai { background: white; align-self: flex-start; border-bottom-left-radius: 0; box-shadow: 0 2px 5px rgba(0,0,0,0.05); border: 1px solid #e5e8e8;}
91        .msg.user { background: #d6eaf8; align-self: flex-end; border-bottom-right-radius: 0; }
92        .chat-input { display: flex; padding: 15px; border-top: 1px solid #eee; background: white; border-radius: 0 0 20px 20px;}
93        .chat-input input { flex: 1; padding: 10px; border: 1px solid #ccc; border-radius: 20px; outline: none; }
94        .chat-input button { background: #2980b9; color: white; border: none; padding: 10px 15px; margin-left: 10px; border-radius: 50%; cursor: pointer; }
95
96        .spinner { width: 20px; height: 20px; border: 3px solid rgba(0,0,0,0.1); border-top-color: #3498db; border-radius: 50%; animation: spin 1s linear infinite; display: inline-block; vertical-align: middle; }
97        @keyframes spin { to { transform: rotate(360deg); } }
98
99        @media (max-width: 900px) {
100            .main-wrapper { flex-direction: column; }
101            .chat-container { width: 100%; height: 500px; position: static; }
102        }
103    </style>
104</head>
105<body>
106
107<!-- Settings Button -->
108<button class="settings-btn" onclick="toggleSettings(true)" title="ตั้งค่า API">⚙️</button>
109
110<!-- Settings Modal -->
111<div class="overlay" id="settingsOverlay">
112    <div class="modal">
113        <h3>⚙️ ตั้งค่าการเชื่อมต่อ AI</h3>
114        
115        <div class="field-group">
116            <label>AI Provider</label>
117            <select id="apiProvider" onchange="handleProviderChange()">
118                <option value="google">Google Gemini</option>
119                <option value="openai">OpenAI (ChatGPT)</option>
120            </select>
121        </div>
122
123        <div class="field-group">
124            <label id="apiKeyLabel">Google API Key</label>
125            <input type="password" id="apiKeyInput" placeholder="ใส่รหัส API ของคุณที่นี่">
126        </div>
127
128        <div class="field-group">
129            <label>Model Name</label>
130            <input type="text" id="apiModelInput" placeholder="เช่น gemini-2.5-flash-preview-09-2025">
131        </div>
132
133        <p style="font-size: 0.75rem; color: #7f8c8d; margin-bottom: 15px;">* ข้อมูลถูกเก็บไว้ในเบราว์เซอร์ของคุณเท่านั้น ไม่มีการส่งไปที่อื่น</p>
134
135        <button class="btn-save" onclick="saveSettings()">💾 บันทึกการตั้งค่า</button>
136        <button class="btn-close" onclick="toggleSettings(false)">ปิด</button>
137    </div>
138</div>
139
140<div class="main-wrapper">
141    <!-- Main Math App Container -->
142    <div class="stream-container">
143        <div class="stream-header">
144            <h1>🧮 จำนวนเต็ม : การบวก</h1>
145            <p>เรียนรู้แบบ Interactive พร้อม AI ผู้ช่วยส่วนตัว</p>
146        </div>
147
148        <div class="step-indicator">
149            <div class="step-dot" onclick="changeStepTo(0)" id="dot0"><span>1</span><span class="step-name">ขั้นนำ</span></div>
150            <div class="step-dot" onclick="changeStepTo(1)" id="dot1"><span>2</span><span class="step-name">ขั้นสอน</span></div>
151            <div class="step-dot" onclick="changeStepTo(2)" id="dot2"><span>3</span><span class="step-name">ขั้นสรุป</span></div>
152            <div class="step-dot" onclick="changeStepTo(3)" id="dot3"><span>4</span><span class="step-name">แบบฝึกหัด</span></div>
153        </div>
154
155        <div class="content-area">
156            <div class="step-section" id="step0">
157                <h2>🌟 ขั้นนำ : เปิดโลกจำนวนเต็ม</h2>
158                <p>ในชีวิตเรามีทั้ง <strong>เพิ่มขึ้น</strong> และ <strong>ลดลง</strong> จำนวนเต็มช่วยบอกปริมาณและทิศทาง</p>
159                <div class="example-box">
160                    <p>🏢 ลิฟต์: เริ่มชั้น G <math-span>0</math-span> ขึ้น 3 ชั้น <math-span>+3</math-span> แล้วลง 2 ชั้น <math-span>-2</math-span> → <math-span>+1</math-span></p>
161                    <p>🌡️ อากาศ: <math-span>-3^\circ C</math-span> ร้อนขึ้น <math-span>5^\circ C</math-span> → <math-span>+2^\circ C</math-span></p>
162                </div>
163            </div>
164
165            <div class="step-section" id="step1">
166                <h2>📖 ขั้นสอน : หลักการบวกจำนวนเต็ม</h2>
167                <ul>
168                    <li><strong>บวก + บวก</strong> → บวกปกติ</li>
169                    <li><strong>ลบ + ลบ</strong> → บวกค่าสัมบูรณ์ แล้วใส่ลบ</li>
170                    <li><strong>บวก + ลบ</strong> → เอาค่าสัมบูรณ์มากลบน้อย ใส่เครื่องหมายของตัวมาก</li>
171                </ul>
172                <div class="example-box">
173                    <strong>🔎 Productive Struggle (ลองคิดก่อนกดดู)</strong>
174                    <details>
175                        <summary><math-span>(+4) + (-7) = ?</math-span></summary>
176                        <p>เครื่องหมายต่าง → ลบกัน <math-span>7-4=3</math-span> → ใช้เครื่องหมายของตัวมาก(ลบ) → <math-span>-3</math-span></p>
177                    </details>
178                    <details>
179                        <summary><math-span>(-2) + (-5) = ?</math-span></summary>
180                        <p>เครื่องหมายเหมือน → บวกกัน <math-span>2+5=7</math-span> → ใส่ลบ → <math-span>-7</math-span></p>
181                    </details>
182                </div>
183            </div>
184
185            <div class="step-section" id="step2">
186                <h2>📝 ขั้นสรุป</h2>
187                <math-div mode="displaystyle">a + (-b) = a - b</math-div>
188                <p style="text-align: center; margin-top: 10px;">✅ <strong>ขั้นตอน:</strong> ดูเครื่องหมาย → คิดค่าสัมบูรณ์ → คำนวณ → ใส่เครื่องหมาย</p>
189            </div>
190
191            <div class="step-section" id="step3">
192                <h2>✏️ แบบฝึกหัดท้าทาย</h2>
193                <div id="exerciseContainer"></div>
194                <div style="display: flex; gap: 10px; margin-top: 20px;">
195                    <button class="btn-check" onclick="checkExercises()">✅ ตรวจคำตอบ</button>
196                    <button class="btn-action secondary" onclick="generateNewQuestions()" id="btnGenerate">
197                        <span id="genSpinner" class="spinner" style="display:none; border-top-color:white; margin-right:5px;"></span>
198                        🔄 สร้างโจทย์ใหม่ด้วย AI
199                    </button>
200                </div>
201                <div id="scoreDisplay" style="font-size: 1.2rem; font-weight: bold; margin-top: 15px;"></div>
202            </div>
203        </div>
204
205        <div class="nav-buttons">
206            <button class="nav-btn" id="prevBtn" onclick="changeStep(-1)">◀ ก่อนหน้า</button>
207            <span style="font-weight:bold;color:#d35400;" id="stepInfo">1 / 4</span>
208            <button class="nav-btn" id="nextBtn" onclick="changeStep(1)">ถัดไป ▶</button>
209        </div>
210    </div>
211
212    <!-- Chat -->
213    <div class="chat-container">
214        <div class="chat-header">
215            <h3>🤠 กัปตันจำนวนเต็ม</h3>
216            <p style="font-size: 0.8rem;">ภารกิจ: สำรวจถ้ำสมการ</p>
217            <div class="mission-progress"><div class="progress-bar" id="missionProgress"></div></div>
218        </div>
219        <div class="chat-messages" id="chatMessages">
220            <div class="msg ai">สวัสดีนักสำรวจ! ฉันคือกัปตันจำนวนเต็ม วันนี้เรามีภารกิจตามหาสมบัติในถ้ำสมการ เธอพร้อมจะใช้ความรู้เรื่องการบวกจำนวนเต็มมาช่วยไขปริศนาเปิดประตูถ้ำหรือยัง?</div>
221        </div>
222        <div class="chat-input">
223            <input type="text" id="chatInput" placeholder="พิมพ์โต้ตอบกับกัปตัน..." onkeypress="if(event.key === 'Enter') sendChatMessage()">
224            <button onclick="sendChatMessage()">➤</button>
225        </div>
226    </div>
227</div>
228
229<script>
230    /* ==========================================
231       SETTINGS & API MANAGEMENT
232       ========================================== */
233    let apiSettings = {
234        provider: 'google',
235        key: '',
236        model: 'gemini-2.5-flash-preview-09-2025'
237    };
238
239    function loadSettings() {
240        const saved = localStorage.getItem('math_ai_settings');
241        if (saved) {
242            apiSettings = JSON.parse(saved);
243            document.getElementById('apiProvider').value = apiSettings.provider;
244            document.getElementById('apiKeyInput').value = apiSettings.key;
245            document.getElementById('apiModelInput').value = apiSettings.model;
246            handleProviderChange();
247        }
248    }
249
250    function saveSettings() {
251        apiSettings.provider = document.getElementById('apiProvider').value;
252        apiSettings.key = document.getElementById('apiKeyInput').value;
253        apiSettings.model = document.getElementById('apiModelInput').value;
254        
255        localStorage.setItem('math_ai_settings', JSON.stringify(apiSettings));
256        alert('✅ บันทึกการตั้งค่าเรียบร้อยแล้ว!');
257        toggleSettings(false);
258    }
259
260    function toggleSettings(show) {
261        document.getElementById('settingsOverlay').style.display = show ? 'flex' : 'none';
262    }
263
264    function handleProviderChange() {
265        const provider = document.getElementById('apiProvider').value;
266        const keyLabel = document.getElementById('apiKeyLabel');
267        const modelInput = document.getElementById('apiModelInput');
268        
269        if (provider === 'google') {
270            keyLabel.innerText = "Google API Key";
271            if(!modelInput.value || modelInput.value.includes('gpt')) {
272                modelInput.value = "gemini-2.5-flash-preview-09-2025";
273            }
274        } else {
275            keyLabel.innerText = "OpenAI API Key";
276            if(!modelInput.value || modelInput.value.includes('gemini')) {
277                modelInput.value = "gpt-4o";
278            }
279        }
280    }
281
282    /* ==========================================
283       PEDAGOGY CONSTRAINTS
284       ========================================== */
285    const PEDAGOGY_SYSTEM_PROMPT = `
286คุณคือ AI ผู้ช่วยสอนวิชาคณิตศาสตร์ (Math EdTech Developer/Pedagogy Specialist)
287ข้อบังคับขั้นสูงสุด (Strict Constraints):
2881. **Socratic Method:** ห้ามบอกคำตอบสุดท้าย หรือเฉลยตรงๆ เด็ดขาด! ให้ใช้วิธีตั้งคำถามกลับเพื่อให้ผู้เรียนฉุกคิด
2892. **Productive Struggle:** ชื่นชมความพยายาม ให้คำใบ้ทีละนิด (Scaffolding) เพื่อให้เด็กแก้ปัญหาด้วยตัวเอง
2903. **Format:** สมการคณิตศาสตร์ต้องใช้ LaTeX format โดยหุ้มด้วย $ สำหรับ inline และ $$ สำหรับ block เสมอ
2914. ตอบเป็นภาษาไทยที่เป็นมิตร เข้าใจง่าย เหมาะกับเด็ก ม.1
292`;
293
294    /* ==========================================
295       UNIVERSAL API CALLER (Supports Google & OpenAI)
296       ========================================== */
297    async function callLLM(userPrompt, specificSystemInstruction, requireJSON = false, history = []) {
298        if (!apiSettings.key) {
299            toggleSettings(true);
300            throw new Error("กรุณาใส่ API Key ในการตั้งค่าก่อนใช้งาน");
301        }
302
303        const finalSystemInstruction = PEDAGOGY_SYSTEM_PROMPT + "\n\n" + specificSystemInstruction;
304        
305        if (apiSettings.provider === 'google') {
306            return callGemini(userPrompt, finalSystemInstruction, requireJSON, history);
307        } else {
308            return callOpenAI(userPrompt, finalSystemInstruction, requireJSON, history);
309        }
310    }
311
312    async function callGemini(userPrompt, sysInst, requireJSON, history) {
313        const url = `https://generativelanguage.googleapis.com/v1beta/models/${apiSettings.model}:generateContent?key=${apiSettings.key}`;
314        
315        let contents = history.map(msg => ({
316            role: msg.role === 'ai' ? 'model' : 'user',
317            parts: [{ text: msg.text }]
318        }));
319        contents.push({ role: 'user', parts: [{ text: userPrompt }] });
320
321        const payload = {
322            systemInstruction: { parts: [{ text: sysInst }] },
323            contents: contents
324        };
325
326        if (requireJSON) {
327            payload.generationConfig = { responseMimeType: "application/json" };
328        }
329
330        const response = await fetch(url, {
331            method: 'POST',
332            headers: { 'Content-Type': 'application/json' },
333            body: JSON.stringify(payload)
334        });
335
336        const data = await response.json();
337        if (!response.ok) throw new Error(data.error?.message || "API Error");
338        return data.candidates[0].content.parts[0].text;
339    }
340
341    async function callOpenAI(userPrompt, sysInst, requireJSON, history) {
342        const url = `https://api.openai.com/v1/chat/completions`;
343        
344        let messages = [{ role: 'system', content: sysInst }];
345        history.forEach(msg => {
346            messages.push({ role: msg.role === 'ai' ? 'assistant' : 'user', content: msg.text });
347        });
348        messages.push({ role: 'user', content: userPrompt });
349
350        const payload = {
351            model: apiSettings.model,
352            messages: messages
353        };
354
355        if (requireJSON) {
356            payload.response_format = { type: "json_object" };
357        }
358
359        const response = await fetch(url, {
360            method: 'POST',
361            headers: { 
362                'Content-Type': 'application/json',
363                'Authorization': `Bearer ${apiSettings.key}`
364            },
365            body: JSON.stringify(payload)
366        });
367
368        const data = await response.json();
369        if (!response.ok) throw new Error(data.error?.message || "API Error");
370        return data.choices[0].message.content;
371    }
372
373    /* ==========================================
374       MATH RENDERING & LOGIC
375       ========================================== */
376    class MathSpan extends HTMLElement {
377        connectedCallback() {
378            const math = this.innerHTML;
379            this.innerHTML = '';
380            try { katex.render(math, this, { throwOnError: false, displayMode: false }); } catch(e) { this.innerText = math; }
381        }
382    }
383    class MathDiv extends HTMLElement {
384        connectedCallback() {
385            const math = this.innerHTML;
386            this.innerHTML = '';
387            try { katex.render(math, this, { throwOnError: false, displayMode: true }); } catch(e) { this.innerText = math; }
388        }
389    }
390    customElements.define('math-span', MathSpan);
391    customElements.define('math-div', MathDiv);
392
393    function renderMathFeedback(container, text) {
394        const rawHtml = marked.parse(text);
395        const cleanHtml = DOMPurify.sanitize(rawHtml);
396        container.innerHTML = cleanHtml;
397        renderMathInElement(container, {
398            delimiters: [
399                {left: '$$', right: '$$', display: true},
400                {left: '$', right: '$', display: false}
401            ],
402            throwOnError: false
403        });
404    }
405
406    /* ==========================================
407       UI NAVIGATION
408       ========================================== */
409    let currentStep = 0;
410    const totalSteps = 4;
411
412    function showStep(step) {
413        document.querySelectorAll('.step-section').forEach(el => el.classList.remove('active'));
414        document.getElementById('step' + step).classList.add('active');
415        document.querySelectorAll('.step-dot').forEach((dot, index) => {
416            dot.classList.toggle('active', index === step);
417        });
418        document.getElementById('stepInfo').innerText = (step+1) + ' / ' + totalSteps;
419        document.getElementById('prevBtn').disabled = (step === 0);
420        document.getElementById('nextBtn').disabled = (step === totalSteps-1);
421    }
422    function changeStep(delta) { changeStepTo(currentStep + delta); }
423    function changeStepTo(step) {
424        if (step >= 0 && step < totalSteps) { currentStep = step; showStep(currentStep); }
425    }
426
427    /* ==========================================
428       EXERCISES & AI MODULES
429       ========================================== */
430    let exercises = [
431        { id: 'q1', q: "(-8) + 3", a: -5 },
432        { id: 'q2', q: "15 + (-22)", a: -7 },
433        { id: 'q3', q: "(-9) + (-6) + 4", a: -11 }
434    ];
435
436    function renderExercises() {
437        const container = document.getElementById('exerciseContainer');
438        container.innerHTML = '';
439        exercises.forEach((ex, index) => {
440            const divItem = document.createElement('div');
441            divItem.className = 'exercise-item';
442            divItem.id = `item_${ex.id}`;
443
444            const divRow = document.createElement('div');
445            divRow.className = 'exercise-row';
446            divRow.innerHTML = `<span>ข้อ ${index+1}) </span><math-span>${ex.q} = </math-span>
447                <input type="number" id="input_${ex.id}" placeholder="?">
448                <span class="feedback" id="fb_${ex.id}"></span>`;
449
450            const explainBtn = document.createElement('button');
451            explainBtn.className = 'btn-explain';
452            explainBtn.id = `btn_explain_${ex.id}`;
453            explainBtn.onclick = () => explainError(ex.id);
454            explainBtn.innerText = '❓ ทำไมข้อนี้ถึงผิด?';
455
456            const aiBox = document.createElement('div');
457            aiBox.className = 'ai-feedback-box';
458            aiBox.id = `ai_box_${ex.id}`;
459
460            divItem.appendChild(divRow);
461            divItem.appendChild(explainBtn);
462            divItem.appendChild(aiBox);
463            container.appendChild(divItem);
464        });
465    }
466
467    function checkExercises() {
468        let score = 0;
469        exercises.forEach(ex => {
470            const userVal = document.getElementById(`input_${ex.id}`).value;
471            const fb = document.getElementById(`fb_${ex.id}`);
472            const btn = document.getElementById(`btn_explain_${ex.id}`);
473            if (userVal !== '' && Number(userVal) === ex.a) {
474                fb.innerHTML = '✅ ถูกต้อง'; fb.style.color = '#27ae60'; btn.style.display = 'none';
475                score++;
476            } else if(userVal !== '') {
477                fb.innerHTML = '❌ ผิด'; fb.style.color = '#e74c3c'; btn.style.display = 'inline-block';
478            }
479        });
480        document.getElementById('scoreDisplay').innerHTML = `คะแนน: ${score}/${exercises.length}`;
481    }
482
483    async function explainError(id) {
484        const ex = exercises.find(e => e.id === id);
485        const val = document.getElementById(`input_${id}`).value;
486        const box = document.getElementById(`ai_box_${id}`);
487        box.style.display = 'block';
488        box.innerHTML = '<span class="spinner"></span> กำลังวิเคราะห์...';
489
490        try {
491            const prompt = `นักเรียนตอบผิดในโจทย์ $$${ex.q} = ?$$ โดยตอบเป็น "${val}" (คำตอบที่ถูกคือ ${ex.a}) ช่วยสอนวิธีคิดที่ถูกโดยใช้ Socratic Method`;
492            const res = await callLLM(prompt, "คุณคือครูที่วิเคราะห์ข้อผิดพลาดของเด็ก");
493            renderMathFeedback(box, res);
494        } catch (e) { box.innerHTML = `<span style="color:red;">❌ ${e.message}</span>`; }
495    }
496
497    async function generateNewQuestions() {
498        const btn = document.getElementById('btnGenerate');
499        btn.disabled = true;
500        document.getElementById('genSpinner').style.display = 'inline-block';
501
502        try {
503            const prompt = `สร้างโจทย์การบวกจำนวนเต็ม ม.1 ใหม่ 3 ข้อ ส่งเป็น JSON Array เท่านั้น รูปแบบ: [{"id":"new1","q":"(-5)+2","a":-3}]`;
504            let res = await callLLM(prompt, "คุณคือโจทย์ Generator ส่งแค่ JSON เปล่าๆ ห้ามมีคำนำหน้าหรือ markdown", true);
505            res = res.replace(/```json/g, '').replace(/```/g, '').trim();
506            const newEx = JSON.parse(res);
507            if(Array.isArray(newEx)) { exercises = newEx; renderExercises(); }
508        } catch (e) { alert(e.message); } finally {
509            btn.disabled = false;
510            document.getElementById('genSpinner').style.display = 'none';
511        }
512    }
513
514    /* ==========================================
515       CHAT MODULE
516       ========================================== */
517    let chatHistory = [];
518    let missionScore = 0;
519
520    async function sendChatMessage() {
521        const input = document.getElementById('chatInput');
522        const text = input.value.trim();
523        if(!text) return;
524
525        appendMessage('user', text);
526        input.value = '';
527        const loadId = appendMessage('ai', '<span class="spinner"></span> กำลังคิด...');
528
529        try {
530            const res = await callLLM(text, "คุณคือกัปตันจำนวนเต็ม ตัวละครสอนเลขแนวผจญภัย ให้คะแนนผ่าน [SCORE+10]", false, chatHistory);
531            let clean = res;
532            if(res.includes("[SCORE+10]")) {
533                clean = res.replace("[SCORE+10]", "");
534                missionScore = Math.min(100, missionScore + 10);
535                document.getElementById('missionProgress').style.width = missionScore + '%';
536            }
537            chatHistory.push({role:'user', text:text}, {role:'ai', text:clean});
538            renderMathFeedback(document.getElementById(loadId), clean);
539        } catch (e) { document.getElementById(loadId).innerHTML = `<span style="color:red;">❌ ${e.message}</span>`; }
540    }
541
542    function appendMessage(role, html) {
543        const div = document.createElement('div');
544        div.className = `msg ${role}`;
545        const id = 'msg_' + Date.now();
546        div.id = id;
547        div.innerHTML = html;
548        document.getElementById('chatMessages').appendChild(div);
549        document.getElementById('chatMessages').scrollTop = document.getElementById('chatMessages').scrollHeight;
550        return id;
551    }
552
553    window.onload = () => { loadSettings(); renderExercises(); showStep(0); };
554</script>
555</body>
556</html>