fix(mobile): proxy review audio downloads
This commit is contained in:
+18
-6
@@ -1,6 +1,7 @@
|
||||
package org.dromara.aihr.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.aihr.domain.AihrPracticeDto.AnnotationResponse;
|
||||
import org.dromara.aihr.domain.AihrPracticeDto.CompetencyResponse;
|
||||
@@ -19,6 +20,7 @@ import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.core.domain.model.LoginUser;
|
||||
import org.dromara.common.core.enums.UserType;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.system.service.ISysOssService;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -27,6 +29,7 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -40,6 +43,7 @@ import java.util.Map;
|
||||
public class AihrMobileController {
|
||||
|
||||
private final AihrMobileSeedService mobileSeedService;
|
||||
private final ISysOssService ossService;
|
||||
|
||||
@SaIgnore
|
||||
@GetMapping("/home/{role}")
|
||||
@@ -65,6 +69,11 @@ public class AihrMobileController {
|
||||
return detail == null ? R.fail("复盘记录不存在") : R.ok(reviewDetailPayload(detail));
|
||||
}
|
||||
|
||||
@GetMapping("/oss/{ossId}")
|
||||
public void oss(@PathVariable Long ossId, HttpServletResponse response) throws IOException {
|
||||
ossService.download(ossId, response);
|
||||
}
|
||||
|
||||
@PostMapping("/practice/reviews/{id}/reviewed")
|
||||
public R<Void> markReviewed(@PathVariable Long id, @RequestBody(required = false) ReviewAdviceRequest request) {
|
||||
return mobileSeedService.markReviewed(id, request, currentAppUsername()) ? R.ok() : R.fail("复盘记录不存在");
|
||||
@@ -142,12 +151,15 @@ public class AihrMobileController {
|
||||
"value", item.value(),
|
||||
"note", item.note()
|
||||
)).toList());
|
||||
data.put("dialogue", detail.dialogue().stream().map(turn -> Map.of(
|
||||
"role", turn.role(),
|
||||
"label", turn.label(),
|
||||
"text", turn.text(),
|
||||
"audioUrl", turn.audioUrl()
|
||||
)).toList());
|
||||
data.put("dialogue", detail.dialogue().stream().map(turn -> {
|
||||
Map<String, Object> item = new LinkedHashMap<>();
|
||||
item.put("role", turn.role());
|
||||
item.put("label", turn.label());
|
||||
item.put("text", turn.text());
|
||||
item.put("audioUrl", turn.audioUrl());
|
||||
item.put("audioOssId", turn.audioOssId());
|
||||
return item;
|
||||
}).toList());
|
||||
data.put("annotations", detail.annotations().stream().map(AihrMobileController::annotationPayload).toList());
|
||||
return data;
|
||||
}
|
||||
|
||||
+3
-3
@@ -116,13 +116,13 @@ public final class AihrPracticeDto {
|
||||
public record RecordResponse(Long id, String sessionId, String time, String trainee, String scene, Integer score, String status, String summary, String extPartyId) {
|
||||
}
|
||||
|
||||
public record DialogueResponse(String role, String label, String text, String audioUrl, Integer emotion, Integer trust, Boolean redFlag, String coachHint) {
|
||||
public record DialogueResponse(String role, String label, String text, String audioUrl, Long audioOssId, Integer emotion, Integer trust, Boolean redFlag, String coachHint) {
|
||||
public DialogueResponse(String role, String label, String text) {
|
||||
this(role, label, text, "", null, null, null, "");
|
||||
this(role, label, text, "", null, null, null, null, "");
|
||||
}
|
||||
|
||||
public DialogueResponse(String role, String label, String text, String audioUrl) {
|
||||
this(role, label, text, audioUrl, null, null, null, "");
|
||||
this(role, label, text, audioUrl, null, null, null, null, "");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
@@ -2111,6 +2111,7 @@ public class AihrPracticeSeedService {
|
||||
item.path("label").asText(),
|
||||
item.path("text").asText(),
|
||||
item.path("audioUrl").asText(""),
|
||||
item.hasNonNull("audioOssId") ? item.path("audioOssId").asLong() : null,
|
||||
item.hasNonNull("emotion") ? item.path("emotion").asInt() : null,
|
||||
item.hasNonNull("trust") ? item.path("trust").asInt() : null,
|
||||
item.hasNonNull("redFlag") ? item.path("redFlag").asBoolean() : null,
|
||||
@@ -2132,6 +2133,7 @@ public class AihrPracticeSeedService {
|
||||
List<DialogueResponse> turns = new ArrayList<>();
|
||||
List<String> replies = snapshot(activeSession == null ? null : activeSession.traineeReplies());
|
||||
List<String> audioUrls = snapshot(activeSession == null ? null : activeSession.traineeAudioUrls());
|
||||
List<Long> audioOssIds = snapshotLong(activeSession == null ? null : activeSession.traineeAudioOssIds());
|
||||
List<String> customerLines = snapshot(activeSession == null ? null : activeSession.customerLines());
|
||||
List<TurnEvidence> evidenceList = snapshotEvidence(activeSession == null ? null : activeSession.turnEvidence());
|
||||
for (int i = 0; i < scenario.rounds().size(); i++) {
|
||||
@@ -2145,6 +2147,7 @@ public class AihrPracticeSeedService {
|
||||
"员工话术",
|
||||
replies.get(i),
|
||||
i < audioUrls.size() ? audioUrls.get(i) : "",
|
||||
i < audioOssIds.size() ? audioOssIds.get(i) : null,
|
||||
evidence == null ? null : evidence.emotion(),
|
||||
evidence == null ? null : evidence.trust(),
|
||||
evidence == null ? null : evidence.redFlag(),
|
||||
@@ -2157,6 +2160,7 @@ public class AihrPracticeSeedService {
|
||||
"AI教练",
|
||||
coachHint,
|
||||
"",
|
||||
null,
|
||||
evidence == null ? null : evidence.emotion(),
|
||||
evidence == null ? null : evidence.trust(),
|
||||
evidence == null ? null : evidence.redFlag(),
|
||||
@@ -2201,6 +2205,7 @@ public class AihrPracticeSeedService {
|
||||
turn.label(),
|
||||
maskSensitiveText(turn.text()),
|
||||
firstNonBlank(turn.audioUrl(), ""),
|
||||
turn.audioOssId(),
|
||||
turn.emotion(),
|
||||
turn.trust(),
|
||||
turn.redFlag(),
|
||||
|
||||
@@ -227,6 +227,7 @@ mobile-uni/
|
||||
- 员工今日页到练习页已补任务上下文传递:首页点“开始”会把当前 assignment 带到练习页,daily 任务高亮并提示填写,主管专项可直接启动,减少跳页后的二次选择。
|
||||
- 移动端 hash 路由滚动复位已补:员工/候选人/主管/登录/建设中页面激活时回到顶部,真实浏览器验证从长页切回今日页后 `scrollY=0`,避免首屏被旧滚动位置截断。
|
||||
- 员工练习页已补语音输入入口:回复区支持浏览器录音转写和选择音频转写,ASR 返回文本进入原提交框且音频 URL/OssId 随 turn 提交供主管复盘回放;真实浏览器验证按钮在 390px 手机视口不换行,文本 turn 回归可进入第 2 轮。
|
||||
- 主管复盘音频回放已补同源下载链路:移动端新增 `/api/aihr/mobile/oss/{ossId}` 下载代理,复盘详情返回 `audioOssId`,主管页用当前 `apiUrl` 生成音频地址;portless 验证 ASR 生成的 WAV 可经 `wygj-api.localhost` 下载,新训练记录详情可回读 `audioOssId`。
|
||||
|
||||
## 7. 风险
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@
|
||||
<view v-for="turn in detail.dialogue" :key="turn.role + turn.text" class="dialogue-row">
|
||||
<text class="dialogue-label">{{ turn.label }}</text>
|
||||
<text class="dialogue-text">{{ turn.text }}</text>
|
||||
<audio v-if="turn.audioUrl" class="dialogue-audio" controls :src="turn.audioUrl"></audio>
|
||||
<audio v-if="audioSrc(turn)" class="dialogue-audio" controls :src="audioSrc(turn)"></audio>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -240,6 +240,7 @@ import { onShow } from '@dcloudio/uni-app';
|
||||
import type { PracticeAlert, PracticeAssignment, PracticeRecord, ReviewDetail } from '@/types/api';
|
||||
import { isLoggedIn, rememberLoginRedirect } from '@/services/auth';
|
||||
import { resetPageScroll } from '@/services/navigation';
|
||||
import { apiUrl } from '@/services/api';
|
||||
import {
|
||||
createPracticeAssignment,
|
||||
getPracticeAlerts,
|
||||
@@ -274,6 +275,9 @@ const scenarioOptions = [
|
||||
{ id: 'repair-elevator-delay', name: '电梯故障' }
|
||||
];
|
||||
|
||||
const audioSrc = (turn: { audioOssId?: number | string; audioUrl?: string }) =>
|
||||
turn.audioOssId ? apiUrl(`/api/aihr/mobile/oss/${turn.audioOssId}`) : turn.audioUrl || '';
|
||||
|
||||
const scenarioIdForText = (text: string) => {
|
||||
if (/电梯/.test(text)) return 'repair-elevator-delay';
|
||||
if (/投诉|报修|漏水|噪音|摔倒/.test(text)) return 'complaint-water';
|
||||
|
||||
@@ -168,6 +168,7 @@ export interface ReviewDialogue {
|
||||
role: string;
|
||||
label: string;
|
||||
text: string;
|
||||
audioOssId?: number | string;
|
||||
audioUrl?: string;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user