feat: AI 控制台增强——①de_read_clipboard 剪贴板回读工具;②任务可中断(stop_event 循环检查点 + API stop + 前端■停止按钮);③智能滚动(回看历史不被打断,贴底才跟随);④右侧实时画面(MJPEG 流自动跟随操作设备,可手动选择)
This commit is contained in:
+71
-8
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user