cocostudy.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659
  1. const express = require("express");
  2. const axios = require("axios");
  3. const schedule = require("node-schedule");
  4. const mysql = require("./mysql");
  5. const router = express.Router();
  6. const crypto = require("crypto");
  7. // 本地
  8. // const _mysqlLabor = ["183.36.26.8", "pbl"];
  9. // const _mysqluser = ["183.36.26.8", "cocorobouser"];
  10. // const _getmysqlLabor2 = ["183.36.26.8", "pbl"];
  11. // const _getmysqlLabor = ["183.36.26.8", "pbl"];
  12. // 线上
  13. const _mysqlLabor = ["172.16.12.5", "pbl"];
  14. // const _mysqluser = ["172.16.12.5", "cocorobouser"];
  15. // const _getmysqlLabor2 = ["172.16.12.7", "pbl"];
  16. // const _getmysqlLabor = ["172.16.12.7", "pbl"];
  17. const testUrl = "https://test-paper-analyzer.cocorobo.cn";
  18. const GRADE_RETRY_TIMES = 3;
  19. const GRADE_RETRY_DELAY = 2000;
  20. const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
  21. const saveToAiBatchCorrection = (aiBatchCorrection, workId, questionId, score, feedback) => {
  22. const target = aiBatchCorrection.find((item) => item.id === workId);
  23. if (!target) {
  24. console.warn(`未找到 aiBatchCorrection 记录 workId=${workId}`);
  25. return;
  26. }
  27. target.work.score[questionId] = score;
  28. target.work.analysis[questionId] = feedback;
  29. };
  30. const callMysqlProc = (procedureName, data) => {
  31. return new Promise((resolve, reject) => {
  32. const p = [_mysqlLabor[0], _mysqlLabor[1], procedureName, ...Object.values(data)];
  33. mysql.usselect(p, (ret) => {
  34. if (ret instanceof Error || (ret && ret.code)) {
  35. reject(ret);
  36. return;
  37. }
  38. resolve(ret);
  39. });
  40. });
  41. };
  42. // const queryAutoScoData = () => {
  43. // return new Promise((resolve, reject) => {
  44. // const p = [];
  45. // p.unshift(_mysqlLabor[0], _mysqlLabor[1], "getcocostudyautosco");
  46. // mysql.usselect(p, (ret) => {
  47. // if (ret instanceof Error || (ret && ret.code)) {
  48. // reject(ret);
  49. // return;
  50. // }
  51. // resolve(ret[0] || []);
  52. // });
  53. // });
  54. // };
  55. // const aiBatchCorrectionBtn = (item, aiBatchCorrection) => {
  56. // if (!item?.work || !item?.testJson) {
  57. // console.warn("缺少 work 或 testJson,跳过", item?.id);
  58. // return;
  59. // }
  60. // let workParsed;
  61. // let testjson;
  62. // try {
  63. // workParsed = JSON.parse(item.work);
  64. // testjson = JSON.parse(item.testJson);
  65. // } catch (err) {
  66. // console.error("JSON 解析失败", item.id, err.message);
  67. // return;
  68. // }
  69. // const _work = workParsed.data || {};
  70. // const work = {
  71. // score: {},
  72. // analysis: {},
  73. // data: _work,
  74. // time: workParsed.time,
  75. // };
  76. // const _test = [];
  77. // testjson.forEach((w) => {
  78. // _test.push(w);
  79. // work.score[w.id] = 0;
  80. // });
  81. // aiBatchCorrection.push({
  82. // id: item.id,
  83. // name: item.name,
  84. // subject: item.subject,
  85. // testId: item.testId,
  86. // userId: item.userId,
  87. // work,
  88. // test: _test,
  89. // });
  90. // };
  91. // const gradeQuestionWithRetry = async (aiBatchCorrection, params, workId, questionId) => {
  92. // let lastErr;
  93. // for (let attempt = 1; attempt <= GRADE_RETRY_TIMES; attempt++) {
  94. // try {
  95. // const res = await axios.post(testUrl + "/llm-extract/analyze/grade-question", params, {
  96. // headers: { "Content-Type": "application/json" },
  97. // });
  98. // const _result = res.data?.result || res.data;
  99. // const _score = _result?.score_awarded ?? 0;
  100. // const _feedback = _result?.overall_feedback ?? "";
  101. // saveToAiBatchCorrection(aiBatchCorrection, workId, questionId, _score, _feedback);
  102. // if (attempt > 1) {
  103. // console.log(`批改重试成功 workId=${workId} questionId=${questionId} 第${attempt}次`);
  104. // }
  105. // return _result;
  106. // } catch (err) {
  107. // lastErr = err;
  108. // console.warn(
  109. // `批改失败 workId=${workId} questionId=${questionId} 第${attempt}/${GRADE_RETRY_TIMES}次`,
  110. // err.response?.data || err.message
  111. // );
  112. // if (attempt < GRADE_RETRY_TIMES) {
  113. // await sleep(GRADE_RETRY_DELAY);
  114. // }
  115. // }
  116. // }
  117. // saveToAiBatchCorrection(aiBatchCorrection, workId, questionId, 0, "批改失败");
  118. // throw lastErr;
  119. // };
  120. // const aiGradingBatchCorrection = async (aiBatchCorrection, val) => {
  121. // const promises = [];
  122. // for (let i = 0; i < val.test.length; i++) {
  123. // const _testIndex = i;
  124. // const _userAnswer = val.work.data[val.test[i].id];
  125. // const _sub_questions = [];
  126. // const _imgSrc = [];
  127. // if (val.test[i].subQuestions) {
  128. // val.test[i].subQuestions.forEach((sub, index) => {
  129. // _sub_questions.push({
  130. // number: `${index + 1}`,
  131. // stem: sub.title,
  132. // knowledge_points: sub.knowledgePoint,
  133. // standard_answer: sub.answer,
  134. // scoring_criteria: sub.scoringCriteria,
  135. // });
  136. // });
  137. // }
  138. // const params = {
  139. // question: {
  140. // number: `${_testIndex}`,
  141. // type: val.test[i].tool,
  142. // is_subjective: true,
  143. // score: val.test[i].score,
  144. // stem: val.test[i].title,
  145. // options: {},
  146. // sub_questions: _sub_questions,
  147. // knowledge_points: val.test[i].knowledgePoint ? val.test[i].knowledgePoint : [],
  148. // standard_answer: val.test[i].answer,
  149. // scoring_criteria: val.test[i].answer,
  150. // figure_description: "",
  151. // figure_bbox: {
  152. // page: 1,
  153. // x_min: 0,
  154. // y_min: 0,
  155. // x_max: 0,
  156. // y_max: 0,
  157. // },
  158. // figure_image_urls: _imgSrc,
  159. // },
  160. // student_file_urls: Array.isArray(_userAnswer) ? _userAnswer.map((u) => u.url) : [],
  161. // student_answer_text: ["fill", "qa"].includes(val.test[i].tool) ? (_userAnswer ? _userAnswer :'暂无数据') : "",
  162. // model: "gpt-5.4",
  163. // };
  164. // const questionId = val.test[i].id;
  165. // promises.push(gradeQuestionWithRetry(aiBatchCorrection, params, val.id, questionId));
  166. // }
  167. // const results = await Promise.allSettled(promises);
  168. // const failed = results.filter((r) => r.status === "rejected").length;
  169. // if (failed > 0) {
  170. // console.warn(`workId=${val.id} 共 ${failed}/${results.length} 道题批改失败,继续保存已有分数`);
  171. // }
  172. // return results;
  173. // };
  174. // const saveStudent = async (e) => {
  175. // const _json = {
  176. // fullScore: 0,
  177. // errorId: [],
  178. // correct: [],
  179. // total: e.test.length,
  180. // };
  181. // e.test.forEach((i) => {
  182. // if (e.work.score[i.id] == i.score) {
  183. // _json.fullScore += 1;
  184. // _json.correct.push(i.id);
  185. // } else {
  186. // _json.errorId.push(i.id);
  187. // }
  188. // });
  189. // for (const i of _json.errorId) {
  190. // let test = e.test.find((t) => t.id == i);
  191. // if (test) {
  192. // test = JSON.parse(JSON.stringify(test));
  193. // test.userAnswer = e.work.data[i];
  194. // const p = {
  195. // uid: e.userId,
  196. // sid: e.subject,
  197. // tit: test.title,
  198. // kno: test.knowledge || "",
  199. // tid: i,
  200. // json: JSON.stringify(test),
  201. // clist: _json.correct.join(","),
  202. // };
  203. // try {
  204. // const res = await callMysqlProc("addcocostudyminbook", p);
  205. // console.log("addcocostudyminbook res", res);
  206. // } catch (err) {
  207. // console.log("addcocostudyminbook err", err.message || err);
  208. // }
  209. // }
  210. // }
  211. // const params = {
  212. // id: e.id,
  213. // status: 2,
  214. // test: JSON.stringify(e.work),
  215. // json: JSON.stringify(_json),
  216. // };
  217. // const res = await callMysqlProc("updateCocostudysco", params);
  218. // console.log("updateCocostudysco res", res);
  219. // if (res === 1) {
  220. // try {
  221. // const spaceRes = await callMysqlProc("addcocostudySpacetea", {
  222. // uid: e.userId,
  223. // rid: e.testId,
  224. // tit: e.name,
  225. // });
  226. // const spaceId = spaceRes[0][0].id;
  227. // console.log("addcocostudySpacetea res", spaceRes, "spaceId", spaceId);
  228. // if (spaceId) {
  229. // return { userId: e.userId, testId: e.testId, spaceId, workId: e.id };
  230. // }
  231. // } catch (err) {
  232. // console.log("addcocostudySpacetea err", err.message || err);
  233. // }
  234. // }
  235. // return null;
  236. // };
  237. // const runAutoSco = async () => {
  238. // console.log("[cocostudy] 自动批改开始", new Date().toISOString());
  239. // const aiBatchCorrection = [];
  240. // const data = await queryAutoScoData();
  241. // if (!data.length) {
  242. // console.log("[cocostudy] 没有待批改数据");
  243. // return { count: 0 };
  244. // }
  245. // data.forEach((item) => aiBatchCorrectionBtn(item, aiBatchCorrection));
  246. // // 1. 批改:全部并发
  247. // const gradeResults = await Promise.allSettled(
  248. // aiBatchCorrection.map((e) => aiGradingBatchCorrection(aiBatchCorrection, e))
  249. // );
  250. // gradeResults.forEach((result, index) => {
  251. // const item = aiBatchCorrection[index];
  252. // if (result.status === "rejected") {
  253. // console.error(`学生批改异常 workId=${item.id}`, result.reason?.message || result.reason);
  254. // }
  255. // });
  256. // // 2. 保存:全部并发(与错题分析解耦)
  257. // const saveResults = await Promise.allSettled(
  258. // aiBatchCorrection.map(async (e) => {
  259. // const meta = await saveStudent(e);
  260. // console.log(`保存成功 workId=${e.id}`);
  261. // return meta;
  262. // })
  263. // );
  264. // const analysisJobs = [];
  265. // saveResults.forEach((result, index) => {
  266. // const item = aiBatchCorrection[index];
  267. // if (result.status === "rejected") {
  268. // console.error(`保存失败 workId=${item.id}`, result.reason?.message || result.reason);
  269. // return;
  270. // }
  271. // if (result.value) {
  272. // analysisJobs.push(result.value);
  273. // }
  274. // });
  275. // // 3. 错题分析:全部并发
  276. // if (analysisJobs.length) {
  277. // console.log(`[cocostudy] 开始错题分析 ${analysisJobs.length} 条(全部并发)`);
  278. // const analysisResults = await Promise.allSettled(
  279. // analysisJobs.map(async ({ userId, testId, spaceId, workId }) => {
  280. // await getRidData(userId, testId, spaceId);
  281. // console.log(`错题分析完成 workId=${workId}`);
  282. // return workId;
  283. // })
  284. // );
  285. // analysisResults.forEach((result, index) => {
  286. // const job = analysisJobs[index];
  287. // if (result.status === "rejected") {
  288. // console.error(
  289. // `错题分析失败 workId=${job.workId}`,
  290. // result.reason?.message || result.reason
  291. // );
  292. // }
  293. // });
  294. // }
  295. // console.log("[cocostudy] 全部处理完成", new Date().toISOString());
  296. // return { count: aiBatchCorrection.length };
  297. // };
  298. let isRunning = false;
  299. // const runAutoScoSafe = async () => {
  300. // if (isRunning) {
  301. // console.warn("[cocostudy] 上一次自动批改/分析仍在执行,跳过本次");
  302. // return;
  303. // }
  304. // isRunning = true;
  305. // try {
  306. // return await runAutoSco();
  307. // } catch (err) {
  308. // console.error("[cocostudy] 自动批改异常", err.message || err);
  309. // throw err;
  310. // } finally {
  311. // isRunning = false;
  312. // }
  313. // };
  314. let agent1a = null;
  315. let agent1b = null;
  316. let agent2a = null;
  317. let agent1c = null;
  318. const AGENT_IDS = {
  319. // 1a
  320. errorAnalysis: "f100cb78-8053-4f5b-9589-cb3a74d1b4da",
  321. // 1b
  322. lectureOutline: "08028b8b-b067-43c3-b8f5-b3a874f0a502",
  323. // 1c
  324. oneCExplanation: "cc43167c-e767-4f81-8a73-b0295de49472",
  325. // 2a
  326. detailed: "a000e7e9-d8f0-47d0-98e6-f5b1c6171934",
  327. };
  328. const tryParseJson = (text) => {
  329. try {
  330. return JSON.parse(text);
  331. } catch (_) {
  332. return undefined;
  333. }
  334. };
  335. const normalizeParsedAgentMessage = (parsed) => {
  336. if (Array.isArray(parsed)) return parsed;
  337. if (parsed && typeof parsed === "object") return [parsed];
  338. return parsed;
  339. };
  340. const collectJsonCandidates = (text) => {
  341. const candidates = [];
  342. const seen = new Set();
  343. const push = (value) => {
  344. const next = (value || "").trim();
  345. if (!next || seen.has(next)) return;
  346. seen.add(next);
  347. candidates.push(next);
  348. };
  349. const fenceRe = /```(?:json)?\s*([\s\S]*?)\s*```/gi;
  350. let match;
  351. while ((match = fenceRe.exec(text)) !== null) {
  352. push(match[1]);
  353. }
  354. push(text);
  355. for (const startChar of ["{", "["]) {
  356. let idx = text.indexOf(startChar);
  357. while (idx !== -1) {
  358. push(text.slice(idx));
  359. idx = text.indexOf(startChar, idx + 1);
  360. if (candidates.length > 30) break;
  361. }
  362. }
  363. return candidates;
  364. };
  365. const parseAgentJsonMessage = (raw) => {
  366. if (Array.isArray(raw)) return raw;
  367. if (raw && typeof raw === "object") return [raw];
  368. if (typeof raw !== "string") return [];
  369. const text = raw.trim();
  370. if (!text) return [];
  371. for (const candidate of collectJsonCandidates(text)) {
  372. const parsed = tryParseJson(candidate);
  373. if (parsed !== undefined) {
  374. return normalizeParsedAgentMessage(parsed);
  375. }
  376. }
  377. console.warn(
  378. "[cocostudy] Agent 返回非 JSON,原样传递下游:",
  379. text.slice(0, 120).replace(/\s+/g, " ")
  380. );
  381. return text;
  382. };
  383. const loadAgents = async () => {
  384. if (agent1a && agent1b && agent2a && agent1c) return;
  385. const [detailedRes, errorRes, outlineRes, oneCExplanationRes] = await Promise.all([
  386. axios.get(`https://appapi.cocorobo.cn/api/agents/agent/${AGENT_IDS.detailed}`),
  387. axios.get(`https://appapi.cocorobo.cn/api/agents/agent/${AGENT_IDS.errorAnalysis}`),
  388. axios.get(`https://appapi.cocorobo.cn/api/agents/agent/${AGENT_IDS.lectureOutline}`),
  389. axios.get(`https://appapi.cocorobo.cn/api/agents/agent/${AGENT_IDS.oneCExplanation}`),
  390. ]);
  391. agent2a = detailedRes.data;
  392. agent1a = errorRes.data;
  393. agent1b = outlineRes.data;
  394. agent1c = oneCExplanationRes.data;
  395. };
  396. const updateSpaceAnalyzeStatus = async (spaceId, isl) => {
  397. try {
  398. await callMysqlProc("updatespaceisanalyze", {
  399. sid: spaceId,
  400. isl,
  401. });
  402. console.log("[cocostudy] updatespaceisanalyze", { spaceId, isl });
  403. } catch (err) {
  404. console.error("[cocostudy] updatespaceisanalyze err", err.message || err);
  405. }
  406. };
  407. const callAgentChat = async (agent, message, userId, stepName = "Agent", spaceId) => {
  408. let lastErr;
  409. for (let attempt = 1; attempt <= GRADE_RETRY_TIMES; attempt++) {
  410. try {
  411. const res = await axios.post("https://appapi.cocorobo.cn/api/agentchats/ai_agent_chat", {
  412. id: agent.id,
  413. message,
  414. userId,
  415. model: agent.modelType,
  416. file_ids: [],
  417. sound_url: "",
  418. temperature: 0.1,
  419. top_p: 1,
  420. max_completion_tokens: 4096000,
  421. stream: false,
  422. uid: crypto.randomUUID(),
  423. session_name: crypto.randomUUID(),
  424. });
  425. const reply = res.data?.message;
  426. if (reply == null || reply === "") {
  427. throw new Error("AI 返回内容为空");
  428. }
  429. if (attempt > 1) {
  430. console.log(`[cocostudy] ${stepName} 重试成功 第${attempt}次`);
  431. }
  432. return reply;
  433. } catch (err) {
  434. lastErr = err;
  435. console.warn(
  436. `[cocostudy] ${stepName} 调用失败 第${attempt}/${GRADE_RETRY_TIMES}次`,
  437. err.response?.data || err.message
  438. );
  439. if (attempt < GRADE_RETRY_TIMES) {
  440. await sleep(GRADE_RETRY_DELAY);
  441. }
  442. }
  443. }
  444. if (spaceId) {
  445. await updateSpaceAnalyzeStatus(spaceId, 2);
  446. }
  447. throw lastErr;
  448. };
  449. const normalizeQuestionData = (questions, workData) => {
  450. return questions.map((item) => {
  451. const next = { ...item };
  452. next.userAnswer = workData.data?.[item.id] ?? "";
  453. next.userScore = workData.score?.[item.id] ?? 0;
  454. return next;
  455. });
  456. };
  457. const buildQuizSummary = (testRow, workRow, questions) => {
  458. const totalScore = questions.reduce((pre, cur) => pre + (cur.score || 0), 0);
  459. const userScore = Object.values(workRow.score || {}).reduce(
  460. (pre, cur) => pre + Number(cur || 0),
  461. 0
  462. );
  463. const totalQuestions = questions.length || 1;
  464. const wrongQuestions = questions.filter((item) => item.userScore != item.score).length;
  465. const correctRate = `${(((totalQuestions - wrongQuestions) / totalQuestions) * 100).toFixed(0)}%`;
  466. return {
  467. 试卷名称: testRow.name,
  468. 学科: testRow.subname,
  469. 年级: testRow.graname,
  470. 章节: testRow.chapters,
  471. 试卷总分: totalScore,
  472. 试卷得分: userScore,
  473. 试卷正确率: correctRate,
  474. 试卷总题目数: totalQuestions,
  475. 错题数量: wrongQuestions,
  476. 错题列表: questions,
  477. };
  478. };
  479. // 生成错题分析
  480. async function getRidData(userId, testId, spaceId) {
  481. await loadAgents();
  482. const res = await callMysqlProc("getCocostudyTestData", {
  483. uid: userId,
  484. tid: testId,
  485. });
  486. const workRow = res?.[0]?.[0];
  487. const testRow = res?.[1]?.[0];
  488. if (!workRow?.work || !testRow?.testJson) {
  489. throw new Error(`getCocostudyTestData 数据不完整 testId=${testId}`);
  490. }
  491. const workData = JSON.parse(workRow.work);
  492. const questions = normalizeQuestionData(JSON.parse(testRow.testJson), workData);
  493. const quizSummary = buildQuizSummary(testRow, workData, questions);
  494. console.log("[cocostudy] 开始错题分析", { userId, testId, spaceId });
  495. await generate1a(quizSummary, userId, spaceId);
  496. }
  497. async function generate1a(quizSummary, userId, spaceId) {
  498. const raw = await callAgentChat(
  499. agent1a,
  500. `题目数据:\n${JSON.stringify(quizSummary)}`,
  501. userId,
  502. "错因分析",
  503. spaceId
  504. );
  505. const errorAnalysis = parseAgentJsonMessage(raw);
  506. await generate1b(quizSummary, errorAnalysis, userId, spaceId);
  507. }
  508. async function generate1b(quizSummary, errorAnalysis, userId, spaceId) {
  509. const lectureResponse = await callAgentChat(
  510. agent1b,
  511. `==\n## 统计性信息如下:\n${JSON.stringify(quizSummary)}\n\n==\n\n## 错题深度分析如下:\n${JSON.stringify(errorAnalysis)}`,
  512. userId,
  513. "讲义大纲",
  514. spaceId
  515. );
  516. const lectureOutline = parseAgentJsonMessage(lectureResponse);
  517. await generate1c(quizSummary, errorAnalysis, lectureOutline, userId, spaceId);
  518. }
  519. async function generate1c(quizSummary, errorAnalysis, lectureOutline, userId, spaceId) {
  520. const oneCExplanation = await callAgentChat(
  521. agent1c,
  522. `==\n## 统计性信息如下:\n${JSON.stringify(quizSummary)}\n\n==\n\n## 错题深度分析如下:\n${JSON.stringify(errorAnalysis)}\n\n==\n\n## 讲义大纲如下:\n${JSON.stringify(lectureOutline)}`,
  523. userId,
  524. "讲解生成",
  525. spaceId
  526. );
  527. await generatedetailedExplanation(quizSummary, errorAnalysis, lectureOutline, oneCExplanation, userId, spaceId);
  528. }
  529. async function generatedetailedExplanation(quizSummary, errorAnalysis, lectureOutline, oneCExplanation, userId, spaceId) {
  530. const quizSummaryString = JSON.stringify(quizSummary).replace(/\\(?![\\"/])/g, "\\\\");
  531. const errorAnalysisString = JSON.stringify(errorAnalysis).replace(/\\(?![\\"/])/g, "\\\\");
  532. const lectureOutlineString = JSON.stringify(lectureOutline).replace(/\\(?![\\"/])/g, "\\\\");
  533. const oneCExplanationString = String(oneCExplanation ?? "");
  534. const displayContent = `==\n## 统计性信息如下:\n${quizSummaryString}\n\n==\n\n## 错题深度分析如下:\n${errorAnalysisString}\n\n==\n\n## 讲解大纲如下:\n${lectureOutlineString}\n\n==\n\n## 详细讲解如下:\n${oneCExplanationString}`;
  535. const answer = await callAgentChat(agent2a, displayContent, userId, "详细讲解", spaceId);
  536. await insertChat(answer, spaceId, userId);
  537. let json = {
  538. quizSummary: quizSummaryString,
  539. errorAnalysis: errorAnalysisString,
  540. lectureOutline: lectureOutlineString,
  541. detailedExplanation: oneCExplanationString,
  542. }
  543. try {
  544. await callMysqlProc("updatecocostudySpacejson", {
  545. id: spaceId,
  546. json: encodeURIComponent(JSON.stringify(json)),
  547. });
  548. } catch (err) {
  549. console.error("[cocostudy] updatecocostudySpacejson err", err.message || err);
  550. }
  551. await updateSpaceAnalyzeStatus(spaceId, 1);
  552. console.log("[cocostudy] 错题分析完成", { userId, spaceId });
  553. }
  554. async function insertChat(answer, spaceId, userId) {
  555. const params = {
  556. userId,
  557. userName: "系统",
  558. groupId: spaceId,
  559. answer: encodeURIComponent(answer),
  560. problem: encodeURIComponent(""),
  561. file_id: "",
  562. session_name: spaceId,
  563. alltext: answer,
  564. type: "chat",
  565. reasoning_content: "",
  566. jsonData: "{}",
  567. };
  568. const res = await axios.post("https://gpt4.cocorobo.cn/insert_chat", params);
  569. console.log("[cocostudy] insert_chat res", res.data);
  570. return res;
  571. }
  572. // 定时任务:每10分钟触发一次
  573. // schedule.scheduleJob("*/10 * * * *", async () => {
  574. // try {
  575. // await runAutoScoSafe();
  576. // } catch (error) {
  577. // console.error("[cocostudy] 定时任务异常", error.message || error);
  578. // }
  579. // });
  580. // 手动触发:GET/POST /api/cocostudy/autosco(暂不开放)
  581. // router.all("/autosco", async (req, res) => {
  582. // try {
  583. // const result = await runAutoScoSafe();
  584. // res.json({ code: 200, msg: "全部处理完成", data: result });
  585. // } catch (err) {
  586. // res.status(500).json({ code: 500, msg: err.message || "自动批改失败" });
  587. // }
  588. // });
  589. module.exports = router;
  590. module.exports.getRidData = getRidData;