Bunpro: Explain incorrect answer

Uses LLM to explain why an answer is wrong

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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";
    }


})()