ai-speech-build/src/util/voice-system.js

451 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

class VoiceControlSystem {
constructor(config) {
this.config = config;
this.isListening = false;
this.recognition = null;
this.apiKey = 'sk-020189889aac40f3b050f7c60ca597f8'; // 替换为您的DeepSeek API密钥
this.init();
}
init() {
console.log(123);
this.setupSpeechRecognition();
this.setupEventListeners();
this.uploadConfigToModel();
this.getSupportedLanguages();
}
// 检测浏览器支持的语音识别语言(部分 Chromium 浏览器支持)
async getSupportedLanguages() {
console.log(123);
if (window.SpeechRecognition?.getSupportedLanguages) {
try {
const languages = await window.SpeechRecognition.getSupportedLanguages();
console.log('浏览器支持的语言列表:', languages);
return languages;
} catch (e) {
console.warn('无法获取支持的语言列表:', e);
}
}
// 若浏览器不支持 getSupportedLanguages返回常见兼容语言
return ['zh-CN', 'en-US', 'zh-TW', 'en-GB'];
}
setupSpeechRecognition() {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SpeechRecognition) {
this.updateStatus('浏览器不支持语音识别功能', 'error');
return;
}
this.recognition = new SpeechRecognition();
this.recognition.lang = 'zh-CN';
this.recognition.extra = {
'alternatives': 5,
'language': ['zh-CN', 'zh-HK', 'zh-TW', 'en-US'] // 提供备选语言
};
this.recognition.continuous = false;
this.recognition.interimResults = false;
this.recognition.onstart = () => {
this.isListening = true;
this.updateStatus('正在聆听...', 'listening');
document.getElementById('voice-btn').classList.add('listening');
};
this.recognition.onend = () => {
this.isListening = false;
document.getElementById('voice-btn').classList.remove('listening');
this.updateStatus('准备就绪', 'ready');
};
this.recognition.onresult = async (event) => {
const transcript = event.results[0][0].transcript;
console.log(transcript,"-----transcript");
this.updateStatus(`识别结果: "${transcript}"`, 'success');
await this.processVoiceCommand(transcript);
};
this.recognition.onerror = (event) => {
this.updateStatus(`语音识别错误: ${event.error}`, 'error');
};
}
setupEventListeners() {
const voiceBtn = document.getElementById('voice-btn');
voiceBtn.addEventListener('click', () => {
const input = document.getElementById('input-text');
let val = input.value;
console.log(val,"---val");
this.toggleListening(val)
});
// 添加API密钥配置界面
this.setupApiKeyConfig();
}
setupApiKeyConfig() {
const configBtn = document.createElement('button');
configBtn.textContent = '配置API密钥';
configBtn.style.marginLeft = '10px';
configBtn.addEventListener('click', () => this.showApiKeyConfig());
document.getElementById('voice-btn').after(configBtn);
}
showApiKeyConfig() {
const apiKey = prompt('请输入DeepSeek API密钥:', this.apiKey);
if (apiKey !== null) {
this.apiKey = apiKey;
this.updateStatus('API密钥已更新', 'success');
}
}
async toggleListening(value) {
if (!this.apiKey) {
this.updateStatus('请先配置DeepSeek API密钥', 'error');
this.showApiKeyConfig();
return;
}
let transcript = value;
await this.processVoiceCommand(transcript);
// if (this.isListening) {
// this.recognition.stop();
// } else {
// this.recognition.start();
// }
}
async uploadConfigToModel() {
try {
this.updateStatus('配置文件已加载', 'info');
console.log('DeepSeek配置已准备:', this.config);
} catch (error) {
this.updateStatus('配置加载失败', 'error');
}
}
async processVoiceCommand(voiceText) {
try {
this.updateStatus('正在调用DeepSeek分析指令...', 'info');
const sequence = await this.callDeepSeekAPI(voiceText);
this.updateStatus('DeepSeek指令序列生成成功', 'success');
await this.executeSequence(sequence);
} catch (error) {
console.error('DeepSeek处理错误:', error);
this.updateStatus(`处理失败: ${error.message}`, 'error');
// 尝试使用备用方案
try {
this.updateStatus('尝试使用备用方案...', 'info');
const fallbackSequence = this.fallbackModelResponse(voiceText);
await this.executeSequence(fallbackSequence);
} catch (fallbackError) {
this.updateStatus('备用方案也失败了', 'error');
}
}
}
async callDeepSeekAPI(voiceText) {
const API_URL = 'https://api.deepseek.com/v1/chat/completions';
if (!this.apiKey) {
throw new Error('请先配置DeepSeek API密钥');
}
const prompt = this.buildDeepSeekPrompt(voiceText);
try {
const response = await fetch(API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.apiKey}`
},
body: JSON.stringify({
model: "deepseek-chat",
messages: [
{
role: "system",
content: `你是一个专业的网页语音控制助手。请严格根据提供的配置文件和用户指令,生成准确的操作序列。
重要规则:
1. 只返回纯JSON格式不要包含任何其他文本
2. JSON结构必须包含sequence数组
3. 每个指令必须存在于配置文件中
4. 参数必须匹配指令定义
5. 按逻辑顺序排列指令`
},
{
role: "user",
content: prompt
}
],
temperature: 0.1,
max_tokens: 1000,
response_format: { type: "json_object" }
})
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(`DeepSeek API错误: ${response.status} ${errorData.message || ''}`);
}
const data = await response.json();
const content = data.choices[0].message.content;
// 解析JSON响应
let result;
try {
result = JSON.parse(content);
} catch (parseError) {
// 尝试提取JSON内容
const jsonMatch = content.match(/\{[\s\S]*\}/);
if (jsonMatch) {
result = JSON.parse(jsonMatch[0]);
} else {
throw new Error('DeepSeek返回了非JSON格式的响应');
}
}
console.log(result,"---result");
// 验证响应结构
if (!result.sequence || !Array.isArray(result.sequence)) {
throw new Error('DeepSeek返回了无效的指令序列格式');
}
return result;
} catch (error) {
console.error('DeepSeek API调用错误:', error);
throw new Error(`DeepSeek处理失败: ${error.message}`);
}
}
buildDeepSeekPrompt(voiceText) {
// 构建指令选择指南
const commandSelectionGuide = this.buildCommandSelectionGuide();
return `网页交互配置文件:
${JSON.stringify(this.config, null, 2)}
用户语音指令:"${voiceText}"
${commandSelectionGuide}
请严格遵循以下规则生成指令序列:
1. 每个用户指令只选择最匹配的一个命令
2. 不要重复执行相同功能的命令
3. 如果多个命令描述相似,选择命令名称最简洁的那个
4. 确保指令顺序合理
5. 只返回纯JSON格式
要求格式:
{
"sequence": [
{"command": "指令名", "params": {参数对象}}
]
}
请为指令"${voiceText}"生成正确的序列:`;
}
buildCommandSelectionGuide() {
// 分组相似命令
const commandGroups = {};
this.config.commands.forEach(cmd => {
const key = cmd.description + cmd.selector;
if (!commandGroups[key]) {
commandGroups[key] = [];
}
commandGroups[key].push(cmd);
});
let guide = "指令选择指南:\n";
Object.values(commandGroups).forEach(group => {
if (group.length > 1) {
guide += `\n相似命令组(选择其中一个):\n`;
group.forEach(cmd => {
guide += `- ${cmd.command}: ${cmd.description}\n`;
});
// 推荐选择规则
const recommended = group.reduce((prev, current) =>
prev.command.length < current.command.length ? prev : current
);
guide += `推荐选择: ${recommended.command} (名称最简洁)\n`;
}
});
return guide;
}
// 备用方案当DeepSeek API不可用时
fallbackModelResponse(voiceText) {
const lowerText = voiceText.toLowerCase();
// 简单的规则匹配
if (lowerText.includes('登录')) {
const username = this.extractParam(voiceText, '用户') || this.extractParam(voiceText, '账号') || 'testuser';
const password = this.extractParam(voiceText, '密码') || '123456';
return {
sequence: [
{ command: "open_login" },
{ command: "input_username", params: { username } },
{ command: "input_password", params: { password } },
{ command: "submit_login" }
]
};
}
if (lowerText.includes('搜索')) {
const keyword = this.extractSearchKeyword(voiceText) || '商品';
return {
sequence: [
{ command: "search_product", params: { keyword } }
]
};
}
if (lowerText.includes('购物车') || lowerText.includes('加入')) {
return {
sequence: [
{ command: "add_to_cart" }
]
};
}
throw new Error('无法识别的指令');
}
extractParam(text, paramName) {
const regex = new RegExp(`${paramName}[:]*\\s*([^\\s]+)`, 'i');
const match = text.match(regex);
return match ? match[1] : null;
}
extractSearchKeyword(text) {
const regex = /搜索[:]*\s*([^。,!?]+)/i;
const match = text.match(regex);
return match ? match[1].trim() : null;
}
async executeSequence(sequence) {
for (const [index, instruction] of sequence.sequence.entries()) {
try {
await this.executeInstruction(instruction);
this.logCommand(`✓ 完成: ${instruction.command}`);
// 在指令之间添加延迟
if (index < sequence.sequence.length - 1) {
await this.delay(800);
}
} catch (error) {
this.logCommand(`✗ 错误: ${instruction.command} - ${error.message}`);
throw error;
}
}
}
async executeInstruction(instruction) {
const commandConfig = this.config.commands.find(c => c.command === instruction.command);
if (!commandConfig) {
throw new Error(`未知指令: ${instruction.command}`);
}
this.logCommand(`开始执行: ${instruction.command}`);
const element = document.querySelector(commandConfig.selector);
if (!element) {
throw new Error(`找不到元素: ${commandConfig.selector}`);
}
// 滚动到元素可见
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
switch (commandConfig.action) {
case 'click':
element.click();
this.logCommand(`点击: ${commandConfig.selector}`);
break;
case 'input':
const inputParam = commandConfig.params[0];
if (instruction.params && instruction.params[inputParam.name]) {
element.value = instruction.params[inputParam.name];
element.dispatchEvent(new Event('input', { bubbles: true }));
this.logCommand(`输入${inputParam.name}: ${instruction.params[inputParam.name]}`);
}
break;
case 'navigate':
if (instruction.params && instruction.params.product_url) {
window.location.href = instruction.params.product_url;
}
break;
case 'input_and_submit':
if (instruction.params && instruction.params.keyword) {
element.value = instruction.params.keyword;
element.dispatchEvent(new Event('input', { bubbles: true }));
// 尝试提交表单或点击相关按钮
if (element.form) {
element.form.submit();
} else {
// 查找提交按钮
const submitBtn = document.querySelector('#search-submit, [type="submit"]');
if (submitBtn) submitBtn.click();
}
this.logCommand(`搜索: ${instruction.params.keyword}`);
}
break;
default:
throw new Error(`未知动作类型: ${commandConfig.action}`);
}
// 添加视觉反馈
this.highlightElement(element);
}
highlightElement(element) {
const originalStyle = element.style.boxShadow;
element.style.boxShadow = '0 0 0 3px #4CAF50';
setTimeout(() => {
element.style.boxShadow = originalStyle;
}, 1000);
}
updateStatus(message, type = 'info') {
const statusElement = document.getElementById('status');
statusElement.textContent = message;
statusElement.className = `status ${type}`;
}
logCommand(message) {
return
const list = document.getElementById('command-list');
const item = document.createElement('li');
item.textContent = `${new Date().toLocaleTimeString()}: ${message}`;
list.appendChild(item);
list.scrollTop = list.scrollHeight;
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// 初始化系统
document.addEventListener('DOMContentLoaded', () => {
new VoiceControlSystem(config);
});