Team Ai
Apppublic

rapan/integer-web-app-demo

sourceHugging Faceupdated 5mo agoView on Hugging Face
0likes
parobora.html705 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>Pythagoras Quest: AI Interactive Math</title>
7    <!-- Tailwind CSS for styling -->
8    <script src="https://cdn.tailwindcss.com"></script>
9    <!-- FontAwesome for Icons -->
10    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
11    <!-- Google Fonts -->
12    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Thai:wght@300;400;600;700&display=swap" rel="stylesheet">
13    
14    <style>
15        body {
16            font-family: 'Noto Sans Thai', sans-serif;
17            background-color: #f0fdf4; /* Light green tint for math theme */
18        }
19        .canvas-container {
20            position: relative;
21            width: 100%;
22            max-width: 400px;
23            margin: 0 auto;
24        }
25        /* Markdown simple styling */
26        .ai-markdown strong { font-weight: 700; color: #166534; }
27        .ai-markdown p { margin-bottom: 0.5rem; }
28        .ai-markdown ul { list-style-type: disc; margin-left: 1.5rem; margin-bottom: 0.5rem; }
29        
30        /* Custom Scrollbar for chat */
31        .chat-container::-webkit-scrollbar { width: 6px; }
32        .chat-container::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 4px; }
33    </style>
34</head>
35<body class="text-gray-800 h-screen flex flex-col overflow-hidden">
36
37    <!-- Header -->
38    <header class="bg-green-700 text-white p-4 shadow-md flex justify-between items-center z-10 shrink-0">
39        <div class="flex items-center gap-3">
40            <i class="fa-solid fa-shapes text-2xl"></i>
41            <h1 class="text-xl font-bold">Pythagoras Quest (AI Tutor)</h1>
42        </div>
43        <button onclick="openSettings()" class="hover:bg-green-600 p-2 rounded-full transition" title="ตั้งค่าระบบ">
44            <i class="fa-solid fa-gear text-xl"></i>
45        </button>
46    </header>
47
48    <!-- Main Content Area -->
49    <main class="flex-1 flex flex-col md:flex-row overflow-hidden relative">
50        
51        <!-- Left Panel: Interactive Math Visual & Tasks -->
52        <div class="w-full md:w-1/2 p-4 md:p-6 overflow-y-auto bg-white shadow-[2px_0_10px_rgba(0,0,0,0.05)] z-0 flex flex-col">
53            
54            <!-- Progress Bar (Module B Support) -->
55            <div class="mb-6 bg-gray-100 rounded-full h-3 w-full border border-gray-200 shadow-inner">
56                <div id="missionProgress" class="bg-gradient-to-r from-green-400 to-green-600 h-3 rounded-full transition-all duration-500 w-0"></div>
57            </div>
58
59            <!-- Visual Canvas for Pythagorean Theorem -->
60            <div class="canvas-container bg-gray-50 rounded-xl border-2 border-dashed border-gray-300 p-4 mb-6 flex justify-center items-center h-[250px]">
61                <canvas id="mathCanvas" width="300" height="200"></canvas>
62            </div>
63
64            <!-- Task Container (Dynamically swapped based on stage) -->
65            <div id="taskContainer" class="flex-1">
66                <!-- Content injected via JS -->
67            </div>
68            
69        </div>
70
71        <!-- Right Panel: AI Chat / Feedback Interface -->
72        <div class="w-full md:w-1/2 bg-gray-50 flex flex-col border-t md:border-t-0 md:border-l border-gray-200">
73            <div class="bg-green-100 px-4 py-3 border-b border-green-200 flex justify-between items-center shrink-0">
74                <span class="font-semibold text-green-800"><i class="fa-solid fa-robot mr-2"></i>ผู้ช่วย AI (Socratic Tutor)</span>
75                <span id="aiStatusBadge" class="text-xs bg-green-500 text-white px-2 py-1 rounded-full animate-pulse hidden">กำลังคิด...</span>
76            </div>
77            
78            <div id="chatHistory" class="chat-container flex-1 overflow-y-auto p-4 space-y-4">
79                <!-- Chat messages go here -->
80            </div>
81
82            <!-- Chat Input (Used primarily in Roleplay) -->
83            <div id="chatInputArea" class="p-3 bg-white border-t border-gray-200 shrink-0">
84                <form id="chatForm" onsubmit="handleChatSubmit(event)" class="flex gap-2">
85                    <input type="text" id="userInput" class="flex-1 border border-gray-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-green-500" placeholder="พิมพ์ข้อความที่นี่..." autocomplete="off">
86                    <button type="submit" class="bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 transition"><i class="fa-solid fa-paper-plane"></i></button>
87                </form>
88            </div>
89        </div>
90    </main>
91
92    <!-- Settings Modal -->
93    <div id="settingsModal" class="fixed inset-0 bg-black/50 hidden items-center justify-center z-50 backdrop-blur-sm">
94        <div class="bg-white rounded-xl shadow-2xl p-6 w-[90%] max-w-md transform transition-all scale-95 opacity-0" id="settingsBox">
95            <h2 class="text-2xl font-bold mb-4 text-gray-800"><i class="fa-solid fa-sliders mr-2 text-green-600"></i>ตั้งค่าระบบ AI</h2>
96            
97            <div class="space-y-4">
98                <div>
99                    <label class="block text-sm font-semibold text-gray-700 mb-1">AI Provider <span class="text-red-500">*</span></label>
100                    <select id="providerInput" onchange="toggleProviderSettings()" class="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-2 focus:ring-green-500">
101                        <option value="gemini">Google Gemini (Official)</option>
102                        <option value="openai">OpenAI Compatible (Custom API)</option>
103                    </select>
104                </div>
105                <div id="baseUrlGroup" class="hidden">
106                    <label class="block text-sm font-semibold text-gray-700 mb-1">Base URL (สำหรับ Custom API)</label>
107                    <input type="text" id="baseUrlInput" class="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-2 focus:ring-green-500" placeholder="https://api.openai.com/v1/chat/completions">
108                </div>
109                <div>
110                    <label class="block text-sm font-semibold text-gray-700 mb-1">API Key <span class="text-red-500">*</span></label>
111                    <input type="password" id="apiKeyInput" class="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-2 focus:ring-green-500" placeholder="ใส่ API Key ที่นี่...">
112                    <p class="text-xs text-gray-500 mt-1">คีย์จะถูกบันทึกไว้ในเบราว์เซอร์ของคุณเท่านั้น (localStorage)</p>
113                </div>
114                <div>
115                    <label class="block text-sm font-semibold text-gray-700 mb-1">Model Name</label>
116                    <input type="text" id="modelInput" class="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-2 focus:ring-green-500" value="gemini-2.5-flash">
117                </div>
118            </div>
119
120            <div id="settingsError" class="mt-3 text-sm text-red-600 hidden"></div>
121
122            <div class="mt-6 flex justify-end gap-3">
123                <button onclick="closeSettings()" id="btnCancelSettings" class="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded-lg transition">ยกเลิก</button>
124                <button onclick="saveSettings()" class="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition">บันทึกและเริ่มเรียน</button>
125            </div>
126        </div>
127    </div>
128
129    <!-- JavaScript Logic -->
130    <script>
131        /* ==========================================
132           1. Core Infrastructure & State Management
133        ========================================== */
134        
135        let currentState = {
136            provider: 'gemini', // 'gemini' or 'openai'
137            baseUrl: '',
138            apiKey: '',
139            modelName: 'gemini-2.5-flash',
140            stage: 'init', // init, roleplay, module_d (MCQ), module_i (Text), summary
141            logs: [], // Store interactions for Module H
142            triangle: { a: 3, b: 4, c: 5 } // Current math state
143        };
144
145        // 1.3 Math-Specific Pedagogy Constraints (Strict System Prompt)
146        const PEDAGOGY_RULES = `
147กฎเหล็กด้านการสอน (Pedagogy Constraints):
1481. ห้ามบอกคำตอบหรือเฉลยตรงๆ โดยเด็ดขาด
1492. ใช้ Socratic Method เสมอ: ถามคำถามกลับเพื่อให้นักเรียนคิดทบทวน (Productive Struggle)
1503. หากนักเรียนตอบผิด ให้ชี้ให้เห็นว่าผิดตรงไหน และใบ้กระบวนการต่อไป
1514. ห้ามถามหรือบันทึกข้อมูลส่วนบุคคลของนักเรียน (PDPA Compliance)
1525. ใช้ภาษาไทย เป็นมิตร ให้กำลังใจ และ In-Character ตามบริบท
153`;
154
155        window.onload = () => {
156            loadSettings();
157            drawTriangle(currentState.triangle.a, currentState.triangle.b, "?");
158            if (!currentState.apiKey) {
159                openSettings(true); // Force open if no key
160            } else {
161                startRoleplay(); // Module B Start
162            }
163        };
164
165        /* --- UI & Canvas Helpers --- */
166        
167        function drawTriangle(a, b, cLabel) {
168            const canvas = document.getElementById('mathCanvas');
169            const ctx = canvas.getContext('2d');
170            ctx.clearRect(0, 0, canvas.width, canvas.height);
171
172            // Base scale and offsets
173            const scale = 20; 
174            const startX = 80;
175            const startY = 160;
176
177            const width = b * scale;
178            const height = a * scale;
179
180            // Draw Triangle
181            ctx.beginPath();
182            ctx.moveTo(startX, startY); // Bottom Left (Right angle)
183            ctx.lineTo(startX + width, startY); // Bottom Right
184            ctx.lineTo(startX, startY - height); // Top Left
185            ctx.closePath();
186
187            // Style
188            ctx.lineWidth = 3;
189            ctx.strokeStyle = '#15803d'; // green-700
190            ctx.fillStyle = '#bbf7d0'; // green-200
191            ctx.fill();
192            ctx.stroke();
193
194            // Draw Right Angle symbol
195            ctx.beginPath();
196            ctx.moveTo(startX + 15, startY);
197            ctx.lineTo(startX + 15, startY - 15);
198            ctx.lineTo(startX, startY - 15);
199            ctx.strokeStyle = '#166534';
200            ctx.stroke();
201
202            // Labels
203            ctx.fillStyle = '#1f2937';
204            ctx.font = 'bold 16px Noto Sans Thai';
205            ctx.fillText(`ฐาน (b) = ${b}`, startX + width/2 - 30, startY + 25);
206            ctx.fillText(`สูง (a) = ${a}`, startX - 70, startY - height/2 + 5);
207            
208            // Hypotenuse
209            ctx.fillStyle = '#dc2626'; // red-600
210            ctx.font = 'bold 18px Noto Sans Thai';
211            ctx.fillText(`c = ${cLabel}`, startX + width/2 + 10, startY - height/2 - 10);
212        }
213
214        function updateProgress(percent) {
215            document.getElementById('missionProgress').style.width = `${percent}%`;
216        }
217
218        function renderMarkdown(text) {
219            // Very simple markdown parser for bold, italics, lists, and line breaks
220            let html = text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
221                           .replace(/\*(.*?)\*/g, '<em>$1</em>')
222                           .replace(/\n/g, '<br>');
223            // Handle simple unordered lists
224            html = html.replace(/<br>- (.*?)(?=<br>|$)/g, '<ul><li>$1</li></ul>');
225            html = html.replace(/<\/ul><ul>/g, ''); // combine adjacent lists
226            return `<div class="ai-markdown text-sm text-gray-700">${html}</div>`;
227        }
228
229        function appendChat(role, text) {
230            const chatHistory = document.getElementById('chatHistory');
231            const msgDiv = document.createElement('div');
232            msgDiv.className = `flex ${role === 'user' ? 'justify-end' : 'justify-start'} animate-fade-in`;
233            
234            const innerDiv = document.createElement('div');
235            innerDiv.className = `max-w-[85%] rounded-2xl px-4 py-3 ${
236                role === 'user' 
237                ? 'bg-green-600 text-white rounded-tr-none shadow-md' 
238                : 'bg-white border border-gray-200 rounded-tl-none shadow-sm'
239            }`;
240
241            if (role === 'ai') {
242                innerDiv.innerHTML = renderMarkdown(text);
243            } else {
244                innerDiv.textContent = text;
245            }
246
247            msgDiv.appendChild(innerDiv);
248            chatHistory.appendChild(msgDiv);
249            chatHistory.scrollTop = chatHistory.scrollHeight;
250        }
251
252        /* --- Universal API Caller (1.1) --- */
253
254        async function callLLM(prompt, systemInstruction) {
255            if (!currentState.apiKey) {
256                alert("กรุณาตั้งค่า API Key ก่อนใช้งาน");
257                openSettings();
258                return null;
259            }
260
261            const badge = document.getElementById('aiStatusBadge');
262            badge.classList.remove('hidden');
263
264            try {
265                let retries = 3;
266                let delay = 1000;
267                let response;
268                let data;
269
270                while (retries > 0) {
271                    if (currentState.provider === 'openai') {
272                        // --- OpenAI Compatible Format ---
273                        const endpoint = currentState.baseUrl || 'https://api.openai.com/v1/chat/completions';
274                        const payload = {
275                            model: currentState.modelName,
276                            messages: [
277                                { role: "system", content: PEDAGOGY_RULES + "\n" + systemInstruction },
278                                { role: "user", content: prompt }
279                            ]
280                        };
281                        response = await fetch(endpoint, {
282                            method: 'POST',
283                            headers: { 
284                                'Content-Type': 'application/json',
285                                'Authorization': `Bearer ${currentState.apiKey}`
286                            },
287                            body: JSON.stringify(payload)
288                        });
289                    } else {
290                        // --- Native Gemini Format ---
291                        const endpoint = `https://generativelanguage.googleapis.com/v1beta/models/${currentState.modelName}:generateContent?key=${currentState.apiKey}`;
292                        const payload = {
293                            contents: [{ parts: [{ text: prompt }] }],
294                            systemInstruction: { parts: [{ text: PEDAGOGY_RULES + "\n" + systemInstruction }] }
295                        };
296                        response = await fetch(endpoint, {
297                            method: 'POST',
298                            headers: { 'Content-Type': 'application/json' },
299                            body: JSON.stringify(payload)
300                        });
301                    }
302
303                    if (response.ok) {
304                        data = await response.json();
305                        break;
306                    }
307                    
308                    // Specific check for Rate Limit (429)
309                    if (response.status === 429) {
310                        throw new Error("RATE_LIMIT");
311                    }
312
313                    retries--;
314                    if(retries === 0) throw new Error(`API Error: ${response.status}`);
315                    await new Promise(r => setTimeout(r, delay));
316                    delay *= 2;
317                }
318
319                badge.classList.add('hidden');
320
321                // Extract text based on provider format
322                if (currentState.provider === 'openai') {
323                    if (data.choices && data.choices[0] && data.choices[0].message) {
324                        return data.choices[0].message.content;
325                    }
326                } else {
327                    if (data.candidates && data.candidates[0].content.parts[0].text) {
328                        return data.candidates[0].content.parts[0].text;
329                    }
330                }
331                
332                throw new Error("Invalid response format from API");
333
334            } catch (error) {
335                console.error("LLM Call Failed:", error);
336                badge.classList.add('hidden');
337                
338                if (error.message === "RATE_LIMIT") {
339                    appendChat('ai', "⚠️ **ขออภัยครับ** ระบบประมวลผลถูกเรียกใช้งานมากเกินไป (Rate Limit 429) กรุณารอสัก 10-15 วินาที แล้วลองส่งข้อความหรือกดปุ่มใหม่อีกครั้งครับ");
340                } else {
341                    appendChat('ai', "❌ **เกิดข้อผิดพลาด** ไม่สามารถติดต่อสมองกลได้ในขณะนี้ โปรดตรวจสอบการตั้งค่า API, โมเดล, หรือ URL ให้ถูกต้อง");
342                }
343                return null;
344            }
345        }
346
347        /* ==========================================
348           1.2 Settings Modal Management
349        ========================================== */
350
351        function loadSettings() {
352            currentState.provider = localStorage.getItem('pythagoras_provider') || 'gemini';
353            currentState.baseUrl = localStorage.getItem('pythagoras_baseurl') || '';
354            currentState.apiKey = localStorage.getItem('pythagoras_apikey') || '';
355            currentState.modelName = localStorage.getItem('pythagoras_model') || 'gemini-2.5-flash';
356        }
357
358        function toggleProviderSettings() {
359            const provider = document.getElementById('providerInput').value;
360            const baseUrlGroup = document.getElementById('baseUrlGroup');
361            const modelInput = document.getElementById('modelInput');
362            
363            if (provider === 'openai') {
364                baseUrlGroup.classList.remove('hidden');
365                if (modelInput.value.includes('gemini')) modelInput.value = 'gpt-3.5-turbo'; // default example for openai
366            } else {
367                baseUrlGroup.classList.add('hidden');
368                if (!modelInput.value.includes('gemini')) modelInput.value = 'gemini-2.5-flash';
369            }
370        }
371
372        function openSettings(force = false) {
373            document.getElementById('providerInput').value = currentState.provider;
374            document.getElementById('baseUrlInput').value = currentState.baseUrl;
375            document.getElementById('apiKeyInput').value = currentState.apiKey;
376            document.getElementById('modelInput').value = currentState.modelName;
377            
378            toggleProviderSettings(); // Set initial UI state
379            
380            document.getElementById('btnCancelSettings').style.display = force ? 'none' : 'block';
381            
382            const modal = document.getElementById('settingsModal');
383            const box = document.getElementById('settingsBox');
384            modal.classList.remove('hidden');
385            modal.classList.add('flex');
386            // Animation trick
387            setTimeout(() => {
388                box.classList.remove('scale-95', 'opacity-0');
389                box.classList.add('scale-100', 'opacity-100');
390            }, 10);
391        }
392
393        function closeSettings() {
394            const modal = document.getElementById('settingsModal');
395            const box = document.getElementById('settingsBox');
396            box.classList.remove('scale-100', 'opacity-100');
397            box.classList.add('scale-95', 'opacity-0');
398            setTimeout(() => {
399                modal.classList.remove('flex');
400                modal.classList.add('hidden');
401            }, 200);
402        }
403
404        function saveSettings() {
405            const provider = document.getElementById('providerInput').value;
406            const baseUrl = document.getElementById('baseUrlInput').value.trim();
407            const key = document.getElementById('apiKeyInput').value.trim();
408            const model = document.getElementById('modelInput').value.trim();
409            const errorDiv = document.getElementById('settingsError');
410
411            if (!key) {
412                errorDiv.textContent = "กรุณาใส่ API Key";
413                errorDiv.classList.remove('hidden');
414                return;
415            }
416
417            if (provider === 'openai' && !baseUrl) {
418                errorDiv.textContent = "กรุณาระบุ Base URL สำหรับ Custom API";
419                errorDiv.classList.remove('hidden');
420                return;
421            }
422
423            localStorage.setItem('pythagoras_provider', provider);
424            localStorage.setItem('pythagoras_baseurl', baseUrl);
425            localStorage.setItem('pythagoras_apikey', key);
426            localStorage.setItem('pythagoras_model', model);
427            
428            currentState.provider = provider;
429            currentState.baseUrl = baseUrl;
430            currentState.apiKey = key;
431            currentState.modelName = model;
432            
433            errorDiv.classList.add('hidden');
434            closeSettings();
435
436            // Restart game if it was stuck
437            if (currentState.stage === 'init') {
438                startRoleplay();
439            }
440        }
441
442        /* ==========================================
443           2. AI Modules Implementation
444        ========================================== */
445
446        // Global chat handler
447        async function handleChatSubmit(e) {
448            e.preventDefault();
449            const input = document.getElementById('userInput');
450            const text = input.value.trim();
451            if (!text) return;
452
453            appendChat('user', text);
454            input.value = '';
455
456            // Handle based on current stage
457            if (currentState.stage === 'roleplay') {
458                currentState.logs.push({ event: "Roleplay Chat User", detail: text });
459                
460                const sysInstruction = `
461คุณคือ "พีทาโกรัส" นักคณิตศาสตร์ชาวกรีก
462ภารกิจตอนนี้: คุณกำลังพยายามสร้างวิหาร แต่ขาดเชือกสำหรับวัดมุมฉาก คุณต้องการสร้างสามเหลี่ยมมุมฉากที่มีด้าน a=3, b=4 
463ให้นักเรียนช่วยคิดว่า ด้านตรงข้ามมุมฉาก (c) ต้องยาวเท่าไหร่?
464คุยกับนักเรียนในบทบาทนี้ ใช้ Socratic method ใบ้สูตร a^2 + b^2 = c^2 อย่างแนบเนียน 
465ถ้าผู้ใช้ตอบ "5" หรือใกล้เคียง ให้กล่าวชม และบอกว่า "ท่านพร้อมแล้วสำหรับการทดสอบต่อไป! พิมพ์ 'พร้อม' เพื่อเริ่ม"`;
466
467                const aiResponse = await callLLM(`นักเรียนตอบว่า: ${text}`, sysInstruction);
468                if (aiResponse) {
469                    appendChat('ai', aiResponse);
470                    currentState.logs.push({ event: "Roleplay Chat AI", detail: aiResponse });
471                    
472                    if (text.includes("พร้อม") || aiResponse.includes("การทดสอบต่อไป")) {
473                        setTimeout(startModuleD, 2000);
474                    }
475                }
476            }
477        }
478
479
480        /* --- Module B: Roleplay Simulation --- */
481        async function startRoleplay() {
482            currentState.stage = 'roleplay';
483            updateProgress(10);
484            
485            document.getElementById('taskContainer').innerHTML = `
486                <div class="bg-blue-50 border border-blue-200 p-4 rounded-xl">
487                    <h3 class="font-bold text-blue-800 text-lg mb-2"><i class="fa-solid fa-scroll mr-2"></i>Mission 1: พบพีทาโกรัส</h3>
488                    <p class="text-sm text-blue-700">พูดคุยกับ AI ทางขวามือเพื่อรับภารกิจแรกของคุณให้สำเร็จ! (หาค่า c ของสามเหลี่ยมด้านบน)</p>
489                </div>
490            `;
491
492            const sysInstruction = `คุณคือ "พีทาโกรัส" ทักทายผู้เล่นด้วยความตื่นเต้น และแจ้งภารกิจว่าคุณกำลังสร้างวิหาร แต่ต้องการเชือกที่ยาวพอดีสำหรับด้านตรงข้ามมุมฉาก (c) เมื่อด้านประกอบมุมฉากคือ 3 และ 4 ให้ถามผู้เล่นว่าเขาจะหามันได้อย่างไร`;
493            
494            const aiResponse = await callLLM("เริ่มการสนทนา ทักทายผู้เล่น", sysInstruction);
495            if (aiResponse) appendChat('ai', aiResponse);
496        }
497
498        /* --- Module D: Explain "Why I'm Wrong" --- */
499        function startModuleD() {
500            currentState.stage = 'module_d';
501            updateProgress(40);
502            document.getElementById('chatInputArea').style.display = 'none'; // Disable general chat
503            
504            // Update Canvas to new problem
505            currentState.triangle = { a: 6, b: 8 };
506            drawTriangle(6, 8, "?");
507
508            const taskHTML = `
509                <div class="bg-white border border-gray-200 p-4 rounded-xl shadow-sm">
510                    <h3 class="font-bold text-gray-800 text-lg mb-3">ด่านที่ 1: หอคอยพังทลาย</h3>
511                    <p class="text-sm text-gray-600 mb-4">หอคอยสูง 6 เมตร (a) ต้องการสร้างบันไดพาดให้ห่างจากฐาน 8 เมตร (b) บันไดต้องยาวเท่าไหร่ (c) ?</p>
512                    
513                    <div class="space-y-2" id="mcqOptions">
514                        <button onclick="checkMCQAnswer(14)" class="w-full text-left px-4 py-3 border border-gray-300 rounded-lg hover:bg-gray-50 transition">A. 14 เมตร</button>
515                        <button onclick="checkMCQAnswer(10)" class="w-full text-left px-4 py-3 border border-gray-300 rounded-lg hover:bg-gray-50 transition">B. 10 เมตร</button>
516                        <button onclick="checkMCQAnswer(100)" class="w-full text-left px-4 py-3 border border-gray-300 rounded-lg hover:bg-gray-50 transition">C. 100 เมตร</button>
517                    </div>
518
519                    <div id="wrongFeedbackArea" class="mt-4 hidden p-3 bg-red-50 border border-red-200 rounded-lg">
520                        <p class="text-red-700 text-sm mb-2"><i class="fa-solid fa-xmark-circle mr-1"></i> ยังไม่ถูกต้อง ลองคิดอีกนิด!</p>
521                        <button onclick="explainWhyWrong()" class="bg-yellow-500 text-white text-xs px-3 py-1.5 rounded shadow hover:bg-yellow-600 transition">
522                            <i class="fa-solid fa-circle-question mr-1"></i> ทำไมข้อนี้ถึงผิด? (AI ช่วยอธิบาย)
523                        </button>
524                    </div>
525                </div>
526            `;
527            document.getElementById('taskContainer').innerHTML = taskHTML;
528            appendChat('ai', "📝 **ด่านที่ 1 มาแล้ว!** ลองดูโจทย์ที่แผงควบคุมด้านซ้าย แล้วเลือกคำตอบที่คิดว่าถูกต้องครับ");
529        }
530
531        let lastWrongAnswer = null;
532
533        function checkMCQAnswer(ans) {
534            currentState.logs.push({ event: "Module D Answer", detail: `User selected ${ans} for a=6, b=8` });
535            const btnArea = document.getElementById('mcqOptions');
536            
537            if (ans === 10) {
538                btnArea.innerHTML = `<div class="p-3 bg-green-100 text-green-800 rounded-lg font-bold"><i class="fa-solid fa-check-circle mr-2"></i> ถูกต้อง! (c = 10)</div>`;
539                document.getElementById('wrongFeedbackArea').classList.add('hidden');
540                drawTriangle(6, 8, "10");
541                appendChat('ai', "🎉 ยอดเยี่ยมมาก! ท่านเข้าใจพื้นฐานแล้ว \n\nมาถึงด่านสุดท้ายกันครับ!");
542                setTimeout(startModuleI, 2500);
543            } else {
544                lastWrongAnswer = ans;
545                document.getElementById('wrongFeedbackArea').classList.remove('hidden');
546            }
547        }
548
549        async function explainWhyWrong() {
550            // Module D Core Functionality
551            const btn = document.querySelector('#wrongFeedbackArea button');
552            btn.innerHTML = `<i class="fa-solid fa-spinner fa-spin"></i> กำลังวิเคราะห์...`;
553            btn.disabled = true;
554
555            const sysInstruction = `
556ผู้เล่นเลือกตอบ ${lastWrongAnswer} สำหรับโจทย์พีทาโกรัส: a=6, b=8 หาค่า c
557หน้าที่ของคุณ (Module D): อธิบาย "Misconception" ว่าทำไมการคิดให้ได้ ${lastWrongAnswer} ถึงไม่ถูกต้องตามหลักคณิตศาสตร์
558ตัวอย่าง: ถ้าตอบ 14 แปลว่าเอา 6+8 โดยไม่ได้ยกกำลังสอง, ถ้าตอบ 100 คือลืมถอดรูท
559**ห้ามบอกเฉลยว่าตอบ 10** ให้ไกด์ว่าพวกเขาข้ามขั้นตอนไหนไป ยกตัวอย่างเปรียบเทียบให้เห็นภาพ`;
560
561            const aiResponse = await callLLM(`ทำไมฉันถึงตอบ ${lastWrongAnswer} แล้วผิด? ช่วยอธิบายหน่อย`, sysInstruction);
562            
563            if (aiResponse) {
564                appendChat('user', `❓ ทำไมตอบ ${lastWrongAnswer} ถึงผิดครับ?`);
565                appendChat('ai', aiResponse);
566                currentState.logs.push({ event: "Module D Explanation", detail: aiResponse });
567            }
568            
569            btn.innerHTML = `<i class="fa-solid fa-circle-question mr-1"></i> อธิบายข้อนี้อีกครั้ง`;
570            btn.disabled = false;
571        }
572
573        /* --- Module I: AI Answer Checker --- */
574        function startModuleI() {
575            currentState.stage = 'module_i';
576            updateProgress(70);
577            
578            // Update Canvas to new visual (Ship)
579            document.getElementById('mathCanvas').style.display = 'none';
580            const visualHTML = `
581                <div class="h-full w-full flex flex-col items-center justify-center text-center p-2">
582                    <i class="fa-solid fa-ship text-5xl text-blue-800 mb-3"></i>
583                    <p class="text-sm font-bold text-gray-700">เรือใบของพีทาโกรัส</p>
584                    <p class="text-xs text-gray-500">เสากระโดงสูง 5m, ระยะกางใบเรือ 12m</p>
585                </div>
586            `;
587            document.querySelector('.canvas-container').innerHTML = visualHTML;
588
589            const taskHTML = `
590                <div class="bg-white border border-gray-200 p-4 rounded-xl shadow-sm">
591                    <h3 class="font-bold text-purple-800 text-lg mb-2">ด่านสุดท้าย: แล่นเรือกลับบ้าน (อัตนัย)</h3>
592                    <p class="text-sm text-gray-600 mb-4">เสากระโดงเรือสูง 5 เมตร ผูกเชือกขึงใบเรือไปที่หัวเรือยาว 12 เมตร เชือกเส้นนั้น (ด้านตรงข้ามมุมฉาก) ยาวเท่าไหร่? <b>จงอธิบายวิธีทำอย่างละเอียด</b></p>
593                    
594                    <textarea id="subjectiveInput" rows="4" class="w-full border border-gray-300 rounded-lg p-3 text-sm focus:ring-2 focus:ring-purple-500 outline-none mb-3" placeholder="เขียนอธิบายวิธีทำ เช่น c^2 = a^2 + b^2..."></textarea>
595                    
596                    <button onclick="checkSubjectiveAnswer()" id="btnCheckSubj" class="w-full bg-purple-600 text-white font-bold py-2 rounded-lg hover:bg-purple-700 transition shadow-md">
597                        <i class="fa-solid fa-magnifying-glass-chart mr-1"></i> ตรวจคำตอบ (AI Rubric)
598                    </button>
599                </div>
600            `;
601            document.getElementById('taskContainer').innerHTML = taskHTML;
602            appendChat('ai', "📝 ด่านนี้ขอให้พิมพ์ **อธิบายวิธีทำ** ลงในช่องว่างนะครับ ระบบ AI ของเราจะตรวจตาม Rubric (ความเข้าใจ, ขั้นตอน, คำตอบ) ขอให้โชคดี!");
603        }
604
605        async function checkSubjectiveAnswer() {
606            const input = document.getElementById('subjectiveInput').value.trim();
607            if(!input) {
608                alert("กรุณาพิมพ์คำตอบก่อนกดตรวจครับ");
609                return;
610            }
611
612            const btn = document.getElementById('btnCheckSubj');
613            btn.innerHTML = `<i class="fa-solid fa-spinner fa-spin"></i> กำลังตรวจตาม Rubric...`;
614            btn.disabled = true;
615
616            currentState.logs.push({ event: "Module I Submit", detail: input });
617
618            const sysInstruction = `
619(Module I: AI Answer Checker)
620ประเมินคำตอบอัตนัยของนักเรียน สำหรับโจทย์: a=5, b=12, หา c (คำตอบที่ถูกคือ 13)
621คำตอบที่นักเรียนส่งมา: "${input}"
622
623ประเมินและตอบกลับโดยจัดรูปแบบเป็น Markdown ตามหัวข้อต่อไปนี้ (ส่งมาเป็น text markdown ปกติ ไม่ต้องใส่ block code JSON):
624**ผลการตรวจให้คะแนน**
625- 🎯 **ความเข้าใจ Concept:** [ระบุว่าเข้าใจสูตรไหม]
626- 📝 **ความสมบูรณ์ของขั้นตอน:** [ระบุว่าแสดงวิธีทำครบไหม เช่น การยกกำลัง การถอดรูท]
627- ✅ **ความถูกต้องของคำตอบ:** [ระบุว่าคำตอบสุดท้ายถูกไหม]
628- ⭐️ **คะแนนรวม:** [ให้คะแนนเต็ม 10]
629- 💡 **Feedback/ข้อเสนอแนะ:** [คำแนะนำเพื่อปรับปรุง ถ้ายังไม่ถูกห้ามเฉลย ให้ไกด์ต่อ]
630
631ถ้าคะแนนรวมมากกว่า 7/10 ให้ลงท้ายด้วยคำว่า "[PASS]" เพื่อให้ระบบทำงานต่อ
632`;
633
634            const aiResponse = await callLLM(`ประเมินคำตอบนี้: ${input}`, sysInstruction);
635            
636            if (aiResponse) {
637                appendChat('user', `ส่งคำตอบ: "${input}"`);
638                appendChat('ai', aiResponse);
639                currentState.logs.push({ event: "Module I Feedback", detail: aiResponse });
640
641                if (aiResponse.includes("[PASS]")) {
642                    setTimeout(generateFinalReport, 3000);
643                }
644            }
645
646            btn.innerHTML = `<i class="fa-solid fa-magnifying-glass-chart mr-1"></i> ตรวจคำตอบอีกครั้ง`;
647            btn.disabled = false;
648        }
649
650        /* --- Module H: Performance Summary --- */
651        async function generateFinalReport() {
652            currentState.stage = 'summary';
653            updateProgress(100);
654            
655            document.getElementById('taskContainer').innerHTML = `
656                <div class="flex flex-col items-center justify-center h-full text-center">
657                    <i class="fa-solid fa-trophy text-6xl text-yellow-500 mb-4 animate-bounce"></i>
658                    <h2 class="text-2xl font-bold text-gray-800">ภารกิจสำเร็จ!</h2>
659                    <p class="text-gray-600 mt-2">AI กำลังประมวลผล Report Card สรุปผลการเรียนของคุณ...</p>
660                </div>
661            `;
662
663            // Prepare log data payload
664            const logString = JSON.stringify(currentState.logs.map(l => `${l.event}: ${l.detail}`));
665
666            const sysInstruction = `
667(Module H: Performance Summary)
668นี่คือ Log การเรียนรู้ของนักเรียนในภารกิจ "ทฤษฎีบทพีทาโกรัส":
669${logString}
670
671จงสร้าง "Report Card สรุปผลการเรียน" ออกมาในรูปแบบ Markdown ที่สวยงาม โดยประกอบด้วย:
6721. 🏆 **จุดแข็ง (Strengths):** สิ่งที่ทำได้ดี (อ้างอิงจาก log)
6732. ⚠️ **จุดอ่อน / Misconception ที่พบ:** ข้อผิดพลาดที่เกิดขึ้นและวิเคราะห์ว่าเกิดจากความเข้าใจผิดใด
6743. 📈 **คำแนะนำเพื่อการพัฒนา (Recommendations):** ก้าวต่อไปควรฝึกเรื่องใด หรือระวังเรื่องใด
675
676พิมพ์ด้วยความเป็นทางการแต่น้ำเสียงให้กำลังใจ (Educator Tone)`;
677
678            const aiResponse = await callLLM("สร้าง Report Card สรุปผลจาก Log", sysInstruction);
679            
680            if (aiResponse) {
681                // Clear chat and show full screen report
682                document.getElementById('chatInputArea').style.display = 'none';
683                const chatHistory = document.getElementById('chatHistory');
684                chatHistory.innerHTML = ''; 
685                
686                const reportHTML = `
687                    <div class="bg-white border-2 border-green-400 rounded-xl p-6 shadow-lg animate-fade-in mx-2 my-4">
688                        <div class="flex items-center gap-3 border-b border-gray-200 pb-4 mb-4">
689                            <i class="fa-solid fa-clipboard-check text-3xl text-green-600"></i>
690                            <h2 class="text-xl font-bold text-gray-800">AI Performance Report Card</h2>
691                        </div>
692                        ${renderMarkdown(aiResponse)}
693                        <div class="mt-6 text-center">
694                            <button onclick="location.reload()" class="bg-green-600 text-white px-6 py-2 rounded-full hover:bg-green-700 transition">
695                                <i class="fa-solid fa-rotate-right mr-2"></i>เริ่มเรียนใหม่อีกครั้ง
696                            </button>
697                        </div>
698                    </div>
699                `;
700                chatHistory.innerHTML = reportHTML;
701            }
702        }
703    </script>
704</body>
705</html>