Bläddra i källkod

修改分析添加1c

11wqe1 1 vecka sedan
förälder
incheckning
a0f1bec042
2 ändrade filer med 411 tillägg och 295 borttagningar
  1. 406 295
      cocostudy.js
  2. 5 0
      pbl.js

+ 406 - 295
cocostudy.js

@@ -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;

+ 5 - 0
pbl.js

@@ -1733,6 +1733,11 @@ router.route("/addcocostudyminbook").all((req, res, next) => {
     postmysql(req, res, "addcocostudyminbook");
 });
 
+// 自主学习更新学习空间json
+router.route("/updatecocostudySpacejson").all((req, res, next) => {
+    postmysql(req, res, "updatecocostudySpacejson");
+});
+
 // 自主学习移除错题 yym
 router.route("/removecocostudyminbook").all((req, res, next) => {
     postmysql(req, res, "removecocostudyminbook");