/
LesTech
/
PrepodGen
Обзор
Документация
Войти
/
LesTech
/
PrepodGen
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
index.html
530 строк
20 KB
LesTech
create: index.html
18 май 2026, 21:54
Верифицирован
18 май 2026, 21:54
fa961b8
Код
Авторство
О чём код?
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>ПреподГен | Генератор вопросов для преподавателей</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif; background: #f1f5f9; color: #0f172a; padding: 2rem 1rem; transition: background 0.2s, color 0.2s; } body.dark { background: #0f172a; color: #e2e8f0; } .container { max-width: 1200px; margin: 0 auto; } /* header */ .header { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 1rem; margin-bottom: 2rem; } h1 { font-size: 1.8rem; font-weight: 600; background: linear-gradient(135deg, #2563eb, #7c3aed); background-clip: text; -webkit-background-clip: text; color: transparent; } .theme-toggle { background: #e2e8f0; border: none; padding: 0.5rem 1rem; border-radius: 40px; cursor: pointer; font-weight: 500; transition: 0.2s; } body.dark .theme-toggle { background: #334155; color: #f1f5f9; } /* карточки */ .card { background: white; border-radius: 1.5rem; padding: 1.5rem; margin-bottom: 1.5rem; box-shadow: 0 4px 12px rgba(0,0,0,0.05); transition: background 0.2s; } body.dark .card { background: #1e293b; box-shadow: 0 4px 12px rgba(0,0,0,0.3); } textarea, input, button { font-family: inherit; } textarea { width: 100%; padding: 1rem; border-radius: 1rem; border: 1px solid #cbd5e1; background: #f8fafc; font-size: 1rem; resize: vertical; } body.dark textarea { background: #0f172a; border-color: #334155; color: #e2e8f0; } .btn-group { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 1rem; margin-bottom: 1rem; } button { background: #2563eb; color: white; border: none; padding: 0.6rem 1.2rem; border-radius: 2rem; font-weight: 500; cursor: pointer; transition: 0.2s; } button:hover { background: #1d4ed8; transform: scale(0.98); } .btn-secondary { background: #475569; } .btn-secondary:hover { background: #334155; } .file-label { background: #e2e8f0; color: #0f172a; padding: 0.6rem 1.2rem; border-radius: 2rem; cursor: pointer; font-weight: 500; display: inline-block; } body.dark .file-label { background: #334155; color: #f1f5f9; } input[type="file"] { display: none; } /* вопросы */ .questions-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 1rem; margin-top: 1rem; } .question-card { background: #f8fafc; border-radius: 1rem; padding: 1rem; border-left: 5px solid #2563eb; } body.dark .question-card { background: #0f172a; } .question-text { font-weight: 500; margin-bottom: 0.75rem; } .copy-btn { background: #e2e8f0; color: #0f172a; padding: 0.3rem 0.8rem; font-size: 0.8rem; } body.dark .copy-btn { background: #334155; color: #e2e8f0; } .history-item { background: #f1f5f9; padding: 0.75rem; border-radius: 1rem; margin-bottom: 0.5rem; cursor: pointer; transition: 0.1s; } body.dark .history-item { background: #334155; } .history-item:hover { background: #e2e8f0; } body.dark .history-item:hover { background: #475569; } hr { margin: 1rem 0; border-color: #cbd5e1; } body.dark hr { border-color: #334155; } .badge { font-size: 0.8rem; color: #2563eb; font-weight: 500; } </style> </head> <body> <div class="container"> <div class="header"> <h1>📚 ПреподГен — генератор учебных вопросов</h1> <button class="theme-toggle" id="themeToggle">🌙 Тёмная тема</button> </div> <div class="card"> <h3>1️⃣ Введите тему или загрузите текст</h3> <textarea id="inputText" rows="3" placeholder="Например: Основы компьютерных сетей, OSI, IP-адресация. Или вставьте текст лекции..."></textarea> <div class="btn-group"> <button id="generateBtn">✨ Сгенерировать вопросы</button> <label class="file-label"> 📂 Загрузить .txt <input type="file" id="fileInput" accept=".txt"> </label> <button id="clearBtn" class="btn-secondary">Очистить</button> </div> <small class="badge">⚡ Работает полностью офлайн. Алгоритм выделяет ключевые слова и превращает их в вопросы.</small> </div> <div class="card"> <h3>2️⃣ Сгенерированные вопросы</h3> <div id="questionsContainer"> <p style="color: #64748b;">Нажмите «Сгенерировать», чтобы создать карточки вопросов.</p> </div> <div class="btn-group" id="exportGroup" style="display: none;"> <button id="copyAllBtn">📋 Скопировать все вопросы</button> </div> </div> <div class="card"> <h3>📜 История (последние 3 набора)</h3> <div id="historyContainer"> <p style="color: #64748b;">Пока пусто. Сгенерируйте первый набор вопросов.</p> </div> </div> </div> <script> // ------------------ СТОП-СЛОВА ДЛЯ РУССКОГО ------------------ const stopWords = new Set([ "и", "в", "во", "на", "с", "со", "по", "к", "у", "о", "об", "от", "до", "за", "из", "без", "под", "над", "через", "для", "при", "быть", "это", "тот", "этот", "весь", "свой", "который", "также", "ещё", "уже", "есть", "как", "так", "что", "чтобы", "потому", "поэтому", "или", "либо", "если", "то", "но", "а", "да", "нет", "не", "ни", "бы", "же", "ли", "ведь", "вот", "вон", "только", "ещё", "даже", "уже", "раз", "два", "три", "будет", "было", "были", "была", "очень", "ну", "вдруг", "там", "тут", "здесь", "где", "когда", "зачем", "почему", "какой", "такой", "тот", "эта", "это", "эти", "те", "все", "всё", "вся", "всех" ]); // ключи для localStorage const STORAGE_THEME = "prepodgen_theme"; const STORAGE_HISTORY = "prepodgen_history"; const STORAGE_LAST_TEXT = "prepodgen_last_text"; let currentQuestions = []; // -------- вспомогательные функции -------- function extractKeywords(text) { // 1. разбиваем на слова const words = text.toLowerCase().match(/[\p{L}\d]+/gu) || []; // 2. частотный словарь const freq = new Map(); for (let w of words) { if (w.length < 3) continue; if (stopWords.has(w)) continue; freq.set(w, (freq.get(w) || 0) + 1); } // 3. сортируем по частоте const sorted = [...freq.entries()].sort((a,b) => b[1] - a[1]); // 4. берём топ-10 уникальных ключевых слов (или меньше) const topKeywords = sorted.slice(0, 10).map(item => item[0]); return [...new Set(topKeywords)]; } function generateQuestionsFromKeywords(keywords, originalText) { if (!keywords.length) { return ["❌ Не удалось выделить ключевые слова. Введите больше текста или конкретную тему."]; } const questions = []; const templates = [ (kw) => `Что такое «${kw}»? Дайте определение.`, (kw) => `Объясните понятие «${kw}» своими словами.`, (kw) => `Приведите пример использования «${kw}» в учебном процессе.`, (kw) => `В чём важность «${kw}» для преподавателя или студента?`, (kw) => `Как «${kw}» связан с темой занятия?`, (kw) => `Опишите основные свойства или характеристики «${kw}».`, ]; // генерируем по 2–3 вопроса на ключевое слово, но не более 12 карточек for (let i = 0; i < Math.min(keywords.length, 8); i++) { const kw = keywords[i]; const templateIdx = i % templates.length; questions.push(templates[templateIdx](kw)); // добавим второй вопрос для самых важных if (i < 4 && keywords.length > 4) { const secondIdx = (i+2) % templates.length; questions.push(templates[secondIdx](kw)); } } // если мало вопросов, добавим общие if (questions.length < 4 && originalText.length > 20) { questions.push("Какие ключевые выводы можно сделать из прочитанного материала?"); questions.push("Какие практические задачи решает рассмотренная тема?"); } // уникализируем и ограничиваем до 12 карточек const unique = [...new Map(questions.map(q => [q.toLowerCase(), q])).values()]; return unique.slice(0, 12); } function generateQuestionsFromText(rawText) { if (!rawText.trim()) return ["Введите тему или загрузите текстовый файл."]; const keywords = extractKeywords(rawText); if (keywords.length === 0) { return ["🔍 Не найдено значимых слов. Попробуйте написать развёрнутую тему или вставить больше текста."]; } return generateQuestionsFromKeywords(keywords, rawText); } function renderQuestions(questions) { const container = document.getElementById("questionsContainer"); const exportGroup = document.getElementById("exportGroup"); if (!questions.length) { container.innerHTML = `<p style="color: #64748b;">❌ Вопросов нет. Попробуйте изменить текст.</p>`; exportGroup.style.display = "none"; return; } let html = `<div class="questions-grid">`; questions.forEach((q, idx) => { html += ` <div class="question-card"> <div class="question-text">${escapeHtml(q)}</div> <button class="copy-btn" data-question="${escapeHtml(q)}">📋 Копировать</button> </div> `; }); html += `</div>`; container.innerHTML = html; exportGroup.style.display = "flex"; // вешаем обработчики копирования document.querySelectorAll('.copy-btn').forEach(btn => { btn.addEventListener('click', (e) => { const q = btn.getAttribute('data-question'); copyToClipboard(q); btn.textContent = "✅ Скопировано!"; setTimeout(() => btn.textContent = "📋 Копировать", 1500); }); }); } function escapeHtml(str) { return str.replace(/[&<>]/g, function(m) { if (m === '&') return '&'; if (m === '<') return '<'; if (m === '>') return '>'; return m; }).replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, function(c) { return c; }); } async function copyToClipboard(text) { try { await navigator.clipboard.writeText(text); } catch (err) { alert("Не удалось скопировать: " + err); } } function saveToHistory(questionSet, sourcePreview) { let history = JSON.parse(localStorage.getItem(STORAGE_HISTORY) || "[]"); const newItem = { id: Date.now(), preview: sourcePreview.substring(0, 60) + (sourcePreview.length > 60 ? "…" : ""), questions: questionSet, timestamp: new Date().toLocaleString() }; history.unshift(newItem); if (history.length > 3) history.pop(); localStorage.setItem(STORAGE_HISTORY, JSON.stringify(history)); renderHistory(); } function renderHistory() { const history = JSON.parse(localStorage.getItem(STORAGE_HISTORY) || "[]"); const container = document.getElementById("historyContainer"); if (!history.length) { container.innerHTML = `<p style="color: #64748b;">Пока пусто. Сгенерируйте первый набор вопросов.</p>`; return; } let html = ""; history.forEach(item => { html += `<div class="history-item" data-history-id="${item.id}"> <strong>📌 ${item.preview}</strong><br> <small>${item.timestamp} | ${item.questions.length} вопросов</small> </div>`; }); container.innerHTML = html; document.querySelectorAll('.history-item').forEach(el => { el.addEventListener('click', (e) => { const id = parseInt(el.getAttribute('data-history-id')); const found = history.find(h => h.id === id); if (found) { currentQuestions = [...found.questions]; renderQuestions(currentQuestions); } }); }); } // основная генерация function runGeneration() { let raw = document.getElementById("inputText").value; if (!raw.trim()) { alert("Введите тему или загрузите файл .txt"); return; } const questions = generateQuestionsFromText(raw); currentQuestions = [...questions]; renderQuestions(currentQuestions); saveToHistory(currentQuestions, raw); // сохраняем последний текст localStorage.setItem(STORAGE_LAST_TEXT, raw); } // инициализация темной темы function initTheme() { const saved = localStorage.getItem(STORAGE_THEME); if (saved === 'dark') { document.body.classList.add('dark'); document.getElementById("themeToggle").textContent = "☀️ Светлая тема"; } else { document.body.classList.remove('dark'); document.getElementById("themeToggle").textContent = "🌙 Тёмная тема"; } } function toggleTheme() { if (document.body.classList.contains('dark')) { document.body.classList.remove('dark'); localStorage.setItem(STORAGE_THEME, 'light'); document.getElementById("themeToggle").textContent = "🌙 Тёмная тема"; } else { document.body.classList.add('dark'); localStorage.setItem(STORAGE_THEME, 'dark'); document.getElementById("themeToggle").textContent = "☀️ Светлая тема"; } } // обработка файла function handleFile(file) { const reader = new FileReader(); reader.onload = function(e) { const content = e.target.result; document.getElementById("inputText").value = content; localStorage.setItem(STORAGE_LAST_TEXT, content); }; reader.readAsText(file, "UTF-8"); } // восстановить последний текст function restoreLastText() { const last = localStorage.getItem(STORAGE_LAST_TEXT); if (last) document.getElementById("inputText").value = last; } // скопировать все вопросы function copyAllQuestions() { if (!currentQuestions.length) { alert("Нет вопросов для копирования."); return; } const allText = currentQuestions.map((q, idx) => `${idx+1}. ${q}`).join("\n\n"); copyToClipboard(allText); alert("✅ Все вопросы скопированы в буфер обмена"); } // события document.getElementById("generateBtn").addEventListener("click", runGeneration); document.getElementById("clearBtn").addEventListener("click", () => { document.getElementById("inputText").value = ""; localStorage.removeItem(STORAGE_LAST_TEXT); currentQuestions = []; renderQuestions([]); }); document.getElementById("themeToggle").addEventListener("click", toggleTheme); document.getElementById("copyAllBtn").addEventListener("click", copyAllQuestions); document.getElementById("fileInput").addEventListener("change", (e) => { if (e.target.files.length) handleFile(e.target.files[0]); }); // старт initTheme(); restoreLastText(); renderHistory(); if (document.getElementById("inputText").value.trim()) { // можно сразу сгенерировать авто, но не будем, чтобы не спамить. но покажем что данные есть. } // на всякий случай — если пользователь хочет быстро что-то пример: const demoText = "Компьютерные сети, OSI, TCP/IP, маршрутизация, DNS, HTTP, безопасность, брандмауэр."; if (!localStorage.getItem(STORAGE_LAST_TEXT)) { document.getElementById("inputText").value = demoText; } </script> </body> </html>