mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 23:23:55 +08:00
update:聊天记录音频播放
This commit is contained in:
@@ -5,6 +5,9 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
@@ -35,6 +38,7 @@ import xiaozhi.modules.agent.dto.AgentDTO;
|
||||
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
|
||||
import xiaozhi.modules.agent.entity.AgentEntity;
|
||||
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
|
||||
import xiaozhi.modules.agent.service.AgentChatAudioService;
|
||||
import xiaozhi.modules.agent.service.AgentChatHistoryService;
|
||||
import xiaozhi.modules.agent.service.AgentService;
|
||||
import xiaozhi.modules.agent.service.AgentTemplateService;
|
||||
@@ -50,6 +54,7 @@ public class AgentController {
|
||||
private final AgentTemplateService agentTemplateService;
|
||||
private final DeviceService deviceService;
|
||||
private final AgentChatHistoryService agentChatHistoryService;
|
||||
private final AgentChatAudioService agentChatAudioService;
|
||||
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "获取用户智能体列表")
|
||||
@@ -229,4 +234,18 @@ public class AgentController {
|
||||
List<AgentChatHistoryDTO> result = agentChatHistoryService.getChatHistoryBySessionId(id, sessionId);
|
||||
return new Result<List<AgentChatHistoryDTO>>().ok(result);
|
||||
}
|
||||
|
||||
@GetMapping("/audio/{audioId}")
|
||||
@Operation(summary = "下载音频")
|
||||
@RequiresPermissions("sys:role:normal")
|
||||
public ResponseEntity<byte[]> downloadAudio(@PathVariable("audioId") String audioId) {
|
||||
byte[] audioData = agentChatAudioService.getAudio(audioId);
|
||||
if (audioData == null) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"play.wav\"")
|
||||
.body(audioData);
|
||||
}
|
||||
}
|
||||
+8
@@ -19,4 +19,12 @@ public interface AgentChatAudioService extends IService<AgentChatAudioEntity> {
|
||||
* @return 音频ID
|
||||
*/
|
||||
String saveAudio(byte[] audioData);
|
||||
|
||||
/**
|
||||
* 获取音频数据
|
||||
*
|
||||
* @param audioId 音频ID
|
||||
* @return 音频数据
|
||||
*/
|
||||
byte[] getAudio(String audioId);
|
||||
}
|
||||
+6
@@ -25,4 +25,10 @@ public class AgentChatAudioServiceImpl extends ServiceImpl<AiAgentChatAudioDao,
|
||||
save(entity);
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getAudio(String audioId) {
|
||||
AgentChatAudioEntity entity = getById(audioId);
|
||||
return entity != null ? entity.getAudio() : null;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
package xiaozhi.modules.security.config;
|
||||
|
||||
import jakarta.servlet.Filter;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.shiro.mgt.SecurityManager;
|
||||
import org.apache.shiro.session.mgt.SessionManager;
|
||||
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
|
||||
@@ -11,15 +14,13 @@ import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
|
||||
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import jakarta.servlet.Filter;
|
||||
import xiaozhi.modules.security.oauth2.Oauth2Filter;
|
||||
import xiaozhi.modules.security.oauth2.Oauth2Realm;
|
||||
import xiaozhi.modules.security.secret.ServerSecretFilter;
|
||||
import xiaozhi.modules.sys.service.SysParamsService;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Shiro的配置文件
|
||||
* Copyright (c) 人人开源 All rights reserved.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.1 KiB |
@@ -5,7 +5,7 @@
|
||||
<div class="session-list" @scroll="handleScroll">
|
||||
<div v-for="session in sessions" :key="session.sessionId" class="session-item"
|
||||
:class="{ active: currentSessionId === session.sessionId }" @click="selectSession(session)">
|
||||
<img src="@/assets/xiaozhi-logo.png" class="avatar" />
|
||||
<img :src="getUserAvatar(session.sessionId)" class="avatar" />
|
||||
<div class="session-info">
|
||||
<div class="session-time">{{ formatTime(session.createdAt) }}</div>
|
||||
<div class="message-count">{{ session.chatCount > 99 ? '99' : session.chatCount }}</div>
|
||||
@@ -21,9 +21,13 @@
|
||||
{{ message.content }}
|
||||
</div>
|
||||
<div v-else class="message-item" :class="{ 'user-message': message.chatType === 1 }">
|
||||
<img :src="message.chatType === 1 ? require('@/assets/user-avatar.png') : require('@/assets/xiaozhi-logo.png')"
|
||||
<img :src="message.chatType === 1 ? getUserAvatar(currentSessionId) : require('@/assets/xiaozhi-logo.png')"
|
||||
class="avatar" />
|
||||
<div class="message-content">{{ message.content }}</div>
|
||||
<div class="message-content">
|
||||
{{ message.content }}
|
||||
<i v-if="message.audioId" :class="getAudioIconClass(message)"
|
||||
@click="playAudio(message)" class="audio-icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -66,7 +70,9 @@ export default {
|
||||
loading: false,
|
||||
hasMore: true,
|
||||
scrollTimer: null,
|
||||
isFirstLoad: true
|
||||
isFirstLoad: true,
|
||||
playingAudioId: null,
|
||||
audioElement: null
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
@@ -207,6 +213,54 @@ export default {
|
||||
const day = date.getDate().toString().padStart(2, '0');
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
||||
}
|
||||
},
|
||||
getAudioIconClass(message) {
|
||||
if (this.playingAudioId === message.audioId) {
|
||||
return 'el-icon-loading';
|
||||
}
|
||||
return 'el-icon-video-play';
|
||||
},
|
||||
playAudio(message) {
|
||||
if (this.playingAudioId === message.audioId) {
|
||||
// 如果正在播放当前音频,则停止播放
|
||||
if (this.audioElement) {
|
||||
this.audioElement.pause();
|
||||
this.audioElement = null;
|
||||
}
|
||||
this.playingAudioId = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// 停止当前正在播放的音频
|
||||
if (this.audioElement) {
|
||||
this.audioElement.pause();
|
||||
this.audioElement = null;
|
||||
}
|
||||
|
||||
// 播放新音频
|
||||
this.playingAudioId = message.audioId;
|
||||
this.audioElement = new Audio(Api.getServiceUrl() + `/agent/audio/${message.audioId}`);
|
||||
|
||||
this.audioElement.onended = () => {
|
||||
this.playingAudioId = null;
|
||||
this.audioElement = null;
|
||||
};
|
||||
|
||||
this.audioElement.play();
|
||||
},
|
||||
getUserAvatar(sessionId) {
|
||||
// 从 sessionId 中提取所有数字
|
||||
const numbers = sessionId.match(/\d+/g);
|
||||
if (!numbers) return require('@/assets/user-avatar1.png');
|
||||
|
||||
// 将所有数字相加
|
||||
const sum = numbers.reduce((acc, num) => acc + parseInt(num), 0);
|
||||
|
||||
// 计算模5并加1,得到1-5之间的数字
|
||||
const avatarIndex = (sum % 5) + 1;
|
||||
|
||||
// 返回对应的头像图片
|
||||
return require(`@/assets/user-avatar${avatarIndex}.png`);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -269,7 +323,7 @@ export default {
|
||||
.message-count {
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
background-color: #1890ff;
|
||||
background-color: #b4b4b4;
|
||||
border-radius: 20px;
|
||||
float: left;
|
||||
width: 20px;
|
||||
@@ -302,11 +356,26 @@ export default {
|
||||
margin: 0 10px;
|
||||
text-align: left;
|
||||
line-height: 20px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.audio-icon {
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
margin: 0 5px;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.user-message .message-content {
|
||||
background-color: #1890ff;
|
||||
color: white;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.user-message .audio-icon {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.loading,
|
||||
@@ -355,6 +424,13 @@ export default {
|
||||
transform: translate(-50%, -50%);
|
||||
height: 80vh;
|
||||
max-width: 85vw;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chat-history-dialog .el-dialog__header {
|
||||
background-color: #e6f7ff;
|
||||
padding: 15px 20px;
|
||||
}
|
||||
|
||||
.chat-history-dialog .el-dialog__body {
|
||||
|
||||
Reference in New Issue
Block a user