feat: AI 控制台增强——①de_read_clipboard 剪贴板回读工具;②任务可中断(stop_event 循环检查点 + API stop + 前端■停止按钮);③智能滚动(回看历史不被打断,贴底才跟随);④右侧实时画面(MJPEG 流自动跟随操作设备,可手动选择)

This commit is contained in:
2026-09-04 13:32:35 +08:00
parent 44cab88546
commit 5145f83f06
5 changed files with 139 additions and 11 deletions
+71 -8
View File
@@ -14,6 +14,8 @@ function initAgentChat(){
+ '例如:「打开抖音搜索奚学东,告诉我第一个视频的标题」<br>'
+ '点击右上角 ⚙ 配置模型与 API Key。</div>';
}
bindChatScroll();
loadLiveDevices();
// 输入框快捷键
const inp = document.getElementById('agent-input');
inp.addEventListener('keydown', ev=>{
@@ -88,9 +90,18 @@ function clearEmpty(){
const empty = document.querySelector('#agent-chat .agent-empty');
if(empty) empty.remove();
}
function scrollChat(){
let _chatPinned = true; // 用户是否在底部(贴底才自动跟随滚动)
function scrollChat(force){
const chat = document.getElementById('agent-chat');
chat.scrollTop = chat.scrollHeight;
if(force || _chatPinned){
chat.scrollTop = chat.scrollHeight;
}
}
function bindChatScroll(){
const chat = document.getElementById('agent-chat');
chat.addEventListener('scroll', ()=>{
_chatPinned = chat.scrollHeight - chat.scrollTop - chat.clientHeight < 60;
});
}
function clearAgentChat(){
document.getElementById('agent-chat').innerHTML =
@@ -109,6 +120,18 @@ function newAgentSession(){
}
// ================== 发送与流式接收 ==================
function setRunning(on){
_agentBusy = on;
document.getElementById('agent-running-tag').style.display = on ? 'inline' : 'none';
document.getElementById('btn-agent-stop').style.display = on ? 'inline-block' : 'none';
document.getElementById('btn-agent-send').disabled = on;
}
function stopAgent(){
apiPost('/api/agent/stop',{}).then(r=>{
if(r&&r.ok) showToast('已请求停止,正在中断…','success');
else showToast((r&&r.error)||'停止失败','error');
});
}
function sendAgentMsg(){
if(_agentBusy){showToast('上一轮还在运行','error');return;}
const inp = document.getElementById('agent-input');
@@ -125,9 +148,7 @@ function sendAgentMsg(){
showToast(msg,'error');
return;
}
_agentBusy = true;
document.getElementById('agent-running-tag').style.display = 'inline';
document.getElementById('btn-agent-send').disabled = true;
setRunning(true);
listenStream(r.run_id);
});
}
@@ -158,6 +179,7 @@ function listenStream(runId){
es.addEventListener('step', ev=>{
const d = JSON.parse(ev.data);
followSerialFromArgs(d.args);
if(!msgEl) msgEl = newAssistantMsg();
const cards = msgEl.querySelector('.agent-toolcards');
const card = document.createElement('div');
@@ -199,6 +221,49 @@ function listenStream(runId){
};
}
// ============ 实时画面(右侧,MJPEG 流) ============
let _liveSerial = '';
function showLive(serial){
if(!serial || serial === _liveSerial) return;
_liveSerial = serial;
const img = document.getElementById('agent-live-img');
img.src = '/api/screen/stream?serial=' + encodeURIComponent(serial) + '&q=80&fps=8&t=' + Date.now();
img.style.display = 'block';
document.getElementById('agent-live-empty').style.display = 'none';
document.getElementById('agent-live-serial').textContent = serial;
const sel = document.getElementById('agent-live-select');
if(sel) sel.value = serial;
}
function stopLive(){
_liveSerial = '';
const img = document.getElementById('agent-live-img');
img.src = '';
img.style.display = 'none';
document.getElementById('agent-live-empty').style.display = 'block';
document.getElementById('agent-live-serial').textContent = '';
}
function followSerialFromArgs(argsStr){
try{
const a = JSON.parse(argsStr || '{}');
if(a.serial) showLive(a.serial);
}catch(e){}
}
function watchDevice(serial){
if(!serial){ stopLive(); return; }
showLive(serial);
}
function loadLiveDevices(){
apiGet('/api/devices').then(r=>{
if(!r||!r.ok)return;
const sel = document.getElementById('agent-live-select');
if(!sel)return;
const cur = sel.value;
const devs = (r.devices||[]).filter(x=>x.indexOf(':')>=0);
sel.innerHTML = '<option value="">选择设备观看…</option>'
+ devs.map(s=>'<option value="'+esc(s)+'"'+(s===cur?' selected':'')+'>'+esc(s)+'</option>').join('');
});
}
// 截图点击放大查看
function zoomScreenshot(img){
const ov = document.getElementById('agent-zoom');
@@ -226,10 +291,8 @@ document.addEventListener('click', ev=>{
});
function endRun(){
_agentBusy = false;
setRunning(false);
_agentStream && _agentStream.close();
_agentStream = null;
document.getElementById('agent-running-tag').style.display = 'none';
document.getElementById('btn-agent-send').disabled = false;
scrollChat();
}