UOOC assistant

【使用前先看介绍/有问题可反馈】UOOC 优课联盟助手:视频助手(倍速/静音/播放/连播)+ AI智能答题。点击⚙️配置API,勾选"LLM答题"后,点击"开始答题"即可。控制台可自动收起,鼠标移开后自动隐藏。键盘←→控制快进/快退,↑↓控制音量,空格控制播放/暂停。

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(У мене вже є менеджер скриптів, дайте мені встановити його!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         UOOC assistant
// @namespace    http://tampermonkey.net/
// @version      1.0.8
// @description  【使用前先看介绍/有问题可反馈】UOOC 优课联盟助手:视频助手(倍速/静音/播放/连播)+ AI智能答题。点击⚙️配置API,勾选"LLM答题"后,点击"开始答题"即可。控制台可自动收起,鼠标移开后自动隐藏。键盘←→控制快进/快退,↑↓控制音量,空格控制播放/暂停。
// @author       cc & wybbb1
// @include      https://www.uooc.net.cn/home/learn/index*
// @include      https://www.uooc.net.cn/home/course/exam/*
// @include      https://www.uooc.net.cn/home/exam/*
// @include      https://www.uooc.net.cn/exam/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_notification
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    console.log('[UOOC助手] 脚本开始加载...');

    // 全局LLM启用状态
    window.llmEnabled = false;

    // ==================== LLM配置管理模块 ====================
    const LLMConfig = {
        get: function() {
            try {
                return GM_getValue('llm_config', null);
            } catch (e) {
                console.log('[UOOC助手] GM_getValue不可用,使用localStorage');
                return JSON.parse(localStorage.getItem('llm_config') || 'null');
            }
        },
        save: function(config) {
            try {
                GM_setValue('llm_config', config);
            } catch (e) {
                console.log('[UOOC助手] GM_setValue不可用,使用localStorage');
                localStorage.setItem('llm_config', JSON.stringify(config));
            }
        },
        showConfigUI: function() {
            const config = this.get() || { baseUrl: '', apiKey: '', model: '' };

            const dialog = document.createElement('div');
            dialog.id = 'llm-config-dialog';
            dialog.style.cssText = `
                position: fixed;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                background: white;
                padding: 20px;
                border-radius: 10px;
                box-shadow: 0 0 20px rgba(0,0,0,0.5);
                z-index: 999999;
                min-width: 400px;
                font-family: Arial, sans-serif;
            `;

            dialog.innerHTML = `
                <h3 style="margin: 0 0 15px 0; color: #333;">AI答题配置</h3>
                <div style="margin-bottom: 10px;">
                    <label style="display: block; margin-bottom: 5px; color: #666;">API Base URL:</label>
                    <input type="text" id="llm-baseurl" value="${config.baseUrl || ''}"
                           placeholder="例如: https://api.openai.com/v1"
                           style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;">
                </div>
                <div style="margin-bottom: 10px;">
                    <label style="display: block; margin-bottom: 5px; color: #666;">API Key:</label>
                    <input type="password" id="llm-apikey" value="${config.apiKey || ''}"
                           placeholder="输入你的API Key"
                           style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;">
                </div>
                <div style="margin-bottom: 15px;">
                    <label style="display: block; margin-bottom: 5px; color: #666;">模型名称 (可选):</label>
                    <input type="text" id="llm-model" value="${config.model || ''}"
                           placeholder="例如: gpt-3.5-turbo (不填则使用默认)"
                           style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;">
                </div>
                <div style="text-align: right;">
                    <button id="llm-config-cancel" style="padding: 8px 20px; margin-right: 10px; border: 1px solid #ddd; background: white; border-radius: 4px; cursor: pointer;">取消</button>
                    <button id="llm-config-save" style="padding: 8px 20px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer;">保存</button>
                </div>
                <div style="margin-top: 10px; padding-top: 10px; border-top: 1px solid #eee; font-size: 11px; color: #999;">
                    提示: 配置会保存在本地,只需配置一次。支持OpenAI兼容格式的API。
                </div>
            `;

            document.body.appendChild(dialog);

            document.getElementById('llm-config-cancel').onclick = function() {
                document.body.removeChild(dialog);
            };

            document.getElementById('llm-config-save').onclick = function() {
                const baseUrl = document.getElementById('llm-baseurl').value.trim();
                const apiKey = document.getElementById('llm-apikey').value.trim();
                const model = document.getElementById('llm-model').value.trim();

                if (!baseUrl || !apiKey) {
                    alert('请填写完整的API配置!');
                    return;
                }

                LLMConfig.save({ baseUrl, apiKey, model });
                document.body.removeChild(dialog);
                alert('AI答题配置已保存!');
            };
        }
    };

    // ==================== 测评页面功能模块 ====================

    // 检测是否在测评页面
    function isQuizPage() {
        // 检查主页面
        if (document.querySelector('.queContainer') || document.querySelector('#examMain')) {
            return true;
        }
        // 检查iframe
        const iframe = document.querySelector('iframe');
        if (iframe) {
            try {
                const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
                return iframeDoc.querySelector('.queContainer') || iframeDoc.querySelector('#examMain');
            } catch (e) {
                return false;
            }
        }
        return false;
    }

    // 获取测评页面的document(可能是iframe)
    function getQuizDocument() {
        // 先检查主页面
        const mainDocContainers = document.querySelectorAll('.queContainer');
        if (mainDocContainers.length > 0) {
            console.log('[UOOC助手-AI] 在主文档中找到题目');
            return document;
        }

        // 检查iframe
        const iframe = document.querySelector('iframe');
        if (iframe) {
            try {
                const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
                const iframeContainers = iframeDoc.querySelectorAll('.queContainer');
                if (iframeContainers.length > 0) {
                    console.log('[UOOC助手-AI] 在iframe中找到题目');
                    return iframeDoc;
                }
            } catch (e) {
                console.log('[UOOC助手-AI] 无法访问iframe:', e.message);
            }
        }

        return null;
    }

    // 提取题目
    function extractQuestions() {
        console.log('[UOOC助手-AI] 开始提取题目...');

        // 获取测评页面的document
        const quizDoc = getQuizDocument();
        if (!quizDoc) {
            console.log('[UOOC助手-AI] 未找到测评页面document');
            return [];
        }

        // 调试:检查页面元素
        const containers = quizDoc.querySelectorAll('.queContainer');
        console.log('[UOOC助手-AI] 找到 .queContainer 元素数量:', containers.length);

        // 如果没找到
        if (containers.length === 0) {
            console.log('[UOOC助手-AI] 未找到题目容器');
            console.log('[UOOC助手-AI] 当前URL:', window.location.href);
            return [];
        }

        const questions = [];

        containers.forEach((container, index) => {
            console.log(`[UOOC助手-AI] 处理第 ${index + 1} 个容器`);

            const typeElem = container.querySelector('input[type="radio"], input[type="checkbox"]');
            if (!typeElem) {
                console.log(`[UOOC助手-AI] 第 ${index + 1} 个容器:未找到 input 元素`);
                return;
            }

            const isRadio = typeElem.type === 'radio';
            const questionText = container.querySelector('.ti-q-c')?.innerText.trim() || '';
            const options = [];

            container.querySelectorAll('.ti-a').forEach((optElem) => {
                const optionLabel = optElem.querySelector('.ti-a-i')?.innerText.trim() || '';
                const optionText = optElem.querySelector('.ti-a-c')?.innerText.trim() || '';
                const inputElem = optElem.querySelector('input');
                options.push({
                    label: optionLabel,
                    text: optionText,
                    value: inputElem?.value || '',
                    input: inputElem
                });
            });

            let questionType = '判断题';
            if (options.length > 2) {
                questionType = isRadio ? '单选题' : '多选题';
            } else if (options.length === 2) {
                questionType = '判断题';
            }

            console.log(`[UOOC助手-AI] 第 ${index + 1} 题:${questionType},选项数量: ${options.length}`);

            questions.push({
                index: index + 1,
                type: questionType,
                question: questionText,
                options: options,
                isRadio: isRadio
            });
        });

        console.log('[UOOC助手-AI] 提取到', questions.length, '道题目');
        return questions;
    }

    // 构建LLM提示词
    function buildPrompt(questions) {
        let prompt = '请回答以下选择题,每题直接给出答案选项字母(如A、B、C、D或A、B等),不需要解释。\n\n';

        questions.forEach(q => {
            prompt += `第${q.index}题 [${q.type}]\n`;
            prompt += `题目:${q.question}\n`;
            q.options.forEach(opt => {
                prompt += `${opt.label}. ${opt.text}\n`;
            });
            prompt += '\n';
        });

        prompt += '\n请按以下格式返回答案(每行一个题号和答案):\n';
        prompt += '1. A\n2. B\n3. A,B\n...\n';
        prompt += '注意:多选题答案用逗号分隔,判断题A表示正确,B表示错误。';

        return prompt;
    }

    // 解析LLM返回的答案
    function parseAnswers(llmResponse, questions) {
        const answers = [];
        const lines = llmResponse.split('\n');

        lines.forEach(line => {
            const match = line.trim().match(/^(\d+)\.\s*([A-Z,]+)$/);
            if (match) {
                const qIndex = parseInt(match[1]) - 1;
                const answer = match[2].toUpperCase().split(',').map(a => a.trim());
                answers[qIndex] = answer;
            }
        });

        // 如果解析数量不匹配,尝试其他格式
        if (answers.filter(a => a).length < questions.length) {
            console.log('[UOOC助手-AI] 标准格式解析失败,尝试智能匹配...');
            // 尝试从文本中提取所有答案
            const allAnswers = llmResponse.match(/\d+\.[\s]*[A-Z]+/g);
            if (allAnswers) {
                allAnswers.forEach((ans, idx) => {
                    const match = ans.match(/\d+\.[\s]*([A-Z,]+)/);
                    if (match) {
                        const answer = match[1].toUpperCase().split(',').map(a => a.trim());
                        answers[idx] = answer;
                    }
                });
            }
        }

        return answers;
    }

    // 调用LLM API
    async function callLLM(questions) {
        const config = LLMConfig.get();
        if (!config || !config.baseUrl || !config.apiKey) {
            alert('请先配置AI API!点击页面右上角的"AI设置"按钮进行配置。');
            return null;
        }

        console.log('[UOOC助手-AI] 开始调用LLM API...');
        const prompt = buildPrompt(questions);

        try {
            const response = await fetch(`${config.baseUrl}/chat/completions`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': `Bearer ${config.apiKey}`
                },
                body: JSON.stringify({
                    model: config.model || 'gpt-3.5-turbo',
                    messages: [
                        { role: 'system', content: '你是一个专业的答题助手,请严格按照要求的格式返回答案。' },
                        { role: 'user', content: prompt }
                    ],
                    temperature: 0.3
                })
            });

            if (!response.ok) {
                throw new Error(`API请求失败: ${response.status} ${response.statusText}`);
            }

            const data = await response.json();
            const answerText = data.choices[0].message.content;
            console.log('[UOOC助手-AI] LLM返回:', answerText);

            return parseAnswers(answerText, questions);
        } catch (error) {
            console.error('[UOOC助手-AI] LLM调用失败:', error);
            alert(`AI答题失败: ${error.message}\n请检查API配置是否正确。`);
            return null;
        }
    }

    // 填入答案
    function fillAnswers(questions, answers) {
        console.log('[UOOC助手-AI] 开始填入答案...');
        let filledCount = 0;

        questions.forEach((q, idx) => {
            const answer = answers[idx];
            if (!answer) {
                console.log(`[UOOC助手-AI] 第${q.index}题:未找到答案`);
                return;
            }

            // 点击对应的选项
            answer.forEach(ans => {
                const option = q.options.find(opt => opt.value === ans);
                if (option && option.input) {
                    option.input.click();
                    console.log(`[UOOC助手-AI] 第${q.index}题:选择 ${ans}`);
                    filledCount++;
                }
            });
        });

        console.log('[UOOC助手-AI] 共填入', filledCount, '个答案');
        return filledCount;
    }

    // 主答题流程
    async function autoAnswerQuiz() {
        console.log('[UOOC助手-AI] ===== 开始自动答题流程 =====');

        // 检查配置
        const config = LLMConfig.get();
        if (!config || !config.baseUrl || !config.apiKey) {
            console.log('[UOOC助手-AI] 未配置API,显示配置界面');
            LLMConfig.showConfigUI();
            return;
        }

        // 提取题目
        const questions = extractQuestions();
        if (questions.length === 0) {
            console.log('[UOOC助手-AI] 未找到题目');
            return;
        }

        console.log(`[UOOC助手-AI] 检测到 ${questions.length} 道题目,开始AI答题...`);

        // 调用LLM
        const answers = await callLLM(questions);
        if (!answers) return;

        // 填入答案
        const filledCount = fillAnswers(questions, answers);

        // 提示用户检查并提交
        setTimeout(function() {
            alert(`✅ AI答题完成!\n\n已自动填入 ${filledCount} 个答案。\n\n请仔细检查答案后,手动点击页面左下方的"提交试卷"按钮。`);
        }, 500);
    }

    // 在测评页面显示提示
    function showQuizPageHint() {
        if (document.getElementById('llm-quiz-hint')) {
            return;
        }

        // 只有在LLM启用时才显示提示
        if (!window.llmEnabled) {
            console.log('[UOOC助手-AI] LLM未启用,不显示答题提示');
            return;
        }

        console.log('[UOOC助手-AI] 检测到测评页面,LLM已启用');

        // 标记已显示
        const marker = document.createElement('div');
        marker.id = 'llm-quiz-hint';
        marker.style.display = 'none';
        document.body.appendChild(marker);

        // 在控制台提示
        console.log('[UOOC助手-AI] 在视频页面点击"🤖 开始答题"按钮即可开始AI答题');
    }

    // ==================== 原有视频助手功能 ====================

    function ckeckTestIgnorable() {
        var cid = location.href.match(/index#\/(\d+)\//)[1];
        console.log('[UOOC助手] 课程ID:', cid);
        $.ajax({
            url: 'https://www.uooc.net.cn/home/learn/getCourseLearn',
            type: 'GET',
            data: { cid: cid },
            success: function(res) {
                window.canIgnoreTest = Boolean(res.data.course_learn_mode === '20');
                console.log('[UOOC助手] 可忽略测验:', window.canIgnoreTest);
            },
            error: function(err) {
                console.log('[UOOC助手] 获取课程学习模式失败,使用默认设置');
                window.canIgnoreTest = false;
            }
        });
    }

    function bindChapterChange() {
        function bindSubChapterChange() {
            var sourceView = document.querySelector('[source-view]');
            if (!sourceView) return;
            var sObserver = new MutationObserver(function(mutations) {
                if (document.querySelector('[source-view] [uooc-video] video')) {
                    console.log('[UOOC助手] 检测到视频变化');
                    setTimeout(start, 250);
                }
            });
            sObserver.observe(sourceView, { childList: true });
        }

        var mainLeft = document.querySelector('.learn-main-left');
        if (!mainLeft) {
            console.log('[UOOC助手] 未找到.learn-main-left元素');
            return;
        }
        var mObserver = new MutationObserver(function(mutations) {
            bindSubChapterChange();
        });
        mObserver.observe(mainLeft, { childList: true });
        bindSubChapterChange();
    }

    function autoQuiz() {
        function autoQuizAnswer() {
            try {
                let quizLayer = document.getElementById('quizLayer');
                let sourceDiv = document.querySelector('div[uooc-video]');
                if (!quizLayer || !sourceDiv) return;

                let source = JSON.parse(sourceDiv.getAttribute('source'));
                let quizQuestion = document.querySelector('.smallTest-view .ti-q-c');
                if (!quizQuestion) return;

                let quizQuestionText = quizQuestion.innerHTML;
                let quizData = source.quiz.find(q => q.question === quizQuestionText);
                if (!quizData) return;

                let quizAnswer = quizData.answer;
                let quizOptions = quizLayer.querySelector('div.ti-alist');
                if (!quizOptions) return;

                for (let ans of eval(quizAnswer)) {
                    if (quizOptions.children[ans.charCodeAt() - 'A'.charCodeAt()]) {
                        quizOptions.children[ans.charCodeAt() - 'A'.charCodeAt()].click();
                    }
                }
                let submitBtn = quizLayer.querySelector('button');
                if (submitBtn) submitBtn.click();
                console.log('[UOOC助手] 自动答题完成');
            } catch (err) {
                console.error('[UOOC助手] 自动答题出错:', err);
            }
        }

        var learnView = document.querySelector('.lean_view');
        if (!learnView) return;
        var observer = new MutationObserver(function(mutations) {
            for (let mutation of mutations) {
                let node = mutation.addedNodes[0];
                if (node && node.id && node.id.includes('layui-layer')) {
                    console.log('[UOOC助手] 检测到测验弹窗');
                    autoQuizAnswer();
                    break;
                }
            }
        });
        observer.observe(learnView, { childList: true });
    }

    function bindVideoEvents() {
        var video = document.querySelector('#player_html5_api');
        if (!video) {
            console.log('[UOOC助手] 未找到视频元素');
            return;
        }
        console.log('[UOOC助手] 绑定视频事件');
        video.onpause = function() { if (document.getElementById('play') && document.getElementById('play').checked) this.play(); };
        video.onended = findNextVideo;
    }

    function start() {
        console.log('[UOOC助手] start() 函数执行');
        bindKeyboardEvents();
        bindVideoEvents();
        autoQuiz();

        var video = document.getElementById('player_html5_api');
        var volume = document.getElementById('volume');
        var play = document.getElementById('play');
        var rate = document.getElementById('rate');

        if (!video) {
            console.log('[UOOC助手] start() 中未找到视频元素');
            return;
        }

        if (volume && volume.checked) video.muted = true;
        if (play && play.checked && video.paused) {
            console.log('[UOOC助手] 自动播放视频');
            video.play();
        }
        if (rate && rate.checked) {
            console.log('[UOOC助手] 设置2倍速');
            video.playbackRate = 2.0;
        }
    }

    function placeComponents() {
        console.log('[UOOC助手] 开始放置UI组件');

        function copyToClipboard(content) {
            var t = document.createElement('textarea');
            t.value = content;
            document.body.appendChild(t);
            t.select();
            document.execCommand('copy');
            document.body.removeChild(t);
        }

        function getCheckbox(name, text) {
            var p = document.createElement('p');
            p.style = 'color: #ccc; padding-left: 10px;';
            var checkbox = document.createElement('input');
            checkbox.id = checkbox.name = checkbox.value = name;
            checkbox.type = 'checkbox';
            checkbox.checked = true;
            checkbox.style = 'margin-left: 15px; width: 12px; height: 12px;';
            p.append(checkbox);
            var label = document.createElement('label');
            label.htmlFor = name;
            label.innerText = text;
            label.style = 'margin-left: 13px; font-size: 12px;';
            p.append(label);
            return p;
        }

        function getContainer(_id) {
            var container = document.createElement('div');
            container.id = _id;
            container.style = 'display: flex; flex-direction: row; align-items: center;';
            return container;
        }

        function getCopyButton() {
            var copyButton = document.createElement('p');
            copyButton.style = 'color: #ccc; padding-left: 10px;';
            var btn = document.createElement('button');
            btn.innerText = '复制题目答案';
            btn.style = 'margin-left: 13px; padding: 0 5px 0; font-size: 12px; cursor: pointer;';
            btn.onclick = function() {
                try {
                    var testPaperTop = frames[0] ? frames[0].document.querySelector('.testPaper-Top') : document.querySelector('.testPaper-Top');
                    if (!testPaperTop) {
                        alert('该页面不是测验页面,无法复制内容');
                    } else {
                        if (testPaperTop.querySelector('.fl_right')) {
                            var queItems = frames[0] ? Array.from(frames[0].document.querySelectorAll('.queItems')) : Array.from(document.querySelectorAll('.queItems'));
                            var content = queItems.map(queType => {
                                var res = '';
                                if (queType.querySelector('.queItems-type').innerText.indexOf('选') >= 0) {
                                    var questions = queType.querySelectorAll('.queContainer');
                                    res += Array.from(questions).map((question) => {
                                        var que = question.querySelector('.queBox').innerText.replace(/\n{2,}/g, '\n').replace(/(\w\.)\n/g, '$1 ');
                                        var ans = question.querySelector('.answerBox div:first-child').innerText.replace(/\n/g, '');
                                        var scoresDiv = question.querySelector('.scores');
                                        var right = false;
                                        if (scoresDiv) {
                                            var match = scoresDiv.innerText.match(/\d+\.?\d+/g);
                                            if (match) {
                                                var right = match.map(score => eval(score));
                                                right = right[0] === right[1];
                                            }
                                        }
                                        return `${que}\n${ans}\n是否正确:${right}\n`;
                                    }).join('\n');
                                }
                                return res;
                            }).join('\n');
                            copyToClipboard(content);
                            alert('题目及答案已复制到剪切板');
                        } else {
                            alert('该测验可能还没提交,无法复制');
                        }
                    }
                } catch (err) {
                    alert('复制出错:' + err.message);
                }
            };
            // copyButton.appendChild(btn);
            return copyButton;
        }

        function setCheckboxes(container) {
            var rateCheckbox = getCheckbox('rate', '倍速');
            var volumeCheckbox = getCheckbox('volume', '静音');
            var playCheckbox = getCheckbox('play', '播放');
            var continueCheckbox = getCheckbox('continue', '连播');
            var copyButton = getCopyButton();
            var video = document.getElementById('player_html5_api');

            // 创建LLM答题复选框和设置按钮
            var llmContainer = document.createElement('p');
            llmContainer.style = 'color: #ccc; padding-left: 10px; display: flex; align-items: center;';

            // 创建设置按钮(齿轮图标)
            var settingBtn = document.createElement('span');
            settingBtn.innerHTML = '⚙️';
            settingBtn.style = 'margin-left: 15px; font-size: 16px; cursor: pointer; margin-right: 5px;';
            settingBtn.title = '配置AI答题参数';
            settingBtn.onclick = function() {
                LLMConfig.showConfigUI();
            };

            // 创建LLM复选框
            var llmCheckbox = document.createElement('input');
            llmCheckbox.id = llmCheckbox.name = llmCheckbox.value = 'llm';
            llmCheckbox.type = 'checkbox';
            llmCheckbox.checked = window.llmEnabled;
            llmCheckbox.style = 'width: 12px; height: 12px;';
            llmCheckbox.onchange = function(event) {
                window.llmEnabled = event.target.checked;
                console.log('[UOOC助手] LLM答题', window.llmEnabled ? '已启用' : '已禁用');

                if (window.llmEnabled) {
                    // 检查配置
                    const config = LLMConfig.get();
                    if (!config || !config.baseUrl || !config.apiKey) {
                        alert('LLM答题已启用,但尚未配置API参数!\n\n请点击⚙️图标进行配置。');
                        setTimeout(function() {
                            LLMConfig.showConfigUI();
                        }, 300);
                    }
                }

                // 更新答题按钮状态
                updateAnswerButtonState();
            };

            var llmLabel = document.createElement('label');
            llmLabel.htmlFor = 'llm';
            llmLabel.innerText = 'LLM答题';
            llmLabel.style = 'margin-left: 13px; font-size: 12px;';

            // 创建"开始答题"按钮
            var answerBtn = document.createElement('button');
            answerBtn.id = 'llm-answer-btn';
            answerBtn.innerHTML = '🤖 开始答题';
            answerBtn.className = 'btn btn-primary';
            answerBtn.style.cssText = `
                margin-left: 10px;
                padding: 4px 12px;
                font-size: 12px;
                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                color: white;
                border: none;
                border-radius: 4px;
                cursor: pointer;
                transition: all 0.3s;
                opacity: 0.5;
                pointer-events: none;
            `;
            answerBtn.onmouseover = function() {
                if (window.llmEnabled) {
                    this.style.transform = 'scale(1.05)';
                    this.style.boxShadow = '0 2px 8px rgba(102, 126, 234, 0.4)';
                }
            };
            answerBtn.onmouseout = function() {
                this.style.transform = 'scale(1)';
                this.style.boxShadow = 'none';
            };
            answerBtn.onclick = async function() {
                if (!window.llmEnabled) {
                    alert('请先勾选"LLM答题"复选框!');
                    return;
                }

                // 禁用按钮,显示loading
                answerBtn.disabled = true;
                answerBtn.innerHTML = '⏳ 正在答题...';
                answerBtn.style.opacity = '0.7';

                try {
                    await autoAnswerQuiz();
                    // 答题完成
                    answerBtn.innerHTML = '✅ 答题完成';
                    answerBtn.style.background = '#28a745';

                    setTimeout(function() {
                        answerBtn.disabled = false;
                        answerBtn.innerHTML = '🤖 开始答题';
                        answerBtn.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
                        answerBtn.style.opacity = '1';
                    }, 3000);
                } catch (error) {
                    // 答题失败
                    answerBtn.innerHTML = '❌ 答题失败';
                    answerBtn.style.background = '#dc3545';

                    setTimeout(function() {
                        answerBtn.disabled = false;
                        answerBtn.innerHTML = '🤖 开始答题';
                        answerBtn.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
                        answerBtn.style.opacity = '1';
                    }, 2000);
                }
            };

            // 更新按钮状态的函数
            function updateAnswerButtonState() {
                if (window.llmEnabled) {
                    answerBtn.style.opacity = '1';
                    answerBtn.style.pointerEvents = 'auto';
                } else {
                    answerBtn.style.opacity = '0.5';
                    answerBtn.style.pointerEvents = 'none';
                }
            }

            llmContainer.appendChild(settingBtn);
            llmContainer.appendChild(llmCheckbox);
            llmContainer.appendChild(llmLabel);
            llmContainer.appendChild(answerBtn);

            if (rateCheckbox.firstElementChild) {
                rateCheckbox.firstElementChild.onchange = function(event) {
                    if (video) {
                        if (event.target.checked) video.playbackRate = 2;
                        else video.playbackRate = 1;
                    }
                };
            }

            if (volumeCheckbox.firstElementChild) {
                volumeCheckbox.firstElementChild.onchange = function(event) {
                    if (video) {
                        video.muted = event.target.checked;
                    }
                };
            }

            if (playCheckbox.firstElementChild) {
                playCheckbox.firstElementChild.onchange = function(event) {
                    if (video) {
                        if (event.target.checked) video.play();
                        else video.pause();
                    }
                };
            }

            container.appendChild(llmContainer);
            container.appendChild(rateCheckbox);
            container.appendChild(volumeCheckbox);
            container.appendChild(playCheckbox);
            container.appendChild(continueCheckbox);
            container.appendChild(copyButton);
        }

        /*function setPrompt(container) {
            var div = document.createElement('div');
            div.innerHTML = `提示:<u>该版本为内测版,使用时请先关闭正式版</u>,<u><a href="https://greasyfork.org/zh-CN/scripts/425837-uooc-assistant-beta/feedback" target="_blank" style="color: yellow;">若出现 BUG 点此反馈</a></u>,键盘的 ← 和 → 可以控制快进/快退,↑ 和 ↓ 可以控制音量增大/减小,空格键可以控制播放/暂停`;
            div.style = 'color: #cccccc; height: min-height; margin: 0 20px 0; padding: 0 5px; border-radius: 5px; font-size: 12px;';
            container.appendChild(div);
        }*/

        function setAppreciationCode(container) {
            var a = document.createElement('a');
            a.href = 'https://s1.ax1x.com/2020/11/08/BTeRqe.png';
            a.target = '_blank';
            a.innerHTML = '<u>本脚本使用完全免费,您的打赏是作者维护下去的最大动力!点此打赏作者😊</u>';
            a.style = 'color: #cccccc; font-weight: bold; height: min-height; margin: 0 20px 0; padding: 0 5px; border-radius: 5px; font-size: 11px;';
            container.appendChild(a);
        }

        var head = document.querySelector('.learn-head');
        if (!head) {
            console.log('[UOOC助手] 未找到.learn-head元素,等待页面加载...');
            return false;
        }

        console.log('[UOOC助手] 找到.learn-head元素');

        // 避免重复添加
        if (document.getElementById('checkbox-container')) {
            console.log('[UOOC助手] UI组件已存在,跳过添加');
            return true;
        }

        // 创建包裹容器(用于包裹head的原有内容)
        const headContent = document.createElement('div');
        headContent.id = 'head-content-wrapper';

        // 将head的所有子元素移动到contentWrapper中
        while (head.firstChild) {
            headContent.appendChild(head.firstChild);
        }

        // 为head添加过渡动画样式
        head.style.transition = 'max-height 0.4s ease, opacity 0.4s ease, margin-top 0.4s ease';
        head.style.overflow = 'visible';
        head.style.position = 'relative';

        // 创建切换按钮(放在head内,但在contentWrapper外)
        const toggleBtn = document.createElement('div');
        toggleBtn.id = 'control-panel-toggle';
        toggleBtn.innerHTML = '▲';
        toggleBtn.style.cssText = `
            position: absolute;
            bottom: -25px;
            left: 50%;
            transform: translateX(-50%);
            width: 80px;
            height: 25px;
            background: rgba(51, 51, 51, 0.95);
            color: #ccc;
            text-align: center;
            line-height: 25px;
            font-size: 12px;
            cursor: pointer;
            border-radius: 0 0 10px 10px;
            transition: all 0.3s ease;
            z-index: 1000;
            user-select: none;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
        `;
        toggleBtn.onmouseover = function() {
            this.style.background = 'rgba(102, 126, 234, 0.95)';
            this.style.color = 'white';
        };
        toggleBtn.onmouseout = function() {
            this.style.background = 'rgba(51, 51, 51, 0.95)';
            this.style.color = '#ccc';
        };

        // 创建控制台容器
        var checkboxContainer = getContainer('checkbox-container');
        checkboxContainer.style.cssText = `
            display: flex;
            flex-direction: row;
            align-items: center;
        `;
        setCheckboxes(checkboxContainer);
        setAppreciationCode(checkboxContainer);

        // 组装元素:contentWrapper在head内,toggleBtn在head内但在contentWrapper外
        head.appendChild(headContent);
        head.appendChild(checkboxContainer);
        head.appendChild(toggleBtn);

        // 记录原始高度
        const originalHeight = head.offsetHeight + 30;
        head.style.height = originalHeight + 'px';

        // 控制显示/隐藏的函数
        let isPanelVisible = true;
        function hidePanel() {
            if (!isPanelVisible) return;
            isPanelVisible = false;

            // 只隐藏contentWrapper和checkboxContainer,不隐藏toggleBtn
            headContent.style.maxHeight = '0px';
            headContent.style.opacity = '0';
            headContent.style.overflow = 'hidden';
            headContent.style.transition = 'max-height 0.4s ease, opacity 0.4s ease';

            checkboxContainer.style.maxHeight = '0px';
            checkboxContainer.style.opacity = '0';
            checkboxContainer.style.overflow = 'hidden';
            checkboxContainer.style.transition = 'max-height 0.4s ease, opacity 0.4s ease';

            // 同时调整整个head的高度
            head.style.maxHeight = '25px'; // 只保留toggleBtn的高度
            head.style.opacity = '1';

            toggleBtn.innerHTML = '▼';
        }

        function showPanel() {
            if (isPanelVisible) return;
            isPanelVisible = true;

            // 恢复contentWrapper和checkboxContainer
            headContent.style.maxHeight = originalHeight + 'px';
            headContent.style.opacity = '1';

            checkboxContainer.style.maxHeight = originalHeight + 'px';
            checkboxContainer.style.opacity = '1';

            // 恢复整个head的高度
            head.style.maxHeight = originalHeight + 'px';
            head.style.opacity = '1';

            toggleBtn.innerHTML = '▲';
        }

        toggleBtn.onclick = function() {
            if (isPanelVisible) {
                hidePanel();
            } else {
                showPanel();
            }
        };

        console.log('[UOOC助手] UI组件添加完成');
        return true;
    }

    function bindKeyboardEvents() {
        console.log('[UOOC助手] 绑定键盘事件');
        document.onkeydown = function(event) {
            var complete = false;
            var basicActiveDiv = document.querySelector('div.basic.active');
            var video = document.getElementById('player_html5_api');
            if (!video) return;

            if (basicActiveDiv && basicActiveDiv.classList.contains('complete')) complete = true;
            switch (event.key) {
                case 'ArrowLeft': {
                    event.preventDefault();
                    video.currentTime -= 10;
                    break;
                }
                case 'ArrowRight': {
                    event.preventDefault();
                    if (complete) video.currentTime += 10;
                    break;
                }
                case 'ArrowUp': {
                    event.preventDefault();
                    if (video.volume + 0.1 <= 1.0) video.volume += 0.1;
                    else video.volume = 1.0;
                    break;
                }
                case 'ArrowDown': {
                    event.preventDefault();
                    if (video.volume - 0.1 >= 0.0) video.volume -= 0.1;
                    else video.volume = 0.0;
                    break;
                }
                case ' ': {
                    event.preventDefault();
                    let continueCheckbox = document.getElementById('play');
                    if (continueCheckbox) continueCheckbox.click();
                    break;
                }
            }
        };
    }

    function findNextVideo() {
        var video = document.getElementById('player_html5_api');
        if (video) {
            if (!document.getElementById('continue') || !document.getElementById('continue').checked) {
                video.currentTime = 0;
            } else {
                let current_video = document.querySelector('.basic.active');
                if (!current_video) return;

                let next_part = current_video.parentNode;
                let next_video = current_video;
                let isVideo = (node) => { return Boolean(node.querySelector('span.icon-video')); };
                let canBack = () => { return Boolean(next_part.parentNode.parentNode.tagName === 'LI'); };
                let toNextVideo = () => {
                    next_video = next_video.nextElementSibling;
                    while (next_video && !isVideo(next_video)) next_video = next_video.nextElementSibling;
                };
                let isExistsVideo = () => {
                    let _video = next_part.firstElementChild;
                    while (_video && !isVideo(_video)) _video = _video.nextElementSibling;
                    return Boolean(_video && isVideo(_video));
                };
                let isExistsNextVideo = () => {
                    let _video = current_video.nextElementSibling;
                    while (_video && !isVideo(_video)) _video = _video.nextElementSibling;
                    return Boolean(_video && isVideo(_video));
                };
                let isExistsNextListAfterFile = () => {
                    let part = next_part.nextElementSibling;
                    return Boolean(part && part.childElementCount > 0);
                };
                let toNextListAfterFile = () => { next_part = next_part.nextElementSibling; };
                let toOuterList = () => { next_part = next_part.parentNode.parentNode; };
                let toOuterItem = () => { next_part = next_part.parentNode; };
                let isExistsNextListAfterList = () => { return Boolean(next_part.nextElementSibling); };
                let toNextListAfterList = () => { next_part = next_part.nextElementSibling; };
                let expandList = () => { next_part.firstElementChild.click(); };
                let toExpandListFirstElement = () => {
                    next_part = next_part.firstElementChild.nextElementSibling;
                    if (next_part && next_part.classList.contains('unfoldInfo')) next_part = next_part.nextElementSibling;
                };
                let isList = () => { return Boolean(next_part.tagName === 'UL'); };
                let toInnerList = () => { next_part = next_part.firstElementChild; };
                let toFirstVideo = () => {
                    next_video = next_part.firstElementChild;
                    while (next_video && !isVideo(next_video)) next_video = next_video.nextElementSibling;
                };

                let mode = {
                    FIRST_VIDEO: 'FIRST_VIDEO',
                    NEXT_VIDEO: 'NEXT_VIDEO',
                    LAST_LIST: 'LAST_LIST',
                    NEXT_LIST: 'NEXT_LIST',
                    INNER_LIST: 'INNER_LIST',
                    OUTER_LIST: 'OUTER_LIST',
                    OUTER_ITEM: 'OUTER_ITEM',
                };

                let search = (_mode) => {
                    switch (_mode) {
                        case mode.FIRST_VIDEO:
                            if (isExistsVideo()) {
                                toFirstVideo();
                                if (next_video) next_video.click();
                                start();
                            } else if (isExistsNextListAfterFile()) {
                                search(mode.LAST_LIST);
                            } else if (window.canIgnoreTest) {
                                next_part = next_part.lastElementChild;
                                search(mode.OUTER_LIST);
                            }
                            break;
                        case mode.NEXT_VIDEO:
                            if (isExistsNextVideo()) {
                                toNextVideo();
                                if (next_video) next_video.click();
                                start();
                            } else if (isExistsNextListAfterFile()) {
                                search(mode.LAST_LIST);
                            } else {
                                search(mode.OUTER_ITEM);
                            }
                            break;
                        case mode.LAST_LIST:
                            toNextListAfterFile();
                            toInnerList();
                            search(mode.INNER_LIST);
                            break;
                        case mode.NEXT_LIST:
                            toNextListAfterList();
                            search(mode.INNER_LIST);
                            break;
                        case mode.INNER_LIST:
                            if (next_part.firstElementChild) {
                                expandList();
                                function waitForExpand() {
                                    if (next_part.firstElementChild.nextElementSibling) {
                                        if (next_part.firstElementChild.nextElementSibling.childElementCount === 0) {
                                            search(mode.OUTER_LIST);
                                        } else {
                                            toExpandListFirstElement();
                                            if (isList()) {
                                                toInnerList();
                                                search(mode.INNER_LIST);
                                            } else {
                                                search(mode.FIRST_VIDEO);
                                            }
                                        }
                                    } else {
                                        setTimeout(waitForExpand, 250);
                                    }
                                }
                                waitForExpand();
                            }
                            break;
                        case mode.OUTER_LIST:
                            toOuterList();
                            if (isExistsNextListAfterList()) {
                                search(mode.NEXT_LIST);
                            } else if (canBack()) {
                                search(mode.OUTER_LIST);
                            } else {
                                // perhaps there is no next list
                            }
                            break;
                        case mode.OUTER_ITEM:
                            toOuterItem();
                            if (isExistsNextListAfterList()) {
                                toNextListAfterList();
                                search(mode.INNER_LIST);
                            } else if (canBack()){
                                search(mode.OUTER_LIST);
                            } else {
                                // perhaps there is no list
                            }
                            break;
                        default:
                            break;
                    }
                };

                try {
                    search(mode.NEXT_VIDEO);
                } catch (err) {
                    console.error('[UOOC助手] 查找下一个视频出错:', err);
                }
            }
        }
    }

    function init() {
        console.log('[UOOC助手] 开始初始化...');

        // 检测页面类型
        const currentUrl = window.location.href;

        // 测评页面
        if (currentUrl.includes('/exam') || isQuizPage()) {
            console.log('[UOOC助手-AI] 检测到测评页面');
            setTimeout(function() {
                showQuizPageHint();
            }, 1000);
            return;
        }

        // 视频学习页面
        // 检查必要的全局对象
        if (typeof $ === 'undefined') {
            console.log('[UOOC助手] jQuery未加载,等待...');
            setTimeout(init, 500);
            return;
        }

        ckeckTestIgnorable();

        function waitHead() {
            if (document.querySelector('.learn-head')) {
                console.log('[UOOC助手] .learn-head已存在,开始放置组件');
                var uiSuccess = placeComponents();
                bindChapterChange();

                function ready() {
                    console.log('[UOOC助手] UOOC assistant beta has initialized.');

                    // 检查LLM启用状态和配置
                    if (window.llmEnabled) {
                        const config = LLMConfig.get();
                        if (!config || !config.baseUrl || !config.apiKey) {
                            console.log('[UOOC助手] LLM已启用但未配置API');
                            setTimeout(function() {
                                alert('LLM答题功能已启用,但尚未配置API参数!\n\n请点击⚙️图标进行配置。');
                                LLMConfig.showConfigUI();
                            }, 1000);
                        } else {
                            console.log('[UOOC助手] LLM配置正常,已准备就绪');
                        }
                    }

                    start();
                }

                if (document.getElementById('player_html5_api')) {
                    ready();
                } else {
                    var iconVideo = document.querySelector('.icon-video');
                    if (iconVideo) {
                        console.log('[UOOC助手] 点击视频图标');
                        iconVideo.click();
                        setTimeout(ready, 250);
                    } else {
                        console.log('[UOOC助手] 未找到视频图标,等待用户手动选择');
                    }
                }
            } else {
                console.log('[UOOC助手] 等待.learn-head元素...');
                setTimeout(waitHead, 250);
            }
        }
        waitHead();
    }

    // 不使用window.onload,直接执行
    console.log('[UOOC助手] 脚本已加载,等待页面就绪');
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();