Team Ai
Apppublic

Suparak/integer-web-app-demo

sourceHugging Faceupdated 5mo agoView on Hugging Face
0likes
integerwithAI.html588 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, maximum-scale=1.5, user-scalable=yes">
6  <title>จำนวนเต็ม ม.1 | สื่อการสอนคณิตศาสตร์ (AI-Powered)</title>
7  
8  <!-- Tailwind CSS -->
9  <script src="https://cdn.tailwindcss.com"></script>
10  <!-- Google Fonts: Sarabun -->
11  <link href="https://fonts.googleapis.com/css2?family=Sarabun:ital,wght@0,300;0,400;0,500;0,600;0,700;1,400&display=swap" rel="stylesheet">
12  
13  <!-- ========================================== -->
14  <!-- 🧩 Module M1: KaTeX & DOMPurify Setup -->
15  <!-- ========================================== -->
16  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
17  <script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
18  <script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"></script>
19  <script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.0.8/purify.min.js"></script>
20
21  <style>
22    * { font-family: 'Sarabun', sans-serif; }
23    body { background-color: #f0fdf4; }
24    
25    /* SPA Navigation */
26    .page { display: none; }
27    .page.active { display: block; animation: fadeIn 0.3s ease-in-out; }
28    @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
29    
30    .nav-btn { transition: all 0.2s ease; border-bottom: 3px solid transparent; font-weight: 500; }
31    .nav-btn:hover { background-color: #dcfce7; border-bottom-color: #22c55e; }
32    .nav-btn.active-nav { background-color: #166534; color: white; border-bottom-color: #facc15; font-weight: 700; }
33    
34    /* Math Diagrams & Elements */
35    .step-badge { background-color: #166534; color: white; border-radius: 9999px; padding: 0.2rem 0.9rem; font-weight: 600; display: inline-block; margin-right: 0.5rem; }
36    .integer-diagram { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 0.5rem; background: #f9fefb; border-radius: 1rem; padding: 1.2rem 0.8rem; }
37    .diagram-block { min-width: 65px; padding: 0.6rem 0.3rem; border-radius: 0.75rem; font-weight: 700; text-align: center; box-shadow: 0 2px 8px rgba(0,0,0,0.05); }
38    .negative-block { background: #fee2e2; color: #991b1b; border: 2px solid #fecaca; }
39    .zero-block { background: #e0f2fe; color: #075985; border: 2px solid #bae6fd; font-size: 1.5rem; }
40    .positive-block { background: #dcfce7; color: #14532d; border: 2px solid #bbf7d0; }
41
42    /* Quiz Enhancements */
43    button.quiz-option { transition: all 0.2s ease; border: 2px solid #dcfce7; width: 100%; text-align: left; }
44    button.quiz-option:hover:not(:disabled) { border-color: #22c55e; background-color: #f0fdf4; }
45    button.quiz-option:disabled { cursor: not-allowed; opacity: 0.7; }
46    
47    /* 🧩 Module A: Chatbot UI */
48    #chat-panel {
49      position: fixed; bottom: 80px; right: 20px; width: 350px; max-width: calc(100vw - 40px);
50      height: 500px; max-height: calc(100vh - 100px);
51      background: white; border-radius: 1rem; box-shadow: 0 10px 25px rgba(0,0,0,0.2);
52      display: flex; flex-direction: column; transform: translateY(150%); transition: transform 0.3s ease-in-out;
53      z-index: 50; overflow: hidden; border: 1px solid #bbf7d0;
54    }
55    #chat-panel.open { transform: translateY(0); }
56    #chat-messages { flex: 1; overflow-y: auto; padding: 1rem; background: #f8fafc; }
57    .chat-bubble { max-width: 85%; padding: 0.75rem 1rem; border-radius: 1rem; margin-bottom: 0.75rem; word-wrap: break-word; }
58    .chat-user { background: #dcfce7; color: #166534; align-self: flex-end; border-bottom-right-radius: 0.25rem; margin-left: auto; }
59    .chat-ai { background: white; color: #1e293b; border: 1px solid #e2e8f0; align-self: flex-start; border-bottom-left-radius: 0.25rem; margin-right: auto; }
60    
61    /* Loading Spinner */
62    .loader { border: 3px solid #f3f3f3; border-top: 3px solid #22c55e; border-radius: 50%; width: 20px; height: 20px; animation: spin 1s linear infinite; display: inline-block; }
63    @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
64
65    /* Modals */
66    .modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 100; display: none; align-items: center; justify-content: center; backdrop-filter: blur(2px); }
67  </style>
68</head>
69<body class="antialiased text-gray-800">
70
71  <!-- ========================================== -->
72  <!-- Header & Settings Button -->
73  <!-- ========================================== -->
74  <div class="fixed top-4 right-4 z-40">
75    <button onclick="toggleSettings()" class="bg-white p-3 rounded-full shadow-lg border border-gray-200 hover:bg-gray-50 transition" title="ตั้งค่า AI">
76      ⚙️
77    </button>
78  </div>
79
80  <div class="max-w-5xl mx-auto px-4 md:px-6 py-4 md:py-6 bg-white/90 shadow-xl rounded-2xl my-4 md:my-8 border border-green-100">
81    
82    <!-- Navigation -->
83    <nav class="flex flex-wrap justify-center gap-2 md:gap-4 bg-green-50 p-2 rounded-xl mb-6 border border-green-200">
84      <button class="nav-btn active-nav px-4 py-2 rounded-lg text-base md:text-lg" onclick="navigateTo('home')" id="nav-home">🏠 หน้าแรก</button>
85      <button class="nav-btn px-4 py-2 rounded-lg text-base md:text-lg" onclick="navigateTo('content')" id="nav-content">🧮 ตัวอย่างโจทย์</button>
86      <button class="nav-btn px-4 py-2 rounded-lg text-base md:text-lg" onclick="navigateTo('quiz')" id="nav-quiz">📝 แบบทดสอบ</button>
87    </nav>
88
89    <!-- ===== หน้า HOME ===== -->
90    <div id="page-home" class="page active">
91      <header class="text-center mb-8">
92        <div class="bg-green-700 text-white py-6 px-4 rounded-xl shadow-md">
93          <h1 class="text-3xl md:text-5xl font-bold tracking-tight">🔢 จำนวนเต็ม ม.1</h1>
94          <p class="text-green-100 mt-2 text-lg md:text-xl">โลกของจำนวนเต็ม บวก ลบ ศูนย์ และความสัมพันธ์</p>
95        </div>
96      </header>
97      <div class="bg-green-50 border-l-4 border-green-600 p-4 md:p-6 rounded-r-lg shadow-sm">
98        <h2 class="text-xl md:text-2xl font-semibold text-green-900 mb-3">📘 จำนวนเต็มคืออะไร?</h2>
99        <p class="text-gray-700 leading-relaxed mb-4">
100          จำนวนเต็ม ประกอบด้วย <span class="font-bold text-green-800">จำนวนเต็มบวก</span>, <span class="font-bold text-green-800">จำนวนเต็มลบ</span> และ <span class="font-bold text-green-800">ศูนย์ (0)</span>
101        </p>
102        <div class="integer-diagram bg-white/90 border border-green-300 shadow-inner">
103          <div class="flex flex-wrap items-center justify-center gap-2 md:gap-3 w-full">
104            <div class="diagram-block negative-block">-3</div>
105            <div class="diagram-block negative-block">-2</div>
106            <div class="diagram-block negative-block">-1</div>
107            <span class="text-gray-400 text-2xl mx-1">→</span>
108            <div class="diagram-block zero-block text-xl md:text-2xl px-3 py-2">0</div>
109            <span class="text-gray-400 text-2xl mx-1">→</span>
110            <div class="diagram-block positive-block">1</div>
111            <div class="diagram-block positive-block">2</div>
112            <div class="diagram-block positive-block">3</div>
113          </div>
114        </div>
115      </div>
116    </div>
117
118    <!-- ===== หน้า CONTENT ===== -->
119    <div id="page-content" class="page">
120      <main>
121        <h2 class="text-2xl font-bold text-green-800 mb-6">🧮 ตัวอย่างโจทย์ พร้อมวิธีคิด</h2>
122        <div class="grid grid-cols-1 md:grid-cols-2 gap-5">
123          <!-- Sample 1 -->
124          <div class="bg-white p-5 rounded-xl shadow border border-green-200">
125            <div class="flex items-center gap-2 mb-3">
126              <span class="bg-green-600 text-white px-3 py-1 rounded-full text-sm font-bold">บวก</span>
127              <h3 class="text-lg font-semibold text-green-900 math-content">5 + (-3)</h3>
128            </div>
129            <div class="space-y-2 text-gray-700">
130              <p><span class="step-badge">1</span> เสมือนลบด้วยจำนวนบวก</p>
131              <p><span class="step-badge">2</span> <span class="math-content">5 - 3 = 2</span></p>
132            </div>
133          </div>
134          <!-- Sample 2 -->
135          <div class="bg-white p-5 rounded-xl shadow border border-green-200">
136            <div class="flex items-center gap-2 mb-3">
137              <span class="bg-green-600 text-white px-3 py-1 rounded-full text-sm font-bold">คูณ</span>
138              <h3 class="text-lg font-semibold text-green-900 math-content">(-6) \times (-2)</h3>
139            </div>
140            <div class="space-y-2 text-gray-700">
141              <p><span class="step-badge">1</span> ลบเจอขั้วเดียวกันเป็นบวก</p>
142              <p><span class="step-badge">2</span> <span class="math-content">6 \times 2 = 12</span></p>
143            </div>
144          </div>
145        </div>
146      </main>
147    </div>
148
149    <!-- ===== หน้า QUIZ (รองรับ Module D & F) ===== -->
150    <div id="page-quiz" class="page">
151      <div class="flex justify-between items-end mb-6">
152        <div>
153          <h2 class="text-2xl font-bold text-green-800">📝 แบบทดสอบฝึกสมอง</h2>
154          <p class="text-gray-600">วิเคราะห์และตอบคำถาม หากตอบผิด AI พร้อมช่วยอธิบาย</p>
155        </div>
156        <!-- 🧩 Module F: Infinite Scenario Generator Button -->
157        <button onclick="generateNewQuestion()" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg font-semibold shadow-md flex items-center gap-2 transition" id="btn-generate-quiz">
158          <span>🔄 สร้างโจทย์ใหม่ด้วย AI</span>
159        </button>
160      </div>
161
162      <section class="bg-green-50/70 p-5 rounded-xl">
163        <div id="quiz-container" class="space-y-6">
164          <!-- JS จะสร้างข้อสอบตรงนี้ -->
165        </div>
166      </section>
167    </div>
168  </div>
169
170  <!-- ========================================== -->
171  <!-- 🧩 Module A: Floating Chatbot -->
172  <!-- ========================================== -->
173  <button onclick="toggleChat()" class="fixed bottom-6 right-6 bg-green-600 hover:bg-green-700 text-white p-4 rounded-full shadow-2xl z-40 transition transform hover:scale-110">
174    💬 ถาม AI
175  </button>
176
177  <div id="chat-panel">
178    <div class="bg-green-700 text-white p-4 flex justify-between items-center rounded-t-xl">
179      <h3 class="font-bold">ติวเตอร์ AI จำนวนเต็ม</h3>
180      <button onclick="toggleChat()" class="text-white hover:text-green-200">✖</button>
181    </div>
182    <div id="chat-messages" class="flex flex-col">
183      <div class="chat-bubble chat-ai">
184        สวัสดีครับ! พี่คือติวเตอร์ AI เรื่องจำนวนเต็ม มีโจทย์ข้อไหนสงสัย หรืออยากให้พี่ช่วยใบ้ให้ พิมพ์มาได้เลยครับ! 🎯
185      </div>
186    </div>
187    <div class="p-3 bg-white border-t border-gray-200 flex gap-2">
188      <input type="text" id="chat-input" placeholder="พิมพ์คำถามที่นี่..." class="flex-1 p-2 border border-gray-300 rounded-lg focus:outline-none focus:border-green-500" onkeypress="if(event.key === 'Enter') sendChatMessage()">
189      <button onclick="sendChatMessage()" class="bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700">ส่ง</button>
190    </div>
191  </div>
192
193  <!-- ========================================== -->
194  <!-- 🛠️ Core 1.2: Settings Modal -->
195  <!-- ========================================== -->
196  <div id="settings-modal" class="modal-overlay">
197    <div class="bg-white p-6 rounded-xl shadow-2xl w-full max-w-md mx-4">
198      <h2 class="text-xl font-bold text-gray-800 mb-4 border-b pb-2">⚙️ ตั้งค่า AI (LLM Integration)</h2>
199      
200      <div class="mb-4">
201        <label class="block text-sm font-semibold text-gray-700 mb-1">Gemini API Key <span class="text-red-500">*</span></label>
202        <input type="password" id="api-key" class="w-full p-2 border border-gray-300 rounded-lg focus:outline-none focus:border-green-500" placeholder="AIzaSy...">
203        <p class="text-xs text-gray-500 mt-1">คีย์จะถูกเก็บไว้ในเครื่องของคุณเท่านั้น (localStorage)</p>
204      </div>
205
206      <div class="mb-6">
207        <label class="block text-sm font-semibold text-gray-700 mb-1">Model Name</label>
208        <input type="text" id="model-name" class="w-full p-2 border border-gray-300 rounded-lg focus:outline-none focus:border-green-500" value="gemini-2.0-flash">
209      </div>
210
211      <div class="flex justify-end gap-3">
212        <button onclick="closeSettings()" class="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded-lg" id="btn-cancel-settings">ยกเลิก</button>
213        <button onclick="saveSettings()" class="bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700">บันทึก</button>
214      </div>
215    </div>
216  </div>
217
218  <!-- ========================================== -->
219  <!-- 💻 JavaScript Logic -->
220  <!-- ========================================== -->
221  <script>
222    // --- 🛠️ Core Infrastructure & Settings ---
223    let apiKey = localStorage.getItem('gemini_api_key') || '';
224    let modelName = localStorage.getItem('gemini_model') || 'gemini-2.0-flash';
225    let currentPage = 'home';
226    let quizScore = { total: 0, correct: 0 };
227    let chatHistory = [];
228
229    // Pedagogy Constraints (1.3)
230    const PEDAGOGY_RULES = `
231      ข้อบังคับในการสอน (Strict Pedagogy Constraints):
232      1. Socratic Method: ห้ามบอกเฉลยหรือคำตอบสุดท้ายโดยตรง ให้ตั้งคำถามกลับเพื่อกระตุ้นให้นักเรียนคิดทบทวน
233      2. Productive Struggle: ชี้แนะแนวทางทีละขั้นตอน ให้โอกาสนักเรียนแก้ปัญหาด้วยตนเอง
234      3. PDPA: ห้ามถามหรืออ้างอิงข้อมูลส่วนตัวของนักเรียน
235      4. Math Rendering: หากมีการเขียนสมการ หรือตัวเลขทางคณิตศาสตร์ ต้องครอบด้วยเครื่องหมาย $...$ สำหรับบรรทัดเดียวกัน และ $$...$$ สำหรับแยกบรรทัดเสมอ เช่น ค่าของ $x = 5$ หรือ $$5 + (-3) = 2$$
236      5. ตอบเป็นภาษาไทยด้วยความเป็นมิตร ให้กำลังใจ
237    `;
238
239    window.onload = () => {
240      // Init KaTeX on static math content
241      renderStaticMath();
242      initQuiz();
243      
244      // Force settings if no API key
245      if (!apiKey) {
246        document.getElementById('btn-cancel-settings').style.display = 'none';
247        toggleSettings(true);
248      }
249    };
250
251    function toggleSettings(forceOpen = false) {
252      const modal = document.getElementById('settings-modal');
253      if (forceOpen || modal.style.display === 'none' || modal.style.display === '') {
254        document.getElementById('api-key').value = apiKey;
255        document.getElementById('model-name').value = modelName;
256        modal.style.display = 'flex';
257      } else {
258        modal.style.display = 'none';
259      }
260    }
261
262    function closeSettings() {
263      if (apiKey) document.getElementById('settings-modal').style.display = 'none';
264      else alert('กรุณาใส่ API Key ก่อนใช้งานระบบ AI');
265    }
266
267    function saveSettings() {
268      const key = document.getElementById('api-key').value.trim();
269      const model = document.getElementById('model-name').value.trim() || 'gemini-2.0-flash';
270      if (!key) { alert('API Key จำเป็นต้องระบุ'); return; }
271      
272      apiKey = key;
273      modelName = model;
274      localStorage.setItem('gemini_api_key', apiKey);
275      localStorage.setItem('gemini_model', modelName);
276      
277      document.getElementById('btn-cancel-settings').style.display = 'inline-block';
278      toggleSettings();
279      alert('บันทึกการตั้งค่า AI เรียบร้อย');
280    }
281
282    // --- 🛠️ Core 1.1: Universal API Caller ---
283    async function callLLM(promptText, systemInstruction, history = []) {
284      if (!apiKey) throw new Error("API Key not found");
285      
286      const endpoint = `https://generativelanguage.googleapis.com/v1beta/models/${modelName}:generateContent?key=${apiKey}`;
287      
288      // Construct contents format for Gemini API
289      let contents = [];
290      if (history.length > 0) {
291        contents = history.map(msg => ({
292          role: msg.role === 'user' ? 'user' : 'model',
293          parts: [{ text: msg.text }]
294        }));
295      }
296      contents.push({ role: "user", parts: [{ text: promptText }] });
297
298      const payload = {
299        contents: contents,
300        systemInstruction: { parts: [{ text: systemInstruction }] },
301        generationConfig: { temperature: 0.7 }
302      };
303
304      try {
305        const response = await fetch(endpoint, {
306          method: 'POST',
307          headers: { 'Content-Type': 'application/json' },
308          body: JSON.stringify(payload)
309        });
310
311        const data = await response.json();
312        if (data.error) throw new Error(data.error.message);
313        
314        return data.candidates[0].content.parts[0].text;
315      } catch (error) {
316        console.error("LLM API Error:", error);
317        throw error;
318      }
319    }
320
321    // --- 🧩 Module M1: KaTeX Static Renderer ---
322    function renderStaticMath() {
323      const elements = document.querySelectorAll('.math-content');
324      elements.forEach(el => {
325        // Assume text inside is math, wrap in $$ if not already
326        const text = el.innerText;
327        if(!text.includes('$')) el.innerText = `$$${text}$$`;
328      });
329      renderMathFeedback(document.body, document.body.innerHTML, false);
330    }
331
332    function renderMathFeedback(container, htmlText, sanitize = true) {
333      if(sanitize) {
334        container.innerHTML = DOMPurify.sanitize(htmlText);
335      }
336      if (window.renderMathInElement) {
337        renderMathInElement(container, {
338          delimiters: [
339            {left: '$$', right: '$$', display: true},
340            {left: '$', right: '$', display: false}
341          ],
342          throwOnError: false
343        });
344      }
345    }
346
347    // --- SPA Navigation & Context ---
348    function navigateTo(pageName) {
349      document.querySelectorAll('.page').forEach(page => page.classList.remove('active'));
350      document.getElementById('page-' + pageName).classList.add('active');
351      document.querySelectorAll('.nav-btn').forEach(btn => btn.classList.remove('active-nav'));
352      document.getElementById('nav-' + pageName).classList.add('active-nav');
353      currentPage = pageName;
354    }
355
356    function getCurrentContext() {
357      let ctx = `นักเรียนกำลังอยู่ที่หน้า: ${currentPage === 'home' ? 'หน้าแรก (ทฤษฎี)' : currentPage === 'content' ? 'ตัวอย่างโจทย์' : 'แบบทดสอบ'}\n`;
358      if (currentPage === 'quiz') {
359        ctx += `คะแนนแบบทดสอบปัจจุบัน: ทำแล้ว ${quizScore.total} ข้อ, ถูก ${quizScore.correct} ข้อ`;
360      }
361      return ctx;
362    }
363
364    // --- 🧩 Module A: Context-Aware Chatbot ---
365    function toggleChat() {
366      const panel = document.getElementById('chat-panel');
367      panel.classList.toggle('open');
368    }
369
370    async function sendChatMessage() {
371      const inputEl = document.getElementById('chat-input');
372      const text = inputEl.value.trim();
373      if (!text) return;
374
375      appendMessage('user', text);
376      inputEl.value = '';
377      
378      const loadingId = appendMessage('ai', '<div class="loader"></div> กำลังคิด...');
379      
380      const systemPrompt = `
381        ${PEDAGOGY_RULES}
382        คุณคือผู้ช่วยสอนคณิตศาสตร์ (AI Tutor) สำหรับนักเรียน ม.1
383        บริบทปัจจุบันของนักเรียน (Context): ${getCurrentContext()}
384        จงตอบคำถามโดยอิงจากบริบทนี้ และใช้ Socratic Method เสมอ
385      `;
386
387      try {
388        const response = await callLLM(text, systemPrompt, chatHistory);
389        updateMessage(loadingId, response);
390        chatHistory.push({role: 'user', text: text});
391        chatHistory.push({role: 'ai', text: response});
392      } catch (error) {
393        updateMessage(loadingId, `❌ ขออภัย เกิดข้อผิดพลาดในการเชื่อมต่อ AI: ${error.message}`);
394      }
395    }
396
397    function appendMessage(role, htmlContent) {
398      const container = document.getElementById('chat-messages');
399      const msgDiv = document.createElement('div');
400      const msgId = 'msg-' + Date.now();
401      msgDiv.id = msgId;
402      msgDiv.className = `chat-bubble ${role === 'user' ? 'chat-user' : 'chat-ai'}`;
403      
404      // Render text/math safely
405      if(role === 'user') {
406         msgDiv.innerText = htmlContent; // text only for user
407      } else {
408         renderMathFeedback(msgDiv, htmlContent); // sanitize and render math for AI
409      }
410      
411      container.appendChild(msgDiv);
412      container.scrollTop = container.scrollHeight;
413      return msgId;
414    }
415
416    function updateMessage(id, newHtmlContent) {
417      const msgDiv = document.getElementById(id);
418      if (msgDiv) {
419        renderMathFeedback(msgDiv, newHtmlContent);
420      }
421    }
422
423    // --- 🧩 Module D & F: Quiz Infrastructure ---
424    let quizData = [
425      { question: "(-8) + 5 = ?", options: ["13", "-3", "3", "-13"], correctIndex: 1 },
426      { question: "4 - (-6) = ?", options: ["-2", "10", "2", "-10"], correctIndex: 1 },
427      { question: "(-3) \\times 7 = ?", options: ["21", "-10", "-21", "4"], correctIndex: 2 }
428    ];
429
430    function initQuiz() {
431      renderQuizQuestions();
432    }
433
434    function renderQuizQuestions() {
435      const container = document.getElementById('quiz-container');
436      container.innerHTML = '';
437      
438      quizData.forEach((q, qIndex) => {
439        const qDiv = document.createElement('div');
440        qDiv.className = "bg-white p-4 rounded-lg shadow-sm border border-gray-100";
441        qDiv.innerHTML = `<p class="font-semibold text-lg mb-3">ข้อ ${qIndex + 1}. $${q.question}$</p>`;
442        
443        const optGrid = document.createElement('div');
444        optGrid.className = "grid grid-cols-2 gap-3";
445        
446        q.options.forEach((opt, optIndex) => {
447          const btn = document.createElement('button');
448          btn.className = "quiz-option bg-gray-50 p-3 rounded-lg hover:bg-green-100 font-medium text-gray-700";
449          btn.innerHTML = `$${opt}$`;
450          btn.onclick = () => checkAnswer(qIndex, optIndex, btn, qDiv);
451          optGrid.appendChild(btn);
452        });
453        
454        qDiv.appendChild(optGrid);
455        
456        // Placeholder for AI Explanation
457        const explanationDiv = document.createElement('div');
458        explanationDiv.id = `explain-${qIndex}`;
459        explanationDiv.className = "mt-4 text-sm hidden";
460        qDiv.appendChild(explanationDiv);
461
462        renderMathFeedback(qDiv, qDiv.innerHTML); // Render math initially
463        container.appendChild(qDiv);
464      });
465    }
466
467    // --- 🧩 Module D: Explain "Why I'm Wrong" ---
468    function checkAnswer(qIndex, optIndex, btnClicked, qDiv) {
469      const q = quizData[qIndex];
470      const buttons = qDiv.querySelectorAll('.quiz-option');
471      buttons.forEach(b => b.disabled = true); // Disable all
472      
473      quizScore.total++;
474
475      if (optIndex === q.correctIndex) {
476        quizScore.correct++;
477        btnClicked.classList.remove('bg-gray-50', 'hover:bg-green-100');
478        btnClicked.classList.add('bg-green-200', 'border-green-500', 'text-green-800');
479        
480        const feedback = document.createElement('p');
481        feedback.className = "mt-3 text-green-700 font-bold";
482        feedback.innerText = "✅ ถูกต้องยอดเยี่ยม!";
483        qDiv.appendChild(feedback);
484      } else {
485        btnClicked.classList.remove('bg-gray-50', 'hover:bg-green-100');
486        btnClicked.classList.add('bg-red-100', 'border-red-400', 'text-red-800');
487        
488        // Highlight correct answer
489        buttons[q.correctIndex].classList.add('bg-green-100', 'border-green-400');
490
491        // Append Module D Button
492        const helpBtn = document.createElement('button');
493        helpBtn.className = "mt-3 flex items-center gap-2 text-blue-600 hover:text-blue-800 font-semibold text-sm bg-blue-50 px-3 py-1.5 rounded-md transition";
494        helpBtn.innerHTML = "<span>❓ ทำไมข้อนี้ถึงผิด? (AI ช่วยอธิบาย)</span>";
495        helpBtn.onclick = () => explainWrongAnswer(qIndex, optIndex, qDiv);
496        qDiv.appendChild(helpBtn);
497      }
498    }
499
500    async function explainWrongAnswer(qIndex, selectedOptIndex, qDiv) {
501      const q = quizData[qIndex];
502      const explainDiv = qDiv.querySelector(`#explain-${qIndex}`);
503      explainDiv.classList.remove('hidden');
504      explainDiv.innerHTML = `<div class="p-3 bg-blue-50 rounded-lg text-blue-800"><span class="loader border-blue-400 border-t-blue-800"></span> AI กำลังวิเคราะห์ข้อผิดพลาด...</div>`;
505      
506      const prompt = `
507        โจทย์: ${q.question}
508        คำตอบที่ถูกต้องคือ: ${q.options[q.correctIndex]}
509        แต่นักเรียนตอบผิด โดยเลือก: ${q.options[selectedOptIndex]}
510        
511        หน้าที่ของคุณ:
512        1. อธิบายว่าทำไมนักเรียนถึงมักจะตอบผิดเป็นตัวเลือกนี้ (Misconception ในเรื่องจำนวนเต็มคืออะไร)
513        2. อธิบายวิธีคิดที่ถูกต้องอย่างเป็นรูปธรรม (เช่น การใช้หนี้สิน-ทรัพย์สิน, เส้นจำนวน, หรืออุณหภูมิ)
514      `;
515
516      const sysInst = `
517        ${PEDAGOGY_RULES}
518        คุณคือผู้เชี่ยวชาญการวิเคราะห์ข้อผิดพลาดทางคณิตศาสตร์ (Error Analysis)
519        จงอธิบายอย่างเป็นมิตร ชัดเจน และใช้สมการ $...$ เสมอ ห้ามดุหรือตำหนินักเรียน
520      `;
521
522      try {
523        const response = await callLLM(prompt, sysInst);
524        const formattedHtml = `<div class="p-4 bg-blue-50 border border-blue-200 rounded-lg text-gray-800">
525                                <strong>💡 AI อธิบาย:</strong><br>${response.replace(/\n/g, '<br>')}
526                               </div>`;
527        renderMathFeedback(explainDiv, formattedHtml);
528      } catch (error) {
529        explainDiv.innerHTML = `<div class="p-3 bg-red-50 text-red-600 rounded-lg">❌ เกิดข้อผิดพลาด: ${error.message}</div>`;
530      }
531    }
532
533    // --- 🧩 Module F: Infinite Scenario Generator ---
534    async function generateNewQuestion() {
535      const btn = document.getElementById('btn-generate-quiz');
536      const originalText = btn.innerHTML;
537      btn.innerHTML = `<span class="loader border-white border-t-gray-300"></span> กำลังสร้าง...`;
538      btn.disabled = true;
539
540      const sysInst = `
541        คุณคือตัวสร้างโจทย์คณิตศาสตร์ (Math Generator) เรื่องจำนวนเต็ม ระดับ ม.1
542        คุณต้องตอบกลับเป็นรูปแบบ JSON เท่านั้น ห้ามมีข้อความอื่นปน
543      `;
544
545      const prompt = `
546        สร้างโจทย์คณิตศาสตร์เรื่องจำนวนเต็ม 1 ข้อ (บวก ลบ คูณ หรือหาร)
547        ให้มีความซับซ้อนระดับกลาง 
548        
549        Return STRICT JSON format EXACTLY like this:
550        {
551          "question": "(-15) + (-7) = ?",
552          "options": ["-22", "8", "-8", "22"],
553          "correctIndex": 0
554        }
555        หมายเหตุ: ในฟิลด์ question และ options ไม่ต้องใส่ $ ครอบ เดี๋ยวระบบจัดการเอง
556      `;
557
558      try {
559        const response = await callLLM(prompt, sysInst);
560        
561        // Parse JSON safely (removing markdown code blocks if AI added them)
562        let cleanedJson = response.replace(/```json/g, '').replace(/```/g, '').trim();
563        const newQ = JSON.parse(cleanedJson);
564        
565        if(newQ.question && newQ.options && newQ.options.length === 4 && newQ.correctIndex !== undefined) {
566          quizData.push(newQ);
567          renderQuizQuestions();
568          
569          // Scroll to the new question
570          setTimeout(() => {
571            const container = document.getElementById('quiz-container');
572            container.lastElementChild.scrollIntoView({ behavior: 'smooth' });
573          }, 100);
574        } else {
575          throw new Error("Invalid schema structure from AI");
576        }
577      } catch (error) {
578        alert("❌ ไม่สามารถสร้างโจทย์ใหม่ได้ในขณะนี้: " + error.message);
579        console.error(error);
580      } finally {
581        btn.innerHTML = originalText;
582        btn.disabled = false;
583      }
584    }
585
586  </script>
587</body>
588</html>