diff --git a/server/index.js b/server/index.js index f81409b..c150f48 100644 --- a/server/index.js +++ b/server/index.js @@ -4,7 +4,12 @@ import cors from 'cors' import path from 'path'; import 'dotenv/config'; -import { createAGVGrpcClient, createARMGrpcClient, __dirname } from './grpcClient.js'; +import { + createAGVGrpcClient, + createARMGrpcClient, + createMicrophoneGrpcClient, + __dirname +} from './grpcClient.js'; import { attachAudioWebSocket } from './audioWebSocket.js'; const app = express() @@ -12,6 +17,72 @@ app.use(express.static(path.join(__dirname, '../dist'))); app.use(cors()) app.use(express.json()) +// 获取机器人麦克风音量(0-100)。 +app.post('/api/audio/microphone/volume/get', (req, res) => { + const { ip, deviceId } = req.body + if (!ip || !deviceId) { + res.status(400).json({ code: 400, message: '机器人地址和麦克风设备 ID 不能为空' }) + return + } + + const grpcClient = createMicrophoneGrpcClient(ip) + grpcClient.getVolume( + { header: { device_id: deviceId } }, + { deadline: Date.now() + 5000 }, + (error, feedback) => { + grpcClient.close() + if (error) { + console.error('[audio] get microphone volume failed:', error) + res.status(502).json({ code: 502, message: error.details || error.message || '获取机器人音量失败' }) + return + } + if (feedback?.header?.success === false) { + res.status(502).json({ + code: 502, + message: feedback.header.error_message || '机器人拒绝获取音量' + }) + return + } + res.json({ code: 200, data: { volume: feedback?.volume ?? 0 } }) + } + ) +}) + +// 设置机器人麦克风音量(0-100)。 +app.post('/api/audio/microphone/volume/set', (req, res) => { + const { ip, deviceId } = req.body + const volume = Number(req.body.volume) + if (!ip || !deviceId) { + res.status(400).json({ code: 400, message: '机器人地址和麦克风设备 ID 不能为空' }) + return + } + if (!Number.isInteger(volume) || volume < 0 || volume > 100) { + res.status(400).json({ code: 400, message: '音量必须是 0 到 100 的整数' }) + return + } + + const grpcClient = createMicrophoneGrpcClient(ip) + grpcClient.setVolume({ + header: { device_id: deviceId }, + volume + }, { deadline: Date.now() + 5000 }, (error, feedback) => { + grpcClient.close() + if (error) { + console.error('[audio] set microphone volume failed:', error) + res.status(502).json({ code: 502, message: error.details || error.message || '设置机器人音量失败' }) + return + } + if (feedback?.header?.success === false) { + res.status(502).json({ + code: 502, + message: feedback.header.error_message || '机器人拒绝设置音量' + }) + return + } + res.json({ code: 200, data: { volume } }) + }) +}) + // 获取agv运行时状态 app.post('/api/agv/getRuntimeState', async (req, res) => { const request = { diff --git a/src/api/audio.js b/src/api/audio.js new file mode 100644 index 0000000..905d8cd --- /dev/null +++ b/src/api/audio.js @@ -0,0 +1,9 @@ +import request from '@/utils/request' + +export function getMicrophoneVolume(params) { + return request('/api/audio/microphone/volume/get', params, 'POST') +} + +export function setMicrophoneVolume(params) { + return request('/api/audio/microphone/volume/set', params, 'POST') +} diff --git a/src/styles/vars.scss b/src/styles/vars.scss index b656fec..2f9da7c 100644 --- a/src/styles/vars.scss +++ b/src/styles/vars.scss @@ -94,3 +94,75 @@ $font-family-body: 'Noto Sans SC', 'PingFang SC', sans-serif; $font-family-monospace: 'JetBrains Mono', monospace; $shadow-input: 0 2px 12px $color-shadow-soft; + +// Real-time voice conversation +$color-voice-white: #fff; +$color-voice-mask-black: #000; +$color-voice-transparent: transparent; +$color-voice-cyan: #46c8ff; +$color-voice-purple: #936cff; +$color-voice-background-start: #071a3e; +$color-voice-background-middle: #08152f; +$color-voice-background-end: #050c21; +$color-voice-background-blue-glow: rgb(44 90 198 / 34%); +$color-voice-background-purple-glow: rgb(81 49 172 / 18%); +$color-voice-grid-line: rgb(32 143 255 / 7%); +$color-voice-grid-diagonal: rgb(89 138 255 / 3%); +$color-voice-outer-ring: rgb(70 200 255 / 7%); +$color-voice-outer-ring-shadow: rgb(36 120 255 / 8%); +$color-voice-title-shadow: rgb(74 168 255 / 30%); +$color-voice-heading-shadow: rgb(79 159 255 / 25%); +$color-voice-subtitle: #86c6f4; +$color-voice-orbit-border: rgb(117 210 255 / 45%); +$color-voice-orbit-blue-fill: rgb(41 146 255 / 20%); +$color-voice-orbit-purple-fill: rgb(120 73 255 / 16%); +$color-voice-orbit-blue-shadow: rgb(35 158 255 / 35%); +$color-voice-orbit-purple-shadow: rgb(117 83 255 / 24%); +$color-voice-orbit-inner-border: rgb(160 124 255 / 55%); +$color-voice-orbit-inner-shadow: rgb(55 186 255 / 22%); +$color-voice-orbit-conic-blue: rgb(58 190 255 / 35%); +$color-voice-orbit-conic-purple: rgb(146 94 255 / 42%); +$color-voice-microphone-blue: #237bfa; +$color-voice-microphone-purple: #794be1; +$color-voice-microphone-shadow: rgb(34 111 255 / 48%); +$color-voice-highlight-strong: rgb(255 255 255 / 35%); +$color-voice-wave-shadow: rgb(72 190 255 / 46%); +$color-voice-status-border: rgb(255 255 255 / 9%); +$color-voice-status-text: #d5d9e4; +$color-voice-status-background: rgb(31 38 58 / 86%); +$color-voice-status-shadow: rgb(0 0 0 / 24%); +$color-voice-status-idle: #8791a7; +$color-voice-status-connecting: #ffd166; +$color-voice-status-connected: #4ef5ae; +$color-voice-status-error: #ff6482; +$color-voice-control-text: #badeff; +$color-voice-slider-track: rgb(106 170 226 / 24%); +$color-voice-slider-thumb: #dff7ff; +$color-voice-slider-glow: rgb(70 200 255 / 65%); +$color-voice-panel-border: rgb(112 192 255 / 24%); +$color-voice-call-border: rgb(125 207 255 / 52%); +$color-voice-call-start: #176ee9; +$color-voice-call-middle: #448eff; +$color-voice-call-end: #7657e9; +$color-voice-call-shadow: rgb(24 105 235 / 35%); +$color-voice-highlight-soft: rgb(255 255 255 / 18%); +$color-voice-call-hover-border: #86d5ff; +$color-voice-call-hover-start: #2280ff; +$color-voice-call-hover-middle: #54a0ff; +$color-voice-call-hover-end: #896afa; +$color-voice-danger-border: rgb(255 126 157 / 55%); +$color-voice-danger-start: #cf3e67; +$color-voice-danger-middle: #e4597e; +$color-voice-danger-end: #8554d9; +$color-voice-muted-background: rgb(16 45 85 / 72%); +$color-voice-muted-hover-background: rgb(23 76 128 / 82%); +$color-voice-card-start: rgb(14 49 91 / 67%); +$color-voice-card-end: rgb(20 30 74 / 55%); +$color-voice-card-shadow: rgb(0 4 24 / 28%); +$color-voice-card-highlight: rgb(255 255 255 / 5%); +$color-voice-card-primary-text: #edf7ff; +$color-voice-card-secondary-text: #8eafd0; +$color-voice-tag-divider: rgb(116 186 255 / 13%); +$color-voice-tag-border: rgb(87 183 255 / 35%); +$color-voice-tag-text: #a9d6f8; +$color-voice-tag-background: rgb(31 104 166 / 13%); diff --git a/src/views/camera/VoiceConversation.vue b/src/views/camera/VoiceConversation.vue index 87f9d92..022c7cb 100644 --- a/src/views/camera/VoiceConversation.vue +++ b/src/views/camera/VoiceConversation.vue @@ -52,6 +52,20 @@ +
+ 机器人麦克风音量 + + {{ robotVolume }}% +
+

当前状态:{{ statusMeta.label }}

@@ -70,9 +84,10 @@