// ==UserScript==
// @name ChatGPT学习通作业考试助手
// @version 3.0.2
// @description 本脚本【采用ChatGPT,DeepSeek,Gemini等顶尖AI直接生成答案】 【🥇操作简单】ChatGPT学习通助手,安装即可使用;推荐使用作业、考试自动答题【✨版本特性】版本特性:无题库,全采用AI回复答案,原作者:Ne-21
// @match *://*.chaoxing.com/*
// @match *://*.edu.cn/*
// @tag 免费试用
// @connect 911285.xyz
// @connect gptjs.808860.xyz
// @connect 127.0.0.1
// @run-at document-end
// @grant unsafeWindow
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_info
// @grant GM_getResourceText
// @require https://gptjs.808860.xyz/libs/TyprMd5.js
// @require https://gptjs.808860.xyz/libs/sweetalert2-11.1.0.all.min.js
// @require https://gptjs.808860.xyz/libs/jquery-3.7.1.min.js
// @resource Table https://gptjs.808860.xyz/libs/table.json
// @icon data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDEiIGhlaWdodD0iNDEiIHZpZXdCb3g9IjAgMCA0MSA0MSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBzdHJva2Utd2lkdGg9IjEuNSIgY2xhc3M9ImgtNiB3LTYiIHJvbGU9ImltZyI+PHRpdGxlPkNoYXRHUFQ8L3RpdGxlPjx0ZXh0IHg9Ii05OTk5IiB5PSItOTk5OSI+Q2hhdEdQVDwvdGV4dD48cGF0aCBkPSJNMzcuNTMyNCAxNi44NzA3QzM3Ljk4MDggMTUuNTI0MSAzOC4xMzYzIDE0LjA5NzQgMzcuOTg4NiAxMi42ODU5QzM3Ljg0MDkgMTEuMjc0NCAzNy4zOTM0IDkuOTEwNzYgMzYuNjc2IDguNjg2MjJDMzUuNjEyNiA2LjgzNDA0IDMzLjk4ODIgNS4zNjc2IDMyLjAzNzMgNC40OTg1QzMwLjA4NjQgMy42Mjk0MSAyNy45MDk4IDMuNDAyNTkgMjUuODIxNSAzLjg1MDc4QzI0Ljg3OTYgMi43ODkzIDIzLjcyMTkgMS45NDEyNSAyMi40MjU3IDEuMzYzNDFDMjEuMTI5NSAwLjc4NTU3NSAxOS43MjQ5IDAuNDkxMjY5IDE4LjMwNTggMC41MDAxOTdDMTYuMTcwOCAwLjQ5NTA0NCAxNC4wODkzIDEuMTY4MDMgMTIuMzYxNCAyLjQyMjE0QzEwLjYzMzUgMy42NzYyNCA5LjM0ODUzIDUuNDQ2NjYgOC42OTE3IDcuNDc4MTVDNy4zMDA4NSA3Ljc2Mjg2IDUuOTg2ODYgOC4zNDE0IDQuODM3NyA5LjE3NTA1QzMuNjg4NTQgMTAuMDA4NyAyLjczMDczIDExLjA3ODIgMi4wMjgzOSAxMi4zMTJDMC45NTY0NjQgMTQuMTU5MSAwLjQ5ODkwNSAxNi4yOTg4IDAuNzIxNjk4IDE4LjQyMjhDMC45NDQ0OTIgMjAuNTQ2NyAxLjgzNjEyIDIyLjU0NDkgMy4yNjggMjQuMTI5M0MyLjgxOTY2IDI1LjQ3NTkgMi42NjQxMyAyNi45MDI2IDIuODExODIgMjguMzE0MUMyLjk1OTUxIDI5LjcyNTYgMy40MDcwMSAzMS4wODkyIDQuMTI0MzcgMzIuMzEzOEM1LjE4NzkxIDM0LjE2NTkgNi44MTIzIDM1LjYzMjIgOC43NjMyMSAzNi41MDEzQzEwLjcxNDEgMzcuMzcwNCAxMi44OTA3IDM3LjU5NzMgMTQuOTc4OSAzNy4xNDkyQzE1LjkyMDggMzguMjEwNyAxNy4wNzg2IDM5LjA1ODcgMTguMzc0NyAzOS42MzY2QzE5LjY3MDkgNDAuMjE0NCAyMS4wNzU1IDQwLjUwODcgMjIuNDk0NiA0MC40OTk4QzI0LjYzMDcgNDAuNTA1NCAyNi43MTMzIDM5LjgzMjEgMjguNDQxOCAzOC41NzcyQzMwLjE3MDQgMzcuMzIyMyAzMS40NTU2IDM1LjU1MDYgMzIuMTExOSAzMy41MTc5QzMzLjUwMjcgMzMuMjMzMiAzNC44MTY3IDMyLjY1NDcgMzUuOTY1OSAzMS44MjFDMzcuMTE1IDMwLjk4NzQgMzguMDcyOCAyOS45MTc4IDM4Ljc3NTIgMjguNjg0QzM5Ljg0NTggMjYuODM3MSA0MC4zMDIzIDI0LjY5NzkgNDAuMDc4OSAyMi41NzQ4QzM5Ljg1NTYgMjAuNDUxNyAzOC45NjM5IDE4LjQ1NDQgMzcuNTMyNCAxNi44NzA3Wk0yMi40OTc4IDM3Ljg4NDlDMjAuNzQ0MyAzNy44ODc0IDE5LjA0NTkgMzcuMjczMyAxNy42OTk0IDM2LjE1MDFDMTcuNzYwMSAzNi4xMTcgMTcuODY2NiAzNi4wNTg2IDE3LjkzNiAzNi4wMTYxTDI1LjkwMDQgMzEuNDE1NkMyNi4xMDAzIDMxLjMwMTkgMjYuMjY2MyAzMS4xMzcgMjYuMzgxMyAzMC45Mzc4QzI2LjQ5NjQgMzAuNzM4NiAyNi41NTYzIDMwLjUxMjQgMjYuNTU0OSAzMC4yODI1VjE5LjA1NDJMMjkuOTIxMyAyMC45OThDMjkuOTM4OSAyMS4wMDY4IDI5Ljk1NDEgMjEuMDE5OCAyOS45NjU2IDIxLjAzNTlDMjkuOTc3IDIxLjA1MiAyOS45ODQyIDIxLjA3MDcgMjkuOTg2NyAyMS4wOTAyVjMwLjM4ODlDMjkuOTg0MiAzMi4zNzUgMjkuMTk0NiAzNC4yNzkxIDI3Ljc5MDkgMzUuNjg0MUMyNi4zODcyIDM3LjA4OTIgMjQuNDgzOCAzNy44ODA2IDIyLjQ5NzggMzcuODg0OVpNNi4zOTIyNyAzMS4wMDY0QzUuNTEzOTcgMjkuNDg4OCA1LjE5NzQyIDI3LjcxMDcgNS40OTgwNCAyNS45ODMyQzUuNTU3MTggMjYuMDE4NyA1LjY2MDQ4IDI2LjA4MTggNS43MzQ2MSAyNi4xMjQ0TDEzLjY5OSAzMC43MjQ4QzEzLjg5NzUgMzAuODQwOCAxNC4xMjMzIDMwLjkwMiAxNC4zNTMyIDMwLjkwMkMxNC41ODMgMzAuOTAyIDE0LjgwODggMzAuODQwOCAxNS4wMDczIDMwLjcyNDhMMjQuNzMxIDI1LjExMDNWMjguOTk3OUMyNC43MzIxIDI5LjAxNzcgMjQuNzI4MyAyOS4wMzc2IDI0LjcxOTkgMjkuMDU1NkMyNC43MTE1IDI5LjA3MzYgMjQuNjk4OCAyOS4wODkzIDI0LjY4MjkgMjkuMTAxMkwxNi42MzE3IDMzLjc0OTdDMTQuOTA5NiAzNC43NDE2IDEyLjg2NDMgMzUuMDA5NyAxMC45NDQ3IDM0LjQ5NTRDOS4wMjUwNiAzMy45ODExIDcuMzg3ODUgMzIuNzI2MyA2LjM5MjI3IDMxLjAwNjRaTTQuMjk3MDcgMTMuNjE5NEM1LjE3MTU2IDEyLjA5OTggNi41NTI3OSAxMC45MzY0IDguMTk4ODUgMTAuMzMyN0M4LjE5ODg1IDEwLjQwMTMgOC4xOTQ5MSAxMC41MjI4IDguMTk0OTEgMTAuNjA3MVYxOS44MDhDOC4xOTM1MSAyMC4wMzc4IDguMjUzMzQgMjAuMjYzOCA4LjM2ODIzIDIwLjQ2MjlDOC40ODMxMiAyMC42NjE5IDguNjQ4OTMgMjAuODI2NyA4Ljg0ODYzIDIwLjk0MDRMMTguNTcyMyAyNi41NTQyTDE1LjIwNiAyOC40OTc5QzE1LjE4OTQgMjguNTA4OSAxNS4xNzAzIDI4LjUxNTUgMTUuMTUwNSAyOC41MTczQzE1LjEzMDcgMjguNTE5MSAxNS4xMTA3IDI4LjUxNiAxNS4wOTI0IDI4LjUwODJMNy4wNDA0NiAyMy44NTU3QzUuMzIxMzUgMjIuODYwMSA0LjA2NzE2IDIxLjIyMzUgMy41NTI4OSAxOS4zMDQ2QzMuMDM4NjIgMTcuMzg1OCAzLjMwNjI0IDE1LjM0MTMgNC4yOTcwNyAxMy42MTk0Wk0zMS45NTUgMjAuMDU1NkwyMi4yMzEyIDE0LjQ0MTFMMjUuNTk3NiAxMi40OTgxQzI1LjYxNDIgMTIuNDg3MiAyNS42MzMzIDEyLjQ4MDUgMjUuNjUzMSAxMi40Nzg3QzI1LjY3MjkgMTIuNDc2OSAyNS42OTI4IDEyLjQ4MDEgMjUuNzExMSAxMi40ODc5TDMzLjc2MzEgMTcuMTM2NEMzNC45OTY3IDE3Ljg0OSAzNi4wMDE3IDE4Ljg5ODIgMzYuNjYwNiAyMC4xNjEzQzM3LjMxOTQgMjEuNDI0NCAzNy42MDQ3IDIyLjg0OSAzNy40ODMyIDI0LjI2ODRDMzcuMzYxNyAyNS42ODc4IDM2LjgzODIgMjcuMDQzMiAzNS45NzQzIDI4LjE3NTlDMzUuMTEwMyAyOS4zMDg2IDMzLjk0MTUgMzAuMTcxNyAzMi42MDQ3IDMwLjY2NDFDMzIuNjA0NyAzMC41OTQ3IDMyLjYwNDcgMzAuNDczMyAzMi42MDQ3IDMwLjM4ODlWMjEuMTg4QzMyLjYwNjYgMjAuOTU4NiAzMi41NDc0IDIwLjczMjggMzIuNDMzMiAyMC41MzM4QzMyLjMxOSAyMC4zMzQ4IDMyLjE1NCAyMC4xNjk4IDMxLjk1NSAyMC4wNTU2Wk0zNS4zMDU1IDE1LjAxMjhDMzUuMjQ2NCAxNC45NzY1IDM1LjE0MzEgMTQuOTE0MiAzNS4wNjkgMTQuODcxN0wyNy4xMDQ1IDEwLjI3MTJDMjYuOTA2IDEwLjE1NTQgMjYuNjgwMyAxMC4wOTQzIDI2LjQ1MDQgMTAuMDk0M0MyNi4yMjA2IDEwLjA5NDMgMjUuOTk0OCAxMC4xNTU0IDI1Ljc5NjMgMTAuMjcxMkwxNi4wNzI2IDE1Ljg4NThWMTEuOTk4MkMxNi4wNzE1IDExLjk3ODMgMTYuMDc1MyAxMS45NTg1IDE2LjA4MzcgMTEuOTQwNUMxNi4wOTIxIDExLjkyMjUgMTYuMTA0OCAxMS45MDY4IDE2LjEyMDcgMTEuODk0OUwyNC4xNzE5IDcuMjUwMjVDMjUuNDA1MyA2LjUzOTAzIDI2LjgxNTggNi4xOTM3NiAyOC4yMzgzIDYuMjU0ODJDMjkuNjYwOCA2LjMxNTg5IDMxLjAzNjQgNi43ODA3NyAzMi4yMDQ0IDcuNTk1MDhDMzMuMzcyMyA4LjQwOTM5IDM0LjI4NDIgOS41Mzk0NSAzNC44MzM0IDEwLjg1MzFDMzUuMzgyNiAxMi4xNjY3IDM1LjU0NjQgMTMuNjA5NSAzNS4zMDU1IDE1LjAxMjhaTTE0LjI0MjQgMjEuOTQxOUwxMC44NzUyIDE5Ljk5ODFDMTAuODU3NiAxOS45ODkzIDEwLjg0MjMgMTkuOTc2MyAxMC44MzA5IDE5Ljk2MDJDMTAuODE5NSAxOS45NDQxIDEwLjgxMjIgMTkuOTI1NCAxMC44MDk4IDE5LjkwNThWMTAuNjA3MUMxMC44MTA3IDkuMTgyOTUgMTEuMjE3MyA3Ljc4ODQ4IDExLjk4MTkgNi41ODY5NkMxMi43NDY2IDUuMzg1NDQgMTMuODM3NyA0LjQyNjU5IDE1LjEyNzUgMy44MjI2NEMxNi40MTczIDMuMjE4NjkgMTcuODUyNCAyLjk5NDY0IDE5LjI2NDkgMy4xNzY3QzIwLjY3NzUgMy4zNTg3NiAyMi4wMDg5IDMuOTM5NDEgMjMuMTAzNCA0Ljg1MDY3QzIzLjA0MjcgNC44ODM3OSAyMi45MzcgNC45NDIxNSAyMi44NjY4IDQuOTg0NzNMMTQuOTAyNCA5LjU4NTE3QzE0LjcwMjUgOS42OTg3OCAxNC41MzY2IDkuODYzNTYgMTQuNDIxNSAxMC4wNjI2QzE0LjMwNjUgMTAuMjYxNiAxNC4yNDY2IDEwLjQ4NzcgMTQuMjQ3OSAxMC43MTc1TDE0LjI0MjQgMjEuOTQxOVpNMTYuMDcxIDE3Ljk5OTFMMjAuNDAxOCAxNS40OTc4TDI0LjczMjUgMTcuOTk3NVYyMi45OTg1TDIwLjQwMTggMjUuNDk4M0wxNi4wNzEgMjIuOTk4NVYxNy45OTkxWiIgZmlsbD0iY3VycmVudENvbG9yIj48L3BhdGg+PC9zdmc+
// @homepage https://scriptcat.org/script-show-page/1027
// ==/UserScript==
/*********************************自定义配置区******************************************************** */
var setting = {
showBox: 1, // 显示脚本浮窗,0为关闭,1为开启,不建议关闭
maskImg: 1, // 显示皮卡丘,0为关闭,1为开启,默认开启,无实质作用,只是为了减少睿智问题
task: 0, // 只处理任务点任务,0为关闭,1为开启
video: 1, // 处理视频,0为关闭,1为开启
audio: 1, // 处理音频,0为关闭,1为开启
rate: 1, // 视频/音频倍速,默认 1(正常),可在浮窗设置中调整(1/1.25/1.5/2)
review: 0, // 复习模式,0为关闭,1为开启可以补挂视频时长
work: 1, // 测验自动处理,0为关闭,1为开启,开启将会处理测验,关闭会跳过测验
time: 2500, // 答题时间间隔,默认5s=5000
sub: 0, // 测验自动提交,0为关闭,1为开启,当没答案时测验将不会提交,如需提交请设置force:1
force: 0, // 测验强制提交,0为关闭,1为开启,开启此功能将会强制提交测验(无论作答与否)
decrypt: 1, // 字体解密,0为关闭,1为开启,推荐开启,方法来自wyn665817大佬
redo: 0, // 重做模式,0为关闭,1为开启,开启后不跳过已答题,重新AI作答覆盖旧答案
fuzzyMatch: 1, // 相似度匹配,0为关闭,1为开启,开启后当精确匹配失败时使用相似度匹配选择最接近的选项
examTurn: 0, // 考试自动跳转下一题,0为关闭,1为开启
examTurnTime: 0, // 考试自动跳转下一题随机间隔时间(3-7s)之间,0为关闭,1为开启
goodStudent: 1, // 好学生模式,不自动选择答案,仅将单选题和多选题的ABCD加粗
alterTitle: 1, //修改题目,将AI回复的答案插入题目中,不建议关闭,AI回复不能完全匹配答案,题目显示答案供手动选择
autoLogin: 0, // 自动登录,0为关闭,1为开启,开启此功能请配置登陆配置项
phone: '', // 登录配置项:登录手机号/超星号
password: '' // 登录配置项:登录密码
}
/************************************************************************************************** */
/*
_ _ _ _ ___ __
/\ | | | | | \ | | |__ \/_ |
/ \ _ _ | |_ | |__ ___ _ __ | \| | ___ ______ ) || |
/ /\ \ | | | || __|| '_ \ / _ \ | '__| | . ` | / _ \|______|/ / | |
/ ____ \| |_| || |_ | | | || (_) || | | |\ || __/ / /_ | |
/_/ \_\\__,_| \__||_| |_| \___/ |_| |_| \_| \___| |____||_|
TG:https://t.me/+REKHQoWVJh45MDg1 Date:20240914
*/
/************************************************************************************************** */
var _w = unsafeWindow,
_l = location,
_d = _w.document,
$ = _w.jQuery || top.jQuery,
md5 = md5 || window.md5,
UE = _w.UE,
Swal = Swal || window.Swal;
// 多域名候选及自动测速选择
var HOST_CANDIDATES = [
"https://911285.xyz",
"https://gptjs.808860.xyz"
];
// 读取缓存的域名与时间戳
var _cachedHost = localStorage.getItem('GPTJsSetting.hostSelected');
var _cachedAt = parseInt(localStorage.getItem('GPTJsSetting.hostSelectedAt') || '0');
// 初始 _host:优先使用缓存,否则使用第一个候选
var _host = _cachedHost || HOST_CANDIDATES[0];
function requestAuth(host) {
return new Promise(function (resolve, reject) {
var startAt = Date.now();
var _u = getCk('_uid') || getCk('UID');
try {
GM_xmlhttpRequest({
method: 'GET',
url: host + '/api/v1/auth?uid=' + _u + '&v=' + GM_info['script']['version'],
timeout: 5000,
onload: function (xhr) {
if (xhr.status == 200) {
resolve({ host: host, ms: Date.now() - startAt, response: xhr.responseText });
} else {
reject(new Error('status error'));
}
},
onerror: function () { reject(new Error('error')); },
ontimeout: function () { reject(new Error('timeout')); }
});
} catch (e) {
reject(e);
}
});
}
function findFastest() {
return new Promise(function (resolve, reject) {
var hasResolved = false;
var errCount = 0;
HOST_CANDIDATES.forEach(function (h) {
requestAuth(h).then(function (res) {
if (!hasResolved) {
hasResolved = true;
_host = res.host;
localStorage.setItem('GPTJsSetting.hostSelected', _host);
localStorage.setItem('GPTJsSetting.hostSelectedAt', String(Date.now()));
try { console.log('[GPTJs] 切换到更快域名:', _host, '延迟', res.ms + 'ms'); } catch (_) { /* empty */ }
resolve(res);
}
}).catch(function () {
errCount++;
if (errCount == HOST_CANDIDATES.length && !hasResolved) reject();
});
});
});
}
function initFastestHost() {
var twoHours = 2 * 60 * 60 * 1000;
if (_cachedAt && (Date.now() - _cachedAt) < twoHours) {
return requestAuth(_host).catch(function () { return findFastest(); });
} else {
return findFastest();
}
}
var _authPromise = initFastestHost();
var _mlist, _defaults, _domList, $subBtn, $saveBtn, $frame_c, $okBtn;
$('.navshow').find('a:contains(体验新版)')[0] ? $('.navshow').find('a:contains(体验新版)')[0].click() : '';
setting.decrypt ? decryptFont() : '';
function waitForJQueryElement(selector) {
return new Promise(function (resolve) {
var interval = setInterval(function () {
if ($(selector).length > 0) {
clearInterval(interval);
resolve();
}
}, 500);
});
}
if (_l.hostname == 'i.mooc.chaoxing.com' || _l.hostname == "i.chaoxing.com") {
//
} else if (_l.pathname == '/login' && setting.autoLogin) {
showBox()
waitForJQueryElement('#phone').then(function () { autoLogin() });
} else if (_l.pathname.includes('/mycourse/studentstudy')) {
showBox()
$('#ne-21log', window.parent.document).html('初始化完毕!')
} else if (_l.pathname.includes('/knowledge/cards')) {
var params = getTaskParams()
var parsedParams = null;
if (params && params !== '$mArg') {
try { parsedParams = $.parseJSON(params); } catch (e) { parsedParams = null; }
}
if (!parsedParams || !parsedParams['attachments'] || parsedParams['attachments'].length <= 0) {
logger('无任务点可处理,即将跳转页面', 'red')
toNext()
} else {
waitForJQueryElement('.wrap .ans-cc .ans-attach-ct').then(function () {
top.checkJob ? top.checkJob = () => false : true
_domList = []
_mlist = parsedParams['attachments']
_defaults = parsedParams['defaults']
$.each($('.wrap .ans-cc .ans-attach-ct'), (i, t) => {
_domList.push($(t).find('iframe'))
})
missonStart()
});
}
} else if (_l.pathname.includes('/exam/test/reVersionTestStartNew')) {
showBox()
waitForJQueryElement('.mark_table .whiteDiv').then(function () { missonExam() });
} else if (_l.pathname.includes('/mooc2/exam/preview')) {
showBox()
waitForJQueryElement('.mark_table .questionLi').then(function () { missonExamPreview() });
} else if (_l.pathname.includes('/mooc2/work/dowork')) {
showBox()
waitForJQueryElement('.mark_table form').then(function () { missonHomeWork() });
} else if (_l.pathname.includes('/work/phone/doHomeWork')) {
var _oldal = _w.alert
_w.alert = function (msg) {
if (msg == '保存成功') {
return;
}
return _oldal(msg)
}
var _oldcf = _w.confirm
_w.confirm = function (msg) {
if (msg.includes('确认提交') || msg.includes('未做完')) {
return true
}
return _oldcf(msg)
}
} else if (_l.pathname.includes('/mooc2/exam/exam-list')) {
// Swal.fire('ChatGPT学习通助手提示', '注意:请谨慎使用脚本考试,开始考试之前请确保该账号已激活脚本。', 'info')
} else if (_l.pathname == '/mycourse/stu') {
checkBrowser()
} else {
// console.log(_l.pathname)
}
function checkBrowser() {
var userAgent = navigator.userAgent
if (userAgent.indexOf('Chrome') == -1 || GM_info.scriptHandler != 'ScriptCat') {
// 非推荐环境,但不弹出警告
// Swal.fire('您使用的不是推荐运行环境(edge、谷歌浏览器+ScriptCat),脚本运行可能会发生问题.')
}
}
function parseUrlParams() {
let query = window.location.search.substring(1);
let vars = query.split("&");
let _p = {}
for (let i = 0; i < vars.length; i++) {
let pair = vars[i].split("=");
_p[pair[0]] = pair[1]
}
return _p
}
function updateLocalStorage(event) {
var checkbox = event.target;
localStorage.setItem(checkbox.id, checkbox.checked);
}
// 判断是否开启重做模式(不跳过已答题)
function isRedoMode() {
var stored = localStorage.getItem('GPTJsSetting.redo');
if (stored !== null) return stored === 'true';
return !!setting.redo;
}
// 判断是否开启相似度匹配
function isFuzzyMatchEnabled() {
var stored = localStorage.getItem('GPTJsSetting.fuzzyMatch');
if (stored !== null) return stored === 'true';
return !!setting.fuzzyMatch;
}
// 计算两个字符串的相似度(基于Levenshtein距离),返回 0~1 之间的值,1表示完全相同
function stringSimilarity(s1, s2) {
if (!s1 && !s2) return 1;
if (!s1 || !s2) return 0;
// 统一小写、去除首尾空白
s1 = s1.toLowerCase().trim();
s2 = s2.toLowerCase().trim();
if (s1 === s2) return 1;
var len1 = s1.length, len2 = s2.length;
if (len1 === 0 || len2 === 0) return 0;
// Levenshtein距离 - 空间优化版
var prev = [], curr = [];
for (var j = 0; j <= len2; j++) prev[j] = j;
for (var i = 1; i <= len1; i++) {
curr[0] = i;
for (var j = 1; j <= len2; j++) {
if (s1[i - 1] === s2[j - 1]) {
curr[j] = prev[j - 1];
} else {
curr[j] = 1 + Math.min(prev[j], curr[j - 1], prev[j - 1]);
}
}
var tmp = prev; prev = curr; curr = tmp;
}
var maxLen = Math.max(len1, len2);
return 1 - prev[len2] / maxLen;
}
// 在选项数组中查找与AI回答最相似的选项,返回索引;相似度低于阈值时返回-1
// threshold 默认 0.5(50%相似度)
function findBestFuzzyMatch(optionTexts, aiAnswer, threshold) {
if (!isFuzzyMatchEnabled()) return -1;
if (!aiAnswer || !optionTexts || optionTexts.length === 0) return -1;
threshold = (threshold !== undefined) ? threshold : 0.5;
var bestIndex = -1, bestScore = 0;
for (var i = 0; i < optionTexts.length; i++) {
var score = stringSimilarity(optionTexts[i], aiAnswer);
if (score > bestScore) {
bestScore = score;
bestIndex = i;
}
}
if (bestScore >= threshold) {
logger('相似度匹配: 最佳匹配项[' + bestIndex + '] 相似度=' + (bestScore * 100).toFixed(1) + '%', 'blue');
return bestIndex;
}
logger('相似度匹配: 所有选项相似度均低于阈值(' + (threshold * 100) + '%),最高=' + (bestScore * 100).toFixed(1) + '%', 'orange');
return -1;
}
// 多选题模糊匹配:AI返回的答案用'|'分割后,对每个答案片段在选项中找最佳匹配
// 返回匹配到的选项索引数组
function findFuzzyMatchMultiple(optionTexts, aiAnswer, threshold) {
if (!isFuzzyMatchEnabled()) return [];
if (!aiAnswer || !optionTexts || optionTexts.length === 0) return [];
threshold = (threshold !== undefined) ? threshold : 0.5;
var parts = aiAnswer.split('|');
var matched = [];
for (var p = 0; p < parts.length; p++) {
var part = parts[p].trim();
if (!part) continue;
var bestIndex = -1, bestScore = 0;
for (var i = 0; i < optionTexts.length; i++) {
var score = stringSimilarity(optionTexts[i], part);
if (score > bestScore) {
bestScore = score;
bestIndex = i;
}
}
if (bestScore >= threshold && matched.indexOf(bestIndex) === -1) {
matched.push(bestIndex);
logger('相似度匹配(多选): "' + part + '" → 选项[' + bestIndex + '] 相似度=' + (bestScore * 100).toFixed(1) + '%', 'blue');
}
}
return matched;
}
// 读取播放倍速:优先 localStorage(UI 设置),否则回退 setting.rate;范围 (0, 16]
function getRate() {
var stored = localStorage.getItem('GPTJsSetting.rate');
var n = stored !== null ? parseFloat(stored) : (setting.rate || 1);
if (!isFinite(n) || n <= 0) n = 1;
if (n > 16) n = 16;
return n;
}
// 统一判断题答案解析:将AI回答归一为 'true' / 'false' / null
// 解决旧版 string.indexOf 子串匹配导致 "正确的"、"True"、"对的" 等变体匹配失败的问题
function parseJudgeAnswer(agrs) {
if (!agrs) return null;
var s = agrs.replace(/[。,.,!!\s]/g, '').toLowerCase();
var trueWords = ['正确', '是', '对', '√', 't', 'true', 'ri', 'right', 'yes'];
var falseWords = ['错误', '否', '错', '×', 'f', 'false', 'wr', 'wrong', 'no'];
// 精确匹配
for (var i = 0; i < trueWords.length; i++) {
if (s === trueWords[i]) return 'true';
}
for (var i = 0; i < falseWords.length; i++) {
if (s === falseWords[i]) return 'false';
}
// 包含匹配(优先判断"错"避免"正确的"误判——先检查否定词)
for (var i = 0; i < falseWords.length; i++) {
if (s.indexOf(falseWords[i]) !== -1) return 'false';
}
for (var i = 0; i < trueWords.length; i++) {
if (s.indexOf(trueWords[i]) !== -1) return 'true';
}
return null;
}
// 在选项列表中查找"正确/对"或"错误/错"对应的索引
function findJudgeOptionIndex(optionTexts, isTrue) {
var trueWords = ['正确', '是', '对', '√', 'T', 'ri'];
var falseWords = ['错误', '否', '错', '×', 'F', 'wr'];
var words = isTrue ? trueWords : falseWords;
for (var i = 0; i < optionTexts.length; i++) {
var t = optionTexts[i];
for (var j = 0; j < words.length; j++) {
if (t.indexOf(words[j]) !== -1) return i;
}
}
return -1;
}
function showBox() {
//公告&充值
if (setting.showBox && top.document.querySelector('#ne-21notice') == undefined) {
// 注入样式(仅一次)
if (!top.document.getElementById('ne-21style')) {
var styleEl = top.document.createElement('style');
styleEl.id = 'ne-21style';
styleEl.textContent = `
/* === Liquid Glass UI (iOS 26 style, neutral light glass) === */
#ne-21box{position:fixed;top:5%;right:16%;width:340px;z-index:99999;font-family:-apple-system,BlinkMacSystemFont,"SF Pro Text","Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;font-size:13px;color:rgba(15,23,42,.86);background:linear-gradient(180deg,rgba(255,255,255,.62) 0%,rgba(241,245,249,.55) 100%);backdrop-filter:blur(22px) saturate(180%) brightness(1.04);-webkit-backdrop-filter:blur(22px) saturate(180%) brightness(1.04);border:1px solid rgba(255,255,255,.65);border-radius:22px;box-shadow:0 0 0 1px rgba(15,23,42,.09),0 24px 48px -12px rgba(15,23,42,.45),0 10px 26px -8px rgba(15,23,42,.3),inset 0 1px 0 rgba(255,255,255,.9),inset 0 -1px 0 rgba(15,23,42,.06);overflow:hidden;transition:opacity .25s ease,transform .25s ease;animation:ne21-in .4s cubic-bezier(.2,.9,.3,1) both;}
@keyframes ne21-in{from{opacity:0;transform:translateY(-8px) scale(.98)}to{opacity:1;transform:none}}
#ne-21box .ne21-header{position:relative;display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:linear-gradient(180deg,rgba(255,255,255,.72) 0%,rgba(248,250,252,.35) 100%);color:rgba(15,23,42,.92);border-bottom:1px solid rgba(15,23,42,.07);cursor:move;user-select:none;}
#ne-21box .ne21-header::after{content:'';position:absolute;left:14px;right:14px;bottom:-1px;height:1px;background:linear-gradient(90deg,transparent,rgba(255,255,255,.85),transparent);pointer-events:none;}
#ne-21box.ne21-collapsed .ne21-body{display:none;}
#ne-21box.ne21-collapsed .ne21-header{border-bottom:none;}
#ne-21box.ne21-collapsed .ne21-header::after{display:none;}
#ne-21box .ne21-title{display:flex;align-items:center;gap:9px;font-weight:600;font-size:14px;letter-spacing:.3px;margin:0;color:inherit;}
#ne-21box .ne21-dot{width:9px;height:9px;border-radius:50%;background:radial-gradient(circle at 32% 28%,rgba(255,255,255,.98),rgba(255,255,255,.5) 55%,rgba(15,23,42,.18) 100%);box-shadow:0 0 0 0 rgba(255,255,255,.7),inset 0 1px 1px rgba(255,255,255,.95);animation:ne21-pulse 2s infinite;flex-shrink:0;}
@keyframes ne21-pulse{0%{box-shadow:0 0 0 0 rgba(255,255,255,.7),inset 0 1px 1px rgba(255,255,255,.95)}70%{box-shadow:0 0 0 8px rgba(255,255,255,0),inset 0 1px 1px rgba(255,255,255,.95)}100%{box-shadow:0 0 0 0 rgba(255,255,255,0),inset 0 1px 1px rgba(255,255,255,.95)}}
#ne-21box #ne-21close{margin:0;width:24px;height:24px;padding:0;display:inline-flex;align-items:center;justify-content:center;font-size:18px;font-weight:600;line-height:1;color:rgba(15,23,42,.7);cursor:pointer;border:1px solid rgba(255,255,255,.65);border-radius:50%;background:rgba(255,255,255,.55);box-shadow:0 0 0 1px rgba(15,23,42,.06),inset 0 1px 0 rgba(255,255,255,.8),0 1px 2px rgba(15,23,42,.08);transition:background .2s,color .2s,transform .15s;user-select:none;font-family:inherit;}
#ne-21box #ne-21close:hover{background:rgba(255,255,255,.78);color:rgba(15,23,42,.92);box-shadow:0 0 0 1px rgba(15,23,42,.08),inset 0 1px 0 rgba(255,255,255,.9),0 1px 2px rgba(15,23,42,.1);}
#ne-21box #ne-21close:active{transform:scale(.92);}
#ne-21box .ne21-body{padding:14px 16px 16px;}
#ne-21box #ne-21notice{border-top:none!important;margin:0 0 6px!important;overflow:visible;}
#ne-21box .ne21-uid{display:flex;align-items:center;gap:6px;color:rgba(15,23,42,.62);font-size:12px;margin-bottom:10px;padding:8px 12px;background:rgba(255,255,255,.5);border:1px solid rgba(255,255,255,.7);border-radius:12px;box-shadow:0 0 0 1px rgba(15,23,42,.05),inset 0 1px 0 rgba(255,255,255,.75),0 1px 2px rgba(15,23,42,.05);}
#ne-21box .ne21-uid b{color:rgba(15,23,42,.92);font-weight:600;}
#ne-21box .ne21-row{display:flex;gap:8px;align-items:center;}
#ne-21box .ne21-btn{display:inline-flex;align-items:center;justify-content:center;padding:7px 14px;font-size:12px;font-weight:500;border-radius:14px;cursor:pointer;border:1px solid rgba(255,255,255,.7);transition:transform .15s,box-shadow .2s,background .2s,color .2s;white-space:nowrap;}
#ne-21box .ne21-btn-primary{color:rgba(15,23,42,.92);background:rgba(255,255,255,.72);box-shadow:0 0 0 1px rgba(15,23,42,.07),inset 0 1px 0 rgba(255,255,255,.95),inset 0 -6px 12px -6px rgba(15,23,42,.08),0 4px 10px -2px rgba(15,23,42,.22);}
#ne-21box .ne21-btn-primary:hover{transform:translateY(-1px);background:rgba(255,255,255,.88);box-shadow:0 0 0 1px rgba(15,23,42,.09),inset 0 1px 0 rgba(255,255,255,1),inset 0 -6px 12px -6px rgba(15,23,42,.1),0 6px 14px -2px rgba(15,23,42,.28);}
#ne-21box .ne21-btn-secondary{color:rgba(15,23,42,.78);background:rgba(255,255,255,.45);box-shadow:0 0 0 1px rgba(15,23,42,.06),inset 0 1px 0 rgba(255,255,255,.75),0 1px 2px rgba(15,23,42,.06);}
#ne-21box .ne21-btn-secondary:hover{background:rgba(255,255,255,.65);color:rgba(15,23,42,.92);box-shadow:0 0 0 1px rgba(15,23,42,.08),inset 0 1px 0 rgba(255,255,255,.85),0 2px 4px rgba(15,23,42,.08);}
#ne-21box .ne21-btn:active{transform:translateY(0) scale(.98);}
#ne-21box #modelSelect{flex:1;min-width:0;padding:7px 10px;font-size:12px;border-radius:14px;border:1px solid rgba(255,255,255,.7);background:rgba(255,255,255,.55);color:rgba(15,23,42,.86);cursor:pointer;outline:none;box-shadow:0 0 0 1px rgba(15,23,42,.06),inset 0 1px 0 rgba(255,255,255,.8);transition:background .2s,box-shadow .2s;}
#ne-21box #modelSelect:hover{background:rgba(255,255,255,.7);}
#ne-21box #modelSelect:focus{background:rgba(255,255,255,.75);box-shadow:0 0 0 1px rgba(15,23,42,.08),inset 0 1px 0 rgba(255,255,255,.85),0 0 0 4px rgba(15,23,42,.1);}
#ne-21box .ne21-select{padding:5px 8px;font-size:12px;border-radius:10px;border:1px solid rgba(255,255,255,.65);background:rgba(255,255,255,.5);color:rgba(15,23,42,.86);cursor:pointer;outline:none;min-width:80px;flex-shrink:0;box-shadow:0 0 0 1px rgba(15,23,42,.05),inset 0 1px 0 rgba(255,255,255,.75);transition:background .2s,box-shadow .2s;}
#ne-21box .ne21-select:hover{background:rgba(255,255,255,.7);}
#ne-21box .ne21-select:focus{background:rgba(255,255,255,.72);box-shadow:0 0 0 1px rgba(15,23,42,.08),inset 0 1px 0 rgba(255,255,255,.82),0 0 0 3px rgba(15,23,42,.06);}
#ne-21box #userInfo{margin:10px 0 0;padding:10px 12px;background:rgba(255,255,255,.5);border:1px solid rgba(255,255,255,.7);border-radius:12px;box-shadow:0 0 0 1px rgba(15,23,42,.05),inset 0 1px 0 rgba(255,255,255,.75);font-size:12px;color:rgba(15,23,42,.66);line-height:1.6;overflow:hidden;}
#ne-21box #userInfo:empty{display:none;}
#ne-21box #userInfo b{color:rgba(15,23,42,.9);font-weight:600;}
#ne-21box #moreSettings{padding:4px 14px;background:rgba(255,255,255,.42);border:1px solid rgba(255,255,255,.65);border-radius:14px;box-shadow:0 0 0 1px rgba(15,23,42,.05),inset 0 1px 0 rgba(255,255,255,.7);margin:10px 0 0;}
#ne-21box #moreSettings label{display:flex;flex-direction:row-reverse;align-items:center;justify-content:space-between;margin:0;padding:8px 2px;font-size:12px;color:rgba(15,23,42,.78);cursor:pointer;user-select:none;line-height:1.4;}
#ne-21box #moreSettings label + label{border-top:1px dashed rgba(15,23,42,.1);}
#ne-21box #moreSettings input[type=checkbox]{appearance:none;-webkit-appearance:none;width:34px;height:20px;border:1px solid rgba(15,23,42,.08);border-radius:20px;cursor:pointer;position:relative;transition:background .25s,box-shadow .25s;background:rgba(15,23,42,.16);box-shadow:inset 0 1px 2px rgba(15,23,42,.12);margin:0 0 0 10px;flex-shrink:0;}
#ne-21box #moreSettings input[type=checkbox]::before{content:'';position:absolute;top:1px;left:1px;width:16px;height:16px;border-radius:50%;background:linear-gradient(180deg,rgba(255,255,255,1),rgba(255,255,255,.85));box-shadow:0 1px 3px rgba(15,23,42,.25),inset 0 1px 0 rgba(255,255,255,1);transition:transform .25s cubic-bezier(.2,.9,.3,1);}
#ne-21box #moreSettings input[type=checkbox]:hover{background:rgba(15,23,42,.24);}
#ne-21box #moreSettings input[type=checkbox]:checked{background:rgba(255,255,255,.78);border-color:rgba(15,23,42,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.95),inset 0 -2px 4px rgba(15,23,42,.08),0 0 0 1px rgba(15,23,42,.06);}
#ne-21box #moreSettings input[type=checkbox]:checked:hover{background:rgba(255,255,255,.92);}
#ne-21box #moreSettings input[type=checkbox]:checked::before{transform:translateX(14px);}
#ne-21box #moreSettings p{display:none;}
#ne-21box #ne-21thinking{display:none!important;}
@keyframes ne21-spin{to{transform:rotate(360deg)}}
@keyframes ne21-dot{0%,80%,100%{transform:scale(.5);opacity:.4}40%{transform:scale(1);opacity:1}}
#ne-21box #ne-21log .ne21-log-spinner{display:inline-block;width:9px;height:9px;margin-right:5px;border:1.5px solid rgba(15,23,42,.18);border-top-color:rgba(15,23,42,.7);border-radius:50%;vertical-align:-1px;animation:ne21-spin .8s linear infinite;}
#ne-21box #ne-21log .ne21-log-dots{display:inline-flex;gap:2px;margin-left:3px;vertical-align:1px;}
#ne-21box #ne-21log .ne21-log-dots i{width:3px;height:3px;border-radius:50%;background:currentColor;opacity:.65;animation:ne21-dot 1.2s infinite ease-in-out both;}
#ne-21box #ne-21log .ne21-log-dots i:nth-child(2){animation-delay:.16s;}
#ne-21box #ne-21log .ne21-log-dots i:nth-child(3){animation-delay:.32s;}
#ne-21box #ne-21log{max-height:140px;overflow-y:auto;margin:12px 0 0;padding:10px 12px;background:rgba(15,23,42,.06);border:1px solid rgba(255,255,255,.55);border-radius:14px;box-shadow:0 0 0 1px rgba(15,23,42,.06),inset 0 1px 0 rgba(255,255,255,.6),inset 0 0 16px rgba(15,23,42,.06);font-family:"SF Mono","Cascadia Code",Consolas,Menlo,monospace;font-size:11px;line-height:1.6;color:rgba(15,23,42,.78);}
#ne-21box #ne-21log:empty{display:none;}
#ne-21box #ne-21log::-webkit-scrollbar{width:5px;}
#ne-21box #ne-21log::-webkit-scrollbar-thumb{background:rgba(15,23,42,.2);border-radius:4px;}
#ne-21box #ne-21log::-webkit-scrollbar-thumb:hover{background:rgba(15,23,42,.32);}
#ne-21box #ne-21log p{margin:0;padding:2px 0;word-break:break-all;}
#ne-21box #ne-21log hr{display:none;}
#ne-21box #ne-21log .ne21-time{color:rgba(15,23,42,.4);margin-right:6px;}
`;
top.document.head.appendChild(styleEl);
}
var box_html = `
`;
$(box_html).appendTo('body');
// 恢复保存的位置与收起/展开状态
(function () {
var $box = $('#ne-21box');
// 恢复位置
try {
var savedPos = localStorage.getItem('GPTJsSetting.boxPosition');
if (savedPos) {
var pos = JSON.parse(savedPos);
if (pos && typeof pos.left === 'number' && typeof pos.top === 'number') {
var vw = window.innerWidth, vh = window.innerHeight;
var w = $box.outerWidth() || 340;
// 约束:避免恢复后跑到视窗外不可见
var nx = Math.max(40 - w, Math.min(pos.left, vw - 40));
var ny = Math.max(0, Math.min(pos.top, vh - 40));
$box.css({ left: nx + 'px', top: ny + 'px', right: 'auto' });
}
}
} catch (_) { /* empty */ }
// 恢复收起/展开状态
if (localStorage.getItem('GPTJsSetting.boxCollapsed') === 'true') {
$box.addClass('ne21-collapsed');
$('#ne-21close').text('+').attr('aria-label', '展开');
}
})();
// 收起/展开按钮:切换 .ne21-collapsed,按钮文本在 − / + 之间切换
$('#ne-21close').on('mousedown', function (e) {
e.stopPropagation(); // 避免触发标题栏拖动
}).on('click', function (e) {
e.stopPropagation();
var collapsed = $('#ne-21box').toggleClass('ne21-collapsed').hasClass('ne21-collapsed');
$(this).text(collapsed ? '+' : '−');
$(this).attr('aria-label', collapsed ? '展开' : '收起');
// 持久化收起/展开状态
try { localStorage.setItem('GPTJsSetting.boxCollapsed', collapsed ? 'true' : 'false'); } catch (_) { /* empty */ }
});
// 标题栏拖动:拖动结束后写入 localStorage,刷新后保持上次位置
(function () {
var $box = $('#ne-21box');
var $header = $box.find('.ne21-header');
var dragging = false, startX = 0, startY = 0, startLeft = 0, startTop = 0;
$header.on('mousedown', function (e) {
if (e.which !== 1) return; // 仅响应鼠标左键
if ($(e.target).closest('#ne-21close').length) return; // 点在按钮上不拖动
dragging = true;
var rect = $box[0].getBoundingClientRect();
startX = e.clientX;
startY = e.clientY;
startLeft = rect.left;
startTop = rect.top;
// 清空 right,把 left/top 改为像素值,避免与默认 left:66% 计算冲突
$box.css({ left: startLeft + 'px', top: startTop + 'px', right: 'auto' });
$('body').css('user-select', 'none');
e.preventDefault();
});
$(document).on('mousemove.ne21drag', function (e) {
if (!dragging) return;
var nx = startLeft + (e.clientX - startX);
var ny = startTop + (e.clientY - startY);
var w = $box.outerWidth();
var vw = window.innerWidth;
var vh = window.innerHeight;
// 约束:保留至少 40px 标题栏在视窗内,便于回拉
nx = Math.max(40 - w, Math.min(nx, vw - 40));
ny = Math.max(0, Math.min(ny, vh - 40));
$box.css({ left: nx + 'px', top: ny + 'px' });
}).on('mouseup.ne21drag', function () {
if (!dragging) return;
dragging = false;
$('body').css('user-select', '');
// 持久化位置
try {
var rect = $box[0].getBoundingClientRect();
localStorage.setItem('GPTJsSetting.boxPosition', JSON.stringify({ left: rect.left, top: rect.top }));
} catch (_) { /* empty */ }
});
})();
// 设置面板切换 + 复选框监听器
(function () {
var moreSettings = document.getElementById('moreSettings');
var userInfo = document.getElementById('userInfo');
if (!moreSettings || !userInfo) return;
// #moreSettingsBtn 由 $('#ne-21notice').html(...) 在后面动态注入,
// 此时还不存在,且每次 showBox() 都会被重建,因此使用事件委托。
var isSettingsVisible = false;
$('#ne-21box').on('click', '#moreSettingsBtn', function () {
userInfo.style.display = isSettingsVisible ? 'block' : 'none';
moreSettings.style.display = isSettingsVisible ? 'none' : 'block';
this.textContent = isSettingsVisible ? '设置' : '返回';
isSettingsVisible = !isSettingsVisible;
});
// 修改题目默认开启(仅初始化一次,避免 forEach 内重复执行)
if (localStorage.getItem('GPTJsSetting.alterTitle') === null) {
localStorage.setItem('GPTJsSetting.alterTitle', 'true');
}
// 相似度匹配默认开启
if (localStorage.getItem('GPTJsSetting.fuzzyMatch') === null) {
localStorage.setItem('GPTJsSetting.fuzzyMatch', 'true');
}
['sub', 'force', 'examTurn', 'goodStudent', 'alterTitle', 'redo', 'fuzzyMatch'].forEach(function (settingId) {
var checkbox = document.getElementById('GPTJsSetting.' + settingId);
if (!checkbox) return;
checkbox.addEventListener('change', updateLocalStorage);
checkbox.checked = localStorage.getItem('GPTJsSetting.' + settingId) === 'true';
});
// 倍速下拉:恢复上次选择并持久化
var rateSelect = document.getElementById('GPTJsSetting.rate');
if (rateSelect) {
rateSelect.value = localStorage.getItem('GPTJsSetting.rate') || '1';
rateSelect.addEventListener('change', function () {
localStorage.setItem('GPTJsSetting.rate', rateSelect.value);
});
}
})();
} else {
$('#ne-21log', window.parent.document).html('')
}
let _u = getCk('_uid') || getCk('UID')
$('#ne-21notice').html(`
学习通账号 UID:${_u || '-'}
`);
// 模型列表缓存:先从缓存加载避免闪烁
var CACHE_KEY_MODELS = 'GPTJsSetting.cachedModels';
var cachedModels = localStorage.getItem(CACHE_KEY_MODELS);
if (cachedModels) {
$('#modelSelect').html(cachedModels);
}
// 同步恢复上次选中的模型,避免等 window.onload 造成的闪烁
var lastSelectedModel = localStorage.getItem('GPTJsSetting.model');
if (lastSelectedModel) {
$('#modelSelect').val(lastSelectedModel);
}
// 同步绑定 change 监听(命名空间避免 showBox 重入时重复绑定)
$('#modelSelect').off('change.gptjsModel').on('change.gptjsModel', function () {
localStorage.setItem('GPTJsSetting.model', $(this).val());
});
//公告&积分
if (_authPromise) {
_authPromise.then(function (res) {
var obj = {};
try { obj = $.parseJSON(res.response) || {}; } catch (e) { /* empty */ }
var data = obj.data || {};
var notice = data.notice || '';
var score = (data.score !== undefined && data.score !== null) ? data.score : '-';
$('#userInfo').html(notice + "积分余额:" + score);
// 模型列表:只有内容变化时才更新DOM和缓存,避免闪烁
if (data.models && data.models !== cachedModels) {
localStorage.setItem(CACHE_KEY_MODELS, data.models);
// 优先使用 localStorage 中保存的模型;否则保留当前