From e1588eba85519ba8770e6ceefa3d6c9c8d7f8b4b Mon Sep 17 00:00:00 2001 From: aedes Date: Thu, 21 May 2026 12:17:05 +0300 Subject: [PATCH] Revert "feat: Playwright script to render 30-question chemistry quiz as page.png" This reverts commit e4823f56875a5e4ff4e69801fa57dfc943b121f9. --- scripts/generate-quiz.js | 607 --------------------------------------- 1 file changed, 607 deletions(-) delete mode 100644 scripts/generate-quiz.js diff --git a/scripts/generate-quiz.js b/scripts/generate-quiz.js deleted file mode 100644 index 90d23c3..0000000 --- a/scripts/generate-quiz.js +++ /dev/null @@ -1,607 +0,0 @@ -// node scripts/generate-quiz.js [output-dir] -// Renders 30 Chemistry questions in Moodle Boost style via Playwright, -// produces public/q/page.png + public/q/index.html with chat slots. - -const { chromium } = require('playwright'); -const fs = require('fs'); -const path = require('path'); - -const OUT = process.argv[2] || '/app/public/q'; - -// ─── 30 questions from ФОС Фарм_ОХ.pdf ─────────────────────────────────────── -const QUESTIONS = [ - { - text: 'РОДОНАЧАЛЬНАЯ СТРУКТУРА СОЕДИНЕНИЯ [структура — задание 001 в PDF]', - opts: ['октан','пентан','гексан','гептан'], - }, - { - text: 'НАЗВАНИЕ СОЕДИНЕНИЯ CF3CHBrCl ПО ЗАМЕСТИТЕЛЬНОЙ НОМЕНКЛАТУРЕ', - opts: ['1-бромо-2,2,2-трифторо-1-хлоропропан','1-бромо-1-хлоро-2,2,2-трифтороэтан','1,1,1-трифторо-2-бромо-2-хлороэтан','2-бромо-1,1,1-трифторо-2-хлороэтан'], - }, - { - text: 'РОДОНАЧАЛЬНАЯ СТРУКТУРА СОЕДИНЕНИЯ [структура — задание 003]', - opts: ['пентан','гептан','октан','гексан'], - }, - { - text: 'НАЗВАНИЕ ХЛОРОПРЕНА CH2=CH−CCl=CH2 ПО ЗАМЕСТИТЕЛЬНОЙ НОМЕНКЛАТУРЕ', - opts: ['2-хлоробутадиен-1,3','3-хлоробутадиен-1,3','2-хлоробутадиен-1,4','2-хлоробутен-1,3'], - }, - { - text: 'НАЗВАНИЕ СОЕДИНЕНИЯ ПО ЗАМЕСТИТЕЛЬНОЙ НОМЕНКЛАТУРЕ [структура — задание 005]', - opts: ['2-оксопентандиовая кислота','4-оксопентандиовая кислота','пентанон-2-диовая кислота','1,5-дикарбоксипентанон-2'], - }, - { - text: 'НАЗВАНИЕ ПАСК ПО ЗАМЕСТИТЕЛЬНОЙ НОМЕНКЛАТУРЕ', - opts: ['4-амино-2-гидроксибензойная кислота','п-аминосалициловая кислота','2-гидрокси-4-аминобензойная кислота','2-гидрокси-6-амино-3-бензойная кислота'], - }, - { - text: 'В МОЛЕКУЛЕ ПАНТОТЕНОВОЙ КИСЛОТЫ НЕ СОДЕРЖИТСЯ', - opts: ['аминогруппа','карбоксильная группа','первичная гидроксильная группа','вторичная гидроксильная группа'], - }, - { - text: 'НАЗВАНИЕ СОЕДИНЕНИЯ ПО ЗАМЕСТИТЕЛЬНОЙ НОМЕНКЛАТУРЕ [структура — задание 008]', - opts: ['1,3-дигидроксипропанон','1,3-дигидрокси-2-оксопропан','1,3-диоксиацетон','дигидроксиацетон'], - }, - { - text: 'ПЕРВИЧНЫЙ СПИРТ [варианты — структурные формулы в задании 009]', - opts: ['[структура спирта 1]','[структура спирта 2]','[структура спирта 3]','[структура спирта 4]'], - }, - { - text: 'ВТОРИЧНЫЙ СПИРТ [варианты — структурные формулы в задании 010]', - opts: ['[структура спирта 1]','[структура спирта 2]','[структура спирта 3]','[структура спирта 4]'], - }, - { - text: 'НАЗВАНИЕ СОЕДИНЕНИЯ ПО ЗАМЕСТИТЕЛЬНОЙ НОМЕНКЛАТУРЕ [структура — задание 011]', - opts: ['1-изопропил-4-метилциклогексадиен-1,3','1-изопропил-4-метилциклогексадиен-1,4','1-метил-4-изопропилциклогексадиен-1,4','1-метил-4-изопропилциклогексадиен-1,3'], - }, - { - text: 'НАЗВАНИЕ СОЕДИНЕНИЯ ПО ЗАМЕСТИТЕЛЬНОЙ НОМЕНКЛАТУРЕ [структура — задание 012]', - opts: ['1-изопропил-4-метилциклогексадиен-1,4','1-изопропил-4-метилциклогексадиен-1,3','1-метил-4-изопропилциклогексадиен-1,4','1-метил-4-изопропилциклогексадиен-1,3'], - }, - { - text: 'НАЗВАНИЕ СОЕДИНЕНИЯ ПО ЗАМЕСТИТЕЛЬНОЙ НОМЕНКЛАТУРЕ [структура — задание 013]', - opts: ['циклогексанон','циклогексанкарбальдегид','оксоциклогексан','циклогексаналь'], - }, - { - text: 'НАЗВАНИЕ СОЕДИНЕНИЯ ПО ЗАМЕСТИТЕЛЬНОЙ НОМЕНКЛАТУРЕ [структура — задание 014]', - opts: ['циклогексанкарбальдегид','циклогексаналь','оксоциклогексан','циклогексанкарбоновая кислота'], - }, - { - text: 'НАЗВАНИЕ СОЕДИНЕНИЯ ПО РАДИКАЛЬНО-ФУНКЦИОНАЛЬНОЙ НОМЕНКЛАТУРЕ [структура — задание 015]', - opts: ['диэтилсульфид','диэтилсульфоксид','диэтилсульфон','диэтиловый тиоэфир'], - }, - { - text: 'НАЗВАНИЕ СОЕДИНЕНИЯ ПО РАДИКАЛЬНО-ФУНКЦИОНАЛЬНОЙ НОМЕНКЛАТУРЕ [структура — задание 016]', - opts: ['метилэтиловый эфир','метоксиэтан','метилэтилоксид','метилэтанол'], - }, - { - text: 'НАЗВАНИЕ СОЕДИНЕНИЯ ПО РАДИКАЛЬНО-ФУНКЦИОНАЛЬНОЙ НОМЕНКЛАТУРЕ [структура — задание 017]', - opts: ['метилэтилкетон','метилэтиловый эфир','бутанон','этилацетат'], - }, - { - text: 'РОДОНАЧАЛЬНАЯ СТРУКТУРА СОЕДИНЕНИЯ [структура — задание 018]', - opts: ['бензальдегид','фенол','толуол','бензол'], - }, - { - text: 'НАЗВАНИЕ РАДИКАЛА C6H5', - opts: ['фенил','бензил','винил','гексил'], - }, - { - text: 'НАЗВАНИЕ РАДИКАЛА C6H5−CH2', - opts: ['бензил','фенил','винил','аллил'], - }, - { - text: 'НАЗВАНИЕ СОЕДИНЕНИЯ C6H5CH(OH)CH2NH2 ПО ЗАМЕСТИТЕЛЬНОЙ НОМЕНКЛАТУРЕ', - opts: ['2-амино-1-фенилэтанол','2-гидрокси-2-фенилэтанамин','2-фенил-1-аминоэтанол','2-амино-1-гидроксиэтилбензол'], - }, - { - text: 'НАЗВАНИЕ СОЕДИНЕНИЯ C6H5CH2CH2NH2 ПО ЗАМЕСТИТЕЛЬНОЙ НОМЕНКЛАТУРЕ', - opts: ['2-фенилэтанамин','2-фенил-1-аминоэтан','2-аминоэтилбензол','2-фенилэтиламин'], - }, - { - text: 'НАЗВАНИЕ СОЕДИНЕНИЯ ПО ЗАМЕСТИТЕЛЬНОЙ НОМЕНКЛАТУРЕ [нафталиновая структура — задание 023]', - opts: ['4-Аминонафталин-1-карбоновая кислота','1-Аминонафталин-4-карбоновая кислота','5-Нитронафталин-1-карбоновая кислота','4-Нитронафталинкарбоновая кислота'], - }, - { - text: 'ПРОЕКЦИОННАЯ ФОРМУЛА S-ИЗОМЕРА ЯБЛОЧНОЙ (ГИДРОКСИБУТАНДИОВОЙ) КИСЛОТЫ [варианты — проекции, задание 023б]', - opts: ['[проекция 1]','[проекция 2]','[проекция 3]','[проекция 4]'], - }, - { - text: 'ПРОЕКЦИОННАЯ ФОРМУЛА (S)-2-АМИНОПРОПАНОВОЙ КИСЛОТЫ [варианты — проекции, задание 024]', - opts: ['[проекция 1]','[проекция 2]','[проекция 3]','[проекция 4]'], - }, - { - text: 'ПРОЕКЦИОННАЯ ФОРМУЛА (R)-2-АМИНО-3-ГИДРОКСИПРОПАНОВОЙ КИСЛОТЫ [варианты — проекции, задание 025]', - opts: ['[проекция 1]','[проекция 2]','[проекция 3]','[проекция 4]'], - }, - { - text: 'СТАРШИНСТВО ЗАМЕСТИТЕЛЕЙ У ХИРАЛЬНОГО АТОМА УГЛЕРОДА ПО R,S-НОМЕНКЛАТУРЕ УМЕНЬШАЕТСЯ В РЯДУ:', - opts: ['−NH₂ > −CH₂SH > −COOH > −H','−NH₂ > −COOH > −CH₂SH > −H','−COOH > −NH₂ > −CH₂SH > −H','−CH₂SH > −COOH > −NH₂ > −H'], - }, - { - text: 'α-АМИНОКИСЛОТА, ИМЕЮЩАЯ R-КОНФИГУРАЦИЮ ЦЕНТРА ХИРАЛЬНОСТИ [варианты — структуры, задание 027]', - opts: ['[структура аминокислоты 1]','[структура аминокислоты 2]','[структура аминокислоты 3]','[структура аминокислоты 4]'], - }, - { - text: 'СОЕДИНЕНИЕ, СПОСОБНОЕ СУЩЕСТВОВАТЬ В ВИДЕ ЦИС-ТРАНС-ИЗОМЕРОВ', - opts: ['CH₃CH=CHCH₃','CH₂=CHCOOH','CH₃CH₂CH=C(CH₃)₂','CH₃CH=CH₂'], - }, - { - text: 'ФОРМУЛА (S)-2-ХЛОРОПРОПАНОВОЙ КИСЛОТЫ [варианты — проекции, задание 030]', - opts: ['[проекция 1]','[проекция 2]','[проекция 3]','[проекция 4]'], - }, -]; - -// ─── HTML template ───────────────────────────────────────────────────────────── -function buildHtml(questions) { - const LETTERS = ['а','б','в','г']; - - const qBlocks = questions.map((q, qi) => ` -
-
-

Вопрос ${qi+1}

-
Пока нет
ответа
-
Балл: 1,00
-
-
-
-
-
${q.text}
-
-
Выберите один ответ:
-
- ${q.opts.map((opt, oi) => ` -
- - -
`).join('')} -
-
-
-
-
`).join('\n'); - - const navBtns = questions.map((_, i) => `${i+1}`).join('\n '); - - return ` - - - - -Экзаменационный билет (ЦТ) - - - - - - - - - -
- -
- - -
-
- - -
-
Основы строения органических соединений
-
- ${qBlocks} -
-
- - -
-
-
Навигация по тесту
-
- - -
-
-
- -
-
- - -`; -} - -// ─── chat slot template (same as mirror.js) ─────────────────────────────────── -const CHAT_SCRIPT = ` -function md(t){var h=t.replace(/&/g,'&').replace(//g,'>');h=h.replace(/\\*\\*([^*]+)\\*\\*/g,'$1');h=h.replace(/\`([^\`]+)\`/g,'$1');return h;} -function readB64(f){return new Promise(function(r){var fr=new FileReader();fr.onload=function(){r({data:fr.result.split(',')[1],media_type:f.type||'image/png'});};fr.readAsDataURL(f);});} -function gasp(it){return new Promise(function(r){it.getAsString(r);});} -function initChat(block){ - var i=block.querySelector('.c-in'),o=block.querySelector('.c-out'); - var busy=false,pendingImages=[],pendingText='',timer=null,CD=5000; - async function flush(){ - if(busy)return;var text=pendingText.trim(); - if(!text&&pendingImages.length===0)return; - busy=true;var payload={model:'opus'}; - if(pendingImages.length)payload.images=pendingImages.slice(); - if(text)payload.text=text; - pendingImages=[];pendingText='';o.innerHTML='';var full=''; - try{ - var r=await fetch('/api/chat',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)}); - var rd=r.body.getReader(),dec=new TextDecoder(),buf=''; - while(true){var x=await rd.read();if(x.done)break;buf+=dec.decode(x.value,{stream:true}); - var parts=buf.split('\\n\\n');buf=parts.pop(); - for(var k=0;k - els.map(el => { - const r = el.getBoundingClientRect(); - return { top: r.top + window.scrollY, bottom: r.top + window.scrollY + r.height }; - }) - ); - const pageHeight = await page.evaluate(() => document.documentElement.scrollHeight); - const pageWidth = await page.evaluate(() => document.documentElement.scrollWidth); - - console.log(`[generate-quiz] ${pageWidth}x${pageHeight} CSS px | ${quePositions.length} questions`); - - const pngPath = path.join(OUT, 'page.png'); - await page.screenshot({ path: pngPath, fullPage: true, type: 'png' }); - console.log(`[generate-quiz] screenshot: ${pngPath} (${fs.statSync(pngPath).size} bytes)`); - - // Chat slots between each pair of questions (29 slots for 30 questions) - const slots = []; - for (let i = 0; i < quePositions.length - 1; i++) { - slots.push({ id: i + 1, top: Math.round(quePositions[i].bottom + 20) }); - } - console.log('[generate-quiz] chat slots:', slots.length); - - const chatBlocks = slots.map(s => { - const topPct = (s.top / pageHeight * 100).toFixed(4); - return `
- -
-
`; - }).join('\n'); - - const indexHtml = ` - - - - -Экзаменационный билет (ЦТ) - - - - -
- -${chatBlocks} -
- - -`; - - fs.writeFileSync(path.join(OUT, 'index.html'), indexHtml, 'utf8'); - console.log(`[generate-quiz] wrote index.html with ${slots.length} chat slots`); - - // Cleanup temp render file - fs.unlinkSync(htmlPath); - await browser.close(); - console.log('[generate-quiz] done'); -} - -main().catch(e => { console.error(e); process.exit(1); });