mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 07:03:53 +08:00
@@ -103,4 +103,11 @@ public class RedisKeys {
|
||||
public static String getDictDataByTypeKey(String dictType) {
|
||||
return "sys:dict:data:" + dictType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取智能体音频ID的缓存key
|
||||
*/
|
||||
public static String getAgentAudioIdKey(String uuid) {
|
||||
return "agent:audio:id:" + uuid;
|
||||
}
|
||||
}
|
||||
|
||||
+30
-5
@@ -3,7 +3,9 @@ package xiaozhi.modules.agent.controller;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -28,6 +30,8 @@ import jakarta.validation.Valid;
|
||||
import lombok.AllArgsConstructor;
|
||||
import xiaozhi.common.constant.Constant;
|
||||
import xiaozhi.common.page.PageData;
|
||||
import xiaozhi.common.redis.RedisKeys;
|
||||
import xiaozhi.common.redis.RedisUtils;
|
||||
import xiaozhi.common.user.UserDetail;
|
||||
import xiaozhi.common.utils.ConvertUtils;
|
||||
import xiaozhi.common.utils.Result;
|
||||
@@ -55,6 +59,7 @@ public class AgentController {
|
||||
private final DeviceService deviceService;
|
||||
private final AgentChatHistoryService agentChatHistoryService;
|
||||
private final AgentChatAudioService agentChatAudioService;
|
||||
private final RedisUtils redisUtils;
|
||||
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "获取用户智能体列表")
|
||||
@@ -89,7 +94,7 @@ public class AgentController {
|
||||
@PostMapping
|
||||
@Operation(summary = "创建智能体")
|
||||
@RequiresPermissions("sys:role:normal")
|
||||
public Result<Void> save(@RequestBody @Valid AgentCreateDTO dto) {
|
||||
public Result<String> save(@RequestBody @Valid AgentCreateDTO dto) {
|
||||
AgentEntity entity = ConvertUtils.sourceToTarget(dto, AgentEntity.class);
|
||||
|
||||
// 获取默认模板
|
||||
@@ -117,7 +122,7 @@ public class AgentController {
|
||||
// ID、智能体编码和排序会在Service层自动生成
|
||||
agentService.insert(entity);
|
||||
|
||||
return new Result<>();
|
||||
return new Result<String>().ok(entity.getId());
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
@@ -235,17 +240,37 @@ public class AgentController {
|
||||
return new Result<List<AgentChatHistoryDTO>>().ok(result);
|
||||
}
|
||||
|
||||
@GetMapping("/audio/{audioId}")
|
||||
@Operation(summary = "下载音频")
|
||||
@PostMapping("/audio/{audioId}")
|
||||
@Operation(summary = "获取音频下载ID")
|
||||
@RequiresPermissions("sys:role:normal")
|
||||
public ResponseEntity<byte[]> downloadAudio(@PathVariable("audioId") String audioId) {
|
||||
public Result<String> getAudioId(@PathVariable("audioId") String audioId) {
|
||||
byte[] audioData = agentChatAudioService.getAudio(audioId);
|
||||
if (audioData == null) {
|
||||
return new Result<String>().error("音频不存在");
|
||||
}
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
redisUtils.set(RedisKeys.getAgentAudioIdKey(uuid), audioId);
|
||||
return new Result<String>().ok(uuid);
|
||||
}
|
||||
|
||||
@GetMapping("/play/{uuid}")
|
||||
@Operation(summary = "播放音频")
|
||||
public ResponseEntity<byte[]> playAudio(@PathVariable("uuid") String uuid) {
|
||||
|
||||
String audioId = (String) redisUtils.get(RedisKeys.getAgentAudioIdKey(uuid));
|
||||
if (StringUtils.isBlank(audioId)) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
byte[] audioData = agentChatAudioService.getAudio(audioId);
|
||||
if (audioData == null) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
redisUtils.delete(RedisKeys.getAgentAudioIdKey(uuid));
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"play.wav\"")
|
||||
.body(audioData);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -86,6 +86,7 @@ public class ShiroConfig {
|
||||
// 将config路径使用server服务过滤器
|
||||
filterMap.put("/config/**", "server");
|
||||
filterMap.put("/agent/chat-history/report", "server");
|
||||
filterMap.put("/agent/play/**", "anon");
|
||||
filterMap.put("/**", "oauth2");
|
||||
shiroFilter.setFilterChainDefinitionMap(filterMap);
|
||||
|
||||
|
||||
@@ -128,19 +128,19 @@ export default {
|
||||
});
|
||||
}).send();
|
||||
},
|
||||
}
|
||||
|
||||
export function getAgentSessions(agentId, params) {
|
||||
return request({
|
||||
url: `/agent/${agentId}/sessions`,
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
export function getAgentChatHistory(agentId, sessionId) {
|
||||
return request({
|
||||
url: `/agent/${agentId}/chat-history/${sessionId}`,
|
||||
method: 'get'
|
||||
});
|
||||
// 获取音频下载ID
|
||||
getAudioId(audioId, callback) {
|
||||
RequestService.sendRequest()
|
||||
.url(`${getServiceUrl()}/agent/audio/${audioId}`)
|
||||
.method('POST')
|
||||
.success((res) => {
|
||||
RequestService.clearRequestTime();
|
||||
callback(res);
|
||||
})
|
||||
.fail(() => {
|
||||
RequestService.reAjaxFun(() => {
|
||||
this.getAudioId(audioId, callback);
|
||||
});
|
||||
}).send();
|
||||
},
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ export default {
|
||||
currentSessionId: '',
|
||||
currentMacAddress: '',
|
||||
page: 1,
|
||||
limit: 10,
|
||||
limit: 20,
|
||||
loading: false,
|
||||
hasMore: true,
|
||||
scrollTimer: null,
|
||||
@@ -237,16 +237,21 @@ export default {
|
||||
this.audioElement = null;
|
||||
}
|
||||
|
||||
// 播放新音频
|
||||
// 先获取音频下载ID
|
||||
this.playingAudioId = message.audioId;
|
||||
this.audioElement = new Audio(Api.getServiceUrl() + `/agent/audio/${message.audioId}`);
|
||||
Api.agent.getAudioId(message.audioId, (res) => {
|
||||
if (res.data && res.data.data) {
|
||||
// 使用获取到的下载ID播放音频
|
||||
this.audioElement = new Audio(Api.getServiceUrl() + `/agent/play/${res.data.data}`);
|
||||
|
||||
this.audioElement.onended = () => {
|
||||
this.playingAudioId = null;
|
||||
this.audioElement = null;
|
||||
};
|
||||
this.audioElement.onended = () => {
|
||||
this.playingAudioId = null;
|
||||
this.audioElement = null;
|
||||
};
|
||||
|
||||
this.audioElement.play();
|
||||
this.audioElement.play();
|
||||
}
|
||||
});
|
||||
},
|
||||
getUserAvatar(sessionId) {
|
||||
// 从 sessionId 中提取所有数字
|
||||
@@ -269,7 +274,7 @@ export default {
|
||||
<style scoped>
|
||||
.chat-container {
|
||||
display: flex;
|
||||
height: 600px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.session-list {
|
||||
@@ -422,7 +427,7 @@ export default {
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
height: 80vh;
|
||||
height: 90vh;
|
||||
max-width: 85vw;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
@@ -436,7 +441,7 @@ export default {
|
||||
.chat-history-dialog .el-dialog__body {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
height: calc(80vh - 54px);
|
||||
height: calc(90vh - 54px);
|
||||
/* 减去标题栏的高度 */
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user