您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Ctrl+Shift+A voor antwoord. Ctrl+Shift+G als de docent langskomt.
// ==UserScript== // @name Noordhoff AI // @version 1.8 // @description Ctrl+Shift+A voor antwoord. Ctrl+Shift+G als de docent langskomt. // @author incomplete_tree // @match https://*.noordhoff.nl/* // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @namespace https://greasyfork.org/users/1511158 // ==/UserScript== (function() { 'use strict'; // --- Configuratie --- const OPENROUTER_API_KEY_GM_ID = 'openrouter_api_key'; const CONCISENESS_LEVEL_GM_ID = 'ai_conciseness_level'; const DEBUG_MODE = true; // Zet op true voor gedetailleerde logs in de console const concisenessLevels = { '-2': 'extreem beknopt. Geef alleen het eindantwoord.', '-1': 'zeer beknopt. Geef de formule en het eindantwoord.', '0': 'standaard. Geef de formule, de ingevulde formule en het eindantwoord.', '1': 'iets gedetailleerder. Leg de stappen kort uit in de berekening.', '2': 'zeer gedetailleerd. Geef een volledige uitleg met de berekening, alsof je het een leerling leert.' }; let currentConciseness = '0'; // Standaard function log(...args) { if (DEBUG_MODE) { console.log('[Noordhoff AI]', ...args); } } // --- API-sleutel Beheer (onveranderd) --- async function getApiKey() { let apiKey = await GM_getValue(OPENROUTER_API_KEY_GM_ID); if (!apiKey || apiKey.trim() === "") { apiKey = window.prompt('Voer alstublieft uw OpenRouter API-sleutel in:'); if (apiKey && apiKey.trim() !== "") { await GM_setValue(OPENROUTER_API_KEY_GM_ID, apiKey); } else { return null; } } return apiKey; } // --- Hoofdscript start hier (onveranderd) --- (async function() { const apiKey = await getApiKey(); if (!apiKey) { alert('OpenRouter API-sleutel is niet ingesteld. De sneltoetsen werken niet.'); return; } currentConciseness = await GM_getValue(CONCISENESS_LEVEL_GM_ID, '0'); log(`Script geladen. Sleutel is ingesteld. Initiële beknoptheid: ${concisenessLevels[currentConciseness]}.`); console.log("Sneltoetsen: Ctrl+Shift+A (Toon Antwoord), Ctrl+Shift+G (Verberg Alles)."); document.addEventListener('keydown', (event) => { if (event.ctrlKey && event.shiftKey && (event.key === 'A' || event.key === 'a')) { event.preventDefault(); handleGenerateHotkey(apiKey); } if (event.ctrlKey && event.shiftKey && (event.key === 'G' || event.key === 'g')) { event.preventDefault(); hideAllAnswers(); } }); })(); // --- Hulpfunctie om het huidige vraagblok te vinden (onveranderd) --- function findCurrentQuestionBlock() { const focusedElement = document.activeElement; if (focusedElement && focusedElement !== document.body) { const block = focusedElement.closest('.pl-particle'); if (block) return block; } for (const block of document.querySelectorAll('.pl-particle')) { const rect = block.getBoundingClientRect(); if (rect.top >= 0 && rect.top <= window.innerHeight * 0.8) return block; } return null; } // --- HOOFDLOGICA: HERSCHREVEN OM FLEXIBELER TE ZIJN --- async function handleGenerateHotkey(apiKey, newConciseness = null) { if (newConciseness !== null) { currentConciseness = newConciseness; await GM_setValue(CONCISENESS_LEVEL_GM_ID, currentConciseness); } const questionBlock = findCurrentQuestionBlock(); if (!questionBlock) { alert('Kon geen vraag vinden. Klik in een vraag en probeer het opnieuw.'); return; } displayAnswer(questionBlock, apiKey, '🤖 Vraag analyseren...', true); try { const questionContentContainer = questionBlock.querySelector('.pl-open-question'); if (!questionContentContainer) throw new Error('Kon de hoofdcontainer van de vraag (.pl-open-question) niet vinden.'); const questionHtml = questionContentContainer.innerHTML; const concisenessInstruction = concisenessLevels[currentConciseness]; let currentAnswerSection = ''; // Lees huidig antwoord uit het flexibel gevonden invoerveld const answerInput = findAnswerInput(questionBlock); if (answerInput && answerInput.innerText.trim() !== "") { currentAnswerSection = `\n\n## Huidig Antwoord van de Leerling (ter context):\n${answerInput.innerText.trim()}`; } const prompt = `Je bent een deskundige Nederlandse onderwijsassistent. Analyseer de HTML van het vraagblok. Je antwoord moet ${concisenessInstruction} en in het Nederlands zijn. Geef ALLEEN de stapsgewijze berekening, zonder extra zinnen. Het antwoord moet direct in een invulveld geplakt kunnen worden.\n\n## Vraagblok HTML:\n\`\`\`html\n${questionHtml}\n\`\`\`${currentAnswerSection}`; const response = await callOpenRouter(prompt, apiKey, 'google/gemini-flash-1.5'); const fullAnswer = response.choices[0].message.content.trim(); log("Antwoord ontvangen van AI:", fullAnswer); displayAnswer(questionBlock, apiKey, fullAnswer, false); autoFillAnswer(questionBlock, fullAnswer); } catch (error) { console.error('AI Assistent Fout:', error); displayAnswer(questionBlock, apiKey, `Fout: ${error.message}`); } } // **NIEUWE, SLIMME FUNCTIE OM HET ANTWOORDVELD TE VINDEN** function findAnswerInput(questionBlock) { if (!questionBlock) return null; // Zoek eerst de algemene container voor het antwoord. const mainAnswerArea = questionBlock.querySelector('.pl-main'); if (!mainAnswerArea) { log('Kon de .pl-main antwoordcontainer niet vinden.'); return null; } // Zoek nu binnen die container naar het specifieke invoerveld. // Hoogste prioriteit voor .ql-editor, want die hebben we bevestigd. let inputElement = mainAnswerArea.querySelector('.ql-editor'); if (inputElement) { log('Specifiek ".ql-editor" invoerveld gevonden!'); return inputElement; } // Fallback voor andere soorten vragen: zoek naar een akit-matharea en kijk daarin. const mathArea = mainAnswerArea.querySelector('akit-matharea'); if (mathArea && mathArea.shadowRoot) { log('akit-matharea gevonden, ik kijk in de Shadow DOM.'); inputElement = mathArea.shadowRoot.querySelector('.ql-editor'); if (inputElement) { log('".ql-editor" gevonden binnen de Shadow DOM van akit-matharea!'); return inputElement; } } // Allerlaatste fallback: zoek naar *iets* bewerkbaars. inputElement = mainAnswerArea.querySelector('textarea, [contenteditable="true"]'); if (inputElement) { log('Algemeen bewerkbaar element gevonden als fallback.'); return inputElement; } log('Kon geen enkel bekend invoerveld vinden in .pl-main.'); return null; } // **AUTOFILL GEBRUIKT NU DE NIEUWE VIND-FUNCTIE** function autoFillAnswer(questionBlock, answerText) { log(`Start automatisch invullen...`); const targetInput = findAnswerInput(questionBlock); if (!targetInput) { log('FATALE FOUT: Kon geen invoerveld vinden om het antwoord in te vullen.'); return; } log('DOELWIT GEVONDEN! Het daadwerkelijke invoerveld is:', targetInput); try { // Maak de inhoud voor de editor, inclusief <p> tags zoals de Quill editor het verwacht. const formattedAnswer = `<p>${answerText.replace(/\n/g, '</p><p>')}</p>`; targetInput.focus(); targetInput.innerHTML = formattedAnswer; targetInput.dispatchEvent(new Event('input', { bubbles: true, cancelable: true })); targetInput.dispatchEvent(new Event('change', { bubbles: true, cancelable: true })); targetInput.blur(); log('Automatisch invullen succesvol voltooid.'); } catch (e) { log('Er is een fout opgetreden tijdens het uitvoeren van de invul-acties:', e); } } // --- Universele API-aanroep Functie (onveranderd) --- function callOpenRouter(prompt, apiKey, model) { return new Promise((resolve, reject) => { const messages = [{ role: 'user', content: prompt }]; GM_xmlhttpRequest({ method: 'POST', url: 'https://openrouter.ai/api/v1/chat/completions', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, data: JSON.stringify({ model, messages }), onload: (response) => { if (response.status >= 200 && response.status < 300) resolve(JSON.parse(response.responseText)); else reject(new Error(`API Fout (${response.status})`)); }, onerror: (response) => reject(new Error(`Netwerkfout`)) }); }); } // --- Functie om het antwoord en de knoppen te tonen (onveranderd) --- function displayAnswer(questionBlock, apiKey, answerText, isThinking = false) { const containerId = 'ai-answer-display'; let answerContainer = questionBlock.querySelector(`#${containerId}`); if (!answerContainer) { answerContainer = document.createElement('div'); answerContainer.id = containerId; const answerTextElement = document.createElement('div'); answerTextElement.style.marginBottom = '8px'; answerTextElement.style.whiteSpace = 'pre-wrap'; const controlsContainer = document.createElement('div'); controlsContainer.style.display = 'flex'; controlsContainer.style.gap = '8px'; controlsContainer.style.marginTop = '8px'; const lessConciseButton = document.createElement('button'); lessConciseButton.innerText = 'Minder Beknopt'; const moreConciseButton = document.createElement('button'); moreConciseButton.innerText = 'Beknopter'; [lessConciseButton, moreConciseButton].forEach(button => Object.assign(button.style, { padding: '4px 8px', border: '1px solid #ccc', borderRadius: '4px', background: '#f0f0f0', cursor: 'pointer' })); lessConciseButton.addEventListener('click', () => handleGenerateHotkey(apiKey, Math.min(2, parseInt(currentConciseness) + 1).toString())); moreConciseButton.addEventListener('click', () => handleGenerateHotkey(apiKey, Math.max(-2, parseInt(currentConciseness) - 1).toString())); controlsContainer.appendChild(moreConciseButton); controlsContainer.appendChild(lessConciseButton); answerContainer.appendChild(answerTextElement); answerContainer.appendChild(controlsContainer); const mainContentArea = questionBlock.querySelector('.pl-main'); if (mainContentArea) mainContentArea.parentNode.insertBefore(answerContainer, mainContentArea); else questionBlock.appendChild(answerContainer); } answerContainer.querySelector('div').innerHTML = answerText.replace(/\n/g, '<br>'); Object.assign(answerContainer.style, { margin: '10px 0', padding: '12px', border: '1px solid', borderRadius: '5px', fontFamily: 'sans-serif', fontSize: '16px', lineHeight: '1.5', borderColor: isThinking ? '#ffc107' : '#007bff', backgroundColor: isThinking ? '#fff3cd' : '#e7f3ff', color: isThinking ? '#856404' : '#004085' }); const controls = answerContainer.querySelector('div:last-child'); if (controls) controls.style.display = isThinking ? 'none' : 'flex'; } // --- Functie om alle gegenereerde antwoorden te verbergen (onveranderd) --- function hideAllAnswers() { log("--- Verberg-sneltoets ingedrukt ---"); document.querySelectorAll('#ai-answer-display').forEach(display => display.remove()); } })();