Bunpro: Explain incorrect answer

Uses LLM to explain why an answer is wrong

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name         Bunpro: Explain incorrect answer
// @namespace    http://tampermonkey.net/
// @version      0.0.1
// @description  Uses LLM to explain why an answer is wrong
// @author       Jacob
// @include      *bunpro.jp/reviews*
// @include      *bunpro.jp/cram*
// @exclude      *bunpro.jp/dashboard*
// @exclude      *community.bunpro.jp*
// @grant        none
// @license MIT
// ==/UserScript==

;(function () {
    const API_URI = "https://api.openai.com/v1/responses";
    const MODEL = "gpt-5.2";

    let apiKey = localStorage.getItem('OPENAI_API_KEY');

    if (!apiKey || apiKey.length < 10 ) {
        apiKey = prompt("Please input Secret API key (will store in local.storage)", "sk-");
        localStorage.setItem('OPENAI_API_KEY', apiKey)
    }

    async function callChatGPT(prompt) {
        const response = await fetch(API_URI, {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                "Authorization": "Bearer " + apiKey
            },
            body: JSON.stringify({
                model: MODEL,
                input: prompt
            })
        });

        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }

        const data = await response.json();
        return data.output?.[0]?.content?.[0]?.text ?? "";
    }

    function waitForElm(selector) {
        return new Promise(resolve => {
            if (document.querySelector(selector)) {
                return resolve(document.querySelector(selector));
            }

            const observer = new MutationObserver(mutations => {
                if (document.querySelector(selector)) {
                    observer.disconnect();
                    resolve(document.querySelector(selector));
                }
            });

            observer.observe(document.body, {
                childList: true,
                subtree: true
            });
        });
    }


    waitForElm('#js-tour-quiz-question').then(function () {
        console.log("Adding Event Listeners")

        const answer_input_el = document.getElementById('js-manual-input');
        const submit_answer_button_el = document.querySelector(".InputManual__button");

        // Listen for submission, either by pressing return or clicking the submit answer button
        answer_input_el.addEventListener('keydown', function (event) {
            if (event.which == 13) {
                showExplanationIfWrong()
            }
        })

        submit_answer_button_el.addEventListener('click', function () {
            showExplanationIfWrong()
        })
    });



    //opens the info if you get the item wrong
    function showExplanationIfWrong() {
        resetExplanation();
       
        setTimeout(function() {
            if(document.querySelector('.bp-quiz-console--incorrect'))
            {
                document.getElementById("ai_explanation").innerText = "Loading explanation...";
                const full_response = document.querySelector('.bp-quiz-question.relative > div > button').parentNode.innerText;
                const user_fragment = document.querySelector('.bp-quiz-question.relative > div > button').innerText;
                const target_sentence = document.querySelector('.QuestionSentenceTransNuance').innerText;

                console.log(user_fragment);

                let prompt = `Explain why the "${user_fragment}" in "${full_response}" is incorrect when translating ${target_sentence}. Ignore the fact that the incorrect translation may use hiragana/katakana instead of kanji. Just explain, do not be sycophantic. Be concise, use two sentences maximum. Explain in English. Answer using English language. Do answer in Japanese. The user does not understand Japanese fluently, so answer in English.`;

                callChatGPT(prompt)
                    .then(result => {
                    document.getElementById("ai_explanation").style = "";
                    document.getElementById("ai_explanation").innerText = result.replaceAll('*', '');
                })
                    .catch(error => {
                    console.error("Error:", error);
                });
            }
        }, 500);
    }

    function resetExplanation() {
        let explanationElement = document.getElementById("ai_explanation");

        if (!explanationElement) {
            explanationElement = document.createElement('div')
            explanationElement.id = "ai_explanation";
            document.getElementById('js-tour-quiz-question').appendChild(explanationElement)
        }

        explanationElement.innerText = "";
        explanationElement.style = "font-style: italic";
    }


})()