Greasy Fork

Greasy Fork is available in English.

🧠AI智慧树知到自动答题助手🧠

AI自动答题,可暂停/重试,复制题目,模型自定义,窗口缩放,显示完整AI思考过程和回复。支持OpenAI, DeepSeek, Gemini等API。

当前为 2025-05-22 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        🧠AI智慧树知到自动答题助手🧠
// @namespace    http://greasyfork.icu/
// @description  AI自动答题,可暂停/重试,复制题目,模型自定义,窗口缩放,显示完整AI思考过程和回复。支持OpenAI, DeepSeek, Gemini等API。
// @author       AI Copilot
// @match        *://*.zhihuishu.com/stuExamWeb*
// @connect      *
// @run-at       document-start
// @grant        unsafeWindow
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @grant        GM_getResourceText
// @grant        GM_setClipboard
// @grant        GM_setValue
// @grant        GM_getValue
// @resource css https://unpkg.com/[email protected]/dist/css/bootstrap.min.css
// @license      MIT
// @version 0.0.1.20250522154758
// ==/UserScript==

enableWebpackHook();

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

// --- 全局状态和配置 ---
let IS_PAUSED = false;
let CURRENT_PROCESSING_QUESTIONS = []; // 存储当前批次题目的详细信息
let IS_WINDOW_MINIMIZED = false;

const MAX_AI_RETRIES = 2;
const AI_RETRY_DELAY = 2000;
const DOCUMENTATION_URL = "http://greasyfork.icu/zh-CN/scripts/your-script-id-or-page"; // 【重要】替换为你的实际文档链接

const AI_PROVIDERS = {
    OPENAI: { name: "OpenAI", defaultUrl: "https://api.openai.com/v1/chat/completions", defaultModel: "gpt-3.5-turbo", getRequestData: (model, promptContent) => ({ model: model, messages: [{ role: "user", content: promptContent }], temperature: 0.1, max_tokens: 250 }), parseResponse: (res) => { if (res.choices && res.choices.length > 0) { const fullText = res.choices[0].message.content.trim(); const cleanAnswer = fullText.replace(/<think>[\s\S]*?<\/think>\s*/gi, "").trim(); return { fullText, cleanAnswer }; } return null; }, parseError: (res) => res.error ? res.error.message : null },
    DEEPSEEK: { name: "DeepSeek", defaultUrl: "https://api.deepseek.com/chat/completions", defaultModel: "deepseek-chat", getRequestData: (model, promptContent) => ({ model: model, messages: [{ role: "user", content: promptContent }], temperature: 0.1, max_tokens: 250 }), parseResponse: (res) => { if (res.choices && res.choices.length > 0) { const fullText = res.choices[0].message.content.trim(); const cleanAnswer = fullText.replace(/<think>[\s\S]*?<\/think>\s*/gi, "").trim(); return { fullText, cleanAnswer }; } return null; }, parseError: (res) => res.error ? res.error.message : null },
    GEMINI: { name: "Google Gemini", defaultUrl: "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.0-pro:generateContent", defaultModel: "gemini-1.0-pro", getRequestData: (model, promptContent) => ({ contents: [{ parts: [{ text: promptContent }] }], generationConfig: { temperature: 0.1, maxOutputTokens: 250, } }), parseResponse: (res) => { if (res.candidates && res.candidates.length > 0 && res.candidates[0].content && res.candidates[0].content.parts && res.candidates[0].content.parts.length > 0) { const fullText = res.candidates[0].content.parts[0].text.trim(); const cleanAnswer = fullText.replace(/<think>[\s\S]*?<\/think>\s*/gi, "").trim(); return { fullText, cleanAnswer }; } return null; }, parseError: (res) => res.error ? (res.error.message + (res.error.details ? ` Details: ${JSON.stringify(res.error.details)}` : '')) : null }
};

const config = { awaitTime: 2500, requestDelay: 3000, questionTypeMap: { '判断题': 'TrueFalse', '单选题': 'SingleChoice', '多选题': 'MultipleChoice', }, defaultQuestionTypeForAI: 'SingleChoice' };

// --- AI 调用核心函数 (与v2.9.1一致) ---
async function getAiAnswer(questionTypeKey, questionDescription, optionsText, questionIndex, retryCount = 0) { const selectedProviderKey = GM_getValue('selectedAiProvider', 'OPENAI'); const provider = AI_PROVIDERS[selectedProviderKey] || AI_PROVIDERS.OPENAI; let userAiUrl = GM_getValue(`aiApiUrl_${selectedProviderKey}`, provider.defaultUrl); const userAiKey = GM_getValue(`aiApiKey_${selectedProviderKey}`, ''); const userAiModel = GM_getValue(`aiModel_${selectedProviderKey}`, provider.defaultModel); if (!userAiUrl || (!userAiKey && provider !== AI_PROVIDERS.GEMINI) || (provider === AI_PROVIDERS.GEMINI && !userAiUrl.includes('key=') && !userAiKey) ) { const errorMsg = `AI配置错误: ${provider.name} API URL或Key未配置`; console.error(errorMsg); updateAiLog(questionIndex, errorMsg, "", {cleanAnswer: "AI配置错误", fullText: errorMsg}); return {cleanAnswer: "AI配置错误", fullText: errorMsg}; } if (provider === AI_PROVIDERS.GEMINI && userAiUrl.includes("{YOUR_API_KEY}") && userAiKey) { userAiUrl = userAiUrl.replace("{YOUR_API_KEY}", userAiKey); } else if (provider === AI_PROVIDERS.GEMINI && !userAiUrl.includes('key=') && userAiKey) { userAiUrl += (userAiUrl.includes('?') ? '&' : '?') + `key=${userAiKey}`; } let promptContent = `你是一个在线学习平台的答题助手。请根据以下题目信息,给出最准确的答案。\n\n题目类型:${questionTypeKey}\n题目描述:\n${questionDescription}\n`; if (optionsText && optionsText.trim() !== "") { promptContent += `\n选项:\n${optionsText}\n`; } promptContent += `\n请严格按照以下格式之一给出答案:\n- 对于单选题,请直接给出正确选项的字母(例如:A)。\n- 对于多选题,请直接给出所有正确选项的字母,用逗号分隔,无空格(例如:A,C,D)。\n- 对于判断题,请直接回答“正确”或“错误”。\n- 不要包含任何解释、题目复述或多余的文字,只需要答案本身。例如,如果答案是A,就只返回 "A"。如果是多选 A 和 C,就返回 "A,C"。\n`; if (retryCount > 0) { console.log(`[题目 ${questionIndex}] (${provider.name}) AI请求重试 #${retryCount}...\nPrompt:\n`, promptContent); updateAiLog(questionIndex, promptContent, `等待AI响应 (重试 ${retryCount}/${MAX_AI_RETRIES})...`, null); } else { console.log(`[题目 ${questionIndex}] (${provider.name}) Prompt:\n`, promptContent); updateAiLog(questionIndex, promptContent, "等待AI响应...", null); } const requestData = provider.getRequestData(userAiModel, promptContent); const headers = { "Content-Type": "application/json" }; if (provider !== AI_PROVIDERS.GEMINI) { headers["Authorization"] = `Bearer ${userAiKey}`; } return new Promise((resolve) => { GM_xmlhttpRequest({ method: "POST", url: userAiUrl, headers: headers, data: JSON.stringify(requestData), timeout: 25000, onload: function(response) { try { const res = JSON.parse(response.responseText); console.log(`[题目 ${questionIndex}] AI Raw Rsp:\n`, res); const parsed = provider.parseResponse(res); const errorMsgFromProvider = provider.parseError(res); if (parsed && parsed.cleanAnswer !== null) { const finalCleanAnswer = parsed.cleanAnswer.replace(/^["']/, '').replace(/["']$/, '').replace(/[。;,.]$/, '').trim(); updateAiLog(questionIndex, promptContent, JSON.stringify(res, null, 2), {cleanAnswer: finalCleanAnswer, fullText: parsed.fullText}); resolve({cleanAnswer: finalCleanAnswer, fullText: parsed.fullText}); } else if (errorMsgFromProvider) { const errorMsg = `AI API错误: ${errorMsgFromProvider}`; console.error(`[题目 ${questionIndex}] ${errorMsg}`); handleAiError(errorMsg, resolve, retryCount, questionIndex, promptContent, JSON.stringify(res, null, 2), { questionTypeKey, questionDescription, optionsText }); } else { const errorMsg = "AI未返回有效答案结构"; console.error(`[题目 ${questionIndex}] ${errorMsg}`); handleAiError(errorMsg, resolve, retryCount, questionIndex, promptContent, JSON.stringify(res, null, 2), { questionTypeKey, questionDescription, optionsText }); } } catch (e) { const errorMsg = `解析AI响应失败: ${e.message}`; console.error(`[题目 ${questionIndex}] ${errorMsg}`, response.responseText); handleAiError(errorMsg, resolve, retryCount, questionIndex, promptContent, response.responseText, { questionTypeKey, questionDescription, optionsText }); } }, onerror: function(error) { const errorMsg = `AI API请求错误: ${error.statusText || 'Network Error'}`; console.error(`[题目 ${questionIndex}] ${errorMsg}`, error); handleAiError(errorMsg, resolve, retryCount, questionIndex, promptContent, JSON.stringify(error, null, 2), { questionTypeKey, questionDescription, optionsText }); }, ontimeout: function() { const errorMsg = "AI API请求超时"; console.error(`[题目 ${questionIndex}] ${errorMsg}`); handleAiError(errorMsg, resolve, retryCount, questionIndex, promptContent, "请求超时", { questionTypeKey, questionDescription, optionsText }); } }); });}
async function handleAiError(errorMsg, resolve, currentRetryCount, questionIndex, promptContent, rawResponse, originalRequestArgs) { if (currentRetryCount < MAX_AI_RETRIES) { updateAiLog(questionIndex, promptContent, rawResponse, {cleanAnswer: `错误: ${errorMsg} (准备重试 ${currentRetryCount + 1})`, fullText: `错误: ${errorMsg} (准备重试 ${currentRetryCount + 1})`}); await sleep(AI_RETRY_DELAY); resolve(getAiAnswer(originalRequestArgs.questionTypeKey, originalRequestArgs.questionDescription, originalRequestArgs.optionsText, questionIndex, currentRetryCount + 1)); } else { updateAiLog(questionIndex, promptContent, rawResponse, {cleanAnswer: `错误: ${errorMsg} (已达最大重试次数)`, fullText: `错误: ${errorMsg} (已达最大重试次数)`}); resolve({cleanAnswer: `AI错误: ${errorMsg}`, fullText: `AI错误: ${errorMsg}`}); }}

// --- 更新AI日志到表格的详情区域 (与v2.9.1一致) ---
function updateAiLog(questionIndex, prompt, rawResponse, aiResult = null) { const logRow = document.querySelector(`#qrow-${questionIndex}-log`); if (logRow) { const promptCell = logRow.querySelector(".ai-prompt-cell"); const responseCell = logRow.querySelector(".ai-response-cell"); if (promptCell) { promptCell.textContent = prompt; } if (responseCell) { let displayText = `原始JSON响应:\n${rawResponse}\n\n`; if (aiResult && aiResult.fullText) { displayText += `AI完整回复 (含思考过程):\n${aiResult.fullText}\n\n`; displayText += `提取的纯净答案: ${aiResult.cleanAnswer !== null ? aiResult.cleanAnswer : "N/A"}`; } else if (aiResult && typeof aiResult === 'string') { displayText += `AI回复/错误: ${aiResult}`; } else { displayText += `AI未提供有效回复或提取结构。`; } responseCell.textContent = displayText; } } }

// --- 主答题逻辑 (与v2.9.1一致) ---
async function answerQuestionWithAI(questionTypeString, questionDescription, questionBody, questionIndex, isRetry = false) { if (!isRetry) { let questionTitle = questionDescription.trim().replace(/%C2%A0/g, '%20').replace(/\s+/g, ' '); appendToTable(questionTitle, "", questionIndex); } else { const answerCell = document.querySelector(`#ai-answer-${questionIndex}`); if (answerCell) answerCell.innerHTML = "正在重试AI..."; const retryButton = document.querySelector(`#retry-btn-${questionIndex}`); if (retryButton) retryButton.disabled = true; updateAiLog(questionIndex, "重试中...", "等待AI响应...", null); } const extractedTypeMatch = questionTypeString.match(/【(.+?)】/); const extractedType = extractedTypeMatch && extractedTypeMatch[1] ? extractedTypeMatch[1] : ''; const aiQuestionTypeKey = config.questionTypeMap[extractedType] || config.defaultQuestionTypeForAI; let optionsText = ""; const optionElements = questionBody.querySelectorAll(".subject_node .nodeLab .node_detail"); if (optionElements && optionElements.length > 0) { optionElements.forEach((opt, idx) => { const optionLabel = String.fromCharCode(65 + idx); let optText = opt.innerText.trim().replace(/^[A-Z][\s.、]*/, ''); if (aiQuestionTypeKey === 'TrueFalse') { optionsText += `${optText}\n`; } else { optionsText += `${optionLabel}. ${optText}\n`; } }); } else { console.warn(`[题目 ${questionIndex}] 未能使用选择器 ".subject_node .nodeLab .node_detail" 提取到选项元素。`); } optionsText = optionsText.trim(); console.log(`[题目 ${questionIndex}] (${isRetry ? '重试' : '首次'}): 类型=${extractedType}(${aiQuestionTypeKey}), 描述=${questionDescription.substring(0,30)}...`); if (!config.questionTypeMap[extractedType]) { const unsupportedMsg = "AI暂不支持此题型"; changeAnswerInTable(unsupportedMsg, questionIndex, false, `${unsupportedMsg},请手动完成。`, true); updateAiLog(questionIndex, "N/A (不支持的题型)", "N/A", {cleanAnswer: unsupportedMsg, fullText: unsupportedMsg}); return; } if (optionsText === "" && (aiQuestionTypeKey === 'SingleChoice' || aiQuestionTypeKey === 'MultipleChoice')) { console.warn(`[题目 ${questionIndex}] 选项提取为空,AI可能无法准确回答选择题。`); if (!isRetry) updateAiLog(questionIndex, "警告:选项提取为空。", "", {cleanAnswer: "警告", fullText: "警告:选项提取为空。AI将仅基于题干作答。"}); } const aiResult = await getAiAnswer(aiQuestionTypeKey, questionDescription, optionsText, questionIndex); const cleanAiAnswer = aiResult.cleanAnswer; console.log(`[题目 ${questionIndex}] AI最终提取答案: "${cleanAiAnswer}"`); let isSelected = false; let isError = cleanAiAnswer.startsWith("AI配置错误") || cleanAiAnswer.startsWith("AI错误") || cleanAiAnswer.startsWith("AI未返回") || cleanAiAnswer.startsWith("AI请求"); if (!isError) { isSelected = chooseAnswerByAI(aiQuestionTypeKey, questionBody, cleanAiAnswer); } changeAnswerInTable(cleanAiAnswer, questionIndex, isSelected, isSelected ? "" : (isError ? cleanAiAnswer : "AI答案可能未成功匹配选项或选择失败,请检查"), !isError || isSelected); if (!isRetry && !IS_PAUSED) { const nextButton = document.querySelector('.switch-btn-box > button:last-child'); if (nextButton && nextButton.innerText.includes('下一题')) { setTimeout(() => nextButton.click(), 500); } else { console.log("未找到明确的'下一题'按钮或已是最后一题。"); } } else if (isRetry) { const retryButton = document.querySelector(`#retry-btn-${questionIndex}`); if (retryButton) retryButton.disabled = false; } }

// --- 根据AI答案选择选项 (与v2.9.1一致) ---
function chooseAnswerByAI(aiQuestionTypeKey, questionBody, aiAnswerString) { let isSelectedSuccessfully = false; const cleanedAiAnswer = aiAnswerString.toUpperCase().replace(/\s+/g, ''); const clickableOptionElements = questionBody.querySelectorAll(".subject_node .nodeLab"); const optionDetailElements = questionBody.querySelectorAll(".subject_node .nodeLab .node_detail"); if (aiQuestionTypeKey === 'TrueFalse') { if (clickableOptionElements.length >= 1 && optionDetailElements.length >= 1) { let targetOptionIndex = -1; if (cleanedAiAnswer.includes("正确") || cleanedAiAnswer.includes("对") || cleanedAiAnswer.includes("T") || cleanedAiAnswer.includes("RIGHT")) { for(let i=0; i < optionDetailElements.length; i++){ const text = optionDetailElements[i].innerText.trim(); if(text.includes("正确") || text.includes("对")){ targetOptionIndex = i; break; } } if (targetOptionIndex === -1 && optionDetailElements.length > 0 && (optionDetailElements[0].innerText.trim().includes("正确") || optionDetailElements[0].innerText.trim().includes("对"))) targetOptionIndex = 0; else if (targetOptionIndex === -1) targetOptionIndex = 0; } else if (cleanedAiAnswer.includes("错误") || cleanedAiAnswer.includes("错") || cleanedAiAnswer.includes("F") || cleanedAiAnswer.includes("WRONG")) { for(let i=0; i < optionDetailElements.length; i++){ const text = optionDetailElements[i].innerText.trim(); if(text.includes("错误") || text.includes("错")){ targetOptionIndex = i; break; } } if (targetOptionIndex === -1 && optionDetailElements.length > 1 && (optionDetailElements[1].innerText.trim().includes("错误") || optionDetailElements[1].innerText.trim().includes("错"))) targetOptionIndex = 1; else if (targetOptionIndex === -1) targetOptionIndex = clickableOptionElements.length > 1 ? 1 : 0; } if(targetOptionIndex !== -1 && clickableOptionElements[targetOptionIndex]){ clickableOptionElements[targetOptionIndex].click(); if (clickableOptionElements[targetOptionIndex].querySelector('.node_detail')?.classList.contains('onChecked') || clickableOptionElements[targetOptionIndex].classList.contains('onChecked') || clickableOptionElements[targetOptionIndex].querySelector('input')?.checked) isSelectedSuccessfully = true; else { clickableOptionElements[targetOptionIndex].click(); if (clickableOptionElements[targetOptionIndex].querySelector('.node_detail')?.classList.contains('onChecked') || clickableOptionElements[targetOptionIndex].classList.contains('onChecked')) isSelectedSuccessfully = true; else console.warn(`[判断题 ${targetOptionIndex}] 点击后未能确认选中。`);} } } } else if (aiQuestionTypeKey === 'SingleChoice') { const targetOptionLetter = cleanedAiAnswer.charAt(0); if (targetOptionLetter >= 'A' && targetOptionLetter <= 'Z') { const optionIndex = targetOptionLetter.charCodeAt(0) - 'A'.charCodeAt(0); if (clickableOptionElements[optionIndex]) { clickableOptionElements[optionIndex].click(); if (clickableOptionElements[optionIndex].querySelector('.node_detail')?.classList.contains('onChecked') || clickableOptionElements[optionIndex].classList.contains('onChecked') || clickableOptionElements[optionIndex].querySelector('input')?.checked) isSelectedSuccessfully = true; else { clickableOptionElements[optionIndex].click(); if (clickableOptionElements[optionIndex].querySelector('.node_detail')?.classList.contains('onChecked') || clickableOptionElements[optionIndex].classList.contains('onChecked')) isSelectedSuccessfully = true; else console.warn(`[单选题 ${targetOptionLetter}] 点击后未能确认选中。`);} } } } else if (aiQuestionTypeKey === 'MultipleChoice') { const targetOptionsLetters = cleanedAiAnswer.split(',').map(s => s.trim()).filter(Boolean); let clickedCount = 0; targetOptionsLetters.forEach(letter => { if (letter >= 'A' && letter <= 'Z') { const optionIndex = letter.charCodeAt(0) - 'A'.charCodeAt(0); if (clickableOptionElements[optionIndex]) { clickableOptionElements[optionIndex].click(); clickedCount++; } } }); if (clickedCount === targetOptionsLetters.length && clickedCount > 0) isSelectedSuccessfully = true; else if (clickedCount > 0) { console.warn("多选题部分选项可能未匹配:", cleanedAiAnswer); isSelectedSuccessfully = true; } } return isSelectedSuccessfully; }

// --- UI 和辅助函数 ---
function truncateTitle(title) { if (title.length > 15) { return title.substring(0, 15) + '...'; } return title; } // 再短一点

function appendToTable(questionTitle, answerString, questionIndex) {
    const truncatedTitle = truncateTitle(questionTitle); const tableBody = document.querySelector("#ai-record-table tbody");
    if (tableBody) {
        const mainRow = document.createElement('tr'); mainRow.id = `qrow-${questionIndex}`;
        mainRow.innerHTML = `
            <td class="qa-actions"> ${questionIndex}
                <button class="btn btn-xs btn-default toggle-log-btn" data-q-idx="${questionIndex}" title="AI交互详情" style="padding:1px 3px; font-size:0.75em; margin-left:1px;">详</button>
                <button class="btn btn-xs btn-info copy-qa-btn" data-q-idx="${questionIndex}" title="复制题目和选项" style="padding:1px 3px; font-size:0.75em;">复</button>
            </td><td>${truncatedTitle}</td>
            <td id="ai-answer-${questionIndex}" style="min-width: 70px;">请求AI...</td>
            <td id="ai-action-${questionIndex}" style="width: 45px;"><button id="retry-btn-${questionIndex}" class="btn btn-xs btn-warning retry-btn" data-question-index="${questionIndex}" style="padding:1px 3px; font-size:0.75em; display:none;">重试</button></td>`;
        tableBody.appendChild(mainRow);
        const logRow = document.createElement('tr'); logRow.id = `qrow-${questionIndex}-log`; logRow.classList.add('ai-log-details'); logRow.style.display = 'none';
        logRow.innerHTML = `<td colspan="4" style="padding: 8px; background-color: #f9f9f9;"><div style="font-weight:bold; margin-bottom:5px;">AI交互详情 (题目 ${questionIndex}):</div><div style="margin-bottom:8px;"><strong style="color:#007bff;">Prompt:</strong><pre class="ai-prompt-cell" style="white-space: pre-wrap; word-break: break-all; max-height: 150px; overflow-y: auto; background-color: #eef; padding: 5px; border-radius: 3px; font-size:0.9em;"></pre></div><div><strong style="color:#28a745;">AI响应:</strong><pre class="ai-response-cell" style="white-space: pre-wrap; word-break: break-all; max-height: 250px; overflow-y: auto; background-color: #efe; padding: 5px; border-radius: 3px; font-size:0.9em;"></pre></div></td>`;
        tableBody.appendChild(logRow);
        mainRow.querySelector('.toggle-log-btn').addEventListener('click', function() { const logTargetId = `qrow-${this.dataset.qIdx}-log`; const targetRow = document.getElementById(logTargetId); if (targetRow) { targetRow.style.display = targetRow.style.display === 'none' ? 'table-row' : 'none'; this.textContent = targetRow.style.display === 'none' ? '详' : '收'; }}); // 改为单字
        mainRow.querySelector('.copy-qa-btn').addEventListener('click', function() { copyQuestionAndOptionsToClipboard(parseInt(this.dataset.qIdx)); });
        mainRow.querySelector('.retry-btn').addEventListener('click', function() { const qIndex = parseInt(this.getAttribute('data-question-index')); const questionData = CURRENT_PROCESSING_QUESTIONS.find(q => q.index === qIndex); if (questionData) { updateMsg(`正在重试第 ${qIndex} 题...`, "#007bff"); answerQuestionWithAI(questionData.type, questionData.description, questionData.body, qIndex, true); } });
    }
}

function copyQuestionAndOptionsToClipboard(questionIndex, all = false) {
    let textToCopy = "";
    if (all) {
        if (CURRENT_PROCESSING_QUESTIONS.length === 0) {
            GM_setClipboard("错误:没有题目信息可复制。"); updateMsg("复制失败:无题目信息。", "red"); return;
        }
        CURRENT_PROCESSING_QUESTIONS.forEach(qData => {
            textToCopy += `题目 ${qData.index}: ${qData.description.trim()}\n`;
            const optionElements = qData.body.querySelectorAll(".subject_node .nodeLab .node_detail");
            if (optionElements && optionElements.length > 0) {
                textToCopy += "选项:\n";
                optionElements.forEach((opt, idx) => {
                    const optionLabel = String.fromCharCode(65 + idx); const optText = opt.innerText.trim().replace(/^[A-Z][\s.、]*/, '');
                    const extractedTypeMatch = qData.type.match(/【(.+?)】/); const isTrueFalse = extractedTypeMatch && extractedTypeMatch[1] === '判断题';
                    if (isTrueFalse) { textToCopy += `${optText}\n`; } else { textToCopy += `${optionLabel}. ${optText}\n`; }
                });
            } else { textToCopy += "(无选项或选项提取失败)\n"; }
            textToCopy += "\n"; // 题目间空一行
        });
        GM_setClipboard(textToCopy.trim()); updateMsg(`全部 ${CURRENT_PROCESSING_QUESTIONS.length} 道题目及选项已复制!`, "green");
    } else {
        const questionData = CURRENT_PROCESSING_QUESTIONS.find(q => q.index === questionIndex);
        if (!questionData) { GM_setClipboard("错误:未找到题目信息。"); updateMsg("复制失败:未找到题目信息。", "red"); return; }
        textToCopy = `题目 ${questionIndex}: ${questionData.description.trim()}\n`;
        const optionElements = questionData.body.querySelectorAll(".subject_node .nodeLab .node_detail");
        if (optionElements && optionElements.length > 0) { textToCopy += "选项:\n"; optionElements.forEach((opt, idx) => { const optionLabel = String.fromCharCode(65 + idx); const optText = opt.innerText.trim().replace(/^[A-Z][\s.、]*/, ''); const extractedTypeMatch = questionData.type.match(/【(.+?)】/); const isTrueFalse = extractedTypeMatch && extractedTypeMatch[1] === '判断题'; if (isTrueFalse) { textToCopy += `${optText}\n`; } else { textToCopy += `${optionLabel}. ${optText}\n`; } }); }
        else { textToCopy += "(无选项或选项提取失败)\n"; }
        GM_setClipboard(textToCopy); updateMsg(`题目 ${questionIndex} 及选项已复制!`, "green");
    }
}

function changeAnswerInTable(answerString, questionIndex, isSelect, errorMessage, showRetry) { const answerCell = document.querySelector(`#ai-answer-${questionIndex}`); const retryButton = document.querySelector(`#retry-btn-${questionIndex}`); if (answerCell) { answerCell.innerHTML = answerString || "AI无回复"; if (errorMessage) { answerCell.insertAdjacentHTML('beforeend', `<div style="color:#ff8c00; font-size:0.85em; margin-top:2px;">${errorMessage}</div>`); } else if (!isSelect && answerString && !answerString.startsWith("AI")) { answerCell.insertAdjacentHTML('beforeend', `<div style="color:#dc3545; font-size:0.85em; margin-top:2px;">未匹配选项</div>`); } else if (isSelect){ answerCell.style.color = "#28a745"; answerCell.style.fontWeight = "bold"; } } if (retryButton) { retryButton.style.display = showRetry ? 'inline-block' : 'none'; if (!showRetry) retryButton.disabled = false; } }
function enableWebpackHook() { const originCall = Function.prototype.call; Function.prototype.call = function (...args) { const result = originCall.apply(this, args); if (args[2]?.default?.version === '2.5.2') { args[2]?.default?.mixin({ mounted: function () { if (this.$el && typeof this.$el === 'object') { this.$el['__Ivue__'] = this; } } }); } return result; }}
function makeElementDraggable(el) { el.style.position = 'fixed'; let shiftX, shiftY; const header = el.querySelector('.panel-heading'); if (!header) { console.warn("Draggable header not found for element:", el); return; } header.style.cursor = 'grab'; header.onmousedown = function(event) { if (event.target.closest('input, button, select, .panel-body-content, .table-panel-body, .ai-log-details pre, .window-controls button')) { return; } event.preventDefault(); header.style.cursor = 'grabbing'; shiftX = event.clientX - el.getBoundingClientRect().left; shiftY = event.clientY - el.getBoundingClientRect().top; el.style.zIndex = 100000; function moveAt(pageX, pageY) { let newLeft = pageX - shiftX; let newTop = pageY - shiftY; const rightEdge = window.innerWidth - el.offsetWidth; const bottomEdge = window.innerHeight - el.offsetHeight; if (newLeft < 0) newLeft = 0; if (newTop < 0) newTop = 0; if (newLeft > rightEdge) newLeft = rightEdge; if (newTop > bottomEdge) newTop = bottomEdge; el.style.left = newLeft + 'px'; el.style.top = newTop + 'px'; } function onMouseMove(event) { moveAt(event.pageX, event.pageY); } function onMouseUp() { header.style.cursor = 'grab'; document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); } document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); }; el.ondragstart = () => false; }
function updateMsg(msg, color = '#007bff') { const displayMsgEl = document.getElementById('ai-display-msg'); if (displayMsgEl) { displayMsgEl.innerText = msg; displayMsgEl.style.color = color; } }

// --- 脚本主逻辑 ---
unsafeWindow.onload = (() => (async () => {
    console.log("AI答题脚本初始化..."); GM_addStyle(GM_getResourceText("css")); GM_addStyle(` #ai-floating-window { box-shadow: 0 4px 12px rgba(0,0,0,0.15); border-radius: 8px; border: 1px solid #dee2e6; background-color: #fff;} #ai-floating-window .panel-heading { background-color: #007bff; color: white; border-top-left-radius: 7px; border-top-right-radius: 7px; padding: 8px 10px; cursor: grab; display: flex; justify-content: space-between; align-items: center; } #ai-floating-window .panel-title { font-size: 1.0em; font-weight: 600; margin:0; } #ai-floating-window .panel-body-content { padding: 10px; background-color: #f8f9fa; border-bottom: 1px solid #dee2e6; } #ai-floating-window label { font-size: 0.8em; margin-bottom: 1px; color: #495057; display: block;} #ai-floating-window .form-control, #ai-floating-window select { font-size: 0.85em; height: calc(1.4em + .5rem + 2px); padding: .2rem .4rem; margin-bottom: 6px; border-radius: 3px; width: 100%; box-sizing: border-box;} #ai-floating-window .btn { font-size: 0.85em; padding: 4px 7px; border-radius: 3px; margin-right: 4px;} #ai-display-msg { font-size: 0.8em; min-height: 16px; margin-top: 4px; font-weight: 500; } #ai-floating-window .table-panel-body { max-height: 150px; overflow-y: auto; padding: 0px; background-color: #ffffff; border-bottom-left-radius: 7px; border-bottom-right-radius: 7px;} #ai-record-table { font-size: 0.75em; margin-bottom: 0; table-layout: fixed; width:100%;} #ai-record-table th, #ai-record-table td { padding: 3px 5px; vertical-align: middle; word-break: break-word; } #ai-record-table th { background-color: #e9ecef; border-top: none !important; text-align: center;} #ai-record-table td.qa-actions { text-align: center; } #ai-record-table td:nth-child(2) { text-align: left; } #ai-record-table tr:last-child td { border-bottom: none; } .ai-log-details td { border-top: 1px dashed #ccc !important; } .toggle-log-btn, .copy-qa-btn, .retry-btn { vertical-align: middle; } #pause-resume-btn.paused { background-color: #28a745 !important; border-color: #28a745 !important; } .window-controls button { background: transparent; border: none; color: white; font-size: 1.1em; padding: 0 5px; line-height: 1; } `);
    let providerOptionsHTML = ""; for (const key in AI_PROVIDERS) { providerOptionsHTML += `<option value="${key}">${AI_PROVIDERS[key].name}</option>`; }
    const uiHTML = `
<div class="panel" id="ai-floating-window" style="position:fixed; left:15px; top:5%; width:320px; z-index:99999;">
  <div class="panel-heading">
    <h3 class="panel-title">🧠 AI答题助手 🧠</h3>
    <div class="window-controls">
        <button id="minimize-btn" title="最小化/恢复窗口">-</button>
    </div>
  </div>
  <div class="panel-content-wrapper"> <!-- 新增一个包装器用于隐藏/显示 -->
    <div class="panel-body-content">
        <div><label for="ai-provider-select">AI服务商:</label><select id="ai-provider-select" class="form-control">${providerOptionsHTML}</select></div>
        <div><label for="ai-api-url-input">API URL:</label><input id="ai-api-url-input" type="text" class="form-control" title="对应服务商的API接口地址"/></div>
        <div><label for="ai-model-input">模型名称:</label><input id="ai-model-input" type="text" class="form-control" title="自定义模型名称, 如gpt-4o"/></div>
        <div><label for="ai-api-key-input">API Key (当前服务商):</label><input id="ai-api-key-input" type="password" class="form-control" placeholder="输入API Key"/></div>
        <div style="display:flex; justify-content: space-around; align-items: center; margin-top:6px; flex-wrap: wrap;">
            <button id="save-ai-config-btn" class="btn btn-sm btn-info">保存配置</button>
            <button id="start-ai-autofill-btn" class="btn btn-sm btn-success">开始答题</button>
            <button id="docs-btn" class="btn btn-sm btn-secondary" title="查看使用说明">使用文档</button>
        </div>
        <div style="display:flex; justify-content: space-around; align-items: center; margin-top:4px; flex-wrap: wrap;">
            <button id="copy-all-qa-btn" class="btn btn-sm btn-primary" title="复制所有已处理的题目和选项" style="display:none;">复制全部</button>
            <button id="pause-resume-btn" class="btn btn-sm btn-warning" style="display:none;">暂停答题</button>
        </div>
        <div id="ai-display-msg" class="text-center">等待操作...</div>
    </div>
    <div class="table-panel-body"><table class="table table-bordered table-condensed" id="ai-record-table">
        <thead><tr><th style="width:22%;">#</th><th style="width:38%;">题目</th><th style="width:25%;">AI答案</th><th style="width:15%;">操作</th></tr></thead>
        <tbody></tbody>
    </table></div>
  </div>
</div>`;
    document.body.insertAdjacentHTML('beforeend', uiHTML); makeElementDraggable(document.getElementById('ai-floating-window'));
    const providerSelect = document.getElementById('ai-provider-select'); const apiUrlInput = document.getElementById('ai-api-url-input'); const apiKeyInput = document.getElementById('ai-api-key-input'); const modelInput = document.getElementById('ai-model-input'); const pauseResumeBtn = document.getElementById('pause-resume-btn'); const docsBtn = document.getElementById('docs-btn'); const minimizeBtn = document.getElementById('minimize-btn'); const panelContentWrapper = document.querySelector('#ai-floating-window .panel-content-wrapper'); const copyAllQaBtn = document.getElementById('copy-all-qa-btn');

    minimizeBtn.onclick = () => {
        IS_WINDOW_MINIMIZED = !IS_WINDOW_MINIMIZED;
        if (IS_WINDOW_MINIMIZED) {
            panelContentWrapper.style.display = 'none';
            minimizeBtn.textContent = '+'; // 或者用图标
            minimizeBtn.title = '恢复窗口';
        } else {
            panelContentWrapper.style.display = 'block';
            minimizeBtn.textContent = '-';
            minimizeBtn.title = '最小化窗口';
        }
    };

    copyAllQaBtn.onclick = () => { copyQuestionAndOptionsToClipboard(null, true); };

    function loadProviderConfig(providerKey) { const provider = AI_PROVIDERS[providerKey]; apiUrlInput.value = GM_getValue(`aiApiUrl_${providerKey}`, provider.defaultUrl); modelInput.value = GM_getValue(`aiModel_${providerKey}`, provider.defaultModel); modelInput.placeholder = `默认: ${provider.defaultModel}`; apiKeyInput.value = GM_getValue(`aiApiKey_${providerKey}`, ''); apiKeyInput.placeholder = `请输入 ${provider.name} 的 API Key`; }
    providerSelect.addEventListener('change', function() { GM_setValue('selectedAiProvider', this.value); loadProviderConfig(this.value); });
    const lastSelectedProvider = GM_getValue('selectedAiProvider', 'OPENAI'); providerSelect.value = lastSelectedProvider; loadProviderConfig(lastSelectedProvider);

    document.getElementById('save-ai-config-btn').onclick = () => { const selectedProviderKey = providerSelect.value; const provider = AI_PROVIDERS[selectedProviderKey]; GM_setValue(`aiApiUrl_${selectedProviderKey}`, apiUrlInput.value.trim()); GM_setValue(`aiApiKey_${selectedProviderKey}`, apiKeyInput.value.trim()); GM_setValue(`aiModel_${selectedProviderKey}`, modelInput.value.trim() || provider.defaultModel); GM_setValue('selectedAiProvider', selectedProviderKey); updateMsg(`${provider.name} 配置已保存!`, "#28a745"); };
    docsBtn.onclick = () => { window.open(DOCUMENTATION_URL, '_blank'); };
    pauseResumeBtn.onclick = () => { IS_PAUSED = !IS_PAUSED; if (IS_PAUSED) { pauseResumeBtn.textContent = "继续答题"; pauseResumeBtn.classList.add("paused"); updateMsg("答题已暂停。", "orange"); } else { pauseResumeBtn.textContent = "暂停答题"; pauseResumeBtn.classList.remove("paused"); updateMsg("答题已恢复,处理下一题...", "#007bff"); processNextQuestionFromQueue(); }};
    let questionQueue = []; let currentQuestionQueueIndex = 0;
    async function processNextQuestionFromQueue() { if (IS_PAUSED) return; if (currentQuestionQueueIndex < questionQueue.length) { const question = questionQueue[currentQuestionQueueIndex]; updateMsg(`处理中: ${question.index} / ${questionQueue.length} (总)`, "#007bff"); question.body.scrollIntoView({ behavior: 'smooth', block: 'center' }); await sleep(600); await answerQuestionWithAI(question.type, question.description, question.body, question.index); currentQuestionQueueIndex++; await sleep(config.requestDelay); processNextQuestionFromQueue(); } else { updateMsg(`所有 ${questionQueue.length} 题处理完毕!`, "#28a745"); document.getElementById('start-ai-autofill-btn').disabled = false; pauseResumeBtn.style.display = 'none'; copyAllQaBtn.style.display = 'inline-block'; const submitButton = document.querySelector('button.btn-submit, button.btn-handExam, input[type="button"][value="交卷"], a[onclick*="submitExam"], div.submit-btn'); if (submitButton) { alert("AI答题完成!请仔细检查答案后手动点击“交卷”或“提交”按钮。"); submitButton.style.outline = "3px solid red"; submitButton.style.transform = "scale(1.1)"; submitButton.scrollIntoView({ behavior: 'smooth', block: 'center' }); } else { alert("AI答题完成!未找到明确的交卷按钮,请手动操作。"); } } }
    document.getElementById('start-ai-autofill-btn').onclick = async () => { const selectedProviderKey = providerSelect.value; const currentApiUrl = GM_getValue(`aiApiUrl_${selectedProviderKey}`); const currentApiKey = GM_getValue(`aiApiKey_${selectedProviderKey}`); const provider = AI_PROVIDERS[selectedProviderKey]; let apiKeyNeeded = true; if (provider === AI_PROVIDERS.GEMINI && currentApiUrl.toLowerCase().includes('key=')) { apiKeyNeeded = false; } if (!currentApiUrl || (apiKeyNeeded && !currentApiKey)) { updateMsg(`请先为 ${provider.name} 配置API URL${apiKeyNeeded ? '和Key':''}并保存!`, "#dc3545"); return; } updateMsg("开始AI自动答题...", "#007bff"); document.getElementById('start-ai-autofill-btn').disabled = true; pauseResumeBtn.style.display = 'inline-block'; pauseResumeBtn.textContent = "暂停答题"; pauseResumeBtn.classList.remove("paused"); IS_PAUSED = false; copyAllQaBtn.style.display = 'none'; document.querySelector("#ai-record-table tbody").innerHTML = ""; await sleep(config.awaitTime); const questionBodyAll = document.querySelectorAll(".examPaper_subject"); if (questionBodyAll.length === 0) { updateMsg("未检测到题目。", "#dc3545"); document.getElementById('start-ai-autofill-btn').disabled = false; pauseResumeBtn.style.display = 'none'; return; } CURRENT_PROCESSING_QUESTIONS = []; questionQueue = []; currentQuestionQueueIndex = 0;
        questionBodyAll.forEach((questionBody, index) => { const subjectNumElement = questionBody.querySelector(".subject_num.fl"); const questionNumberText = subjectNumElement ? subjectNumElement.textContent.trim() : `${index + 1}`; const questionTypeElement = questionBody.querySelector(".subject_type_annex") || questionBody.querySelector(".subject_type span:first-child"); const questionTypeStringFromDOM = questionTypeElement ? questionTypeElement.textContent.trim() : '【未知题型】'; let questionDescription = ''; let smallStemPElement = questionBody.querySelector(".smallStem_describe p") || questionBody.querySelector(".smallStem_describe"); if (smallStemPElement && typeof smallStemPElement.textContent === 'string') { questionDescription = smallStemPElement.textContent.trim(); } if (!questionDescription) { const descriptionDivs = questionBody.querySelectorAll(".subject_describe div"); let foundViaIvue = false; for (const div of descriptionDivs) { if (div.__Ivue__ && div.__Ivue__._data && typeof div.__Ivue__._data.shadowDom?.textContent === 'string' && div.__Ivue__._data.shadowDom.textContent.trim() !== "") { questionDescription = div.__Ivue__._data.shadowDom.textContent.trim(); foundViaIvue = true; break; } } if (!foundViaIvue && descriptionDivs.length > 0 && typeof descriptionDivs[0].textContent === 'string') { questionDescription = descriptionDivs[0].textContent.trim(); } } if (!questionDescription) { const subjectDescribeElement = questionBody.querySelector(".subject_describe"); if (subjectDescribeElement && typeof subjectDescribeElement.innerText === 'string') { questionDescription = subjectDescribeElement.innerText.trim(); }} if (questionDescription) { questionDescription = questionDescription.replace(/^\d+\s*[\.、.]\s*/, '').trim(); if (questionTypeStringFromDOM && questionDescription.startsWith(questionTypeStringFromDOM)) { questionDescription = questionDescription.substring(questionTypeStringFromDOM.length).trim(); } const typeTextOnly = questionTypeStringFromDOM.replace(/[【】]/g, ""); if (typeTextOnly && questionDescription.startsWith(typeTextOnly)) { if (questionDescription.length > typeTextOnly.length && (questionDescription[typeTextOnly.length] === ' ' || !isNaN(parseInt(questionDescription[typeTextOnly.length])) ) ) { questionDescription = questionDescription.substring(typeTextOnly.length).trim(); } } questionDescription = questionDescription.replace(/^【.*?】\s*/, '').trim().replace(/^(题目|题干)[::\s]*/, '').trim(); }
            if (questionDescription) { const questionData = { index: index + 1, type: questionTypeStringFromDOM, description: questionDescription, body: questionBody }; CURRENT_PROCESSING_QUESTIONS.push(questionData); questionQueue.push(questionData); }
            else { console.warn(`[题目 ${index + 1}] 描述提取失败。`); updateMsg(`警告: 第 ${index + 1} 题目描述提取失败。`, "#ffc107"); }
        });
        if (questionQueue.length === 0) { updateMsg("未能成功提取任何题目信息。", "#dc3545"); document.getElementById('start-ai-autofill-btn').disabled = false; pauseResumeBtn.style.display = 'none'; return; }
        updateMsg(`共 ${questionQueue.length} 题,开始处理...`, "#007bff"); processNextQuestionFromQueue();
    };
}))();