增加AI文本回复

This commit is contained in:
张梦南 2026-01-15 22:49:48 +08:00
parent eaf75ac17f
commit c43b5e5ac1
3 changed files with 64 additions and 63 deletions

32
main.js
View File

@ -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;

View File

@ -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) => {

View File

@ -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);
} }
} }