diff --git a/scripts/generate-quiz.js b/scripts/generate-quiz.js new file mode 100644 index 0000000..90d23c3 --- /dev/null +++ b/scripts/generate-quiz.js @@ -0,0 +1,607 @@ +// 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) => ` +
Единый образовательный портал Сеченовского Университета
+$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
+${chatBlocks}
+