feat(mqtt): 集中式 MQTT 调度器(页面级操作 + vision 开关 + 页面上报)

useMqttControl 扩展命令分发(opc/display/command):
- vision_set:全局人物识别开/关(dpm:vision-control → 语音页 visionEnabled)
- ai_input / ai_preset / ai_company / ai_zone:AI 助手页操作(dpm:ai-action)
- voice_start / voice_stop / voice_refresh:语音对话页操作(dpm:voice-control)
- 页面状态即时上报 opc/frontend/state(路由变化即发,心跳外补充)

页面监听:
- AiChatPanel:输入并发送 / 选择预设问题发送
- ParkSidePanel:切换分区(加速区/国际区/成长区)/ 切换展示企业(next/prev)
- VoiceAssistant:启动/关闭对话 / 刷新页面;vision 全局开关
- MediaScreen:play/pause/next/prev 已有覆盖

切页(navigate/navigate_rel)、心跳上报沿用既有通道。
This commit is contained in:
Pine
2026-08-18 02:20:37 +08:00
parent c5a06ac160
commit 4d693e895d
4 changed files with 103 additions and 0 deletions
+16
View File
@@ -184,6 +184,22 @@ export default function AiChatPanel() {
});
}, [send]);
// MQTT 调度器 → AI 页操作:输入并发送 / 选择预设问题
useEffect(() => {
const onAiAction = (e) => {
const { type, text, index } = e.detail || {};
if (type === 'input' && text && String(text).trim()) {
setInput(String(text).trim());
void send(String(text).trim());
} else if (type === 'preset') {
const q = QUICK_QUESTIONS[Number(index)];
if (q) void send(q);
}
};
window.addEventListener('dpm:ai-action', onAiAction);
return () => window.removeEventListener('dpm:ai-action', onAiAction);
}, [send]);
const toggleVoice = useCallback(() => {
if (voiceStateRef.current === 'recording') {
const rec = recorderRef.current;
+22
View File
@@ -85,6 +85,28 @@ export default function ParkSidePanel() {
const list = COMPANIES.filter((c) => c.zone === activeZone);
// MQTT 调度器 → 企业面板操作:切换分区 / 切换展示企业
useEffect(() => {
const onAiAction = (e) => {
const { type, zone, action } = e.detail || {};
if (type === 'zone' && zone) {
const z = String(zone);
if (COMPANIES.some((c) => c.zone === z)) setActiveZone(z);
return;
}
if (type === 'company' && (action === 'next' || action === 'prev')) {
const cur = COMPANIES.filter((c) => c.zone === activeZone);
if (!cur.length) return;
const idx = cur.findIndex((c) => selected && c.room === selected.room);
const base = idx >= 0 ? idx : 0;
const n = (base + (action === 'next' ? 1 : -1) + cur.length) % cur.length;
setSelected(cur[n]);
}
};
window.addEventListener('dpm:ai-action', onAiAction);
return () => window.removeEventListener('dpm:ai-action', onAiAction);
}, [activeZone, selected]);
return (
<aside className="bd-twin-side">
{/* 分区切换 */}