// ==UserScript== // @name 华南理工作业互评增强版 v6.0 // @namespace http://tampermonkey.net/ // @version 6.0 // @description 4种模式:默认满分/AI自动评分/导出题目数据/导入评分结果。支持10+供应商,双重冷却防刷,跳过已评,max_tokens=5000兼容思考模型 // @author WanderLandWalker // @match http://1024.se.scut.edu.cn/* // @match https://1024.se.scut.edu.cn/* // @grant none // @license GPL-3.0 // ==/UserScript== (function () { 'use strict'; try { var PROVIDERS = [ { name: 'DeepSeek', url: 'https://api.deepseek.com/v1', models: ['deepseek-chat', 'deepseek-reasoner'] }, { name: 'OpenAI', url: 'https://api.openai.com/v1', models: ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'gpt-3.5-turbo'] }, { name: '通义千问(阿里)', url: 'https://dashscope.aliyuncs.com/compatible-mode/v1', models: ['qwen-plus', 'qwen-turbo', 'qwen-max', 'qwen-long'] }, { name: '硅基流动', url: 'https://api.siliconflow.cn/v1', models: ['deepseek-ai/DeepSeek-V3', 'deepseek-ai/DeepSeek-R1', 'Qwen/Qwen2.5-72B-Instruct'] }, { name: '智谱(GLM)', url: 'https://open.bigmodel.cn/api/paas/v4', models: ['glm-4-flash', 'glm-4', 'glm-4-plus'] }, { name: '月之暗面(Kimi)', url: 'https://api.moonshot.cn/v1', models: ['moonshot-v1-8k', 'moonshot-v1-32k', 'moonshot-v1-128k'] }, { name: '豆包(字节)', url: 'https://ark.cn-beijing.volces.com/api/v3', models: ['doubao-1.5-pro-32k', 'doubao-1.5-lite-32k'] }, { name: '百川', url: 'https://api.baichuan-ai.com/v1', models: ['Baichuan4'] }, { name: '零一万物', url: 'https://api.lingyiwanwu.com/v1', models: ['yi-large', 'yi-medium'] }, { name: '自定义', url: '', models: [] } ]; var AI_PROMPT = '你是大学操作系统课程的作业评分助教。根据参考答案对学生回答评分。满分100分,根据回答质量给分:核心思路正确完整90-100,基本正确有小错70-89,部分正确有明显错误50-69,大部分错误30-49,完全错误0-29。评语50字以内中文。重要要求:必须且只能返回纯JSON格式,绝不能包含任何Markdown标记(如```json),绝不能包含任何解释性文字!格式严格为:{"score":分数,"comment":"评语"}'; var P = document.createElement('div'); P.id = 'gp44'; var providerOpts = ''; for (var pi = 0; pi < PROVIDERS.length; pi++) providerOpts += ''; P.innerHTML = '' +'' +'

互评助手 v6.0

' +'
' +'
就绪
' +'
评分模式
' +'
' +'
默认满分
' +'
AI自动评分
' +'
导出题目数据
' +'
导入评分结果
' +'
' +'
默认满分:所有学生直接打100分,自动遍历
' +' ' +' ' +'
' +' 跳过已评过的学生' +' ' +'
' +'
' +' ' +' ' +'
' +' ' +' ' +'
运行日志
' +'
' +'
'; document.body.appendChild(P); var $log=document.getElementById('gp-lg'),$body=document.getElementById('gp-body'),$ai=document.getElementById('gp-ai'),$imp=document.getElementById('gp-imp'); var mode='full',imported=[],exportDirHandle=null,exportDirName=''; function setStatus(text,ok){ var dot=document.getElementById('gp-dot'),txt=document.getElementById('gp-st-text'); if(dot)dot.style.background=(ok===false)?'#f38ba8':'#a6e3a1'; if(txt)txt.textContent=text; } function log(m,c){c=c||'i';var d=document.createElement('div');d.className=c;d.textContent='['+new Date().toLocaleTimeString()+'] '+m;$log.appendChild(d);$log.scrollTop=$log.scrollHeight;console.log('[互评] '+m)} function save(k,v){try{localStorage.setItem('gp44_'+k,typeof v==='string'?v:JSON.stringify(v))}catch(e){}} function load(k){try{return localStorage.getItem('gp44_'+k)}catch(e){return null}} function loadJ(k){try{var s=localStorage.getItem('gp44_'+k);return s?JSON.parse(s):null}catch(e){return null}} function del(k){try{localStorage.removeItem('gp44_'+k)}catch(e){}} function getSelText(id){var el=document.getElementById(id);return(el&&el.selectedIndex>=0)?el.options[el.selectedIndex].text:''} function getSelValue(id){var el=document.getElementById(id);return el?el.value:''} function getModel(){var v=document.getElementById('gp-mdl').value.trim();return v||load('gp-mdl')||'deepseek-chat'} function shouldSkip(){return document.getElementById('gp-skip').checked} function onProviderChange(){ var idx=parseInt(getSelValue('gp-provider')),p=PROVIDERS[idx];if(!p)return; save('gp-provider',idx); // 只填入默认URL,不保存(用户手动改才保存) document.getElementById('gp-url').value=p.url; var inp=document.getElementById('gp-mdl'),sel=document.getElementById('gp-mdl-sel'); sel.innerHTML=''; if(p.models.length){ for(var i=0;i0} var noSubmit=text.indexOf('该学生未提交作业')>=0; var hasFile=!!(document.querySelector('#downfile')||document.querySelector('input[value*="下载"]')); if(!hasFile&&!noSubmit&&qType==='综合题')hasFile=true; var mainEl=document.querySelector('.main')||document.body; var pageText=mainEl.innerText||''; var alreadyGraded = pageText.indexOf('您评定的成绩') >= 0 || pageText.indexOf('评定时间') >= 0 || pageText.indexOf('评定成功') >= 0; if(!alreadyGraded){ var gradeText=document.body.innerText||''; if(gradeText.indexOf('评定时间') >= 0 || gradeText.indexOf('您评定的成绩') >= 0 || gradeText.indexOf('评定成功') >= 0) alreadyGraded=true } if(!alreadyGraded){ var submitted=loadJ('submitted')||{}; var key=(getSelText('MainContent_dropTitleList')+'|||'+getSelText('MainContent_dropStudent')); if(submitted[key])alreadyGraded=true } return{qType:qType,qText:qText,refAns:refAns,stuAns:stuAns,hasAns:hasAns,hasFile:hasFile,noSubmit:noSubmit,alreadyGraded:alreadyGraded} } function setScore(s,c){ var se=document.getElementById('MainContent_dropScore'),ce=document.getElementById('MainContent_txtRemark'); if(se){se.value=String(s);se.dispatchEvent(new Event('change',{bubbles:true}))} if(ce){ce.value=c;ce.dispatchEvent(new Event('input',{bubbles:true}))} } function markSubmitted(){ var submitted=loadJ('submitted')||{}; var key=getSelText('MainContent_dropTitleList')+'|||'+getSelText('MainContent_dropStudent'); submitted[key]=Date.now(); save('submitted',submitted) } function clickSubmit(){ var now=Date.now(); var lastSubmit=load('lastSubmitTime'); var cooldown=15500; if(lastSubmit&&(now-parseInt(lastSubmit))|]/g,'_'); var student=getSelText('MainContent_dropStudent').replace(/[\t\/\\:*?"<>|]/g,'_'); var ct=r.headers.get('Content-Type')||'',ext='.doc'; if(ct.indexOf('pdf')>=0)ext='.pdf';else if(ct.indexOf('zip')>=0)ext='.zip';else if(ct.indexOf('rar')>=0)ext='.rar'; var fn=title+'_'+student+ext; var u=URL.createObjectURL(blob),a=document.createElement('a');a.href=u;a.download=fn;document.body.appendChild(a);a.click();document.body.removeChild(a);URL.revokeObjectURL(u); log('已下载: '+fn+' ('+(blob.size/1024).toFixed(1)+'KB)','o');cb(true); }) }).catch(function(e){log('下载异常: '+e.message,'e');cb(false)}) } async function startTask(t){ del('lastOp'); if(t==='export'){ if(window.showDirectoryPicker){try{log('请选择保存文件夹...','i');var h=await window.showDirectoryPicker({mode:'readwrite'});exportDirName='互评导出_'+new Date().toISOString().slice(0,19).replace(/[:\-T]/g,'');exportDirHandle=await h.getDirectoryHandle(exportDirName,{create:true});log('保存到: '+h.name+'/'+exportDirName,'o') }catch(e){log('已取消','w');setStatus('已取消',false);return}} else log('浏览器不支持选文件夹,用默认下载','w') } var ts=document.getElementById('MainContent_dropTitleList'),ss=document.getElementById('MainContent_dropStudent'); var st={task:t,tIdx:ts?ts.selectedIndex:0,sIdx:ss?ss.selectedIndex:0,phase:'work',data:[]}; save('task',t);save('state',st);log('启动 ['+t+']','o');setStatus('运行中...',true);doStep() } function doStep(){ var t=load('task'),s=loadJ('state');if(!t||!s)return; if(s.phase==='work'){ var pg=readPage();if(!pg)return; if(pg.alreadyGraded&&shouldSkip()){ log(getSelText('MainContent_dropStudent')+' 已评过,跳过','w'); skipAdvance(s);return } if(t==='full')doFull(s);else if(t==='ai')doAI(s);else if(t==='export')doExport(s);else if(t==='import')doImport(s) }else if(s.phase==='advance')doAdvance(s) } function skipAdvance(s){ var ts=document.getElementById('MainContent_dropTitleList'),ss=document.getElementById('MainContent_dropStudent');if(!ts||!ss)return; if(s.sIdx+1 '+ss.options[s.sIdx].text,'w'); __doPostBack('ctl00$MainContent$dropStudent','') }else if(s.tIdx+1 下一题 '+ts.options[s.tIdx].text,'w'); __doPostBack('ctl00$MainContent$dropTitleList','') }else{ if(s.task==='export'&&s.data&&s.data.length)finishExport(s.data); del('task');del('state');log('全部完成!','o');setStatus('全部完成!',true) } } function waitAndRun(fn,s){fn(s)} function doFull(s){ var pg=readPage();if(!pg)return;var stu=getSelText('MainContent_dropStudent'); if(pg.alreadyGraded&&shouldSkip()){log(stu+' 已评过,跳过','w');s.phase='advance';save('state',s);doAdvance(s);return} waitAndRun(doFullSubmit,s) } function doFullSubmit(s){ var pg=readPage();if(!pg)return;if(pg.alreadyGraded&&shouldSkip()){doAdvance(s);return} var stu=getSelText('MainContent_dropStudent'); if(pg.noSubmit){setScore(0,'该学生未提交作业。');log(stu+' -> 0分','w')}else{setScore(100,'回答得很好,100分。');log(stu+' -> 100分','o')} s.phase='advance';save('state',s);clickSubmit() } function doAI(s){ var pg=readPage();if(!pg)return;var stu=getSelText('MainContent_dropStudent'); if(pg.alreadyGraded&&shouldSkip()){log(stu+' 已评过,跳过','w');s.phase='advance';save('state',s);doAdvance(s);return} if(pg.noSubmit){setScore(0,'该学生未提交作业。');s.phase='advance';save('state',s);clickSubmit();return} if(pg.qType==='综合题'&&pg.hasFile&&!pg.hasAns){s.phase='advance';save('state',s);doAdvance(s);return} if(!pg.hasAns){setScore(0,'未提交答案。');s.phase='advance';save('state',s);clickSubmit();return} doAICall(s) } function doAICall(s){ var pg=readPage();if(!pg)return;if(pg.alreadyGraded&&shouldSkip()){doAdvance(s);return} var stu=getSelText('MainContent_dropStudent'); var url=document.getElementById('gp-url').value.trim(),key=document.getElementById('gp-key').value.trim(),mdl=getModel(); if(!key){log('请填写API Key','e');return} log('AI评分: '+stu+' ['+mdl+']...','i'); callAI(url,key,mdl,AI_PROMPT,pg).then(function(r){ if(r){setScore(r.score,r.comment);log('-> '+r.score+'分','o');s.phase='advance';save('state',s);clickSubmit()} else{log('AI打分失败,已停止任务,请查看日志排查。','e');setStatus('AI错误,已停止',false);del('task');del('state')} }) } function doExport(s){ var pg=readPage();if(!pg)return; if(pg.alreadyGraded&&shouldSkip()){log(getSelText('MainContent_dropStudent')+' 已评过,跳过','w');s.phase='advance';save('state',s);doAdvance(s);return} s.data=s.data||[]; s.data.push({title:getSelText('MainContent_dropTitleList'),student:getSelText('MainContent_dropStudent'),qType:pg.qType,qText:pg.qText,refAns:pg.refAns,stuAns:pg.stuAns,hasAns:pg.hasAns,noSubmit:pg.noSubmit,hasFile:pg.hasFile}); save('state',s);log('导出: '+getSelText('MainContent_dropStudent'),'i');setStatus('导出中 '+s.data.length+'条',true); if(pg.qType==='综合题'&&pg.hasFile&&!pg.noSubmit){downloadStudentFile(function(){s.phase='advance';save('state',s);doAdvance(s)})} else{s.phase='advance';save('state',s);doAdvance(s)} } function doImport(s){ if(!imported.length){log('请先导入','e');del('task');del('state');return} var title=getSelText('MainContent_dropTitleList'),student=getSelText('MainContent_dropStudent'),pg=readPage(); if(pg&&pg.alreadyGraded&&shouldSkip()){log(student+' 已评过,跳过','w');s.phase='advance';save('state',s);doAdvance(s);return} var matched=null; for(var i=0;i 0 && j.choices[0].message){ reply = j.choices[0].message.content || ''; } else if (j.response) { reply = j.response; } else if (j.data && j.data.response) { reply = j.data.response; } if(!reply || reply.trim() === '') { log('AI未返回有效内容!服务器回包:\n' + JSON.stringify(j), 'e'); return null; } reply=reply.replace(/```json\s*/g,'').replace(/```\s*/g,'').trim(); var score = -1; var comment = "回答正确。"; try { var p = JSON.parse(reply); if (p.score !== undefined) { score = parseInt(p.score); comment = p.comment || comment; } } catch(e) { var nm = reply.match(/"?score"?\s*[::]\s*(\d+)/i) || reply.match(/分数\s*[::]\s*(\d+)/i) || reply.match(/^(\d{1,3})$/); var cm = reply.match(/"?comment"?\s*[::]\s*"([^"]+)"/i) || reply.match(/评语\s*[::]\s*"([^"]+)"/i); if(nm) { score = parseInt(nm[1]); if(cm) comment = cm[1]; } } if (score >= 0 && score <= 100) { return {score: score, comment: String(comment).substring(0,50)}; } else { log('解析失败!AI返回:\n' + reply, 'e'); return null; } }catch(e){ log('API调用异常:'+e.message,'e'); return null; } } function doAdvance(s){ var ts=document.getElementById('MainContent_dropTitleList'),ss=document.getElementById('MainContent_dropStudent');if(!ts||!ss)return; if(s.sIdx+1

导出完成! 共 '+data.length+' 条 (可评 '+gradable.length+' 条)

' +'
  1. 点下方 [复制Prompt] 按钮
  2. ' +'
  3. 打开任意AI(ChatGPT/DeepSeek/通义等)
  4. ' +'
  5. 粘贴发送,AI返回评分JSON
  6. ' +'
  7. 回到本脚本选 [导入评分结果]
  8. ' +'
  9. 粘贴AI返回的JSON → 点开始
' +'' +'' +''; document.getElementById('gp-copy-btn').onclick=function(e){ e.preventDefault();var ta=document.getElementById('gp-copy-ta');ta.select(); navigator.clipboard.writeText(ta.value).then(function(){ document.getElementById('gp-copy-btn').textContent='已复制! 去粘贴给AI吧';document.getElementById('gp-copy-btn').className='btn btn-purple'; log('已复制到剪贴板!','o'); setTimeout(function(){document.getElementById('gp-copy-btn').textContent='复制Prompt到剪贴板';document.getElementById('gp-copy-btn').className='btn btn-green'},3000) }).catch(function(){ta.style.height='200px';ta.select();log('自动复制失败,请手动Ctrl+C','w')}) }; document.getElementById('gp-toggle-text').onclick=function(e){ e.preventDefault();var ta=document.getElementById('gp-copy-ta'); ta.style.height=(ta.style.height==='0px'||ta.style.height==='')?'250px':'0px';ta.style.overflow=(ta.style.height==='0px')?'hidden':'auto' }; log('====================','o');log('导出完成! 共 '+data.length+' 条','o');log('可评分: '+gradable.length+' | 跳过: '+skipped.length,'o'); if(exportDirHandle)log('文件已保存到: '+exportDirName+'/','o');else log('文件已下载','o'); log('↑ 点绿色按钮复制Prompt给AI','o');log('====================','o');setStatus('完成! 复制Prompt给AI',true) } async function checkResume(){var t=load('task'),s=loadJ('state');if(!t||!s)return;log('恢复任务: '+t,'w');setStatus('恢复运行...',true);mode=t; var btns=document.querySelectorAll('#gp-modes .m');for(var i=0;i默认满分:所有学生直接打100分,自动遍历',ai:'AI自动评分:选供应商→填Key→获取模型→开始,每15秒一题',export:'导出流程:开始→遍历→完成后复制Prompt给AI→AI返回JSON→导入',import:'导入流程:粘贴AI返回的JSON→点导入→开始自动填入'}; h.innerHTML=hints[mode]||''}})(modeBtns[i])} document.getElementById('gp-go').onclick=function(e){e.preventDefault();startTask(mode)}; document.getElementById('gp-stop').onclick=function(e){e.preventDefault();del('task');del('state');exportDirHandle=null;log('已停止','w');setStatus('已停止',false)}; document.getElementById('gp-exp').onclick=function(e){e.preventDefault();startTask('export')}; document.getElementById('gp-provider').onchange=function(e){e.preventDefault();onProviderChange()}; document.getElementById('gp-fetch').onclick=function(e){e.preventDefault();fetchModels()}; document.getElementById('gp-mdl').oninput=function(){save('gp-mdl',this.value)}; document.getElementById('gp-mdl-sel').onclick=function(e){e.preventDefault();if(this.value){document.getElementById('gp-mdl').value=this.value;save('gp-mdl',this.value)}}; document.getElementById('gp-mdl-sel').onchange=function(){if(this.value){document.getElementById('gp-mdl').value=this.value;save('gp-mdl',this.value)}}; document.getElementById('gp-ai1').onclick=function(e){e.preventDefault();var pg=readPage();if(!pg)return;var url=document.getElementById('gp-url').value.trim(),key=document.getElementById('gp-key').value.trim(),mdl=getModel(); if(!key){log('请填写API Key','e');return}log('AI评当前题 ['+mdl+']...','i');callAI(url,key,mdl,AI_PROMPT,pg).then(function(r){if(r){setScore(r.score,r.comment);log('-> '+r.score+'分','o')}else log('AI失败','e')})}; document.getElementById('gp-file').onchange=function(e){var f=e.target.files[0];if(!f)return;var rd=new FileReader();rd.onload=function(ev){try{imported=JSON.parse(ev.target.result);log('导入成功: '+imported.length+'条','o')}catch(e){log('JSON格式错误','e')}};rd.readAsText(f)}; document.getElementById('gp-paste-btn').onclick=function(e){e.preventDefault();var t=document.getElementById('gp-paste').value.trim();if(!t){log('请先粘贴JSON','e');return} try{imported=JSON.parse(t);log('粘贴导入: '+imported.length+'条','o');document.getElementById('gp-paste-btn').textContent='已导入 '+imported.length+' 条!';setTimeout(function(){document.getElementById('gp-paste-btn').textContent='导入粘贴的评分'},2000)}catch(e){log('JSON格式错误','e')}}; var skipEl=document.getElementById('gp-skip'); skipEl.onchange=function(){save('gp-skip',this.checked?'1':'0')}; var savedSkip=load('gp-skip'); skipEl.checked=(savedSkip!=='0'); document.getElementById('gp-url').onchange=function(){save('gp-url',this.value)}; var savedUrl = load('gp-url'); var savedMdl = load('gp-mdl'); var sp=load('gp-provider'); if(sp!==null){ document.getElementById('gp-provider').value=sp; onProviderChange(); } else { onProviderChange(); } if(savedUrl) { document.getElementById('gp-url').value = savedUrl; save('gp-url', savedUrl); } if(savedMdl) { document.getElementById('gp-mdl').value = savedMdl; save('gp-mdl', savedMdl); } var sk=load('gp-key');if(sk)document.getElementById('gp-key').value=sk; document.getElementById('gp-key').onchange=function(){save('gp-key',this.value)}; // 清除API Key按钮 document.getElementById('gp-clear-key').onclick=function(e){ e.preventDefault(); del('gp-key'); document.getElementById('gp-key').value=''; log('API Key已清除','o'); }; // 隐私提示 if(!load('privacy_acknowledged')){ var ack=confirm('隐私提示:\n\nAI评分模式会将【题目、参考答案、学生回答】发送到你选择的AI供应商服务器。\n\n请确认你了解此风险后继续使用。\n\n点击"确定"不再提示,点"取消"暂不使用AI功能。'); if(ack)save('privacy_acknowledged','1'); } log('脚本已加载 v6.0','o');checkResume(); }catch(err){console.error('[互评脚本] 错误:',err);alert('[互评脚本] 出错: '+err.message)} })();