初版Demo完善

This commit is contained in:
张梦南 2026-01-22 13:43:59 +08:00
parent 1b8f833789
commit 34d658c2b9
10 changed files with 75 additions and 75 deletions

View File

@ -1,9 +1,7 @@
// background/background.js
// 监听来自 content.js 的消息转发 // 监听来自 content.js 的消息转发
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.type === "AI_TRANSLATE") { if (request.type === "AI_TRANSLATE") {
// 1. 同时获取 AI 配置信息和语音开关状态 // 获取 AI 配置信息和语音开关状态
chrome.storage.sync.get(['aiConfig', 'voiceEnabled'], async (result) => { chrome.storage.sync.get(['aiConfig', 'voiceEnabled'], async (result) => {
const config = result.aiConfig; const config = result.aiConfig;
const voiceEnabled = result.voiceEnabled || false; // 默认为关闭 const voiceEnabled = result.voiceEnabled || false; // 默认为关闭
@ -14,7 +12,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
} }
try { try {
// 2. 向 AI 平台发起 fetch 请求 // 向 AI 平台发起 fetch 请求
const response = await fetch(`${config.apiUrl}/chat/completions`, { const response = await fetch(`${config.apiUrl}/chat/completions`, {
method: 'POST', method: 'POST',
headers: { headers: {
@ -38,7 +36,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
const data = await response.json(); const data = await response.json();
// 3. 将 AI 结果和语音开关状态一并返回给 content.js // 将 AI 结果和语音开关状态返回给 content.js
sendResponse({ sendResponse({
success: true, success: true,
data: data, data: data,
@ -51,7 +49,6 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
} }
}); });
// 返回 true 表示我们将异步发送响应
return true; return true;
} }
}); });

View File

@ -3,7 +3,7 @@ script.type = 'module';
script.src = chrome.runtime.getURL('main.js'); script.src = chrome.runtime.getURL('main.js');
document.head.appendChild(script); document.head.appendChild(script);
// 监听来自网页(main.js)的请求 // 监听来自 main.js 的请求
window.addEventListener("DO_AI_REQUEST", async (event) => { window.addEventListener("DO_AI_REQUEST", async (event) => {
const { userInput, systemPrompt } = event.detail; const { userInput, systemPrompt } = event.detail;
@ -13,7 +13,7 @@ window.addEventListener("DO_AI_REQUEST", async (event) => {
userInput, userInput,
systemPrompt systemPrompt
}, (response) => { }, (response) => {
// 将结果传回给网页(main.js) // 将结果传回给 main.js
window.dispatchEvent(new CustomEvent("AI_RESULT", { detail: response })); window.dispatchEvent(new CustomEvent("AI_RESULT", { detail: response }));
}); });
}); });

18
main.js
View File

@ -1,4 +1,3 @@
// main.js
import { createPanel } from './scripts/panel.js'; import { createPanel } from './scripts/panel.js';
import { handleCommand, COMMANDS } from './scripts/commands.js'; import { handleCommand, COMMANDS } from './scripts/commands.js';
import { initVoice } from './scripts/voice.js'; import { initVoice } from './scripts/voice.js';
@ -9,29 +8,24 @@ const init = async () => {
let spaceTimer = null; let spaceTimer = null;
let isRecording = false; let isRecording = false;
// 统一定义 UI 更新引用,方便 handleCommand 调用 // 统一定义 UI 更新引用
const uiRefs = { const uiRefs = {
updateStatus: (text) => { updateStatus: (text) => {
const statusText = document.getElementById("statusText"); const statusText = document.getElementById("statusText");
if (statusText) { if (statusText) {
statusText.innerText = text; statusText.innerText = text;
statusText.style.color = "#409eff"; // 使用蓝色区分对话与就绪状态 statusText.style.color = "#409eff";
} }
} }
}; };
/**
* 处理 AI 流程的主函数
*/
async function startProcess(text) { async function startProcess(text) {
if (!text) return; if (!text) return;
ui.setLoading(true); ui.setLoading(true);
try { try {
// 获取 AI 响应结果(包含内容和语音开关状态)
const aiResponse = await translateToCommand(text); const aiResponse = await translateToCommand(text);
if (aiResponse && aiResponse.content) { if (aiResponse && aiResponse.content) {
// 将内容、UI 引用以及语音开关状态传给指令处理器
handleCommand(aiResponse.content, uiRefs, aiResponse.voiceEnabled); handleCommand(aiResponse.content, uiRefs, aiResponse.voiceEnabled);
} else { } else {
uiRefs.updateStatus("未识别到有效指令"); uiRefs.updateStatus("未识别到有效指令");
@ -67,7 +61,6 @@ const init = async () => {
voiceCtrl.start(); voiceCtrl.start();
ui.setRecording(true); ui.setRecording(true);
isRecording = true; isRecording = true;
// 4秒自动停止录音保护
setTimeout(() => { setTimeout(() => {
if (isRecording) { if (isRecording) {
voiceCtrl.stop(); voiceCtrl.stop();
@ -82,18 +75,17 @@ const init = async () => {
} }
}; };
// 空格长按触发录音逻辑 // 空格长按逻辑
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;
spaceTimer = setTimeout(() => { spaceTimer = setTimeout(() => {
if (voiceCtrl.supportSpeech) { if (voiceCtrl.supportSpeech) {
voiceCtrl.start(); voiceCtrl.start();
ui.setRecording(true); ui.setRecording(true);
isRecording = true; isRecording = true;
} }
}, 500); // 判定为长按 }, 500);
} }
}); });
@ -104,7 +96,6 @@ const init = async () => {
spaceTimer = null; spaceTimer = null;
} }
if (isRecording) { if (isRecording) {
// 延迟停止以捕捉最后一段语音
setTimeout(() => { setTimeout(() => {
voiceCtrl.stop(); voiceCtrl.stop();
ui.setRecording(false); ui.setRecording(false);
@ -115,7 +106,6 @@ const init = async () => {
}); });
}; };
// 启动初始化
if (document.readyState === "complete" || document.readyState === "interactive") { if (document.readyState === "complete" || document.readyState === "interactive") {
init(); init();
} else { } else {

View File

@ -1,7 +1,7 @@
{ {
"manifest_version": 3, "manifest_version": 3,
"name": "Supmea Automation AI", "name": "Agent_For_Supmea",
"version": "0.1.0", "version": "0.1.17",
"permissions": ["activeTab", "storage"], "permissions": ["activeTab", "storage"],
"background": { "background": {
"service_worker": "background/background.js" "service_worker": "background/background.js"

View File

@ -30,7 +30,7 @@
</style> </style>
</head> </head>
<body> <body>
<h3>⚙️ AI 模型配置</h3> <h3> AI 模型配置</h3>
<div class="item"> <div class="item">
<label>API Base URL</label> <label>API Base URL</label>

View File

@ -1,15 +1,14 @@
// 页面加载时读取存储的配置 // 页面加载时读取存储的配置
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
// 同时获取 aiConfig 和 voiceEnabled 状态
chrome.storage.sync.get(['aiConfig', 'voiceEnabled'], (result) => { chrome.storage.sync.get(['aiConfig', 'voiceEnabled'], (result) => {
const config = result.aiConfig || {}; const config = result.aiConfig || {};
// 填充 API 配置
document.getElementById('apiUrl').value = config.apiUrl || DEFAULT_URL; document.getElementById('apiUrl').value = config.apiUrl || DEFAULT_URL;
document.getElementById('apiKey').value = config.apiKey || ""; document.getElementById('apiKey').value = config.apiKey || "";
document.getElementById('modelName').value = config.modelName || DEFAULT_MODEL; document.getElementById('modelName').value = config.modelName || DEFAULT_MODEL;
// 填充语音开关状态(默认为关闭 false // 语音开关状态(默认为关闭)
document.getElementById('voiceEnabled').checked = result.voiceEnabled || false; document.getElementById('voiceEnabled').checked = result.voiceEnabled || false;
}); });
}); });
@ -26,7 +25,7 @@ document.getElementById('save').addEventListener('click', () => {
// 验证 API Key 是否填写 // 验证 API Key 是否填写
if (!config.apiKey) { if (!config.apiKey) {
showStatus("请输入 API Key", "#f56c6c"); showStatus("请输入 API Key", "#f56c6c");
return; return;
} }
@ -39,7 +38,6 @@ document.getElementById('save').addEventListener('click', () => {
}); });
}); });
// 状态提示函数
function showStatus(text, color) { function showStatus(text, color) {
const status = document.getElementById('status'); const status = document.getElementById('status');
status.textContent = text; status.textContent = text;

View File

@ -1,10 +1,9 @@
// scripts/ai.js
import { COMMANDS } from './commands.js'; import { COMMANDS } from './commands.js';
export async function translateToCommand(userInput) { export async function translateToCommand(userInput) {
const availableKeys = COMMANDS.map(c => c.key).join(', '); const availableKeys = COMMANDS.map(c => c.key).join(', ');
const systemPrompt = `你是一个工业自动化系统助手。 const systemPrompt = `你是美小智,是仪表云平台的自动化小助手。
标准指令列表[${availableKeys}] 标准指令列表[${availableKeys}]
任务规则 任务规则
@ -12,7 +11,6 @@ export async function translateToCommand(userInput) {
2. **意图识别**如果用户意图匹配指令列表输出 <cmd>标准指令</cmd> <arg></arg> 2. **意图识别**如果用户意图匹配指令列表输出 <cmd>标准指令</cmd> <arg></arg>
3. **示例** 3. **示例**
- 用户打开监控中心 -> <communication>好的正在为您进入监控中心</communication><cmd></cmd> - 用户打开监控中心 -> <communication>好的正在为您进入监控中心</communication><cmd></cmd>
- 用户你是谁 -> <communication>我是您的自动化 AI 助手可以帮您操作平台</communication>
- 用户添加设备 888 -> <communication>没问题正在为您添加编号为 888 的设备</communication><cmd></cmd><arg>888</arg> - 用户添加设备 888 -> <communication>没问题正在为您添加编号为 888 的设备</communication><cmd></cmd><arg>888</arg>
4. 只输出 XML 结果不要输出任何解释说明保持简洁`; 4. 只输出 XML 结果不要输出任何解释说明保持简洁`;

View File

@ -1,6 +1,3 @@
// scripts/commands.js
// 标准指令路由映射表
export const COMMANDS = [ export const COMMANDS = [
{ key: "首页", menu: "首页", route: "/首页" }, { key: "首页", menu: "首页", route: "/首页" },
{ key: "添加设备", menu: "添加设备", route: "/添加设备/添加设备" }, { key: "添加设备", menu: "添加设备", route: "/添加设备/添加设备" },
@ -16,15 +13,32 @@ export const COMMANDS = [
{ key: "报警通知", menu: "报警通知", route: "/报警管理/报警通知" }, { key: "报警通知", menu: "报警通知", route: "/报警管理/报警通知" },
{ key: "报警记录", menu: "报警记录", route: "/报警管理/报警记录" }, { key: "报警记录", menu: "报警记录", route: "/报警管理/报警记录" },
{ key: "基础报表", menu: "基础报表", route: "/报表管理/基础报表" }, { key: "基础报表", menu: "基础报表", route: "/报表管理/基础报表" },
{ key: "分析报表", menu: "分析报表", route: "/报表管理/分析报表" }, { key: "高级报表配置", menu: "高级报表配置", route: "/报表管理/高级报表配置" },
{ key: "区域管理", menu: "区域管理", route: "/系统管理/区域管理" }, { key: "高级报表", menu: "高级报表", route: "/报表管理/高级报表" },
{ key: "角色管理", menu: "角色管理", route: "/系统管理/角色管理" }, { key: "应用场景", menu: "应用场景", route: "/仪表管理/应用场景" },
{ key: "用户管理", menu: "用户管理", route: "/系统管理/用户管理" } { key: "仪表管理", menu: "仪表管理", route: "/仪表管理/仪表管理" },
{ key: "虚拟仪表", menu: "虚拟仪表", route: "/仪表管理/虚拟仪表" },
{ key: "物位监测", menu: "物位监测", route: "/场景管理/物位监测/物位监测" },
{ key: "物位监测配置", menu: "物位监测配置", route: "/场景管理/物位监测/物位监测配置" },
{ key: "车间看板", menu: "车间看板", route: "/场景管理/车间看板/车间看板" },
{ key: "车间看板配置", menu: "车间看板配置", route: "/场景管理/车间看板/车间看板配置" },
{ key: "能源结算", menu: "能源结算", route: "/场景管理/能源抄表/能源结算" },
{ key: "能源结算配置", menu: "能源结算配置", route: "/场景管理/能源抄表/能源结算配置" },
{ key: "多租户能源结算", menu: "多租户能源结算", route: "/场景管理/多租户结算/多租户能源结算" },
{ key: "租户管理", menu: "租户管理", route: "/场景管理/多租户结算/租户管理" },
{ key: "计价方式管理", menu: "计价方式管理", route: "/场景管理/多租户结算/计价方式管理" },
{ key: "单染缸印染结算", menu: "单染缸印染结算", route: "/场景管理/印染结算/单染缸印染结算" },
{ key: "多染缸印染结算", menu: "多染缸印染结算", route: "/场景管理/印染结算/多染缸印染结算" },
{ key: "染缸能耗一览表", menu: "染缸能耗一览表", route: "/场景管理/印染结算/染缸能耗一览表" },
{ key: "印染结算配置", menu: "印染结算配置", route: "/场景管理/印染结算/印染结算配置" },
{ key: "尘埃粒子车间", menu: "尘埃粒子车间", route: "/场景管理/尘埃粒子/尘埃粒子车间" },
{ key: "洁净度一览表", menu: "洁净度一览表", route: "/场景管理/尘埃粒子/洁净度一览表" },
{ key: "尘埃粒子配置", menu: "尘埃粒子配置", route: "/场景管理/尘埃粒子/尘埃粒子配置" },
{ key: "消息管理", menu: "消息管理", route: "/系统管理/消息管理" },
{ key: "数据服务", menu: "数据服务", route: "/系统管理/数据服务" },
{ key: "数据下云", menu: "数据下云", route: "/系统管理/数据下云" },
]; ];
/**
* 展开侧边栏父级菜单
*/
function expandParentMenu(span) { function expandParentMenu(span) {
let parent = span.closest('li'); let parent = span.closest('li');
while (parent) { while (parent) {
@ -38,42 +52,52 @@ function expandParentMenu(span) {
} }
} }
/**
* 自动填充并提交搜索模拟针对某些页面的操作
*/
function autoFillAndSubmit(arg) { function autoFillAndSubmit(arg) {
if (!arg) return; if (!arg) return;
// 等待页面跳转和组件渲染
setTimeout(() => { setTimeout(() => {
const input = document.querySelector('input[placeholder*="名称"], input[placeholder*="编号"]'); // 尝试寻找各种可能的输入框(针对 Element UI
const input = document.querySelector('input[placeholder*="编号"]') ||
document.querySelector('input[placeholder*="名称"]') ||
document.querySelector('.el-input__inner');
if (input) { if (input) {
input.value = arg; input.value = arg;
// 触发 input 事件让 Vue 监听到数据变化
input.dispatchEvent(new Event('input', { bubbles: true })); input.dispatchEvent(new Event('input', { bubbles: true }));
const searchBtn = document.querySelector('button.el-button--primary');
if (searchBtn) searchBtn.click(); // 寻找包含“添加”、“查询”
setTimeout(() => {
const buttons = Array.from(document.querySelectorAll('button'));
const submitBtn = buttons.find(btn =>
btn.innerText.includes("添加") ||
btn.innerText.includes("查询") ||
btn.innerText.includes("确认") ||
btn.classList.contains('el-button--primary')
);
if (submitBtn) {
submitBtn.click();
console.log("已自动点击按钮:", submitBtn.innerText);
} }
}, 1000); }, 500);
}
}, 1200);
} }
/**
* 核心处理器根据 AI 结果执行动作
* @param {string} aiResult AI 返回的 XML 字符串
* @param {object} uiRefs UI 更新引用的对象
* @param {boolean} voiceEnabled 是否允许播放语音 ( ai.js 传入)
*/
export function handleCommand(aiResult, uiRefs, voiceEnabled = false) { export function handleCommand(aiResult, uiRefs, voiceEnabled = false) {
if (!aiResult || aiResult === "UNKNOWN") return;
// 1. 解析对话内容并反馈 if (typeof aiResult !== 'string' || aiResult === "UNKNOWN") return;
// 解析对话内容
const commMatch = aiResult.match(/<communication>([\s\S]*?)<\/communication>/); const commMatch = aiResult.match(/<communication>([\s\S]*?)<\/communication>/);
if (commMatch && commMatch[1]) { if (commMatch && commMatch[1]) {
const speechText = commMatch[1].trim(); const speechText = commMatch[1].trim();
// 始终更新 UI 界面上的文字状态
if (uiRefs && uiRefs.updateStatus) { if (uiRefs && uiRefs.updateStatus) {
uiRefs.updateStatus(speechText); uiRefs.updateStatus(speechText);
} }
// 仅在语音开关开启时播报语音 // 语音播放判断
if (voiceEnabled) { if (voiceEnabled) {
const utterance = new SpeechSynthesisUtterance(speechText); const utterance = new SpeechSynthesisUtterance(speechText);
utterance.lang = "zh-CN"; utterance.lang = "zh-CN";
@ -81,7 +105,7 @@ export function handleCommand(aiResult, uiRefs, voiceEnabled = false) {
} }
} }
// 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>/);
@ -89,14 +113,11 @@ export function handleCommand(aiResult, uiRefs, voiceEnabled = false) {
const arg = argMatch ? argMatch[1].trim() : null; const arg = argMatch ? argMatch[1].trim() : null;
if (key) { if (key) {
// 模糊匹配指令
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) {
console.log("🚀 执行指令:", command.key, "参数:", arg);
// 尝试点击侧边栏菜单(针对 Element UI 结构)
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);
@ -105,15 +126,13 @@ export function handleCommand(aiResult, uiRefs, voiceEnabled = false) {
span.click(); span.click();
} }
// 路由跳转 // 执行跳转
window.location.hash = command.route; window.location.hash = command.route;
// 自动填充搜索参数 // 如果有参数(如设备编号),执行自动填充
if (arg) { if (arg) {
autoFillAndSubmit(arg); autoFillAndSubmit(arg);
} }
} else {
console.warn("⚠️ 未找到匹配指令:", key);
} }
} }
} }

View File

@ -1,4 +1,3 @@
// scripts/panel.js
export function createPanel() { export function createPanel() {
const panelId = "automation-ai-panel"; const panelId = "automation-ai-panel";
let panel = document.getElementById(panelId); let panel = document.getElementById(panelId);

View File

@ -1,17 +1,16 @@
// scripts/voice.js
export function initVoice(panel, onResultCallback) { export function initVoice(panel, onResultCallback) {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SpeechRecognition) return { supportSpeech: false }; if (!SpeechRecognition) return { supportSpeech: false };
const recognition = new SpeechRecognition(); const recognition = new SpeechRecognition();
recognition.lang = "zh-CN"; recognition.lang = "zh-CN";
recognition.continuous = false; // 改为 false确保每次停止都能立即结算 recognition.continuous = false;
recognition.interimResults = false; recognition.interimResults = false;
recognition.onresult = (event) => { recognition.onresult = (event) => {
const text = event.results[0][0].transcript.trim(); const text = event.results[0][0].transcript.trim();
if (text) { if (text) {
console.log("🎤 识别结果:", text); console.log("识别结果:", text);
onResultCallback(text); onResultCallback(text);
} }
}; };