hacktoberfesthacktoberfest-acceptedinvalid
Repository metrics
- Stars
- (553 stars)
- PR merge metrics
- (PR metrics pending)
Description
<nav class="bg-blue-600 text-white p-4 shadow-lg sticky top-0 z-50">
<div class="container mx-auto flex justify-between items-center">
<div class="flex items-center gap-3">
<i class="fa-solid fa-school text-2xl"></i>
<h1 class="text-xl font-bold">School AI Time</h1>
</div>
<div class="flex items-center gap-4">
<div id="realtime-clock" class="text-2xl font-bold clock-font tracking-wider">00:00:00</div>
</div>
</div>
</nav>
<div class="container mx-auto p-4 grid grid-cols-1 lg:grid-cols-3 gap-6">
<div class="lg:col-span-2 space-y-6">
<div class="bg-white rounded-xl shadow-lg p-8 text-center border-l-8 border-blue-500">
<h2 class="text-gray-500 text-lg mb-2">สถานะปัจจุบัน</h2>
<div id="current-status-text" class="text-3xl font-bold text-blue-600 mb-4">รอการประกาศ</div>
<div class="bg-blue-50 rounded-lg p-4">
<p class="text-sm text-gray-600 mb-1">เสียงถัดไป</p>
<div class="flex justify-between items-center">
<span id="next-schedule-name" class="font-semibold">-</span>
<span id="next-schedule-time" class="text-blue-600 font-bold">--:--</span>
</div>
</div>
</div>
<div class="bg-white rounded-xl shadow-lg p-6">
<h3 class="text-xl font-bold mb-4 border-b pb-2 flex items-center gap-2">
<i class="fa-solid fa-microphone-lines text-blue-500"></i>
<span>ประกาศด้วยข้อความฉุกเฉิน (Text-to-Speech)</span>
</h3>
<div class="flex flex-wrap gap-2 mb-4">
<button onclick="setTemplate('ประกาศจากห้องวิชาการ ขอเชิญคณะครูประชุมด่วนที่ห้องโสตทัศนศึกษาค่ะ')" class="text-xs bg-gray-100 hover:bg-gray-200 text-gray-700 py-1 px-3 rounded-full transition">เรียกประชุมครู</button>
<button onclick="setTemplate('ประกาศจากฝ่ายกิจการนักเรียน ขอให้นักเรียนทุกคนลงมาเข้าแถวเคารพธงชาติที่ลานกิจกรรมในเวลานี้ค่ะ')" class="text-xs bg-gray-100 hover:bg-gray-200 text-gray-700 py-1 px-3 rounded-full transition">เรียกเข้าแถว</button>
<button onclick="setTemplate('โรงเรียนสุมณฑาศึกษา ยินดีต้อนรับคณะผู้บริหารและแขกผู้มีเกียรติทุกท่านค่ะ')" class="text-xs bg-gray-100 hover:bg-gray-200 text-gray-700 py-1 px-3 rounded-full transition">กล่าวต้อนรับ</button>
</div>
<div class="flex flex-col md:flex-row gap-4">
<input type="text" id="tts-input" class="flex-1 border p-3 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="พิมพ์ข้อความที่ต้องการประกาศที่นี่...">
<button onclick="speakText()" class="bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700 transition font-bold shadow-md">
<i class="fa-solid fa-volume-high"></i> พูดทันที
</button>
</div>
</div>
</div>
<div class="lg:col-span-1">
<div class="bg-white rounded-xl shadow-lg p-6 sticky top-24">
<div class="flex justify-between items-center border-b pb-2 mb-4">
<h3 class="text-xl font-bold flex items-center gap-2">
<i class="fa-solid fa-gear text-gray-500"></i> จัดการตารางเวลา
</h3>
<label class="flex items-center gap-2 text-sm text-gray-600 bg-gray-50 px-2 py-1 rounded border">
<input type="checkbox" id="workday-only" onchange="saveSettings()" checked>
เล่นเฉพาะ จ.-ศ.
</label>
</div>
<div class="overflow-y-auto max-h-[500px] pr-2 custom-scrollbar">
<div id="schedule-container" class="space-y-3">
</div>
</div>
</div>
</div>
</div>
<audio id="main-audio-player"></audio>
<script>
// --- State Management ---
const STORAGE_KEY = 'school_ai_time_schedule';
const SETTINGS_KEY = 'school_ai_time_settings';
let schedule = [];
let lastPlayedTime = ""; // Prevent double play in same minute
// Default Presets for a standard school day
const defaultSchedule = [
{ id: 0, time: '07:50', text: 'เตรียมตัวเข้าแถวเคารพธงชาติ' },
{ id: 1, time: '08:30', text: 'หมดเวลาโฮมรูม เริ่มเรียนคาบที่ 1' },
{ id: 2, time: '12:00', text: 'พักรับประทานอาหารกลางวัน' },
{ id: 3, time: '16:00', text: 'หมดเวลาเรียน ขอให้นักเรียนเดินทางกลับบ้านโดยสวัสดิภาพ' }
];
document.addEventListener('DOMContentLoaded', () => {
loadSettings();
renderSchedule();
setInterval(updateClock, 1000);
setInterval(checkSchedule, 1000);
updateClock();
});
function updateClock() {
const now = new Date();
const timeString = now.toLocaleTimeString('en-GB', { hour12: false });
document.getElementById('realtime-clock').innerText = timeString;
updateNextScheduleDisplay(now);
}
// --- TTS Functions ---
function setTemplate(text) {
document.getElementById('tts-input').value = text;
}
function speakText(textToSpeak = null) {
const text = textToSpeak || document.getElementById('tts-input').value;
if (!text) return alert("กรุณาพิมพ์ข้อความ");
const utterance = new SpeechSynthesisUtterance(text);
utterance.lang = 'th-TH'; // บังคับภาษาไทย
utterance.rate = 0.9; // ปรับให้พูดช้าลงนิดหน่อยเพื่อให้ฟังชัดเจนเหมือนประกาศโรงเรียน
// แสดงสถานะ
document.getElementById('current-status-text').innerText = "กำลังประกาศข้อความ...";
utterance.onend = () => {
document.getElementById('current-status-text').innerText = "รอการประกาศ";
};
window.speechSynthesis.speak(utterance);
}
// --- Schedule & Persistence Logic ---
function loadSettings() {
const savedSchedule = localStorage.getItem(STORAGE_KEY);
const savedSettings = localStorage.getItem(SETTINGS_KEY);
if (savedSchedule) {
schedule = JSON.parse(savedSchedule);
} else {
// Initialize with 10 blank slots + defaults if empty
schedule = Array.from({length: 10}, (_, i) => {
const defaultSlot = defaultSchedule.find(d => d.id === i);
return defaultSlot ? { time: defaultSlot.time, text: defaultSlot.text } : { time: '', text: '' };
});
}
if(savedSettings) {
const settings = JSON.parse(savedSettings);
document.getElementById('workday-only').checked = settings.workdayOnly;
}
}
function saveSettings() {
localStorage.setItem(STORAGE_KEY, JSON.stringify(schedule));
localStorage.setItem(SETTINGS_KEY, JSON.stringify({
workdayOnly: document.getElementById('workday-only').checked
}));
renderSchedule();
}
function renderSchedule() {
const container = document.getElementById('schedule-container');
container.innerHTML = '';
schedule.forEach((slot, index) => {
const div = document.createElement('div');
div.className = "flex flex-col gap-2 bg-gray-50 p-3 rounded-lg border border-gray-200 focus-within:border-blue-500 focus-within:ring-1 focus-within:ring-blue-500 transition shadow-sm";
div.innerHTML = `
<div class="flex items-center gap-2">
<div class="w-6 font-bold text-gray-400 text-xs">#${index+1}</div>
<input type="time" value="${slot.time}" onchange="updateSlot(${index}, 'time', this.value)" class="border rounded p-1 text-sm w-24 outline-none">
<input type="text" value="${slot.text}" onchange="updateSlot(${index}, 'text', this.value)" placeholder="ข้อความที่ให้ AI อ่าน..." class="flex-1 border rounded p-1 text-sm outline-none w-full">
<button onclick="clearSlot(${index})" class="text-red-400 hover:text-red-600 px-1" title="ล้างข้อมูล"><i class="fa-solid fa-xmark"></i></button>
</div>
`;
container.appendChild(div);
});
}
function updateSlot(index, field, value) {
schedule[index][field] = value;
saveSettings();
}
function clearSlot(index) {
schedule[index].time = '';
schedule[index].text = '';
saveSettings();
}
// --- Automation Core ---
function checkSchedule() {
const now = new Date();
// ตรวจสอบวันทำงาน (จันทร์-ศุกร์ คือ 1-5)
const isWorkdayOnly = document.getElementById('workday-only').checked;
const currentDay = now.getDay();
if (isWorkdayOnly && (currentDay === 0 || currentDay === 6)) {
return; // ข้ามการทำงานถ้านี่คือวันหยุด
}
const currentTimeStr = now.toTimeString().substring(0, 5);
const currentSeconds = now.getSeconds();
if (currentSeconds !== 0) return; // ทำงานแค่ตอนวินาทีที่ 00
// ป้องกันการทริกเกอร์ซ้ำในนาทีเดียวกัน
if (lastPlayedTime === currentTimeStr) return;
const activeSlot = schedule.find(slot => slot.time === currentTimeStr && slot.text.trim() !== "");
if (activeSlot) {
lastPlayedTime = currentTimeStr;
speakText(activeSlot.text);
}
}
function updateNextScheduleDisplay(now) {
const currentTimeMin = now.getHours() * 60 + now.getMinutes();
let nextEvent = null;
let minDiff = 1440;
schedule.forEach(slot => {
if (!slot.time || !slot.text) return;
const [h, m] = slot.time.split(':').map(Number);
const slotTimeMin = h * 60 + m;
let diff = slotTimeMin - currentTimeMin;
if (diff <= 0) diff += 1440;
if (diff > 0 && diff < minDiff) {
minDiff = diff;
nextEvent = slot;
}
});
if (nextEvent) {
document.getElementById('next-schedule-name').innerText = nextEvent.text;
document.getElementById('next-schedule-time').innerText = nextEvent.time;
} else {
document.getElementById('next-schedule-name').innerText = "ไม่มีกำหนดการ";
document.getElementById('next-schedule-time').innerText = "--:--";
}
}
</script>