diff --git a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/domain/AihrPracticeDto.java b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/domain/AihrPracticeDto.java index a8a41ebb..4ca720c9 100644 --- a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/domain/AihrPracticeDto.java +++ b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/domain/AihrPracticeDto.java @@ -271,6 +271,7 @@ public final class AihrPracticeDto { String feedback, Boolean correct, Integer score, + String scoreMode, String audioUrl, Long audioOssId ) { @@ -297,6 +298,7 @@ public final class AihrPracticeDto { Integer score, String feedback, String referenceAnswer, + String scoreMode, String audioUrl, Long audioOssId ) { diff --git a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeLlmService.java b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeLlmService.java index 71f6ed39..96163b7e 100644 --- a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeLlmService.java +++ b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeLlmService.java @@ -22,6 +22,7 @@ public class AihrPracticeLlmService { private static final String PROMPT_VERSION = "practice-p0-v1"; private static final double CUSTOMER_TEMPERATURE = 0.7; private static final double SCORE_TEMPERATURE = 0.0; + private static final double DAILY_DRILL_TEMPERATURE = 0.0; private static final int MAX_CUSTOMER_CHARS = 80; private final AihrModelSeedService modelService; @@ -53,6 +54,9 @@ public class AihrPracticeLlmService { ) { } + public record DailyDrillAssessment(int score, String feedback) { + } + /** * 生成 AI 客户(业主)的下一句回应。nextBeat 为 seed 剧本中下一轮台词,作为剧情推进锚点。 */ @@ -159,6 +163,29 @@ public class AihrPracticeLlmService { return modelService.tryChat(system, user, SCORE_TEMPERATURE).flatMap(this::parseScore); } + /** + * 每日三题的语义评估。模型未配置、成本闸门关闭或结构化结果解析失败时由调用方回退规则评分。 + */ + public Optional scoreDailyDrill(String question, String referenceAnswer, String answer) { + if (answer == null || answer.isBlank()) { + return Optional.empty(); + } + String system = """ + 你是物业行业新员工训练教练,请评估员工对每日微场景的处理话术。 + 只根据员工实际回答评分,不要臆造员工没有说过的动作。 + 评分标准:0-59为未覆盖关键动作,60-74为部分覆盖,75-89为基本完整,90-100为完整且表达清晰。 + 只输出 JSON,不要输出其他文字或 markdown: + {"score":0,"feedback":"不超过80字的可执行反馈"} + """; + String user = """ + 题目:%s + 参考要点:%s + 员工回答:%s + """.formatted(question, referenceAnswer, answer); + return modelService.tryChat(system, user, DAILY_DRILL_TEMPERATURE) + .flatMap(this::parseDailyDrillAssessment); + } + private Optional parseScore(String content) { try { JsonNode root = objectMapper.readTree(extractJson(content)); @@ -184,6 +211,25 @@ public class AihrPracticeLlmService { } } + private Optional parseDailyDrillAssessment(String content) { + try { + JsonNode root = objectMapper.readTree(extractJson(content)); + JsonNode scoreNode = root.path("score"); + if (!scoreNode.isNumber() && !scoreNode.isTextual()) { + return Optional.empty(); + } + int score = clampScore(scoreNode.asInt()); + String feedback = truncateText(root.path("feedback").asText(""), 300); + if (feedback.isBlank()) { + return Optional.empty(); + } + return Optional.of(new DailyDrillAssessment(score, feedback)); + } catch (Exception e) { + log.warn("daily drill score parse failed, falls back to rule score(处理错误已隐藏)"); + return Optional.empty(); + } + } + private Optional parseTurn(String content, int defaultEmotion, int defaultTrust, String defaultCoachHint) { try { JsonNode root = objectMapper.readTree(extractJson(content)); diff --git a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java index 45838ae4..615ebb93 100644 --- a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java +++ b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java @@ -45,6 +45,7 @@ import org.dromara.aihr.domain.AihrPracticeDto.TurnRequest; import org.dromara.aihr.domain.AihrPracticeDto.TurnResponse; import org.dromara.aihr.domain.AihrPracticeDto.TeamMemberResponse; import org.dromara.aihr.service.AihrPracticeLlmService.PracticeTurn; +import org.dromara.aihr.service.AihrPracticeLlmService.DailyDrillAssessment; import org.dromara.common.core.exception.ServiceException; import org.dromara.common.satoken.utils.LoginHelper; import org.springframework.beans.factory.annotation.Autowired; @@ -1053,7 +1054,7 @@ public class AihrPracticeSeedService { args.add(limit); String sql = """ SELECT id, ext_party_id, trainee_name, scenario_id, scenario_name, source, reason, status, create_time, - answer_text, answer_audio_url, answer_audio_oss_id, feedback, correct, score + answer_text, answer_audio_url, answer_audio_oss_id, feedback, correct, score, score_mode FROM aihr_practice_assignment %s ORDER BY create_time DESC, id DESC @@ -1826,7 +1827,7 @@ public class AihrPracticeSeedService { ensureDailyDrills(party); return jdbcTemplate.query(""" SELECT id, ext_party_id, trainee_name, scenario_id, scenario_name, source, reason, status, create_time, - answer_text, answer_audio_url, answer_audio_oss_id, feedback, correct, score + answer_text, answer_audio_url, answer_audio_oss_id, feedback, correct, score, score_mode FROM aihr_practice_assignment WHERE tenant_id = ? AND (? = '' OR ext_party_id = ?) @@ -1864,7 +1865,7 @@ public class AihrPracticeSeedService { args.add(normalizeLimit(limit)); return jdbcTemplate.query(""" SELECT id, ext_party_id, trainee_name, scenario_id, scenario_name, source, reason, status, create_time, - answer_text, answer_audio_url, answer_audio_oss_id, feedback, correct, score + answer_text, answer_audio_url, answer_audio_oss_id, feedback, correct, score, score_mode FROM aihr_practice_assignment WHERE tenant_id = ? AND ext_party_id IN (%s) @@ -1993,13 +1994,13 @@ public class AihrPracticeSeedService { """, tenantId(), extPartyId, traineeName, scenario.id(), scenario.name(), source, reason, createdAt, createdAt); List rows = jdbcTemplate.query(""" SELECT id, ext_party_id, trainee_name, scenario_id, scenario_name, source, reason, status, create_time, - answer_text, answer_audio_url, answer_audio_oss_id, feedback, correct, score + answer_text, answer_audio_url, answer_audio_oss_id, feedback, correct, score, score_mode FROM aihr_practice_assignment WHERE tenant_id = ? AND ext_party_id = ? AND scenario_id = ? AND source = ? AND reason = ? ORDER BY id DESC LIMIT 1 """, this::mapAssignment, tenantId(), extPartyId, scenario.id(), source, reason); - return rows.isEmpty() ? new PracticeAssignmentResponse(null, extPartyId, traineeName, scenario.id(), scenario.name(), source, reason, "待训练", now.format(TIME_FORMATTER), null, null, null, null, null, null, null, null) : rows.get(0); + return rows.isEmpty() ? new PracticeAssignmentResponse(null, extPartyId, traineeName, scenario.id(), scenario.name(), source, reason, "待训练", now.format(TIME_FORMATTER), null, null, null, null, null, null, null, null, null) : rows.get(0); } public PracticeAssignmentResponse createAssignment(PracticeAssignmentRequest request, String supervisorExtPartyId) { @@ -2061,7 +2062,7 @@ public class AihrPracticeSeedService { String answer = maskSensitiveText(firstNonBlank(request == null ? null : request.answer(), "")); String audioUrl = firstNonBlank(request == null ? null : request.audioUrl(), ""); Long audioOssId = request == null ? null : request.audioOssId(); - DailyDrillScore score = scoreDailyDrill(answer, parts.referenceAnswer()); + DailyDrillScore score = scoreDailyDrill(parts.question(), answer, parts.referenceAnswer()); LocalDateTime now = LocalDateTime.now(); int updated = jdbcTemplate.update(""" UPDATE aihr_practice_assignment @@ -2072,6 +2073,7 @@ public class AihrPracticeSeedService { feedback = ?, correct = ?, score = ?, + score_mode = ?, finished_time = ?, update_time = ? WHERE tenant_id = ? AND id = ? AND source = 'daily' @@ -2084,6 +2086,7 @@ public class AihrPracticeSeedService { score.feedback(), score.correct() ? 1 : 0, score.score(), + score.mode(), Timestamp.valueOf(now), Timestamp.valueOf(now), tenantId(), @@ -2092,7 +2095,7 @@ public class AihrPracticeSeedService { if (updated == 0) { return null; } - return new DailyDrillAnswerResponse(id, "已完成", score.correct(), score.score(), score.feedback(), parts.referenceAnswer(), audioUrl, audioOssId); + return new DailyDrillAnswerResponse(id, "已完成", score.correct(), score.score(), score.feedback(), parts.referenceAnswer(), score.mode(), audioUrl, audioOssId); } public List promptTemplates(String category, String code, Boolean enabled) { @@ -2674,6 +2677,7 @@ public class AihrPracticeSeedService { "daily".equals(source) ? stripReferenceAnswer(rs.getString("feedback")) : rs.getString("feedback"), correctValue == null ? null : rs.getBoolean("correct"), rs.getObject("score") == null ? null : rs.getInt("score"), + firstNonBlank(rs.getString("score_mode"), "RULE_FALLBACK"), rs.getString("answer_audio_url"), rs.getObject("answer_audio_oss_id") == null ? null : rs.getLong("answer_audio_oss_id") ); @@ -2704,10 +2708,22 @@ public class AihrPracticeSeedService { return new DailyDrillParts(question, reference); } - private DailyDrillScore scoreDailyDrill(String answer, String reference) { + private DailyDrillScore scoreDailyDrill(String question, String answer, String reference) { if (isBlank(answer)) { - return new DailyDrillScore(false, 0, "请先写出你的处理话术,再对照参考答案补齐关键动作。"); + return new DailyDrillScore(false, 0, "请先写出你的处理话术,再对照参考答案补齐关键动作。", "RULE_FALLBACK"); } + if (practiceLlmService != null) { + Optional assessment = practiceLlmService.scoreDailyDrill(question, reference, answer); + if (assessment.isPresent()) { + DailyDrillAssessment value = assessment.get(); + int score = Math.max(0, Math.min(100, value.score())); + return new DailyDrillScore(score >= 70, score, value.feedback(), "LLM"); + } + } + return scoreDailyDrillFallback(answer, reference); + } + + private DailyDrillScore scoreDailyDrillFallback(String answer, String reference) { List required = dailyDrillKeywords(reference); List hit = new ArrayList<>(); List missing = new ArrayList<>(); @@ -2727,7 +2743,7 @@ public class AihrPracticeSeedService { String feedback = (correct ? "回答方向正确。" : "还缺关键动作。") + (hit.isEmpty() ? "" : "已命中:" + String.join("、", hit) + "。") + (missing.isEmpty() ? "" : "建议补充:" + String.join("、", missing) + "。"); - return new DailyDrillScore(correct, score, feedback); + return new DailyDrillScore(correct, score, feedback, "RULE_FALLBACK"); } private String stripReferenceAnswer(String feedback) { @@ -3581,6 +3597,7 @@ public class AihrPracticeSeedService { `feedback` varchar(1000) DEFAULT NULL COMMENT '每日三题反馈', `correct` tinyint DEFAULT NULL COMMENT '是否答对', `score` int DEFAULT NULL COMMENT '每日三题得分', + `score_mode` varchar(30) DEFAULT NULL COMMENT '评分模式 LLM/RULE_FALLBACK', `finished_time` datetime DEFAULT NULL COMMENT '完成时间', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', @@ -3595,7 +3612,8 @@ public class AihrPracticeSeedService { ensureColumn("aihr_practice_assignment", "feedback", "ALTER TABLE aihr_practice_assignment ADD COLUMN `feedback` varchar(1000) DEFAULT NULL COMMENT '每日三题反馈' AFTER `answer_audio_oss_id`"); ensureColumn("aihr_practice_assignment", "correct", "ALTER TABLE aihr_practice_assignment ADD COLUMN `correct` tinyint DEFAULT NULL COMMENT '是否答对' AFTER `feedback`"); ensureColumn("aihr_practice_assignment", "score", "ALTER TABLE aihr_practice_assignment ADD COLUMN `score` int DEFAULT NULL COMMENT '每日三题得分' AFTER `correct`"); - ensureColumn("aihr_practice_assignment", "finished_time", "ALTER TABLE aihr_practice_assignment ADD COLUMN `finished_time` datetime DEFAULT NULL COMMENT '完成时间' AFTER `score`"); + ensureColumn("aihr_practice_assignment", "score_mode", "ALTER TABLE aihr_practice_assignment ADD COLUMN `score_mode` varchar(30) DEFAULT NULL COMMENT '评分模式 LLM/RULE_FALLBACK' AFTER `score`"); + ensureColumn("aihr_practice_assignment", "finished_time", "ALTER TABLE aihr_practice_assignment ADD COLUMN `finished_time` datetime DEFAULT NULL COMMENT '完成时间' AFTER `score_mode`"); assignmentTableReady = true; } } @@ -4123,7 +4141,7 @@ public class AihrPracticeSeedService { private record DailyDrillParts(String question, String referenceAnswer) { } - private record DailyDrillScore(boolean correct, int score, String feedback) { + private record DailyDrillScore(boolean correct, int score, String feedback, String mode) { } private record TurnEvidence(Integer emotion, Integer trust, Boolean redFlag, String coachHint) { diff --git a/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrPracticeLlmServiceTest.java b/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrPracticeLlmServiceTest.java new file mode 100644 index 00000000..38323d4e --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrPracticeLlmServiceTest.java @@ -0,0 +1,46 @@ +package org.dromara.aihr.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@Tag("dev") +class AihrPracticeLlmServiceTest { + + @Test + void dailyDrillAssessmentParsesStructuredModelResponse() { + AihrModelSeedService modelService = mock(AihrModelSeedService.class); + when(modelService.tryChat(anyString(), anyString(), eq(0.0))) + .thenReturn(Optional.of("{\"score\":86,\"feedback\":\"先承接情绪,再补责任人和反馈时间。\"}")); + AihrPracticeLlmService service = new AihrPracticeLlmService(modelService, new ObjectMapper()); + + var result = service.scoreDailyDrill( + "业主投诉车库积水,第一句话先说什么?", + "先承接情绪,再确认点位、责任人和首次反馈时间。", + "我先向您道歉并确认具体点位,安排责任人跟进,十分钟内给您首次反馈。" + ); + + assertTrue(result.isPresent()); + assertEquals(86, result.get().score()); + assertEquals("先承接情绪,再补责任人和反馈时间。", result.get().feedback()); + } + + @Test + void dailyDrillAssessmentRejectsUnstructuredResponse() { + AihrModelSeedService modelService = mock(AihrModelSeedService.class); + when(modelService.tryChat(anyString(), anyString(), eq(0.0))) + .thenReturn(Optional.of("模型暂时无法给出结构化结果")); + AihrPracticeLlmService service = new AihrPracticeLlmService(modelService, new ObjectMapper()); + + assertTrue(service.scoreDailyDrill("题目", "参考答案", "员工回答").isEmpty()); + } +} diff --git a/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrPracticeSeedServiceTest.java b/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrPracticeSeedServiceTest.java index e2b87470..508e66b2 100644 --- a/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrPracticeSeedServiceTest.java +++ b/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrPracticeSeedServiceTest.java @@ -230,10 +230,13 @@ public class AihrPracticeSeedServiceTest { assertTrue(answerStart >= 0 && answerEnd > answerStart); String answerMethod = source.substring(answerStart, answerEnd); assertTrue(answerMethod.contains("score = ?")); + assertTrue(answerMethod.contains("score_mode = ?")); assertTrue(answerMethod.contains("score.score()")); - assertTrue(source.contains("feedback, correct, score")); + assertTrue(source.contains("feedback, correct, score, score_mode")); assertTrue(source.contains("rs.getObject(\"score\")")); + assertTrue(source.contains("score.mode()")); assertTrue(source.contains("ADD COLUMN `score` int")); + assertTrue(source.contains("ADD COLUMN `score_mode` varchar(30)")); } @Test diff --git a/backend/script/sql/aihr_practice_mysql8.sql b/backend/script/sql/aihr_practice_mysql8.sql index c278665b..bd4110b4 100644 --- a/backend/script/sql/aihr_practice_mysql8.sql +++ b/backend/script/sql/aihr_practice_mysql8.sql @@ -107,6 +107,7 @@ CREATE TABLE IF NOT EXISTS `aihr_practice_assignment` ( `feedback` varchar(1000) DEFAULT NULL COMMENT '每日三题反馈', `correct` tinyint DEFAULT NULL COMMENT '是否答对', `score` int DEFAULT NULL COMMENT '每日三题得分', + `score_mode` varchar(30) DEFAULT NULL COMMENT '评分模式 LLM/RULE_FALLBACK', `finished_time` datetime DEFAULT NULL COMMENT '完成时间', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', diff --git a/backend/script/sql/update/aihr_20260714_daily_drill_score_mode_mysql8.sql b/backend/script/sql/update/aihr_20260714_daily_drill_score_mode_mysql8.sql new file mode 100644 index 00000000..5416b2d2 --- /dev/null +++ b/backend/script/sql/update/aihr_20260714_daily_drill_score_mode_mysql8.sql @@ -0,0 +1,23 @@ +-- 每日三题评分模式留痕:幂等补列,不写入业务数据。 +SET @db_name = DATABASE(); +SET @table_exists = ( + SELECT COUNT(*) + FROM information_schema.tables + WHERE table_schema = @db_name + AND table_name = 'aihr_practice_assignment' +); +SET @column_exists = ( + SELECT COUNT(*) + FROM information_schema.columns + WHERE table_schema = @db_name + AND table_name = 'aihr_practice_assignment' + AND column_name = 'score_mode' +); +SET @ddl = IF( + @table_exists = 1 AND @column_exists = 0, + 'ALTER TABLE aihr_practice_assignment ADD COLUMN score_mode varchar(30) DEFAULT NULL COMMENT ''评分模式 LLM/RULE_FALLBACK'' AFTER score', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; diff --git a/docs/BRD_IMPLEMENTATION_AUDIT.md b/docs/BRD_IMPLEMENTATION_AUDIT.md index 34f0743a..4fbb7009 100644 --- a/docs/BRD_IMPLEMENTATION_AUDIT.md +++ b/docs/BRD_IMPLEMENTATION_AUDIT.md @@ -299,3 +299,4 @@ - 2026-07-14 发布预检业务码误匹配修复:业务码校验进一步收紧为 JSON 顶层首字段 `code=200`,不再接受嵌套对象中的同名 `code=200`;本地成功/嵌套失败样例与线上租户接口均验证通过。三端产物已重建且时间不早于当前提交,完整预检仍因两份用户未提交的 Figma 文档停止,本轮未发布生产。 - 2026-07-14 BRD 最新线上只读复核:生产根站与 `/h5/` 返回 `200`,线上仍加载管理端 `assets/index-CJZ3Ax3Z.js` 与 H5 `assets/index-D4-NrEpb.js`;当前本地提交 `94d483d3` 构建资源分别为 `assets/index-Cv2wI9UI.js`(SHA-256 `cd6e3eab4a684141ccd64617dcde4d99030c9c13f7da618c11e1709c04f244a0`)和 `assets/index-Dq7sfWZP.js`(SHA-256 `f8499e5f261cc06d02de275e285e4a7c946dd6ef54fd29b3a1c0bc7a9a689c3d`)。本轮只读核验,未执行生产静态资源同步、后端重启或业务数据写入;五维修复仍未被证明在线上生效。 - 2026-07-14 BRD 4.7/M5 SOP 试点评审主体留痕修复:SOP 搜索生成评审记录时,现仅从已认证 APP 用户上下文保存 `requester_ext_party_id`,不接受客户端传入主体;管理端/系统搜索保持空值。正式 SOP 评审统计和严格门禁只统计已评审、有提问员工主体且能命中当前租户在职组织快照的记录,避免管理员烟测、临时 APP 账号或无组织身份数据混入正式可用率;同步补 reset SQL、幂等迁移脚本、运行时补列和回归门禁。该修复只完善证据归属,仍需真实员工提问和人工评审样本,不能把有主体字段等同于 M5 已达标。 +- 2026-07-14 BRD 4.4 每日三题评分模式补齐:每日题提交现优先调用现有 chat 模型做 temperature=0 的结构化语义评估;模型未配置、成本闸门关闭或 JSON 解析失败时继续使用原关键词/长度规则,并在 `aihr_practice_assignment.score_mode` 留痕为 `LLM` 或 `RULE_FALLBACK`。员工端完成反馈同步显示评分模式,避免把规则分数误称为 AI 评测;新增幂等迁移、AIHR 单测和 demo marker。正式试点仍需确认模型供应商、成本预算和人工抽检,不把 LLM 路径可调用等同于 BRD 4.4 的质量达标。 diff --git a/mobile-uni/src/pages/user/practice/index.vue b/mobile-uni/src/pages/user/practice/index.vue index e3e9909a..c8df1576 100644 --- a/mobile-uni/src/pages/user/practice/index.vue +++ b/mobile-uni/src/pages/user/practice/index.vue @@ -370,7 +370,8 @@ const assignmentDesc = (item: PracticeAssignment) => { if (item.source === 'daily' && item.status !== '已完成') return '提交后显示反馈与参考答案'; if (item.source === 'daily' && item.status === '已完成') { const score = typeof item.score === 'number' ? `得分 ${item.score} 分` : ''; - return [score, item.feedback || item.reason].filter(Boolean).join(' · '); + const mode = item.scoreMode === 'LLM' ? 'AI语义评估' : '规则兜底'; + return [score, mode, item.feedback || item.reason].filter(Boolean).join(' · '); } return item.feedback || item.reason; }; @@ -682,12 +683,14 @@ const submitDaily = async (item: PracticeAssignment) => { feedback: result.feedback, referenceAnswer: result.referenceAnswer, correct: result.correct, + score: result.score, + scoreMode: result.scoreMode, audioUrl: result.audioUrl || audioUrl, audioOssId: result.audioOssId || audioOssId } : row ); - message.value = `每日三题得分 ${result.score}`; + message.value = `每日三题得分 ${result.score}(${result.scoreMode === 'LLM' ? 'AI语义评估' : '规则兜底'})`; } catch (error) { handleError(error, '每日三题提交失败'); } finally { diff --git a/mobile-uni/src/types/api.ts b/mobile-uni/src/types/api.ts index 170e686c..20e6bff1 100644 --- a/mobile-uni/src/types/api.ts +++ b/mobile-uni/src/types/api.ts @@ -310,6 +310,7 @@ export interface PracticeAssignment { feedback?: string; correct?: boolean | null; score?: number | null; + scoreMode?: string; audioUrl?: string; audioOssId?: number | string; } @@ -330,6 +331,7 @@ export interface DailyDrillAnswerResponse { score: number; feedback: string; referenceAnswer: string; + scoreMode?: string; audioUrl?: string; audioOssId?: number | string; } diff --git a/scripts/demo-check.sh b/scripts/demo-check.sh index 837cc1c0..ac13d6f8 100755 --- a/scripts/demo-check.sh +++ b/scripts/demo-check.sh @@ -565,6 +565,10 @@ contains mobile-uni/src/pages/supervisor/review/index.vue "人工贡献分" contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrMobileController.java "/practice/reviews/{id}/reviewed" contains mobile-uni/src/pages/supervisor/review/index.vue "标记已复盘" contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "source = 'retry'" +contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "score_mode" +contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeLlmService.java "scoreDailyDrill" +contains backend/script/sql/update/aihr_20260714_daily_drill_score_mode_mysql8.sql "score_mode" +contains mobile-uni/src/pages/user/practice/index.vue "AI语义评估" contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java 'String source = "manual"' contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "训练场景已停用,无法开始" contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "scenario.id(), scenario.name(), source"