Everyday Mandarin – Lesson Planner /* Custom styles */ body { font-family: ‘Inter’, sans-serif; } /* Simple loading spinner */ .spinner { border: 4px solid rgba(0, 0, 0, 0.1); border-left-color: #ffffff; border-radius: 50%; width: 24px; height: 24px; animation: spin 1s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } }

Everyday Mandarin

AI Daily Lesson Planner

// — DOM Elements — const form = document.getElementById(‘lesson-form’); const reviewInput = document.getElementById(‘review-content’); const teachInput = document.getElementById(‘teach-content’); const generateBtn = document.getElementById(‘generate-btn’); const btnText = document.getElementById(‘btn-text’); const loadingSpinner = document.getElementById(‘loading-spinner’); const errorMessage = document.getElementById(‘error-message’); const formContainer = document.getElementById(‘form-container’); const scheduleContainer = document.getElementById(‘schedule-container’); const scheduleGrid = document.getElementById(‘schedule-grid’); const resetBtn = document.getElementById(‘reset-btn’); // — Gemini API Configuration — const apiKey = “”; // Leave as-is, will be populated by the environment const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-09-2025:generateContent?key=${apiKey}`; // — System Prompt & JSON Schema — // This system prompt instructs the AI on its role, the template, and the JSON output format. const systemPrompt = `You are an expert curriculum designer for ‘Everyday Mandarin,’ creating engaging 45-60 minute after-school lesson plans for elementary students (TK-5). A teacher will provide ‘Content to Review’ and ‘Content to Teach’. Your task is to generate a detailed 6-part daily schedule based on this template: 1. **Part 1 (0-5 min): Set the Mood.** Activity: Mandarin Music/Video. Suggest a specific song or video type related to the ‘Content to Teach’. 2. **Part 2 (6-11 min): Review.** Activity: Review [Content to Review]. Suggest a fun, quick review game or activity suitable for TK-5. 3. **Part 3 (12-22 min): Instruction.** Activity: Instruction [Content to Teach]. Suggest a ‘Direct Instruction’ or ‘I Do, We Do’ method for introducing the new content. 4. **Part 4 (23-33 min): Practice and Reinforce.** Activity: Group Practice. Suggest a specific, interactive learning group activity or practice game related to the new content. 5. **Part 5 (34-5 min before end): Test and Practice.** Activity: Independent Practice/Quiz. Suggest a simple worksheet idea or a fun quiz game (like ‘Around the World’ or ‘Fly Swatter’) to check understanding. 6. **Part 6 (Last 5 min): Wrap Up.** Activity: Rewards and Clean Up. This part is standard: ‘Teacher awards 3-2-1 reward points, students pack folders, clean up, and wait for dismissal.’ Provide this as the suggestion. You must return the schedule as a JSON object following the provided schema. Fill in the ‘title’, ‘time’, ‘activity’ (using the teacher’s input where specified), and ‘suggestion’ (your creative, age-appropriate idea) for all 6 parts.`; // This schema forces the AI to return a JSON object in the exact structure we need. const responseSchema = { “type”: “OBJECT”, “properties”: { “part1”: { “type”: “OBJECT”, “properties”: { “title”: { “type”: “STRING” }, “time”: { “type”: “STRING” }, “activity”: { “type”: “STRING” }, “suggestion”: { “type”: “STRING” } } }, “part2”: { “type”: “OBJECT”, “properties”: { “title”: { “type”: “STRING” }, “time”: { “type”: “STRING” }, “activity”: { “type”: “STRING” }, “suggestion”: { “type”: “STRING” } } }, “part3”: { “type”: “OBJECT”, “properties”: { “title”: { “type”: “STRING” }, “time”: { “type”: “STRING” }, “activity”: { “type”: “STRING” }, “suggestion”: { “type”: “STRING” } } }, “part4”: { “type”: “OBJECT”, “properties”: { “title”: { “type”: “STRING” }, “time”: { “type”: “STRING” }, “activity”: { “type”: “STRING” }, “suggestion”: { “type”: “STRING” } } }, “part5”: { “type”: “OBJECT”, “properties”: { “title”: { “type”: “STRING” }, “time”: { “type”: “STRING” }, “activity”: { “type”: “STRING” }, “suggestion”: { “type”: “STRING” } } }, “part6”: { “type”: “OBJECT”, “properties”: { “title”: { “type”: “STRING” }, “time”: { “type”: “STRING” }, “activity”: { “type”: “STRING” }, “suggestion”: { “type”: “STRING” } } } } }; // — Event Listeners — /** * Handles the form submission */ form.addEventListener(‘submit’, async (e) => { e.preventDefault(); const reviewText = reviewInput.value.trim(); const teachText = teachInput.value.trim(); if (!reviewText || !teachText) { showError(“Please fill out both fields.”); return; } setLoading(true); try { const scheduleData = await generateSchedule(reviewText, teachText); if (scheduleData) { displaySchedule(scheduleData); // Toggle views formContainer.classList.add(‘hidden’); scheduleContainer.classList.remove(‘hidden’); } } catch (error) { console.error(“Error generating schedule:”, error); showError(“Could not generate schedule. Please try again.”); } finally { setLoading(false); } }); /** * Handles the “Start Over” button click */ resetBtn.addEventListener(‘click’, () => { scheduleContainer.classList.add(‘hidden’); formContainer.classList.remove(‘hidden’); reviewInput.value = ”; teachInput.value = ”; scheduleGrid.innerHTML = ”; showError(”); // Clear any old errors }); // — Core Functions — /** * Calls the Gemini API to generate the schedule * @param {string} reviewContent – The content to review * @param {string} teachContent – The content to teach * @returns {object | null} – The parsed schedule object or null on error */ async function generateSchedule(reviewContent, teachContent) { const userQuery = `Please generate a daily class schedule. Content to Review: “${reviewContent}”. Content to Teach: “${teachContent}”.`; const payload = { contents: [{ parts: [{ text: userQuery }] }], systemInstruction: { parts: [{ text: systemPrompt }] }, generationConfig: { responseMimeType: “application/json”, responseSchema: responseSchema, } }; try { const response = await fetchWithRetry(apiUrl, { method: ‘POST’, headers: { ‘Content-Type’: ‘application/json’ }, body: JSON.stringify(payload) }); if (!response.ok) { // Added more detailed logging for debugging the 401 error console.error(“API Response Error:”, response.status, response.statusText); await response.text().then(text => console.error(“API Error Body:”, text)); throw new Error(`API request failed with status ${response.status}`); } const result = await response.json(); const candidate = result.candidates?.[0]; if (candidate && candidate.content?.parts?.[0]?.text) { const jsonText = candidate.content.parts[0].text; return JSON.parse(jsonText); } else { console.error(“Invalid response structure:”, result); throw new Error(“Invalid response from AI.”); } } catch (error) { console.error(“Error in generateSchedule:”, error); showError(“Failed to parse AI response. Please try again.”); return null; } } /** * Displays the generated schedule on the page * @param {object} scheduleData – The schedule object from the AI */ function displaySchedule(scheduleData) { scheduleGrid.innerHTML = ”; // Clear previous results // Loop through the 6 parts of the schedule for (const [key, part] of Object.entries(scheduleData)) { const card = document.createElement(‘div’); card.className = “flex flex-col rounded-lg shadow-lg overflow-hidden bg-white border border-gray-200”; // Determine card header color let headerColor = “bg-gray-500”; if (key === ‘part2’) headerColor = “bg-blue-600”; if (key === ‘part3’) headerColor = “bg-green-600”; if (key === ‘part4’ || key === ‘part5’) headerColor = “bg-yellow-500”; if (key === ‘part6’) headerColor = “bg-red-500”; card.innerHTML = `

${part.title}

${part.time}

Activity

${part.activity}

AI Suggestion

${part.suggestion}

`; scheduleGrid.appendChild(card); } } // — Utility Functions — /** * Sets the loading state of the generate button * @param {boolean} isLoading – Whether to show the loading state */ function setLoading(isLoading) { if (isLoading) { generateBtn.disabled = true; btnText.classList.add(‘hidden’); loadingSpinner.classList.remove(‘hidden’); showError(”); // Clear error on new request } else { generateBtn.disabled = false; btnText.classList.remove(‘hidden’); loadingSpinner.classList.add(‘hidden’); } } /** * Displays an error message to the user * @param {string} message – The error message to display */ function showError(message) { errorMessage.textContent = message; } /** * Implements exponential backoff for API retries */ async function fetchWithRetry(url, options, retries = 3, delay = 1000) { for (let i = 0; i < retries; i++) { try { const response = await fetch(url, options); // 429 is "Too Many Requests" – a retryable error if (response.status === 429 && i setTimeout(resolve, delay)); delay *= 2; // Exponential backoff continue; } return response; // Success or non-retryable error } catch (error) { if (i setTimeout(resolve, delay)); delay *= 2; continue; } throw error; // Rethrow last error } } }

window.sTagConfig = window.sTagConfig || []; function sTag(){sTagConfig.push(arguments);} sTag('ut', 'c0cf599fe19ed69ef0db086573535403');