Suparak/integer-web-app-demo
0
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>