Merge branch 'main' of http://172.16.1.12/jiwanjun/ai-speech-build
This commit is contained in:
commit
c3cdb0d0a1
|
|
@ -19,7 +19,7 @@ const showSpeechControl = ref(true);
|
|||
// 监听路由变化,切换页面时隐藏语音控制组件
|
||||
router.afterEach((to) => {
|
||||
if (to.path === "/LargeScreen") {
|
||||
showSpeechControl.value = false;
|
||||
showSpeechControl.value = true;
|
||||
} else {
|
||||
showSpeechControl.value = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<!-- <div class="TopBox">
|
||||
<h1>仓储可视化数据大屏</h1>
|
||||
</div> -->
|
||||
<iframe src="http://172.16.1.130:81/sz/" frameborder="0" style="width: 100%;height: 100%;"></iframe>
|
||||
<iframe src="http://172.16.1.130:81/sz/" id="suzhoudaping" frameborder="0" style="width: 100%;height: 100%;"></iframe>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@
|
|||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, onMounted, watch } from "vue";
|
||||
import { ref, onMounted, watch, nextTick } from "vue";
|
||||
|
||||
const config = {
|
||||
page: "Ecommerce Home",
|
||||
|
|
@ -71,9 +71,98 @@ const config = {
|
|||
selector: "#ai-speech-add-project",
|
||||
params: [],
|
||||
},
|
||||
{
|
||||
command: "switch_jsc",
|
||||
description: "驾驶舱",
|
||||
action: "click",
|
||||
selector: "#ai_speech_jsc",
|
||||
params: [],
|
||||
isIframe: true,
|
||||
},
|
||||
{
|
||||
command: "switch_zngk",
|
||||
description: "智能管控",
|
||||
action: "click",
|
||||
selector: "#ai_speech_zngk",
|
||||
params: [],
|
||||
isIframe: true,
|
||||
},
|
||||
{
|
||||
command: "switch_zndd",
|
||||
description: "智能调度",
|
||||
action: "click",
|
||||
selector: "#ai_speech_zndd",
|
||||
params: [],
|
||||
isIframe: true,
|
||||
},
|
||||
{
|
||||
command: "switch_znyw",
|
||||
description: "智能运维",
|
||||
action: "click",
|
||||
selector: "#ai_speech_znyw",
|
||||
params: [],
|
||||
isIframe: true,
|
||||
},
|
||||
{
|
||||
command: "switch_nyszzx",
|
||||
description: "能源数据中心",
|
||||
action: "click",
|
||||
selector: "#ai_speech_nyszzx",
|
||||
params: [],
|
||||
isIframe: true,
|
||||
},
|
||||
{
|
||||
command: "switch_spjkzx",
|
||||
description: "碳排放分析",
|
||||
action: "click",
|
||||
selector: "#ai_speech_spjkzx",
|
||||
params: [],
|
||||
isIframe: true,
|
||||
},
|
||||
{
|
||||
command: "ai_speech_rygl",
|
||||
description: "人员管理",
|
||||
action: "click",
|
||||
selector: "#ai_speech_rygl",
|
||||
params: [],
|
||||
isIframe: true,
|
||||
},
|
||||
{
|
||||
command: "ai_speech_sbgl",
|
||||
description: "设备管理",
|
||||
action: "click",
|
||||
selector: "#ai_speech_sbgl",
|
||||
params: [],
|
||||
isIframe: true,
|
||||
},
|
||||
{
|
||||
command: "ai_speech_gjgl",
|
||||
description: "告警管理",
|
||||
action: "click",
|
||||
selector: "#ai_speech_gjgl",
|
||||
params: [],
|
||||
isIframe: true,
|
||||
},
|
||||
{
|
||||
command: "ai_speech_zsk",
|
||||
description: "知识库",
|
||||
action: "click",
|
||||
selector: "#ai_speech_zsk",
|
||||
params: [],
|
||||
isIframe: true,
|
||||
},
|
||||
{
|
||||
command: "ai_speech_xcgl",
|
||||
description: "巡查管理",
|
||||
action: "click",
|
||||
selector: "#ai_speech_xcgl",
|
||||
params: [],
|
||||
isIframe: true,
|
||||
},
|
||||
|
||||
{
|
||||
command: "search_project",
|
||||
description: "搜索项目",
|
||||
description: "搜索",
|
||||
action: "input",
|
||||
selector: "#search_project",
|
||||
params: [
|
||||
|
|
@ -86,7 +175,7 @@ const config = {
|
|||
},
|
||||
{
|
||||
command: "input_project_name",
|
||||
description: "项目名称",
|
||||
description: "名称",
|
||||
action: "input",
|
||||
selector: "#ai-speech-project-name",
|
||||
params: [
|
||||
|
|
@ -97,14 +186,20 @@ const config = {
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
command: "close_build_project",
|
||||
description: "取消",
|
||||
action: "click",
|
||||
selector: "#ai-speech-close_buildproject",
|
||||
params: [],
|
||||
},
|
||||
{
|
||||
command: "build_project",
|
||||
description: "创建项目",
|
||||
description: "创建",
|
||||
action: "click",
|
||||
selector: "#ai-speech-buildproject",
|
||||
params: [],
|
||||
},
|
||||
|
||||
],
|
||||
};
|
||||
|
||||
|
|
@ -118,16 +213,15 @@ const props = defineProps({
|
|||
});
|
||||
|
||||
watch(config, (newVal, oldVal) => {
|
||||
if (voiceControl.value && voiceControl.value.isListening) {
|
||||
voiceControl.value.stopListening();
|
||||
}
|
||||
initVoiceControl();
|
||||
// if (voiceControl.value && voiceControl.value.isListening) {
|
||||
// voiceControl.value.stopListening();
|
||||
// }
|
||||
// initVoiceControl();
|
||||
});
|
||||
|
||||
class VoiceControl {
|
||||
constructor(callback) {
|
||||
this.config = config;
|
||||
this.isListening = false;
|
||||
this.apiKey = "sk-020189889aac40f3b050f7c60ca597f8";
|
||||
this.setupSpeechRecognition();
|
||||
this.updateUI();
|
||||
|
|
@ -145,13 +239,13 @@ class VoiceControl {
|
|||
this.recognition = new SpeechRecognition();
|
||||
|
||||
// 配置识别参数以提高精度
|
||||
this.recognition.continuous = true; // 长时间识别
|
||||
this.recognition.continuous = false; // 长时间识别
|
||||
this.recognition.interimResults = true; // 返回临时结果
|
||||
this.recognition.maxAlternatives = 1; // 只返回最可能的结果
|
||||
this.recognition.lang = "zh-CN"; // 设置为中文识别
|
||||
|
||||
// 音频处理参数(重要)
|
||||
this.recognition.energy_threshold = 300; // 能量阈值
|
||||
this.recognition.energy_threshold = 500; // 能量阈值
|
||||
this.recognition.pause_threshold = 0.3; // 停顿时间阈值(秒)
|
||||
this.recognition.phrase_threshold = 0.2; // 短语识别阈值
|
||||
|
||||
|
|
@ -159,10 +253,7 @@ class VoiceControl {
|
|||
this.isProcessing = false; // 防止并发处理
|
||||
|
||||
this.recognition.onstart = () => {
|
||||
this.isListening = true;
|
||||
this.updateUI();
|
||||
this.showStatus("正在聆听...", "info");
|
||||
document.querySelector(".status-text").textContent = "正在聆听...";
|
||||
};
|
||||
|
||||
this.recognition.onresult = async (event) => {
|
||||
|
|
@ -174,15 +265,13 @@ class VoiceControl {
|
|||
const result = event.results[lastResultIndex];
|
||||
const transcript = result[0].transcript;
|
||||
|
||||
console.log(event, "-------event");
|
||||
this.showTranscript(transcript);
|
||||
this.showStatus("正在处理指令...", "info");
|
||||
|
||||
// 显示中间结果(灰色)和最终结果(黑色)
|
||||
const commandDisplay = document.querySelector(".command-display");
|
||||
const commandText = commandDisplay.querySelector(".command-text");
|
||||
|
||||
console.log(result.isFinal, "----result.isFinal");
|
||||
console.log(result.isFinal, "=====> isFinal");
|
||||
|
||||
if (!result.isFinal) {
|
||||
// 中间结果 - 可以快速显示但不处理
|
||||
|
|
@ -199,14 +288,13 @@ class VoiceControl {
|
|||
commandText.style.color = "#00000085";
|
||||
}
|
||||
|
||||
console.log(transcript, "----");
|
||||
console.log(transcript, "=====> 识别文字");
|
||||
|
||||
try {
|
||||
// 使用setTimeout将处理放入下一个事件循环,避免阻塞UI
|
||||
setTimeout(async () => {
|
||||
if (transcript) {
|
||||
const sequence = await this.queryDeepSeek(transcript);
|
||||
this.showStatus("指令执行完成", "success");
|
||||
|
||||
this.executeSequence(sequence);
|
||||
|
||||
|
|
@ -220,36 +308,33 @@ class VoiceControl {
|
|||
}, 0);
|
||||
} catch (error) {
|
||||
console.error("处理过程中出错:", error);
|
||||
this.showError(`执行出错: ${error.message}`);
|
||||
this.isProcessing = false;
|
||||
}
|
||||
};
|
||||
|
||||
this.recognition.onerror = (event) => {
|
||||
console.log(event);
|
||||
this.isListening = false;
|
||||
this.updateUI();
|
||||
this.showError(`语音识别错误: ${event.error}`);
|
||||
document.querySelector(".status-text").textContent = "点击开始语音识别";
|
||||
this.isProcessing = false;
|
||||
};
|
||||
// this.recognition.onerror = (event) => {
|
||||
// console.log(event, "=====> error event");
|
||||
// setTimeout(() => {
|
||||
// this.recognition.start();
|
||||
// }, 100); // 短暂延迟后重启
|
||||
// };
|
||||
|
||||
this.recognition.onend = () => {
|
||||
// 快速重启识别,减少等待时间
|
||||
if (this.isListening) {
|
||||
console.log("====> 走到这 ", listenStatus.value);
|
||||
|
||||
if (listenStatus.value) {
|
||||
// 只有在用户希望继续聆听时才重启
|
||||
setTimeout(() => {
|
||||
this.recognition.start();
|
||||
}, 100); // 短暂延迟后重启
|
||||
} else {
|
||||
this.updateUI();
|
||||
document.querySelector(".status-text").textContent = "点击开始语音识别";
|
||||
setTimeout(() => {
|
||||
document.querySelector(".status-text").classList.remove("show");
|
||||
}, 3000);
|
||||
}
|
||||
this.isProcessing = false;
|
||||
// this.isListening = false;
|
||||
// this.updateUI();
|
||||
// document.querySelector(".status-text").textContent = "点击开始语音识别";
|
||||
|
||||
|
|
@ -317,7 +402,7 @@ class VoiceControl {
|
|||
],
|
||||
temperature: 0.1,
|
||||
stream: false,
|
||||
max_tokens: 200,
|
||||
max_tokens: 500,
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
|
@ -334,7 +419,7 @@ class VoiceControl {
|
|||
|
||||
// 解析JSON响应
|
||||
let result = JSON.parse(content);
|
||||
console.log(result, "---result");
|
||||
console.log(result, "=====> deepseek 返回");
|
||||
// 验证响应结构
|
||||
if (!result.sequence || !Array.isArray(result.sequence)) {
|
||||
throw new Error("DeepSeek返回了无效的指令序列格式");
|
||||
|
|
@ -353,9 +438,8 @@ class VoiceControl {
|
|||
}
|
||||
|
||||
stopListening() {
|
||||
if (this.recognition && this.isListening) {
|
||||
this.recognition.stop();
|
||||
}
|
||||
this.updateUI();
|
||||
}
|
||||
|
||||
async executeSequence(sequence) {
|
||||
|
|
@ -377,18 +461,47 @@ class VoiceControl {
|
|||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
// 向iframe发送命令
|
||||
sendCommand(command) {
|
||||
const targetFrame = document.getElementById("suzhoudaping");
|
||||
|
||||
nextTick(() => {
|
||||
const message = {
|
||||
type: "CONTROL_COMMAND",
|
||||
action: command.action,
|
||||
selector: command.selector,
|
||||
value: command.value,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
try {
|
||||
targetFrame.contentWindow.postMessage(message, "*");
|
||||
console.log("=====> 发送到iframe");
|
||||
} catch (error) {
|
||||
console.log(error, "=====> 发送到iframe");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async executeInstruction(instruction) {
|
||||
const commandConfig = this.config.commands.find(
|
||||
(c) => c.command === instruction.command
|
||||
);
|
||||
|
||||
console.log(commandConfig, "----commandConfig");
|
||||
|
||||
if (!commandConfig) {
|
||||
throw new Error(`未知指令: ${instruction.command}`);
|
||||
}
|
||||
|
||||
if (commandConfig && commandConfig.isIframe) {
|
||||
this.sendCommand(commandConfig);
|
||||
return;
|
||||
}
|
||||
|
||||
const element = document.querySelector(commandConfig.selector);
|
||||
|
||||
console.log(element, "-----element");
|
||||
console.log(element, "====> 控制元素");
|
||||
|
||||
if (!element) {
|
||||
throw new Error(`找不到元素: ${commandConfig.selector}`);
|
||||
|
|
@ -455,32 +568,22 @@ class VoiceControl {
|
|||
updateUI() {
|
||||
const voiceBtn = document.getElementById("voice-btn");
|
||||
if (voiceBtn) {
|
||||
if (this.isListening) {
|
||||
if (listenStatus.value) {
|
||||
voiceBtn.classList.add("listening");
|
||||
document.querySelector(".status-text").textContent = "正在聆听...";
|
||||
} else {
|
||||
voiceBtn.classList.remove("listening");
|
||||
document.querySelector(".status-text").textContent = "点击开始语音识别";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showStatus(message, type = "info") {
|
||||
const statusEl = document.getElementById("status");
|
||||
if (statusEl) {
|
||||
statusEl.textContent = `状态: ${message}`;
|
||||
statusEl.className = `status ${type}`;
|
||||
}
|
||||
}
|
||||
|
||||
showTranscript(text) {
|
||||
const transcriptEl = document.getElementById("transcript");
|
||||
if (transcriptEl) {
|
||||
transcriptEl.textContent = `识别结果: ${text}`;
|
||||
}
|
||||
}
|
||||
|
||||
showError(message) {
|
||||
this.showStatus(message, "error");
|
||||
}
|
||||
}
|
||||
|
||||
const voiceControl = ref(null);
|
||||
|
|
@ -492,7 +595,6 @@ const initVoiceControl = () => {
|
|||
|
||||
const callBackFun = (data) => {
|
||||
action.value = data;
|
||||
console.log(data);
|
||||
};
|
||||
const toggleListening = () => {
|
||||
if (!voiceControl.value) {
|
||||
|
|
@ -500,7 +602,7 @@ const toggleListening = () => {
|
|||
return;
|
||||
}
|
||||
|
||||
if (voiceControl.value.isListening) {
|
||||
if (listenStatus.value) {
|
||||
listenStatus.value = false;
|
||||
voiceControl.value.stopListening();
|
||||
const voiceBtn = document.getElementById("voice-btn");
|
||||
|
|
@ -535,22 +637,6 @@ body {
|
|||
color: #fff;
|
||||
}
|
||||
|
||||
.container {
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.8rem;
|
||||
margin-bottom: 20px;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1.2rem;
|
||||
max-width: 800px;
|
||||
margin: 0 auto 30px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* 固定在左下角的语音控制组件 */
|
||||
.voice-control-container {
|
||||
position: fixed;
|
||||
|
|
@ -676,34 +762,6 @@ p {
|
|||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status {
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
border-radius: 5px;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.info {
|
||||
background: rgba(0, 123, 255, 0.2);
|
||||
border-left: 4px solid #007bff;
|
||||
}
|
||||
|
||||
.success {
|
||||
background: rgba(40, 167, 69, 0.2);
|
||||
border-left: 4px solid #28a745;
|
||||
}
|
||||
|
||||
.error {
|
||||
background: rgba(220, 53, 69, 0.2);
|
||||
border-left: 4px solid #dc3545;
|
||||
}
|
||||
|
||||
.warning {
|
||||
background: rgba(255, 193, 7, 0.2);
|
||||
border-left: 4px solid #ffc107;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 rgba(255, 94, 98, 0.7);
|
||||
|
|
@ -735,24 +793,4 @@ p {
|
|||
transform: scale(1.3);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.voice-control-container {
|
||||
left: 20px;
|
||||
bottom: 20px;
|
||||
}
|
||||
|
||||
.command-display {
|
||||
left: 100px;
|
||||
max-width: calc(100vw - 120px);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue