增加AI文本回复
This commit is contained in:
parent
eaf75ac17f
commit
c43b5e5ac1
32
main.js
32
main.js
@ -9,17 +9,30 @@ const init = async () => {
|
|||||||
let spaceTimer = null;
|
let spaceTimer = null;
|
||||||
let isRecording = false;
|
let isRecording = false;
|
||||||
|
|
||||||
|
// 统一定义 UI 更新引用,方便 handleCommand 调用
|
||||||
|
const uiRefs = {
|
||||||
|
updateStatus: (text) => {
|
||||||
|
const statusText = document.getElementById("statusText");
|
||||||
|
if (statusText) {
|
||||||
|
statusText.innerText = text;
|
||||||
|
statusText.style.color = "#409eff"; // 使用蓝色区分对话与就绪状态
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
async function startProcess(text) {
|
async function startProcess(text) {
|
||||||
if (!text) return;
|
if (!text) return;
|
||||||
ui.setLoading(true);
|
ui.setLoading(true);
|
||||||
try {
|
try {
|
||||||
const localMatch = COMMANDS.find(c => text.includes(c.key));
|
// 无论是否本地匹配,统一走 translateToCommand 以获取对话回复
|
||||||
if (localMatch) {
|
// 如果你希望本地极速响应,可以在此保留逻辑,但建议统一走 AI 获取 <communication>
|
||||||
handleCommand(localMatch.key);
|
|
||||||
} else {
|
|
||||||
const aiResult = await translateToCommand(text);
|
const aiResult = await translateToCommand(text);
|
||||||
if (aiResult) handleCommand(aiResult);
|
if (aiResult) {
|
||||||
|
handleCommand(aiResult, uiRefs);
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("处理流程错误:", err);
|
||||||
|
uiRefs.updateStatus("处理指令时出错");
|
||||||
} finally {
|
} finally {
|
||||||
ui.setLoading(false);
|
ui.setLoading(false);
|
||||||
}
|
}
|
||||||
@ -30,24 +43,23 @@ const init = async () => {
|
|||||||
startProcess(text);
|
startProcess(text);
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- 逻辑 A: 保留原有按钮点击录音 ---
|
// 按钮触发录音
|
||||||
ui.btn.onclick = () => {
|
ui.btn.onclick = () => {
|
||||||
if (!isRecording) {
|
if (!isRecording) {
|
||||||
voiceCtrl.start();
|
voiceCtrl.start();
|
||||||
ui.setRecording(true);
|
ui.setRecording(true);
|
||||||
isRecording = true;
|
isRecording = true;
|
||||||
// 按钮模式下 3秒后自动停止,或靠 recognition 自动结束
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (isRecording) {
|
if (isRecording) {
|
||||||
voiceCtrl.stop();
|
voiceCtrl.stop();
|
||||||
ui.setRecording(false);
|
ui.setRecording(false);
|
||||||
isRecording = false;
|
isRecording = false;
|
||||||
}
|
}
|
||||||
}, 3000);
|
}, 4000); // 按钮模式延长到 4s,确保说话完整
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- 逻辑 B: 空格长按 0.5s 触发 ---
|
// 空格长按触发录音
|
||||||
window.addEventListener("keydown", (e) => {
|
window.addEventListener("keydown", (e) => {
|
||||||
if (e.code === "Space" && e.target.tagName !== "INPUT" && e.target.tagName !== "TEXTAREA") {
|
if (e.code === "Space" && e.target.tagName !== "INPUT" && e.target.tagName !== "TEXTAREA") {
|
||||||
if (spaceTimer || isRecording) return;
|
if (spaceTimer || isRecording) return;
|
||||||
@ -69,7 +81,6 @@ const init = async () => {
|
|||||||
spaceTimer = null;
|
spaceTimer = null;
|
||||||
}
|
}
|
||||||
if (isRecording) {
|
if (isRecording) {
|
||||||
// 松开空格,延迟一小会儿停止,确保最后几个字能录进去
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
voiceCtrl.stop();
|
voiceCtrl.stop();
|
||||||
ui.setRecording(false);
|
ui.setRecording(false);
|
||||||
@ -80,6 +91,7 @@ const init = async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 输入框回车触发
|
||||||
ui.input.onkeydown = (e) => {
|
ui.input.onkeydown = (e) => {
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
const val = ui.input.value;
|
const val = ui.input.value;
|
||||||
|
|||||||
@ -8,15 +8,13 @@ export async function translateToCommand(userInput) {
|
|||||||
标准指令列表:[${availableKeys}]
|
标准指令列表:[${availableKeys}]
|
||||||
|
|
||||||
任务规则:
|
任务规则:
|
||||||
1. 识别用户意图并按以下 XML 格式输出:
|
1. **必须包含对话**:无论是否执行指令,你都必须在 <communication>XXXX</communication> 标签中放入你对用户说的话。
|
||||||
- 基础格式:<cmd>标准指令</cmd>
|
2. **意图识别**:如果用户意图匹配指令列表,输出 <cmd>标准指令</cmd>。如有参数(如设备号),输出 <arg>参数</arg>。
|
||||||
- 带参数格式(如设备号、序列号):<cmd>标准指令</cmd><arg>参数内容</arg>
|
3. **示例**:
|
||||||
2. 示例:
|
- 用户:“打开监控中心” -> “<communication>好的,正在为您进入监控中心。</communication><cmd>监控中心</cmd>”
|
||||||
- “帮我添加一台编号为20260114的设备” -> 输出 “<cmd>添加设备</cmd><arg>20260114</arg>”
|
- 用户:“你是谁” -> “<communication>我是您的自动化 AI 助手,可以帮您操作平台。</communication>”
|
||||||
- “打开监控中心” -> 输出 “<cmd>监控中心</cmd>”
|
- 用户:“添加设备 888” -> “<communication>没问题,正在为您添加编号为 888 的设备。</communication><cmd>添加设备</cmd><arg>888</arg>”
|
||||||
3. “主界面”、“主页”映射为“首页”。
|
4. 只输出 XML 结果,不要输出任何解释说明,保持简洁。`;
|
||||||
4. 只输出 XML 结果,不要输出任何解释说明或标点。
|
|
||||||
5. 无法匹配请输出 UNKNOWN。`;
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const handler = (event) => {
|
const handler = (event) => {
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
// scripts/commands.js
|
||||||
export const COMMANDS = [
|
export const COMMANDS = [
|
||||||
{ key: "首页", menu: "首页", route: "/首页" },
|
{ key: "首页", menu: "首页", route: "/首页" },
|
||||||
{ key: "添加设备", menu: "添加设备", route: "/添加设备/添加设备" },
|
{ key: "添加设备", menu: "添加设备", route: "/添加设备/添加设备" },
|
||||||
@ -53,28 +54,16 @@ function waitForElement(selector, callback, timeout = 5000) {
|
|||||||
}, 200);
|
}, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 自动化填写和点击逻辑
|
|
||||||
*/
|
|
||||||
function autoFillAndSubmit(value) {
|
function autoFillAndSubmit(value) {
|
||||||
// 1. 等待输入框出现 (针对 Element Plus 使用 .el-input__inner)
|
|
||||||
// 提示:如果有多个框,可改用 'input[placeholder*="上云码"]' 增加精度
|
|
||||||
waitForElement('.el-input__inner', (input) => {
|
waitForElement('.el-input__inner', (input) => {
|
||||||
console.log("🔍 已定位输入框,开始填充:", value);
|
|
||||||
|
|
||||||
// 填写内容
|
|
||||||
input.value = value;
|
input.value = value;
|
||||||
|
|
||||||
// 关键:手动触发事件以同步 Vue 的双向绑定 (v-model)
|
|
||||||
input.dispatchEvent(new Event('input', { bubbles: true }));
|
input.dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
input.dispatchEvent(new Event('change', { bubbles: true }));
|
input.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
|
||||||
// 2. 自动点击“确认”按钮 (通常类名为 el-button--primary)
|
|
||||||
const submitBtn = document.querySelector('.el-button--primary');
|
const submitBtn = document.querySelector('.el-button--primary');
|
||||||
if (submitBtn) {
|
if (submitBtn) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
submitBtn.click();
|
submitBtn.click();
|
||||||
console.log("✅ 自动化流程已触发提交");
|
|
||||||
}, 600);
|
}, 600);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -88,28 +77,38 @@ export function expandParentMenu(span) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleCommand(aiResult) {
|
/**
|
||||||
|
* 处理 AI 返回的混合指令 (对话 + 操作)
|
||||||
|
*/
|
||||||
|
export function handleCommand(aiResult, uiRefs) {
|
||||||
if (!aiResult || aiResult === "UNKNOWN") return;
|
if (!aiResult || aiResult === "UNKNOWN") return;
|
||||||
console.log("🚀 收到结构化指令:", aiResult);
|
|
||||||
|
|
||||||
// 使用正则解析 XML 标签内容
|
// 1. 解析对话内容并反馈
|
||||||
|
const commMatch = aiResult.match(/<communication>([\s\S]*?)<\/communication>/);
|
||||||
|
if (commMatch && commMatch[1]) {
|
||||||
|
const speechText = commMatch[1].trim();
|
||||||
|
// 更新 UI 状态
|
||||||
|
if (uiRefs && uiRefs.updateStatus) {
|
||||||
|
uiRefs.updateStatus(speechText);
|
||||||
|
}
|
||||||
|
// 语音播报
|
||||||
|
const utterance = new SpeechSynthesisUtterance(speechText);
|
||||||
|
utterance.lang = "zh-CN";
|
||||||
|
window.speechSynthesis.speak(utterance);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 解析指令逻辑
|
||||||
const cmdMatch = aiResult.match(/<cmd>([\s\S]*?)<\/cmd>/);
|
const cmdMatch = aiResult.match(/<cmd>([\s\S]*?)<\/cmd>/);
|
||||||
const argMatch = aiResult.match(/<arg>([\s\S]*?)<\/arg>/);
|
const argMatch = aiResult.match(/<arg>([\s\S]*?)<\/arg>/);
|
||||||
|
|
||||||
const key = cmdMatch ? cmdMatch[1].trim() : null;
|
const key = cmdMatch ? cmdMatch[1].trim() : null;
|
||||||
const arg = argMatch ? argMatch[1].trim() : null;
|
const arg = argMatch ? argMatch[1].trim() : null;
|
||||||
|
|
||||||
if (!key) {
|
if (key) {
|
||||||
console.warn("未能解析出有效的 <cmd> 标签");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 在命令库中寻找匹配项
|
|
||||||
const command = COMMANDS.find(c => c.key === key) ||
|
const command = COMMANDS.find(c => c.key === key) ||
|
||||||
COMMANDS.find(c => key.includes(c.key));
|
COMMANDS.find(c => key.includes(c.key));
|
||||||
|
|
||||||
if (command) {
|
if (command) {
|
||||||
// 第一步:查找菜单并跳转
|
|
||||||
const allSpans = Array.from(document.querySelectorAll("span"));
|
const allSpans = Array.from(document.querySelectorAll("span"));
|
||||||
let span = allSpans.find(el => el.innerText.trim() === command.menu);
|
let span = allSpans.find(el => el.innerText.trim() === command.menu);
|
||||||
|
|
||||||
@ -117,19 +116,11 @@ export function handleCommand(aiResult) {
|
|||||||
expandParentMenu(span);
|
expandParentMenu(span);
|
||||||
span.click();
|
span.click();
|
||||||
window.location.hash = command.route;
|
window.location.hash = command.route;
|
||||||
|
if (arg) autoFillAndSubmit(arg);
|
||||||
// 第二步:如果有参数(如设备号),启动后续填充流程
|
|
||||||
if (arg) {
|
|
||||||
console.log(`⏳ 检测到参数,准备自动填充: ${arg}`);
|
|
||||||
autoFillAndSubmit(arg);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// 容错:如果没找到菜单元素,依然尝试直接跳转路由
|
|
||||||
console.log("未发现菜单 DOM,尝试直接通过路由跳转");
|
|
||||||
window.location.hash = command.route;
|
window.location.hash = command.route;
|
||||||
if (arg) autoFillAndSubmit(arg);
|
if (arg) autoFillAndSubmit(arg);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
console.warn("未匹配到标准库指令:", key);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user