|
|
@@ -45,311 +45,399 @@ const callMysqlProc = (procedureName, data) => {
|
|
|
});
|
|
|
};
|
|
|
|
|
|
-const queryAutoScoData = () => {
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- const p = [];
|
|
|
- p.unshift(_mysqlLabor[0], _mysqlLabor[1], "getcocostudyautosco");
|
|
|
- mysql.usselect(p, (ret) => {
|
|
|
- if (ret instanceof Error || (ret && ret.code)) {
|
|
|
- reject(ret);
|
|
|
- return;
|
|
|
- }
|
|
|
- resolve(ret[0] || []);
|
|
|
- });
|
|
|
- });
|
|
|
-};
|
|
|
+// const queryAutoScoData = () => {
|
|
|
+// return new Promise((resolve, reject) => {
|
|
|
+// const p = [];
|
|
|
+// p.unshift(_mysqlLabor[0], _mysqlLabor[1], "getcocostudyautosco");
|
|
|
+// mysql.usselect(p, (ret) => {
|
|
|
+// if (ret instanceof Error || (ret && ret.code)) {
|
|
|
+// reject(ret);
|
|
|
+// return;
|
|
|
+// }
|
|
|
+// resolve(ret[0] || []);
|
|
|
+// });
|
|
|
+// });
|
|
|
+// };
|
|
|
+
|
|
|
+// const aiBatchCorrectionBtn = (item, aiBatchCorrection) => {
|
|
|
+// if (!item?.work || !item?.testJson) {
|
|
|
+// console.warn("缺少 work 或 testJson,跳过", item?.id);
|
|
|
+// return;
|
|
|
+// }
|
|
|
|
|
|
-const aiBatchCorrectionBtn = (item, aiBatchCorrection) => {
|
|
|
- if (!item?.work || !item?.testJson) {
|
|
|
- console.warn("缺少 work 或 testJson,跳过", item?.id);
|
|
|
- return;
|
|
|
- }
|
|
|
+// let workParsed;
|
|
|
+// let testjson;
|
|
|
+// try {
|
|
|
+// workParsed = JSON.parse(item.work);
|
|
|
+// testjson = JSON.parse(item.testJson);
|
|
|
+// } catch (err) {
|
|
|
+// console.error("JSON 解析失败", item.id, err.message);
|
|
|
+// return;
|
|
|
+// }
|
|
|
|
|
|
- let workParsed;
|
|
|
- let testjson;
|
|
|
- try {
|
|
|
- workParsed = JSON.parse(item.work);
|
|
|
- testjson = JSON.parse(item.testJson);
|
|
|
- } catch (err) {
|
|
|
- console.error("JSON 解析失败", item.id, err.message);
|
|
|
- return;
|
|
|
- }
|
|
|
+// const _work = workParsed.data || {};
|
|
|
+// const work = {
|
|
|
+// score: {},
|
|
|
+// analysis: {},
|
|
|
+// data: _work,
|
|
|
+// time: workParsed.time,
|
|
|
+// };
|
|
|
+// const _test = [];
|
|
|
+
|
|
|
+// testjson.forEach((w) => {
|
|
|
+// _test.push(w);
|
|
|
+// work.score[w.id] = 0;
|
|
|
+// });
|
|
|
+
|
|
|
+// aiBatchCorrection.push({
|
|
|
+// id: item.id,
|
|
|
+// name: item.name,
|
|
|
+// subject: item.subject,
|
|
|
+// testId: item.testId,
|
|
|
+// userId: item.userId,
|
|
|
+// work,
|
|
|
+// test: _test,
|
|
|
+// });
|
|
|
+// };
|
|
|
+
|
|
|
+// const gradeQuestionWithRetry = async (aiBatchCorrection, params, workId, questionId) => {
|
|
|
+// let lastErr;
|
|
|
+// for (let attempt = 1; attempt <= GRADE_RETRY_TIMES; attempt++) {
|
|
|
+// try {
|
|
|
+// const res = await axios.post(testUrl + "/llm-extract/analyze/grade-question", params, {
|
|
|
+// headers: { "Content-Type": "application/json" },
|
|
|
+// });
|
|
|
+// const _result = res.data?.result || res.data;
|
|
|
+// const _score = _result?.score_awarded ?? 0;
|
|
|
+// const _feedback = _result?.overall_feedback ?? "";
|
|
|
+// saveToAiBatchCorrection(aiBatchCorrection, workId, questionId, _score, _feedback);
|
|
|
+// if (attempt > 1) {
|
|
|
+// console.log(`批改重试成功 workId=${workId} questionId=${questionId} 第${attempt}次`);
|
|
|
+// }
|
|
|
+// return _result;
|
|
|
+// } catch (err) {
|
|
|
+// lastErr = err;
|
|
|
+// console.warn(
|
|
|
+// `批改失败 workId=${workId} questionId=${questionId} 第${attempt}/${GRADE_RETRY_TIMES}次`,
|
|
|
+// err.response?.data || err.message
|
|
|
+// );
|
|
|
+// if (attempt < GRADE_RETRY_TIMES) {
|
|
|
+// await sleep(GRADE_RETRY_DELAY);
|
|
|
+// }
|
|
|
+// }
|
|
|
+// }
|
|
|
+// saveToAiBatchCorrection(aiBatchCorrection, workId, questionId, 0, "批改失败");
|
|
|
+// throw lastErr;
|
|
|
+// };
|
|
|
+
|
|
|
+// const aiGradingBatchCorrection = async (aiBatchCorrection, val) => {
|
|
|
+// const promises = [];
|
|
|
+// for (let i = 0; i < val.test.length; i++) {
|
|
|
+// const _testIndex = i;
|
|
|
+// const _userAnswer = val.work.data[val.test[i].id];
|
|
|
+// const _sub_questions = [];
|
|
|
+// const _imgSrc = [];
|
|
|
+
|
|
|
+// if (val.test[i].subQuestions) {
|
|
|
+// val.test[i].subQuestions.forEach((sub, index) => {
|
|
|
+// _sub_questions.push({
|
|
|
+// number: `${index + 1}`,
|
|
|
+// stem: sub.title,
|
|
|
+// knowledge_points: sub.knowledgePoint,
|
|
|
+// standard_answer: sub.answer,
|
|
|
+// scoring_criteria: sub.scoringCriteria,
|
|
|
+// });
|
|
|
+// });
|
|
|
+// }
|
|
|
+
|
|
|
+// const params = {
|
|
|
+// question: {
|
|
|
+// number: `${_testIndex}`,
|
|
|
+// type: val.test[i].tool,
|
|
|
+// is_subjective: true,
|
|
|
+// score: val.test[i].score,
|
|
|
+// stem: val.test[i].title,
|
|
|
+// options: {},
|
|
|
+// sub_questions: _sub_questions,
|
|
|
+// knowledge_points: val.test[i].knowledgePoint ? val.test[i].knowledgePoint : [],
|
|
|
+// standard_answer: val.test[i].answer,
|
|
|
+// scoring_criteria: val.test[i].answer,
|
|
|
+// figure_description: "",
|
|
|
+// figure_bbox: {
|
|
|
+// page: 1,
|
|
|
+// x_min: 0,
|
|
|
+// y_min: 0,
|
|
|
+// x_max: 0,
|
|
|
+// y_max: 0,
|
|
|
+// },
|
|
|
+// figure_image_urls: _imgSrc,
|
|
|
+// },
|
|
|
+// student_file_urls: Array.isArray(_userAnswer) ? _userAnswer.map((u) => u.url) : [],
|
|
|
+// student_answer_text: ["fill", "qa"].includes(val.test[i].tool) ? (_userAnswer ? _userAnswer :'暂无数据') : "",
|
|
|
+// model: "gpt-5.4",
|
|
|
+// };
|
|
|
+
|
|
|
+// const questionId = val.test[i].id;
|
|
|
+// promises.push(gradeQuestionWithRetry(aiBatchCorrection, params, val.id, questionId));
|
|
|
+// }
|
|
|
|
|
|
- const _work = workParsed.data || {};
|
|
|
- const work = {
|
|
|
- score: {},
|
|
|
- analysis: {},
|
|
|
- data: _work,
|
|
|
- time: workParsed.time,
|
|
|
- };
|
|
|
- const _test = [];
|
|
|
+// const results = await Promise.allSettled(promises);
|
|
|
+// const failed = results.filter((r) => r.status === "rejected").length;
|
|
|
+// if (failed > 0) {
|
|
|
+// console.warn(`workId=${val.id} 共 ${failed}/${results.length} 道题批改失败,继续保存已有分数`);
|
|
|
+// }
|
|
|
+// return results;
|
|
|
+// };
|
|
|
+
|
|
|
+// const saveStudent = async (e) => {
|
|
|
+// const _json = {
|
|
|
+// fullScore: 0,
|
|
|
+// errorId: [],
|
|
|
+// correct: [],
|
|
|
+// total: e.test.length,
|
|
|
+// };
|
|
|
+
|
|
|
+// e.test.forEach((i) => {
|
|
|
+// if (e.work.score[i.id] == i.score) {
|
|
|
+// _json.fullScore += 1;
|
|
|
+// _json.correct.push(i.id);
|
|
|
+// } else {
|
|
|
+// _json.errorId.push(i.id);
|
|
|
+// }
|
|
|
+// });
|
|
|
+
|
|
|
+// for (const i of _json.errorId) {
|
|
|
+// let test = e.test.find((t) => t.id == i);
|
|
|
+// if (test) {
|
|
|
+// test = JSON.parse(JSON.stringify(test));
|
|
|
+// test.userAnswer = e.work.data[i];
|
|
|
+// const p = {
|
|
|
+// uid: e.userId,
|
|
|
+// sid: e.subject,
|
|
|
+// tit: test.title,
|
|
|
+// kno: test.knowledge || "",
|
|
|
+// tid: i,
|
|
|
+// json: JSON.stringify(test),
|
|
|
+// clist: _json.correct.join(","),
|
|
|
+// };
|
|
|
+
|
|
|
+// try {
|
|
|
+// const res = await callMysqlProc("addcocostudyminbook", p);
|
|
|
+// console.log("addcocostudyminbook res", res);
|
|
|
+// } catch (err) {
|
|
|
+// console.log("addcocostudyminbook err", err.message || err);
|
|
|
+// }
|
|
|
+// }
|
|
|
+// }
|
|
|
|
|
|
- testjson.forEach((w) => {
|
|
|
- _test.push(w);
|
|
|
- work.score[w.id] = 0;
|
|
|
- });
|
|
|
+// const params = {
|
|
|
+// id: e.id,
|
|
|
+// status: 2,
|
|
|
+// test: JSON.stringify(e.work),
|
|
|
+// json: JSON.stringify(_json),
|
|
|
+// };
|
|
|
+
|
|
|
+// const res = await callMysqlProc("updateCocostudysco", params);
|
|
|
+// console.log("updateCocostudysco res", res);
|
|
|
+// if (res === 1) {
|
|
|
+// try {
|
|
|
+// const spaceRes = await callMysqlProc("addcocostudySpacetea", {
|
|
|
+// uid: e.userId,
|
|
|
+// rid: e.testId,
|
|
|
+// tit: e.name,
|
|
|
+// });
|
|
|
+// const spaceId = spaceRes[0][0].id;
|
|
|
+// console.log("addcocostudySpacetea res", spaceRes, "spaceId", spaceId);
|
|
|
+// if (spaceId) {
|
|
|
+// return { userId: e.userId, testId: e.testId, spaceId, workId: e.id };
|
|
|
+// }
|
|
|
+// } catch (err) {
|
|
|
+// console.log("addcocostudySpacetea err", err.message || err);
|
|
|
+// }
|
|
|
+// }
|
|
|
+// return null;
|
|
|
+// };
|
|
|
|
|
|
- aiBatchCorrection.push({
|
|
|
- id: item.id,
|
|
|
- name: item.name,
|
|
|
- subject: item.subject,
|
|
|
- testId: item.testId,
|
|
|
- userId: item.userId,
|
|
|
- work,
|
|
|
- test: _test,
|
|
|
- });
|
|
|
-};
|
|
|
+// const runAutoSco = async () => {
|
|
|
+// console.log("[cocostudy] 自动批改开始", new Date().toISOString());
|
|
|
+// const aiBatchCorrection = [];
|
|
|
+// const data = await queryAutoScoData();
|
|
|
|
|
|
-const gradeQuestionWithRetry = async (aiBatchCorrection, params, workId, questionId) => {
|
|
|
- let lastErr;
|
|
|
- for (let attempt = 1; attempt <= GRADE_RETRY_TIMES; attempt++) {
|
|
|
- try {
|
|
|
- const res = await axios.post(testUrl + "/llm-extract/analyze/grade-question", params, {
|
|
|
- headers: { "Content-Type": "application/json" },
|
|
|
- });
|
|
|
- const _result = res.data?.result || res.data;
|
|
|
- const _score = _result?.score_awarded ?? 0;
|
|
|
- const _feedback = _result?.overall_feedback ?? "";
|
|
|
- saveToAiBatchCorrection(aiBatchCorrection, workId, questionId, _score, _feedback);
|
|
|
- if (attempt > 1) {
|
|
|
- console.log(`批改重试成功 workId=${workId} questionId=${questionId} 第${attempt}次`);
|
|
|
- }
|
|
|
- return _result;
|
|
|
- } catch (err) {
|
|
|
- lastErr = err;
|
|
|
- console.warn(
|
|
|
- `批改失败 workId=${workId} questionId=${questionId} 第${attempt}/${GRADE_RETRY_TIMES}次`,
|
|
|
- err.response?.data || err.message
|
|
|
- );
|
|
|
- if (attempt < GRADE_RETRY_TIMES) {
|
|
|
- await sleep(GRADE_RETRY_DELAY);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- saveToAiBatchCorrection(aiBatchCorrection, workId, questionId, 0, "批改失败");
|
|
|
- throw lastErr;
|
|
|
-};
|
|
|
+// if (!data.length) {
|
|
|
+// console.log("[cocostudy] 没有待批改数据");
|
|
|
+// return { count: 0 };
|
|
|
+// }
|
|
|
|
|
|
-const aiGradingBatchCorrection = async (aiBatchCorrection, val) => {
|
|
|
- const promises = [];
|
|
|
- for (let i = 0; i < val.test.length; i++) {
|
|
|
- const _testIndex = i;
|
|
|
- const _userAnswer = val.work.data[val.test[i].id];
|
|
|
- const _sub_questions = [];
|
|
|
- const _imgSrc = [];
|
|
|
-
|
|
|
- if (val.test[i].subQuestions) {
|
|
|
- val.test[i].subQuestions.forEach((sub, index) => {
|
|
|
- _sub_questions.push({
|
|
|
- number: `${index + 1}`,
|
|
|
- stem: sub.title,
|
|
|
- knowledge_points: sub.knowledgePoint,
|
|
|
- standard_answer: sub.answer,
|
|
|
- scoring_criteria: sub.scoringCriteria,
|
|
|
- });
|
|
|
- });
|
|
|
- }
|
|
|
+// data.forEach((item) => aiBatchCorrectionBtn(item, aiBatchCorrection));
|
|
|
+
|
|
|
+// // 1. 批改:全部并发
|
|
|
+// const gradeResults = await Promise.allSettled(
|
|
|
+// aiBatchCorrection.map((e) => aiGradingBatchCorrection(aiBatchCorrection, e))
|
|
|
+// );
|
|
|
+// gradeResults.forEach((result, index) => {
|
|
|
+// const item = aiBatchCorrection[index];
|
|
|
+// if (result.status === "rejected") {
|
|
|
+// console.error(`学生批改异常 workId=${item.id}`, result.reason?.message || result.reason);
|
|
|
+// }
|
|
|
+// });
|
|
|
+
|
|
|
+// // 2. 保存:全部并发(与错题分析解耦)
|
|
|
+// const saveResults = await Promise.allSettled(
|
|
|
+// aiBatchCorrection.map(async (e) => {
|
|
|
+// const meta = await saveStudent(e);
|
|
|
+// console.log(`保存成功 workId=${e.id}`);
|
|
|
+// return meta;
|
|
|
+// })
|
|
|
+// );
|
|
|
+
|
|
|
+// const analysisJobs = [];
|
|
|
+// saveResults.forEach((result, index) => {
|
|
|
+// const item = aiBatchCorrection[index];
|
|
|
+// if (result.status === "rejected") {
|
|
|
+// console.error(`保存失败 workId=${item.id}`, result.reason?.message || result.reason);
|
|
|
+// return;
|
|
|
+// }
|
|
|
+// if (result.value) {
|
|
|
+// analysisJobs.push(result.value);
|
|
|
+// }
|
|
|
+// });
|
|
|
+
|
|
|
+// // 3. 错题分析:全部并发
|
|
|
+// if (analysisJobs.length) {
|
|
|
+// console.log(`[cocostudy] 开始错题分析 ${analysisJobs.length} 条(全部并发)`);
|
|
|
+// const analysisResults = await Promise.allSettled(
|
|
|
+// analysisJobs.map(async ({ userId, testId, spaceId, workId }) => {
|
|
|
+// await getRidData(userId, testId, spaceId);
|
|
|
+// console.log(`错题分析完成 workId=${workId}`);
|
|
|
+// return workId;
|
|
|
+// })
|
|
|
+// );
|
|
|
+// analysisResults.forEach((result, index) => {
|
|
|
+// const job = analysisJobs[index];
|
|
|
+// if (result.status === "rejected") {
|
|
|
+// console.error(
|
|
|
+// `错题分析失败 workId=${job.workId}`,
|
|
|
+// result.reason?.message || result.reason
|
|
|
+// );
|
|
|
+// }
|
|
|
+// });
|
|
|
+// }
|
|
|
|
|
|
- const params = {
|
|
|
- question: {
|
|
|
- number: `${_testIndex}`,
|
|
|
- type: val.test[i].tool,
|
|
|
- is_subjective: true,
|
|
|
- score: val.test[i].score,
|
|
|
- stem: val.test[i].title,
|
|
|
- options: {},
|
|
|
- sub_questions: _sub_questions,
|
|
|
- knowledge_points: val.test[i].knowledgePoint ? val.test[i].knowledgePoint : [],
|
|
|
- standard_answer: val.test[i].answer,
|
|
|
- scoring_criteria: val.test[i].answer,
|
|
|
- figure_description: "",
|
|
|
- figure_bbox: {
|
|
|
- page: 1,
|
|
|
- x_min: 0,
|
|
|
- y_min: 0,
|
|
|
- x_max: 0,
|
|
|
- y_max: 0,
|
|
|
- },
|
|
|
- figure_image_urls: _imgSrc,
|
|
|
- },
|
|
|
- student_file_urls: Array.isArray(_userAnswer) ? _userAnswer.map((u) => u.url) : [],
|
|
|
- student_answer_text: ["fill", "qa", "choice"].includes(val.test[i].tool) ? _userAnswer : "",
|
|
|
- model: "gpt-5.4",
|
|
|
- };
|
|
|
-
|
|
|
- const questionId = val.test[i].id;
|
|
|
- promises.push(gradeQuestionWithRetry(aiBatchCorrection, params, val.id, questionId));
|
|
|
- }
|
|
|
+// console.log("[cocostudy] 全部处理完成", new Date().toISOString());
|
|
|
+// return { count: aiBatchCorrection.length };
|
|
|
+// };
|
|
|
|
|
|
- const results = await Promise.allSettled(promises);
|
|
|
- const failed = results.filter((r) => r.status === "rejected").length;
|
|
|
- if (failed > 0) {
|
|
|
- console.warn(`workId=${val.id} 共 ${failed}/${results.length} 道题批改失败,继续保存已有分数`);
|
|
|
- }
|
|
|
- return results;
|
|
|
-};
|
|
|
+let isRunning = false;
|
|
|
|
|
|
-const saveStudent = async (e) => {
|
|
|
- const _json = {
|
|
|
- fullScore: 0,
|
|
|
- errorId: [],
|
|
|
- correct: [],
|
|
|
- total: e.test.length,
|
|
|
- };
|
|
|
+// const runAutoScoSafe = async () => {
|
|
|
+// if (isRunning) {
|
|
|
+// console.warn("[cocostudy] 上一次自动批改/分析仍在执行,跳过本次");
|
|
|
+// return;
|
|
|
+// }
|
|
|
+// isRunning = true;
|
|
|
+// try {
|
|
|
+// return await runAutoSco();
|
|
|
+// } catch (err) {
|
|
|
+// console.error("[cocostudy] 自动批改异常", err.message || err);
|
|
|
+// throw err;
|
|
|
+// } finally {
|
|
|
+// isRunning = false;
|
|
|
+// }
|
|
|
+// };
|
|
|
+let agent1a = null;
|
|
|
+let agent1b = null;
|
|
|
+let agent2a = null;
|
|
|
+let agent1c = null;
|
|
|
|
|
|
- e.test.forEach((i) => {
|
|
|
- if (e.work.score[i.id] == i.score) {
|
|
|
- _json.fullScore += 1;
|
|
|
- _json.correct.push(i.id);
|
|
|
- } else {
|
|
|
- _json.errorId.push(i.id);
|
|
|
- }
|
|
|
- });
|
|
|
+const AGENT_IDS = {
|
|
|
+ // 1a
|
|
|
+ errorAnalysis: "f100cb78-8053-4f5b-9589-cb3a74d1b4da",
|
|
|
+ // 1b
|
|
|
+ lectureOutline: "08028b8b-b067-43c3-b8f5-b3a874f0a502",
|
|
|
+ // 1c
|
|
|
+ oneCExplanation: "cc43167c-e767-4f81-8a73-b0295de49472",
|
|
|
+ // 2a
|
|
|
+ detailed: "a000e7e9-d8f0-47d0-98e6-f5b1c6171934",
|
|
|
+};
|
|
|
|
|
|
- for (const i of _json.errorId) {
|
|
|
- let test = e.test.find((t) => t.id == i);
|
|
|
- if (test) {
|
|
|
- test = JSON.parse(JSON.stringify(test));
|
|
|
- test.userAnswer = e.work.data[i];
|
|
|
- const p = {
|
|
|
- uid: e.userId,
|
|
|
- sid: e.subject,
|
|
|
- tit: test.title,
|
|
|
- kno: test.knowledge || "",
|
|
|
- tid: i,
|
|
|
- json: JSON.stringify(test),
|
|
|
- clist: _json.correct.join(","),
|
|
|
- };
|
|
|
-
|
|
|
- try {
|
|
|
- const res = await callMysqlProc("addcocostudyminbook", p);
|
|
|
- console.log("addcocostudyminbook res", res);
|
|
|
- } catch (err) {
|
|
|
- console.log("addcocostudyminbook err", err.message || err);
|
|
|
- }
|
|
|
- }
|
|
|
+const tryParseJson = (text) => {
|
|
|
+ try {
|
|
|
+ return JSON.parse(text);
|
|
|
+ } catch (_) {
|
|
|
+ return undefined;
|
|
|
}
|
|
|
+};
|
|
|
|
|
|
- const params = {
|
|
|
- id: e.id,
|
|
|
- status: 2,
|
|
|
- test: JSON.stringify(e.work),
|
|
|
- json: JSON.stringify(_json),
|
|
|
- };
|
|
|
-
|
|
|
- const res = await callMysqlProc("updateCocostudysco", params);
|
|
|
- console.log("updateCocostudysco res", res);
|
|
|
- if (res === 1) {
|
|
|
- try {
|
|
|
- const spaceRes = await callMysqlProc("addcocostudySpacetea", {
|
|
|
- uid: e.userId,
|
|
|
- rid: e.testId,
|
|
|
- tit: e.name,
|
|
|
- });
|
|
|
- const spaceId = spaceRes[0][0].id;
|
|
|
- console.log("addcocostudySpacetea res", spaceRes, "spaceId", spaceId);
|
|
|
- if (spaceId) {
|
|
|
- return getRidData(e.userId, e.testId, spaceId);
|
|
|
- }
|
|
|
- } catch (err) {
|
|
|
- console.log("addcocostudySpacetea err", err.message || err);
|
|
|
- }
|
|
|
- }
|
|
|
- return null;
|
|
|
+const normalizeParsedAgentMessage = (parsed) => {
|
|
|
+ if (Array.isArray(parsed)) return parsed;
|
|
|
+ if (parsed && typeof parsed === "object") return [parsed];
|
|
|
+ return parsed;
|
|
|
};
|
|
|
|
|
|
-const runAutoSco = async () => {
|
|
|
- console.log("[cocostudy] 自动批改开始", new Date().toISOString());
|
|
|
- const aiBatchCorrection = [];
|
|
|
- const data = await queryAutoScoData();
|
|
|
+const collectJsonCandidates = (text) => {
|
|
|
+ const candidates = [];
|
|
|
+ const seen = new Set();
|
|
|
+ const push = (value) => {
|
|
|
+ const next = (value || "").trim();
|
|
|
+ if (!next || seen.has(next)) return;
|
|
|
+ seen.add(next);
|
|
|
+ candidates.push(next);
|
|
|
+ };
|
|
|
|
|
|
- if (!data.length) {
|
|
|
- console.log("[cocostudy] 没有待批改数据");
|
|
|
- return { count: 0 };
|
|
|
+ const fenceRe = /```(?:json)?\s*([\s\S]*?)\s*```/gi;
|
|
|
+ let match;
|
|
|
+ while ((match = fenceRe.exec(text)) !== null) {
|
|
|
+ push(match[1]);
|
|
|
}
|
|
|
|
|
|
- data.forEach((item) => aiBatchCorrectionBtn(item, aiBatchCorrection));
|
|
|
+ push(text);
|
|
|
|
|
|
- const gradeResults = await Promise.allSettled(
|
|
|
- aiBatchCorrection.map((e) => aiGradingBatchCorrection(aiBatchCorrection, e))
|
|
|
- );
|
|
|
- gradeResults.forEach((result, index) => {
|
|
|
- const item = aiBatchCorrection[index];
|
|
|
- if (result.status === "rejected") {
|
|
|
- console.error(`学生批改异常 workId=${item.id}`, result.reason?.message || result.reason);
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- for (const e of aiBatchCorrection) {
|
|
|
- try {
|
|
|
- const analysisTask = await saveStudent(e);
|
|
|
- console.log(`保存成功 workId=${e.id}`);
|
|
|
- if (analysisTask) {
|
|
|
- try {
|
|
|
- await analysisTask;
|
|
|
- console.log(`错题分析完成 workId=${e.id}`);
|
|
|
- } catch (err) {
|
|
|
- console.error(`错题分析失败 workId=${e.id}`, err.message || err);
|
|
|
- }
|
|
|
- }
|
|
|
- } catch (err) {
|
|
|
- console.error(`保存失败 workId=${e.id}`, err.message || err);
|
|
|
+ for (const startChar of ["{", "["]) {
|
|
|
+ let idx = text.indexOf(startChar);
|
|
|
+ while (idx !== -1) {
|
|
|
+ push(text.slice(idx));
|
|
|
+ idx = text.indexOf(startChar, idx + 1);
|
|
|
+ if (candidates.length > 30) break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- console.log("[cocostudy] 全部处理完成", new Date().toISOString());
|
|
|
- return { count: aiBatchCorrection.length };
|
|
|
-};
|
|
|
-
|
|
|
-let isRunning = false;
|
|
|
-
|
|
|
-const runAutoScoSafe = async () => {
|
|
|
- if (isRunning) {
|
|
|
- console.warn("[cocostudy] 上一次自动批改/分析仍在执行,跳过本次");
|
|
|
- return;
|
|
|
- }
|
|
|
- isRunning = true;
|
|
|
- try {
|
|
|
- return await runAutoSco();
|
|
|
- } catch (err) {
|
|
|
- console.error("[cocostudy] 自动批改异常", err.message || err);
|
|
|
- throw err;
|
|
|
- } finally {
|
|
|
- isRunning = false;
|
|
|
- }
|
|
|
-};
|
|
|
-let agent1a = null;
|
|
|
-let agent2a = null;
|
|
|
-let agent_data2 = null;
|
|
|
-
|
|
|
-const AGENT_IDS = {
|
|
|
- detailed: "fe58652f-d32e-4aec-ba8e-fc5a3398ac96",
|
|
|
- errorAnalysis: "f100cb78-8053-4f5b-9589-cb3a74d1b4da",
|
|
|
- lectureOutline: "ccb95f55-bf4e-4132-b342-07dfe5bb759e",
|
|
|
+ return candidates;
|
|
|
};
|
|
|
|
|
|
const parseAgentJsonMessage = (raw) => {
|
|
|
if (Array.isArray(raw)) return raw;
|
|
|
- if (typeof raw === "string") {
|
|
|
- const text = raw.trim();
|
|
|
- const fenced = text.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
|
|
|
- const jsonText = (fenced && fenced[1] ? fenced[1] : text).trim();
|
|
|
- return JSON.parse(jsonText);
|
|
|
- }
|
|
|
if (raw && typeof raw === "object") return [raw];
|
|
|
- return [];
|
|
|
+ if (typeof raw !== "string") return [];
|
|
|
+
|
|
|
+ const text = raw.trim();
|
|
|
+ if (!text) return [];
|
|
|
+
|
|
|
+ for (const candidate of collectJsonCandidates(text)) {
|
|
|
+ const parsed = tryParseJson(candidate);
|
|
|
+ if (parsed !== undefined) {
|
|
|
+ return normalizeParsedAgentMessage(parsed);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ console.warn(
|
|
|
+ "[cocostudy] Agent 返回非 JSON,原样传递下游:",
|
|
|
+ text.slice(0, 120).replace(/\s+/g, " ")
|
|
|
+ );
|
|
|
+ return text;
|
|
|
};
|
|
|
|
|
|
const loadAgents = async () => {
|
|
|
- if (agent1a && agent2a && agent_data2) return;
|
|
|
- const [detailedRes, errorRes, outlineRes] = await Promise.all([
|
|
|
+ if (agent1a && agent1b && agent2a && agent1c) return;
|
|
|
+ const [detailedRes, errorRes, outlineRes, oneCExplanationRes] = await Promise.all([
|
|
|
axios.get(`https://appapi.cocorobo.cn/api/agents/agent/${AGENT_IDS.detailed}`),
|
|
|
axios.get(`https://appapi.cocorobo.cn/api/agents/agent/${AGENT_IDS.errorAnalysis}`),
|
|
|
axios.get(`https://appapi.cocorobo.cn/api/agents/agent/${AGENT_IDS.lectureOutline}`),
|
|
|
+ axios.get(`https://appapi.cocorobo.cn/api/agents/agent/${AGENT_IDS.oneCExplanation}`),
|
|
|
]);
|
|
|
- agent_data2 = detailedRes.data;
|
|
|
+ agent2a = detailedRes.data;
|
|
|
agent1a = errorRes.data;
|
|
|
- agent2a = outlineRes.data;
|
|
|
+ agent1b = outlineRes.data;
|
|
|
+ agent1c = oneCExplanationRes.data;
|
|
|
};
|
|
|
|
|
|
const updateSpaceAnalyzeStatus = async (spaceId, isl) => {
|
|
|
@@ -461,10 +549,10 @@ async function getRidData(userId, testId, spaceId) {
|
|
|
const quizSummary = buildQuizSummary(testRow, workData, questions);
|
|
|
|
|
|
console.log("[cocostudy] 开始错题分析", { userId, testId, spaceId });
|
|
|
- await generateErrorAnalysis(quizSummary, userId, spaceId);
|
|
|
+ await generate1a(quizSummary, userId, spaceId);
|
|
|
}
|
|
|
|
|
|
-async function generateErrorAnalysis(quizSummary, userId, spaceId) {
|
|
|
+async function generate1a(quizSummary, userId, spaceId) {
|
|
|
const raw = await callAgentChat(
|
|
|
agent1a,
|
|
|
`题目数据:\n${JSON.stringify(quizSummary)}`,
|
|
|
@@ -473,32 +561,55 @@ async function generateErrorAnalysis(quizSummary, userId, spaceId) {
|
|
|
spaceId
|
|
|
);
|
|
|
const errorAnalysis = parseAgentJsonMessage(raw);
|
|
|
- await generateLectureOutline(quizSummary, errorAnalysis, userId, spaceId);
|
|
|
+ await generate1b(quizSummary, errorAnalysis, userId, spaceId);
|
|
|
}
|
|
|
|
|
|
-async function generateLectureOutline(quizSummary, errorAnalysis, userId, spaceId) {
|
|
|
- const lectureOutline = await callAgentChat(
|
|
|
- agent2a,
|
|
|
+async function generate1b(quizSummary, errorAnalysis, userId, spaceId) {
|
|
|
+ const lectureResponse = await callAgentChat(
|
|
|
+ agent1b,
|
|
|
`==\n## 统计性信息如下:\n${JSON.stringify(quizSummary)}\n\n==\n\n## 错题深度分析如下:\n${JSON.stringify(errorAnalysis)}`,
|
|
|
userId,
|
|
|
"讲义大纲",
|
|
|
spaceId
|
|
|
);
|
|
|
- await generatedetailedExplanation(quizSummary, errorAnalysis, lectureOutline, userId, spaceId);
|
|
|
+ const lectureOutline = parseAgentJsonMessage(lectureResponse);
|
|
|
+ await generate1c(quizSummary, errorAnalysis, lectureOutline, userId, spaceId);
|
|
|
+}
|
|
|
+async function generate1c(quizSummary, errorAnalysis, lectureOutline, userId, spaceId) {
|
|
|
+ const oneCExplanation = await callAgentChat(
|
|
|
+ agent1c,
|
|
|
+ `==\n## 统计性信息如下:\n${JSON.stringify(quizSummary)}\n\n==\n\n## 错题深度分析如下:\n${JSON.stringify(errorAnalysis)}\n\n==\n\n## 讲义大纲如下:\n${JSON.stringify(lectureOutline)}`,
|
|
|
+ userId,
|
|
|
+ "讲解生成",
|
|
|
+ spaceId
|
|
|
+ );
|
|
|
+ await generatedetailedExplanation(quizSummary, errorAnalysis, lectureOutline, oneCExplanation, userId, spaceId);
|
|
|
}
|
|
|
|
|
|
-async function generatedetailedExplanation(quizSummary, errorAnalysis, lectureOutline, userId, spaceId) {
|
|
|
+async function generatedetailedExplanation(quizSummary, errorAnalysis, lectureOutline, oneCExplanation, userId, spaceId) {
|
|
|
const quizSummaryString = JSON.stringify(quizSummary).replace(/\\(?![\\"/])/g, "\\\\");
|
|
|
- const displayContent =
|
|
|
- "统计性信息:" +
|
|
|
- quizSummaryString +
|
|
|
- "\n错题深度分析:" +
|
|
|
- JSON.stringify(errorAnalysis) +
|
|
|
- "\n生成的Agenda顺序:" +
|
|
|
- JSON.stringify(lectureOutline);
|
|
|
-
|
|
|
- const answer = await callAgentChat(agent_data2, displayContent, userId, "详细讲解", spaceId);
|
|
|
+ const errorAnalysisString = JSON.stringify(errorAnalysis).replace(/\\(?![\\"/])/g, "\\\\");
|
|
|
+ const lectureOutlineString = JSON.stringify(lectureOutline).replace(/\\(?![\\"/])/g, "\\\\");
|
|
|
+ const oneCExplanationString = String(oneCExplanation ?? "");
|
|
|
+ const displayContent = `==\n## 统计性信息如下:\n${quizSummaryString}\n\n==\n\n## 错题深度分析如下:\n${errorAnalysisString}\n\n==\n\n## 讲解大纲如下:\n${lectureOutlineString}\n\n==\n\n## 详细讲解如下:\n${oneCExplanationString}`;
|
|
|
+
|
|
|
+ const answer = await callAgentChat(agent2a, displayContent, userId, "详细讲解", spaceId);
|
|
|
await insertChat(answer, spaceId, userId);
|
|
|
+
|
|
|
+ let json = {
|
|
|
+ quizSummary: quizSummaryString,
|
|
|
+ errorAnalysis: errorAnalysisString,
|
|
|
+ lectureOutline: lectureOutlineString,
|
|
|
+ detailedExplanation: oneCExplanationString,
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ await callMysqlProc("updatecocostudySpacejson", {
|
|
|
+ id: spaceId,
|
|
|
+ json: encodeURIComponent(JSON.stringify(json)),
|
|
|
+ });
|
|
|
+ } catch (err) {
|
|
|
+ console.error("[cocostudy] updatecocostudySpacejson err", err.message || err);
|
|
|
+ }
|
|
|
await updateSpaceAnalyzeStatus(spaceId, 1);
|
|
|
|
|
|
console.log("[cocostudy] 错题分析完成", { userId, spaceId });
|
|
|
@@ -534,15 +645,15 @@ async function insertChat(answer, spaceId, userId) {
|
|
|
// }
|
|
|
// });
|
|
|
|
|
|
-// 手动触发:GET/POST /api/cocostudy/autosco
|
|
|
-router.all("/autosco", async (req, res) => {
|
|
|
- try {
|
|
|
- const result = await runAutoScoSafe();
|
|
|
- res.json({ code: 200, msg: "全部处理完成", data: result });
|
|
|
- } catch (err) {
|
|
|
- res.status(500).json({ code: 500, msg: err.message || "自动批改失败" });
|
|
|
- }
|
|
|
-});
|
|
|
+// 手动触发:GET/POST /api/cocostudy/autosco(暂不开放)
|
|
|
+// router.all("/autosco", async (req, res) => {
|
|
|
+// try {
|
|
|
+// const result = await runAutoScoSafe();
|
|
|
+// res.json({ code: 200, msg: "全部处理完成", data: result });
|
|
|
+// } catch (err) {
|
|
|
+// res.status(500).json({ code: 500, msg: err.message || "自动批改失败" });
|
|
|
+// }
|
|
|
+// });
|
|
|
|
|
|
module.exports = router;
|
|
|
module.exports.getRidData = getRidData;
|