nothumanallowed 9.5.2 → 9.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +154 -305
- package/bin/nha.mjs +34 -3
- package/package.json +2 -2
- package/src/cli.mjs +105 -153
- package/src/commands/ask.mjs +18 -206
- package/src/commands/chat.mjs +64 -482
- package/src/commands/ui.mjs +77 -843
- package/src/config.mjs +0 -2
- package/src/constants.mjs +1 -1
- package/src/services/google-oauth.mjs +21 -12
- package/src/services/llm.mjs +89 -120
- package/src/services/ops-daemon.mjs +236 -0
- package/src/services/screen-capture.mjs +160 -0
- package/src/services/tool-executor.mjs +86 -340
- package/src/services/web-ui.mjs +173 -422
- package/src/services/browser-engine.mjs +0 -1240
- package/src/services/conversations.mjs +0 -277
- package/src/services/web-tools.mjs +0 -430
package/src/services/web-ui.mjs
CHANGED
|
@@ -46,7 +46,10 @@ input:focus,textarea:focus{border-color:var(--green3)}
|
|
|
46
46
|
|
|
47
47
|
/* ---- LAYOUT: mobile-first ---- */
|
|
48
48
|
.app{display:flex;flex-direction:column;height:100vh;height:100dvh}
|
|
49
|
-
|
|
49
|
+
.header{display:flex;align-items:center;gap:12px;padding:10px 16px;border-bottom:1px solid var(--border);background:var(--bg);position:relative;z-index:60;flex-shrink:0}
|
|
50
|
+
.header__burger{background:none;color:var(--green);font-size:22px;padding:4px 8px;line-height:1}
|
|
51
|
+
.header__title{font-size:14px;color:var(--bright);font-weight:700;flex:1}
|
|
52
|
+
.header__clock{font-size:10px;color:var(--dim)}
|
|
50
53
|
|
|
51
54
|
.sidebar{display:none;position:fixed;top:0;left:0;width:260px;height:100vh;height:100dvh;background:var(--bg2);border-right:1px solid var(--border);z-index:200;flex-direction:column;overflow-y:auto;box-shadow:4px 0 20px rgba(0,0,0,0.8)}
|
|
52
55
|
.sidebar--open{display:flex}
|
|
@@ -84,9 +87,8 @@ input:focus,textarea:focus{border-color:var(--green3)}
|
|
|
84
87
|
.section-title{font-size:12px;color:var(--cyan);text-transform:uppercase;letter-spacing:1px;margin-bottom:10px}
|
|
85
88
|
|
|
86
89
|
/* ---- CHAT ---- */
|
|
87
|
-
.
|
|
88
|
-
.chat{
|
|
89
|
-
@media(min-width:901px){.content--chat{padding:0!important}}
|
|
90
|
+
.chat{display:flex;flex-direction:column;height:calc(100vh - 52px);height:calc(100dvh - 52px)}
|
|
91
|
+
@media(min-width:901px){.chat{height:calc(100vh - 52px)}}
|
|
90
92
|
.chat__messages{flex:1;overflow-y:auto;padding-bottom:12px;-webkit-overflow-scrolling:touch}
|
|
91
93
|
.chat__empty{text-align:center;padding:60px 16px;color:var(--dim)}
|
|
92
94
|
.chat__empty-title{font-size:28px;color:var(--green);margin-bottom:12px}
|
|
@@ -96,30 +98,10 @@ input:focus,textarea:focus{border-color:var(--green3)}
|
|
|
96
98
|
.msg--assistant .msg__bubble{background:var(--greendim);border:1px solid var(--green3);border-radius:8px 8px 8px 2px;padding:10px 14px;max-width:85%;color:var(--text);white-space:pre-wrap;word-wrap:break-word}
|
|
97
99
|
.msg__label{font-size:10px;color:var(--dim);margin-bottom:2px}
|
|
98
100
|
.msg--thinking{color:var(--dim);font-style:italic}
|
|
99
|
-
.
|
|
100
|
-
.tool-indicator--browser{border-color:#9c27b0;color:#ce93d8}
|
|
101
|
-
.tool-indicator--web{border-color:var(--cyan);color:var(--cyan)}
|
|
102
|
-
.tool-indicator--email{border-color:var(--green3);color:var(--green)}
|
|
103
|
-
.screenshot-preview{max-width:100%;border-radius:var(--r);margin:8px 0;border:1px solid var(--border)}
|
|
104
|
-
.browser-viewer{position:fixed;top:12px;left:12px;width:440px;background:var(--bg2);border:2px solid #9c27b0;border-radius:8px;box-shadow:0 8px 32px rgba(0,0,0,0.6);z-index:300;overflow:hidden;display:none;transition:all .3s ease}
|
|
105
|
-
.browser-viewer--open{display:block}
|
|
106
|
-
.browser-viewer__header{display:flex;align-items:center;gap:6px;padding:6px 10px;background:#1a1a2e;border-bottom:1px solid #9c27b0;font-size:10px;color:#ce93d8}
|
|
107
|
-
.browser-viewer__dot{width:6px;height:6px;border-radius:50%;background:#9c27b0;animation:bvpulse 1.5s infinite}
|
|
108
|
-
@keyframes bvpulse{0%,100%{opacity:1}50%{opacity:.3}}
|
|
109
|
-
.browser-viewer__title{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
110
|
-
.browser-viewer__close{background:none;border:none;color:#666;cursor:pointer;font-size:14px;padding:0 4px}
|
|
111
|
-
.browser-viewer__close:hover{color:#fff}
|
|
112
|
-
.browser-viewer__frame{width:100%;aspect-ratio:16/9;background:#000;display:flex;align-items:center;justify-content:center}
|
|
113
|
-
.browser-viewer__frame img{width:100%;height:100%;object-fit:contain}
|
|
114
|
-
.browser-viewer__status{padding:4px 10px;font-size:9px;color:var(--dim);border-top:1px solid var(--border)}
|
|
115
|
-
@media(max-width:600px){.browser-viewer{width:calc(100vw - 24px);top:8px;left:8px}}
|
|
116
|
-
@media(min-width:901px){.browser-viewer{left:232px}}
|
|
117
|
-
.chat__bar{display:flex;gap:8px;padding:10px 0 12px 0;border-top:1px solid var(--border);flex-shrink:0}
|
|
101
|
+
.chat__bar{display:flex;gap:8px;padding:10px 0 0 0;border-top:1px solid var(--border);flex-shrink:0}
|
|
118
102
|
.chat__input{flex:1;resize:none;min-height:40px;max-height:100px;padding:10px 14px}
|
|
119
103
|
.chat__send{background:var(--green3);color:var(--bg);padding:10px 16px;border-radius:var(--r);font-weight:700;font-size:12px}
|
|
120
104
|
.chat__send:disabled{opacity:.4}
|
|
121
|
-
.chat__stop{background:var(--red);color:var(--bright);padding:10px 16px;border-radius:var(--r);font-weight:700;font-size:12px;display:none}
|
|
122
|
-
.chat__stop--visible{display:block}
|
|
123
105
|
|
|
124
106
|
/* ---- TASKS ---- */
|
|
125
107
|
.task-bar{display:flex;gap:8px;margin-bottom:12px;flex-wrap:wrap}
|
|
@@ -216,52 +198,11 @@ input:focus,textarea:focus{border-color:var(--green3)}
|
|
|
216
198
|
const JS = `
|
|
217
199
|
var API = '';
|
|
218
200
|
var currentView = 'dashboard';
|
|
219
|
-
var chatHistory = [];
|
|
220
|
-
var activeConvId = null;
|
|
221
|
-
var convList = [];
|
|
201
|
+
var chatHistory = (function(){try{var s=localStorage.getItem('nha_chat_history');return s?JSON.parse(s):[];}catch(e){return [];}})();
|
|
222
202
|
var dash = {emails:[],events:[],tasks:[],plan:null,status:null};
|
|
223
|
-
var dashLoaded = {emails:false,events:false,tasks:false,contacts:false,notes:false,drive:false,github:false,notion:false,slack:false};
|
|
224
|
-
var chatStreaming = false;
|
|
225
|
-
var chatAbortController = null;
|
|
226
|
-
|
|
227
|
-
function endStreaming(){
|
|
228
|
-
chatStreaming=false;chatAbortController=null;
|
|
229
|
-
var stopBtn=document.getElementById('chatStop');if(stopBtn)stopBtn.classList.remove('chat__stop--visible');
|
|
230
|
-
var sendBtn=document.getElementById('chatSend');if(sendBtn)sendBtn.style.display='';
|
|
231
|
-
}
|
|
232
|
-
function stopChat(){
|
|
233
|
-
if(chatAbortController){try{chatAbortController.abort()}catch(e){}}
|
|
234
|
-
endStreaming();
|
|
235
|
-
if(chatHistory.length>0){
|
|
236
|
-
var last=chatHistory[chatHistory.length-1];
|
|
237
|
-
if(last.role==='assistant'&&(!last.content||last.content===''))last.content='[Stopped]';
|
|
238
|
-
}
|
|
239
|
-
renderMessages();
|
|
240
|
-
}
|
|
241
203
|
|
|
242
|
-
|
|
243
|
-
function
|
|
244
|
-
var v=document.getElementById('browserViewer');if(!v)return;
|
|
245
|
-
v.classList.add('browser-viewer--open');
|
|
246
|
-
var t=document.getElementById('bvTitle');if(t)t.textContent=title||'Browser';
|
|
247
|
-
var s=document.getElementById('bvStatus');if(s)s.textContent=status||'Loading...';
|
|
248
|
-
}
|
|
249
|
-
function updateBrowserFrame(base64,format){
|
|
250
|
-
var f=document.getElementById('bvFrame');if(!f)return;
|
|
251
|
-
f.innerHTML='<img src="data:image/'+(format||'jpeg')+';base64,'+base64+'" alt="Browser view">';
|
|
252
|
-
}
|
|
253
|
-
function updateBrowserStatus(status){
|
|
254
|
-
var s=document.getElementById('bvStatus');if(s)s.textContent=status;
|
|
255
|
-
}
|
|
256
|
-
function closeBrowserViewer(){
|
|
257
|
-
var v=document.getElementById('browserViewer');if(v)v.classList.remove('browser-viewer--open');
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
function loadConvList(){return apiGet('/api/conversations').then(function(r){convList=(r&&r.conversations)||[];renderConvSidebar();})}
|
|
261
|
-
function loadConv(id){return apiGet('/api/conversations/'+id).then(function(r){if(r&&r.conversation){activeConvId=r.conversation.id;chatHistory=r.conversation.messages||[];renderMessages();renderConvSidebar();}})}
|
|
262
|
-
function createNewConv(){return apiPost('/api/conversations',{}).then(function(r){if(r&&r.conversation){activeConvId=r.conversation.id;chatHistory=[];renderMessages();loadConvList();}})}
|
|
263
|
-
function deleteConv(id){return fetch(API+'/api/conversations/'+id,{method:'DELETE'}).then(function(){loadConvList();if(id===activeConvId)createNewConv();})}
|
|
264
|
-
function clearChatHistory(){createNewConv()}
|
|
204
|
+
function saveChatToStorage(){try{localStorage.setItem('nha_chat_history',JSON.stringify(chatHistory.slice(-40)));}catch(e){}}
|
|
205
|
+
function clearChatHistory(){chatHistory=[];saveChatToStorage();renderMessages();}
|
|
265
206
|
var agentsList = [];
|
|
266
207
|
var selectedAgent = null;
|
|
267
208
|
|
|
@@ -278,11 +219,7 @@ function switchView(v) {
|
|
|
278
219
|
if(el.dataset.view===v){el.classList.add('nav-item--active')}else{el.classList.remove('nav-item--active')}
|
|
279
220
|
});
|
|
280
221
|
var titles = {dashboard:'Dashboard',chat:'Chat',plan:'Daily Plan',tasks:'Tasks',emails:'Emails',calendar:'Calendar',drive:'Drive',contacts:'Contacts',notes:'Notes',onedrive:'OneDrive',mstodo:'Microsoft To Do',agents:'Agents',settings:'Settings'};
|
|
281
|
-
|
|
282
|
-
if(spt)spt.textContent=titles[v]||v;
|
|
283
|
-
// Toggle content--chat class for proper chat layout (no overflow, flex column)
|
|
284
|
-
var ct=document.getElementById('content');
|
|
285
|
-
if(ct){if(v==='chat'){ct.classList.add('content--chat')}else{ct.classList.remove('content--chat')}}
|
|
222
|
+
document.getElementById('headerTitle').textContent = titles[v]||v;
|
|
286
223
|
closeSidebar();
|
|
287
224
|
render();
|
|
288
225
|
}
|
|
@@ -314,11 +251,10 @@ function apiPatch(p){return fetch(API+p,{method:'PATCH'}).then(function(r){retur
|
|
|
314
251
|
|
|
315
252
|
// ---- LOAD DATA ----
|
|
316
253
|
function loadDash(){
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
return apiGet('/api/emails?page=0&pageSize=25').then(function(r){dash.emails=(r&&r.emails)||[];dash._emailHasMore=r&&r.hasMore;dashLoaded.emails=true;emailPage=0;updateBadges();render()});
|
|
254
|
+
return Promise.all([apiGet('/api/status'),apiGet('/api/emails'),apiGet('/api/calendar'),apiGet('/api/tasks')]).then(function(r){
|
|
255
|
+
dash.status=r[0];dash.emails=(r[1]&&r[1].emails)||[];dash.events=(r[2]&&r[2].events)||[];dash.tasks=(r[3]&&r[3].tasks)||[];
|
|
256
|
+
updateBadges();
|
|
257
|
+
});
|
|
322
258
|
}
|
|
323
259
|
function loadAgents(){return apiGet('/api/agents').then(function(r){agentsList=(r&&r.agents)||[]})}
|
|
324
260
|
function updateBadges(){
|
|
@@ -332,7 +268,6 @@ function updateBadges(){
|
|
|
332
268
|
// ---- HELPERS ----
|
|
333
269
|
function esc(s){return s?String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'):''}
|
|
334
270
|
function fmtTime(iso){if(!iso)return '';try{return new Date(iso).toLocaleTimeString('en',{hour:'2-digit',minute:'2-digit',hour12:true})}catch(e){return iso}}
|
|
335
|
-
function loadingHTML(label){return '<div style="text-align:center;padding:40px"><div class="spinner"></div><div style="color:var(--dim);margin-top:8px;font-size:12px">Loading '+esc(label)+'...</div></div>'}
|
|
336
271
|
|
|
337
272
|
// ---- RENDER ----
|
|
338
273
|
function render(){
|
|
@@ -361,14 +296,13 @@ function render(){
|
|
|
361
296
|
|
|
362
297
|
// ---- DASHBOARD ----
|
|
363
298
|
function renderDash(el){
|
|
364
|
-
if(!dashLoaded.tasks&&!dashLoaded.events&&!dashLoaded.emails){el.innerHTML=loadingHTML('dashboard');return}
|
|
365
299
|
var t=dash.tasks,e=dash.emails,ev=dash.events;
|
|
366
300
|
var done=t.filter(function(x){return x.status==='done'}).length;
|
|
367
301
|
var pend=t.length-done;
|
|
368
302
|
var pct=t.length>0?Math.round(done/t.length*100):0;
|
|
369
303
|
var h='<div class="dash-grid">'+
|
|
370
304
|
'<div class="card"><div class="card__title">Tasks</div><div class="card__value">'+pend+'</div><div class="card__sub">'+done+'/'+t.length+' done ('+pct+'%)</div></div>'+
|
|
371
|
-
'<div class="card"><div class="card__title">Emails</div><div class="card__value">'+
|
|
305
|
+
'<div class="card"><div class="card__title">Emails</div><div class="card__value">'+e.length+'</div><div class="card__sub">'+(e.length>0?esc(e[0].from):'Inbox zero')+'</div></div>'+
|
|
372
306
|
'<div class="card"><div class="card__title">Events</div><div class="card__value">'+ev.length+'</div><div class="card__sub">'+(ev.length>0?esc(ev[0].summary):'No events')+'</div></div>'+
|
|
373
307
|
'<div class="card"><div class="card__title">Agents</div><div class="card__value">38</div><div class="card__sub">Ready</div></div>'+
|
|
374
308
|
'</div>';
|
|
@@ -382,225 +316,115 @@ function renderDash(el){
|
|
|
382
316
|
var chatReady=false;
|
|
383
317
|
function renderChat(el){
|
|
384
318
|
if(!chatReady||!document.getElementById('chatMessages')){
|
|
385
|
-
el.innerHTML='<div style="
|
|
386
|
-
'<div id="convSidebar" style="width:220px;border-right:1px solid var(--border);overflow-y:auto;flex-shrink:0;background:var(--bg);display:'+(localStorage.getItem('nha_conv_sidebar')==='hidden'?'none':'')+'">'+
|
|
387
|
-
'<div style="padding:8px"><button onclick="createNewConv()" style="width:100%;padding:8px;border-radius:var(--r);border:1px solid var(--green);background:transparent;color:var(--green);cursor:pointer;font-size:11px">+ New Chat</button></div>'+
|
|
388
|
-
'<div id="convList"></div>'+
|
|
389
|
-
'</div>'+
|
|
390
|
-
'<div style="flex:1;display:flex;flex-direction:column;min-width:0">'+
|
|
391
|
-
'<div style="padding:6px 12px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px">'+
|
|
392
|
-
'<button onclick="toggleConvSidebar()" style="background:none;border:none;cursor:pointer;font-size:14px;color:var(--dim);padding:2px 6px" title="Toggle conversations">💬</button>'+
|
|
393
|
-
'<span id="convTitle" style="flex:1;font-size:12px;color:var(--fg);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">New Chat</span>'+
|
|
394
|
-
'<button onclick="createNewConv()" style="background:none;border:1px solid var(--green);color:var(--green);padding:4px 10px;border-radius:var(--r);cursor:pointer;font-size:10px">+ New</button>'+
|
|
395
|
-
'<button onclick="exportConvMd()" style="background:none;border:1px solid var(--border);color:var(--dim);padding:4px 8px;border-radius:var(--r);cursor:pointer;font-size:10px" title="Export Markdown">Export</button>'+
|
|
396
|
-
'</div>'+
|
|
397
|
-
'<div class="chat"><div class="chat__messages" id="chatMessages"></div>'+
|
|
398
|
-
'<div id="chatAttachInfo" style="display:none;padding:4px 12px;font-size:11px;color:var(--cyan);background:var(--bg2);border-top:1px solid var(--border)"><span id="chatAttachName"></span> <button onclick="clearChatAttach()" style="background:none;border:none;color:#f44;cursor:pointer;font-size:14px;font-weight:700">×</button></div>'+
|
|
399
|
-
'<div class="chat__bar"><button class="chat__mic" id="chatMic" onclick="toggleVoiceInput()" title="Voice input">🎤</button><button onclick="document.getElementById(\\x27chatFileInput\\x27).click()" style="background:none;border:none;cursor:pointer;font-size:16px;padding:4px" title="Attach file">📎</button><button onclick="document.getElementById(\\x27chatImageInput\\x27).click()" style="background:none;border:none;cursor:pointer;font-size:16px;padding:4px" title="Attach image">📷</button><input type="file" id="chatFileInput" style="display:none" onchange="handleChatFile(this)"><input type="file" id="chatImageInput" accept="image/*" style="display:none" onchange="handleChatImage(this)"><textarea class="chat__input" id="chatInput" placeholder="Ask anything... (or attach file/image first)" rows="1"></textarea><button class="chat__send" id="chatSend">Send</button><button class="chat__stop" id="chatStop" onclick="stopChat()">Stop</button></div>'+
|
|
400
|
-
'</div>'+
|
|
401
|
-
'</div>'+
|
|
402
|
-
'</div>';
|
|
319
|
+
el.innerHTML='<div class="chat"><div class="chat__messages" id="chatMessages"></div><div class="chat__bar"><button class="chat__mic" id="chatMic" onclick="toggleVoiceInput()" title="Voice input">🎤</button><textarea class="chat__input" id="chatInput" placeholder="Ask anything..." rows="1"></textarea><button class="chat__send" id="chatSend">Send</button><button onclick="clearChatHistory()" style="background:none;color:var(--dim);font-size:10px;padding:4px 8px" title="Clear chat history">Clear</button></div></div>';
|
|
403
320
|
chatReady=true;
|
|
404
321
|
document.getElementById('chatSend').onclick=sendChat;
|
|
405
322
|
document.getElementById('chatInput').onkeydown=function(e){if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();sendChat()}};
|
|
406
|
-
|
|
407
|
-
if(!activeConvId&&convList.length>0){loadConv(convList[0].id)}
|
|
408
|
-
else if(!activeConvId){createNewConv()}
|
|
409
|
-
else{loadConv(activeConvId)}
|
|
410
|
-
});
|
|
323
|
+
renderMessages();
|
|
411
324
|
setTimeout(function(){var i=document.getElementById('chatInput');if(i)i.focus()},100);
|
|
412
325
|
}
|
|
413
326
|
}
|
|
414
|
-
function toggleConvSidebar(){var s=document.getElementById('convSidebar');if(!s)return;var hide=s.style.display!=='none';s.style.display=hide?'none':'';try{localStorage.setItem('nha_conv_sidebar',hide?'hidden':'visible')}catch(e){}}
|
|
415
|
-
function renderConvSidebar(){
|
|
416
|
-
var el=document.getElementById('convList');if(!el)return;
|
|
417
|
-
var h='';convList.forEach(function(c){
|
|
418
|
-
var active=c.id===activeConvId;
|
|
419
|
-
var turns=Math.floor(c.messageCount/2);
|
|
420
|
-
h+='<div onclick="loadConv(\\x27'+c.id+'\\x27)" style="padding:8px 12px;cursor:pointer;border-left:3px solid '+(active?'var(--green)':'transparent')+';background:'+(active?'var(--bg2)':'transparent')+'" onmouseover="this.style.background=\\x27var(--bg2)\\x27" onmouseout="this.style.background='+(active?"\\x27var(--bg2)\\x27":"\\x27transparent\\x27")+'">'+
|
|
421
|
-
'<div style="font-size:11px;color:var(--fg);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">'+esc(c.title)+'</div>'+
|
|
422
|
-
'<div style="font-size:9px;color:var(--dim);display:flex;gap:6px;margin-top:2px"><span>'+turns+' turns</span>'+(active?'':'<span onclick="event.stopPropagation();deleteConv(\\x27'+c.id+'\\x27)" style="color:var(--red);cursor:pointer">del</span>')+'</div>'+
|
|
423
|
-
'</div>';
|
|
424
|
-
});
|
|
425
|
-
el.innerHTML=h;
|
|
426
|
-
var t=document.getElementById('convTitle');
|
|
427
|
-
if(t){var ac=convList.find(function(c){return c.id===activeConvId});t.textContent=ac?ac.title:'New Chat';}
|
|
428
|
-
}
|
|
429
|
-
function exportConvMd(){if(!activeConvId)return;window.open(API+'/api/conversations/'+activeConvId+'/export?format=md','_blank');}
|
|
430
327
|
function renderMessages(){
|
|
431
328
|
var el=document.getElementById('chatMessages');if(!el)return;
|
|
432
329
|
if(chatHistory.length===0){
|
|
433
|
-
el.innerHTML='<div class="chat__empty"><div class="chat__empty-title">NHA Chat</div><div>Personal Operations Assistant
|
|
330
|
+
el.innerHTML='<div class="chat__empty"><div class="chat__empty-title">NHA Chat</div><div>Personal Operations Assistant</div><div class="chat__empty-hint">Try: Show my unread emails / What is on my calendar? / Add a task</div></div>';
|
|
434
331
|
return;
|
|
435
332
|
}
|
|
436
|
-
var h='';chatHistory.forEach(function(m){
|
|
437
|
-
var
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
333
|
+
var h='';chatHistory.forEach(function(m,idx){
|
|
334
|
+
var content = m.content || '';
|
|
335
|
+
var isAssistant = m.role === 'assistant';
|
|
336
|
+
var extraHtml = '';
|
|
337
|
+
|
|
338
|
+
if (isAssistant) {
|
|
339
|
+
// Handle canvas render markers
|
|
340
|
+
var canvasMatch = content.match(/\[CANVAS_RENDER\]([\s\S]*?)\[\/CANVAS_RENDER\]/);
|
|
341
|
+
if (canvasMatch) {
|
|
342
|
+
try { var cd = JSON.parse(canvasMatch[1]); showCanvas(cd.html, cd.title); } catch(e){}
|
|
343
|
+
content = content.replace(/\[CANVAS_RENDER\][\s\S]*?\[\/CANVAS_RENDER\]/, '').trim();
|
|
344
|
+
}
|
|
345
|
+
if (content.indexOf('[CANVAS_CLEAR]') !== -1) {
|
|
346
|
+
closeCanvas();
|
|
347
|
+
content = content.replace(/\[CANVAS_CLEAR\][\s\S]*?\[\/CANVAS_CLEAR\]/, '').trim();
|
|
348
|
+
}
|
|
349
|
+
// Handle screenshot file markers
|
|
350
|
+
var ssMatch = content.match(/\[SCREENSHOT_FILE\](.*?)\[\/SCREENSHOT_FILE\]/);
|
|
351
|
+
if (ssMatch) {
|
|
352
|
+
var fname = ssMatch[1].split('/').pop();
|
|
353
|
+
extraHtml = '<img src="/api/screenshots/' + encodeURIComponent(fname) + '" style="max-width:100%;border-radius:8px;margin:8px 0;border:1px solid rgba(0,255,65,0.2)" />';
|
|
354
|
+
content = content.replace(/\[SCREENSHOT_FILE\].*?\[\/SCREENSHOT_FILE\]/, '').trim();
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
var bubbleContent = isAssistant ? extraHtml + esc(content).replace(/\n/g, '<br>') : esc(content).replace(/\n/g, '<br>');
|
|
359
|
+
|
|
360
|
+
// Action buttons for each message
|
|
361
|
+
var actions = '<div class="msg__actions" style="display:flex;gap:6px;margin-top:4px;opacity:0.3">';
|
|
362
|
+
actions += '<button onclick="copyMessage('+idx+')" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:10px;font-family:var(--mono)" title="Copy">Copy</button>';
|
|
363
|
+
if (isAssistant) {
|
|
364
|
+
actions += '<button onclick="retryMessage('+idx+')" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:10px;font-family:var(--mono)" title="Retry">Retry</button>';
|
|
365
|
+
} else {
|
|
366
|
+
actions += '<button onclick="editMessage('+idx+')" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:10px;font-family:var(--mono)" title="Edit">Edit</button>';
|
|
367
|
+
}
|
|
368
|
+
actions += '</div>';
|
|
369
|
+
|
|
370
|
+
h+='<div class="msg msg--'+esc(m.role)+'" onmouseenter="this.querySelector(\'.msg__actions\').style.opacity=1" onmouseleave="this.querySelector(\'.msg__actions\').style.opacity=0.3"><div class="msg__label">'+esc(m.role==='user'?'You':'NHA')+'</div><div class="msg__bubble">'+bubbleContent+'</div>'+actions+'</div>';
|
|
447
371
|
});
|
|
448
372
|
el.innerHTML=h;el.scrollTop=el.scrollHeight;
|
|
449
373
|
}
|
|
450
|
-
|
|
451
|
-
var
|
|
452
|
-
|
|
453
|
-
function
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
chatAttachedImage=null;
|
|
472
|
-
document.getElementById('chatAttachInfo').style.display='';
|
|
473
|
-
document.getElementById('chatAttachName').textContent='📎 '+file.name+' ('+Math.round(file.size/1024)+' KB)';
|
|
474
|
-
};
|
|
475
|
-
reader.readAsText(file);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
function handleChatImage(input){
|
|
480
|
-
var file=input.files&&input.files[0];if(!file)return;
|
|
481
|
-
var reader=new FileReader();
|
|
482
|
-
reader.onload=function(e){
|
|
483
|
-
var base64=e.target.result.split(',')[1];
|
|
484
|
-
chatAttachedImage={name:file.name,size:file.size,base64:base64,mimeType:file.type||'image/jpeg'};
|
|
485
|
-
chatAttachedFile=null;
|
|
486
|
-
document.getElementById('chatAttachInfo').style.display='';
|
|
487
|
-
document.getElementById('chatAttachName').textContent='📷 '+file.name+' ('+Math.round(file.size/1024)+' KB)';
|
|
488
|
-
};
|
|
489
|
-
reader.readAsDataURL(file);
|
|
374
|
+
function copyMessage(idx){
|
|
375
|
+
var m=chatHistory[idx];if(!m)return;
|
|
376
|
+
var text=m.content.replace(/\[SCREENSHOT_FILE\].*?\[\/SCREENSHOT_FILE\]/g,'').replace(/\[CANVAS_RENDER\][\s\S]*?\[\/CANVAS_RENDER\]/g,'').trim();
|
|
377
|
+
navigator.clipboard.writeText(text).then(function(){showToast('copy','Copied','Message copied to clipboard',2000)}).catch(function(){});
|
|
378
|
+
}
|
|
379
|
+
function retryMessage(idx){
|
|
380
|
+
// Retry = re-send the user message that preceded this assistant message
|
|
381
|
+
if(idx<1||chatHistory[idx].role!=='assistant')return;
|
|
382
|
+
var userMsg=chatHistory[idx-1];
|
|
383
|
+
if(!userMsg||userMsg.role!=='user')return;
|
|
384
|
+
// Remove this assistant response and re-send
|
|
385
|
+
chatHistory.splice(idx,1);
|
|
386
|
+
saveChatToStorage();renderMessages();
|
|
387
|
+
chatHistory.push({role:'assistant',content:'Thinking...'});renderMessages();
|
|
388
|
+
apiPost('/api/chat',{message:userMsg.content,history:chatHistory.slice(0,-1)}).then(function(r){
|
|
389
|
+
chatHistory.pop();
|
|
390
|
+
if(r&&r.response){chatHistory.push({role:'assistant',content:r.response})}
|
|
391
|
+
else if(r&&r.error){chatHistory.push({role:'assistant',content:'Error: '+r.error})}
|
|
392
|
+
else{chatHistory.push({role:'assistant',content:'Error: no response from server'})}
|
|
393
|
+
saveChatToStorage();renderMessages();
|
|
394
|
+
});
|
|
490
395
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
396
|
+
function editMessage(idx){
|
|
397
|
+
if(chatHistory[idx].role!=='user')return;
|
|
398
|
+
var inp=document.getElementById('chatInput');if(!inp)return;
|
|
399
|
+
inp.value=chatHistory[idx].content;
|
|
400
|
+
inp.focus();
|
|
401
|
+
// Remove this message and all subsequent messages
|
|
402
|
+
chatHistory.splice(idx);
|
|
403
|
+
saveChatToStorage();renderMessages();
|
|
497
404
|
}
|
|
498
|
-
|
|
499
405
|
function sendChat(){
|
|
500
406
|
var inp=document.getElementById('chatInput');if(!inp)return;
|
|
501
|
-
var msg=inp.value.trim();
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
407
|
+
var msg=inp.value.trim();if(!msg)return;
|
|
408
|
+
chatHistory.push({role:'user',content:msg});
|
|
409
|
+
inp.value='';saveChatToStorage();renderMessages();
|
|
410
|
+
chatHistory.push({role:'assistant',content:'Thinking...'});renderMessages();
|
|
411
|
+
apiPost('/api/chat',{message:msg,history:chatHistory.slice(0,-1)}).then(function(r){
|
|
412
|
+
chatHistory.pop();
|
|
413
|
+
if(r&&r.response){chatHistory.push({role:'assistant',content:r.response})}
|
|
414
|
+
else if(r&&r.error){chatHistory.push({role:'assistant',content:'Error: '+r.error})}
|
|
415
|
+
else{chatHistory.push({role:'assistant',content:'Error: no response from server'})}
|
|
416
|
+
saveChatToStorage();renderMessages();
|
|
417
|
+
// Refresh ALL data after any tool execution
|
|
418
|
+
if(r&&((r.actions&&r.actions.length>0)||(r.toolResults&&r.toolResults.length>0))){
|
|
419
|
+
calEventsCache={};
|
|
420
|
+
contactsData=null;
|
|
421
|
+
notesData=null;
|
|
422
|
+
driveData=null;
|
|
423
|
+
onedriveData=null;
|
|
424
|
+
mstodoData=null;
|
|
425
|
+
loadDash().then(function(){render()}).catch(function(){});
|
|
520
426
|
}
|
|
521
|
-
|
|
522
|
-
clearChatAttach();
|
|
523
|
-
apiPost('/api/chat',payload).then(function(r){
|
|
524
|
-
chatHistory.pop();
|
|
525
|
-
if(r&&r.response){chatHistory.push({role:'assistant',content:r.response})}
|
|
526
|
-
else if(r&&r.error){chatHistory.push({role:'assistant',content:'Error: '+r.error})}
|
|
527
|
-
else{chatHistory.push({role:'assistant',content:'Error: no response'})}
|
|
528
|
-
renderMessages();loadConvList();
|
|
529
|
-
});
|
|
530
|
-
return;
|
|
531
|
-
}
|
|
532
|
-
clearChatAttach();
|
|
533
|
-
|
|
534
|
-
// Streaming SSE
|
|
535
|
-
chatStreaming=true;
|
|
536
|
-
chatAbortController=new AbortController();
|
|
537
|
-
// Show Stop button, hide Send button
|
|
538
|
-
var stopBtn=document.getElementById('chatStop');if(stopBtn)stopBtn.classList.add('chat__stop--visible');
|
|
539
|
-
var sendBtn=document.getElementById('chatSend');if(sendBtn)sendBtn.style.display='none';
|
|
540
|
-
chatHistory.push({role:'assistant',content:''});
|
|
541
|
-
renderMessages();
|
|
542
|
-
var streamIdx=chatHistory.length-1;
|
|
543
|
-
var allHistory=chatHistory.slice(0,-1).map(function(m){return{role:m.role,content:(m.content||'').replace(/!\\[Screenshot\\]\\(data:image\\/[^)]+\\)/g,'[Screenshot taken]')};});
|
|
544
|
-
var payload={message:msg,history:allHistory,conversationId:activeConvId};
|
|
545
|
-
|
|
546
|
-
fetch(API+'/api/chat/stream',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload),signal:chatAbortController.signal}).then(function(response){
|
|
547
|
-
if(!response.ok||!response.body){chatHistory[streamIdx].content='Error: connection failed';endStreaming();renderMessages();return;}
|
|
548
|
-
var reader=response.body.getReader();var decoder=new TextDecoder();var buffer='';var currentEvent='';
|
|
549
|
-
function pump(){
|
|
550
|
-
reader.read().then(function(result){
|
|
551
|
-
if(result.done){endStreaming();renderMessages();loadConvList();return;}
|
|
552
|
-
buffer+=decoder.decode(result.value,{stream:true});
|
|
553
|
-
var lines=buffer.split('\\n');buffer=lines.pop()||'';
|
|
554
|
-
for(var i=0;i<lines.length;i++){
|
|
555
|
-
var line=lines[i];
|
|
556
|
-
if(line.startsWith('event: ')){currentEvent=line.slice(7).trim();}
|
|
557
|
-
else if(line.startsWith('data: ')){
|
|
558
|
-
try{
|
|
559
|
-
var data=JSON.parse(line.slice(6));
|
|
560
|
-
if(currentEvent==='token'&&data.content){
|
|
561
|
-
chatHistory[streamIdx].content+=data.content;
|
|
562
|
-
var el=document.getElementById('chatMessages');
|
|
563
|
-
if(el){var msgs=el.querySelectorAll('.msg');var last=msgs[msgs.length-1];if(last){var bub=last.querySelector('.msg__bubble');if(bub)bub.textContent=chatHistory[streamIdx].content;}el.scrollTop=el.scrollHeight;}
|
|
564
|
-
}
|
|
565
|
-
if(currentEvent==='tool'){
|
|
566
|
-
var toolLabels={browser_open:'Opening page',browser_screenshot:'Taking screenshot',browser_click:'Clicking element',browser_type:'Typing text',browser_extract:'Extracting content',browser_js:'Running JavaScript',browser_wait:'Waiting for element',browser_scroll:'Scrolling page',browser_key:'Pressing key',browser_close:'Closing browser',web_search:'Searching the web',fetch_url:'Fetching URL',gmail_list:'Searching emails',gmail_read:'Reading email',gmail_send:'Sending email',calendar_today:'Loading calendar',calendar_create:'Creating event'};
|
|
567
|
-
var label=toolLabels[data.action]||data.action;
|
|
568
|
-
var indicator=data.status==='executing'?'\\u23f3 '+label+'...':'\\u2705 '+label;
|
|
569
|
-
if(data.status==='error')indicator='\\u274c '+label+' failed';
|
|
570
|
-
// Show browser viewer for browser and web_search actions
|
|
571
|
-
var isBrowserAction=data.action&&(data.action.startsWith('browser_')||data.action==='web_search');
|
|
572
|
-
if(isBrowserAction&&data.status==='executing'){showBrowserViewer(label,'Executing...');}
|
|
573
|
-
if(isBrowserAction&&data.status==='done'){updateBrowserStatus('\\u2705 '+label);}
|
|
574
|
-
if(isBrowserAction&&data.status==='error'){updateBrowserStatus('\\u274c '+label);}
|
|
575
|
-
if(data.action==='browser_close'&&data.status==='done'){setTimeout(closeBrowserViewer,2000);}
|
|
576
|
-
// Strip JSON action blocks from streamed content (they are internal tool calls, not for the user)
|
|
577
|
-
if(data.status==='executing'){chatHistory[streamIdx].content=chatHistory[streamIdx].content.replace(new RegExp('\\x60\\x60\\x60json[\\\\s\\\\S]*?\\x60\\x60\\x60','g'),'').trim()+'\\n';}
|
|
578
|
-
chatHistory[streamIdx].content+=indicator+'\\n';
|
|
579
|
-
renderMessages();
|
|
580
|
-
}
|
|
581
|
-
if(currentEvent==='screenshot'&&data.base64){
|
|
582
|
-
// Only update the browser viewer — the actual image in chat is handled by the 'done' event via screenshotFiles
|
|
583
|
-
showBrowserViewer('Screenshot','Captured');
|
|
584
|
-
updateBrowserFrame(data.base64,data.format||'jpeg');
|
|
585
|
-
updateBrowserStatus('Screenshot captured');
|
|
586
|
-
}
|
|
587
|
-
if(currentEvent==='browser_frame'&&data.base64){
|
|
588
|
-
// Live frame update — also ensure viewer is open
|
|
589
|
-
showBrowserViewer(data.url||'Browser','Live');
|
|
590
|
-
updateBrowserFrame(data.base64,data.format||'jpeg');
|
|
591
|
-
if(data.url)updateBrowserStatus(data.url);
|
|
592
|
-
}
|
|
593
|
-
if(currentEvent==='tool_synthesis'){chatHistory[streamIdx].content='';renderMessages();}
|
|
594
|
-
if(currentEvent==='done'){endStreaming();if(data.content)chatHistory[streamIdx].content=data.content;var ssf=data.screenshotFiles||[];for(var fi=0;fi<ssf.length;fi++){chatHistory[streamIdx].content+='\\n\\n';}renderMessages();loadConvList();if(ssf.length>0)setTimeout(closeBrowserViewer,5000);}
|
|
595
|
-
if(currentEvent==='error'){endStreaming();chatHistory[streamIdx].content='Error: '+(data.message||'Unknown');renderMessages();}
|
|
596
|
-
}catch(e){}
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
pump();
|
|
600
|
-
}).catch(function(e){endStreaming();if(e.name!=='AbortError'){chatHistory[streamIdx].content='Error: '+e.message;renderMessages();}});
|
|
601
|
-
}
|
|
602
|
-
pump();
|
|
603
|
-
}).catch(function(e){endStreaming();if(e.name!=='AbortError'){chatHistory[streamIdx].content='Error: '+e.message;renderMessages();}});
|
|
427
|
+
});
|
|
604
428
|
}
|
|
605
429
|
|
|
606
430
|
// ---- TASKS ----
|
|
@@ -670,64 +494,17 @@ function refreshPlan(){
|
|
|
670
494
|
|
|
671
495
|
// ---- EMAILS ----
|
|
672
496
|
function renderEmails(el){
|
|
673
|
-
if(!dashLoaded.emails){el.innerHTML=loadingHTML('emails');return}
|
|
674
497
|
var e=dash.emails;
|
|
675
|
-
if(e.length===0){el.innerHTML='<div class="card" style="text-align:center;color:var(--dim);padding:30px">
|
|
676
|
-
var
|
|
677
|
-
var h='<div style="display:flex;gap:8px;margin-bottom:10px;align-items:center">';
|
|
678
|
-
h+='<span style="font-size:12px;color:var(--dim)">'+e.length+' emails'+(unreadCount>0?' ('+unreadCount+' unread)':'')+'</span>';
|
|
679
|
-
if(unreadCount>0)h+='<button class="btn btn--secondary" style="font-size:10px;padding:4px 10px" onclick="markAllEmailsRead()">Mark all read</button>';
|
|
680
|
-
h+='</div>';
|
|
681
|
-
e.forEach(function(x){
|
|
498
|
+
if(e.length===0){el.innerHTML='<div class="card" style="text-align:center;color:var(--dim);padding:30px">No unread emails</div>';return}
|
|
499
|
+
var h='';e.forEach(function(x){
|
|
682
500
|
var unreadStyle=x.isUnread?'border-left:3px solid var(--green);font-weight:700':'border-left:3px solid transparent;opacity:0.7';
|
|
683
501
|
h+='<div class="card email" style="cursor:pointer;'+unreadStyle+'" onclick="openEmail(\\x27'+esc(x.id)+'\\x27)"><div class="email__header"><span class="email__from">'+esc(x.from)+'</span><span class="email__date">'+esc(x.date)+(x.isUnread?' <span style="color:var(--green);font-size:9px">NEW</span>':'')+'</span></div><div class="email__subject">'+esc(x.subject)+'</div><div class="email__snippet" style="font-weight:400">'+esc((x.snippet||'').slice(0,150))+'</div></div>';
|
|
684
502
|
});
|
|
685
|
-
// Load More button
|
|
686
|
-
if(dash._emailHasMore!==false){
|
|
687
|
-
h+='<button id="loadMoreEmails" onclick="loadMoreEmails()" style="width:100%;padding:12px;margin-top:8px;background:var(--bg3);border:1px solid var(--border);border-radius:var(--r);color:var(--cyan);font-family:var(--font);font-size:12px;cursor:pointer;font-weight:700">Load More Emails</button>';
|
|
688
|
-
}
|
|
689
503
|
el.innerHTML=h;
|
|
690
504
|
}
|
|
691
|
-
var emailPage=0;
|
|
692
|
-
function loadMoreEmails(){
|
|
693
|
-
var btn=document.getElementById('loadMoreEmails');
|
|
694
|
-
if(btn){btn.textContent='Loading...';btn.disabled=true;}
|
|
695
|
-
emailPage++;
|
|
696
|
-
apiGet('/api/emails?page='+emailPage+'&pageSize=25').then(function(r){
|
|
697
|
-
if(r&&r.emails){
|
|
698
|
-
for(var i=0;i<r.emails.length;i++){
|
|
699
|
-
if(!dash.emails.find(function(e){return e.id===r.emails[i].id})){
|
|
700
|
-
dash.emails.push(r.emails[i]);
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
dash._emailHasMore=r.hasMore;
|
|
704
|
-
updateBadges();
|
|
705
|
-
render();
|
|
706
|
-
}
|
|
707
|
-
});
|
|
708
|
-
}
|
|
709
|
-
function markAllEmailsRead(){
|
|
710
|
-
apiPost('/api/email/mark-all-read',{}).then(function(r){
|
|
711
|
-
if(r&&r.ok){
|
|
712
|
-
dash.emails.forEach(function(e){e.isUnread=false});
|
|
713
|
-
updateBadges();
|
|
714
|
-
renderEmails(document.getElementById('content'));
|
|
715
|
-
showToast('success','All Read','Marked '+( r.count||0)+' emails as read');
|
|
716
|
-
}else{
|
|
717
|
-
showToast('error','Error',r&&r.error||'Failed');
|
|
718
|
-
}
|
|
719
|
-
});
|
|
720
|
-
}
|
|
721
505
|
var openEmailId=null;
|
|
722
506
|
function openEmail(id){
|
|
723
507
|
openEmailId=id;
|
|
724
|
-
// Mark as read locally + on server
|
|
725
|
-
var emailObj=dash.emails.find(function(e){return e.id===id});
|
|
726
|
-
if(emailObj&&emailObj.isUnread){
|
|
727
|
-
emailObj.isUnread=false;
|
|
728
|
-
updateBadges();
|
|
729
|
-
apiPost('/api/email/mark-read',{messageId:id}).catch(function(){});
|
|
730
|
-
}
|
|
731
508
|
var el=document.getElementById('content');
|
|
732
509
|
el.innerHTML='<div style="text-align:center;padding:40px"><div class="spinner"></div><div style="color:var(--dim)">Loading email...</div></div>';
|
|
733
510
|
apiPost('/api/email/read',{messageId:id}).then(function(r){
|
|
@@ -1376,68 +1153,22 @@ function renderAgents(el){
|
|
|
1376
1153
|
|
|
1377
1154
|
var filtered=agentFilter?agentsList.filter(function(a){return a.category===agentFilter}):agentsList;
|
|
1378
1155
|
|
|
1379
|
-
h+='<div style="margin-bottom:10px"><button class="btn btn--primary" style="font-size:11px" onclick="showCreateAgentForm()">+ Create Agent</button></div>';
|
|
1380
1156
|
h+='<div class="agents-grid">';
|
|
1381
1157
|
filtered.forEach(function(a){
|
|
1382
1158
|
var name=a.name||a.agentName;
|
|
1383
1159
|
var display=a.displayName||name;
|
|
1384
1160
|
var icon=AGENT_ICONS[name.toLowerCase()]||'\\u{1F916}';
|
|
1385
1161
|
var desc=AGENT_DESCRIPTIONS[name.toLowerCase()]||a.tagline||a.description||'';
|
|
1386
|
-
|
|
1387
|
-
h+='<div class="card agent-card" style="position:relative">'+
|
|
1388
|
-
'<div style="flex:1;cursor:pointer" onclick="openAgent(\\''+esc(name)+'\\',\\''+esc(display)+'\\')">'+
|
|
1389
|
-
'<div style="display:flex;align-items:center;gap:8px">'+
|
|
1162
|
+
h+='<div class="card agent-card" onclick="openAgent(\\''+esc(name)+'\\',\\''+esc(display)+'\\')">'+
|
|
1390
1163
|
'<div class="agent-card__icon">'+icon+'</div>'+
|
|
1391
1164
|
'<div class="agent-card__body"><div class="agent-card__name">'+esc(display)+'</div>'+
|
|
1392
1165
|
'<div class="agent-card__tagline">'+esc(desc)+'</div></div>'+
|
|
1393
|
-
'</div></div>'+
|
|
1394
|
-
'<div style="display:flex;gap:4px;flex-shrink:0">'+
|
|
1395
|
-
'<button onclick="editAgent(\\''+esc(name)+'\\')" style="background:none;border:none;cursor:pointer;font-size:12px;padding:2px" title="Edit">\\u{270F}\\u{FE0F}</button>'+
|
|
1396
|
-
'<button onclick="deleteAgent(\\''+esc(name)+'\\')" style="background:none;border:none;cursor:pointer;font-size:12px;padding:2px;color:#f44" title="Delete">\\u{1F5D1}</button>'+
|
|
1397
|
-
'</div>'+
|
|
1398
1166
|
'</div>';
|
|
1399
1167
|
});
|
|
1400
1168
|
h+='</div>';
|
|
1401
1169
|
el.innerHTML=h;
|
|
1402
1170
|
}
|
|
1403
1171
|
var agentFilter=null;
|
|
1404
|
-
|
|
1405
|
-
function showCreateAgentForm(){
|
|
1406
|
-
var name=prompt('Agent name (lowercase, no spaces):');
|
|
1407
|
-
if(!name)return;
|
|
1408
|
-
name=name.toLowerCase().replace(/[^a-z0-9_-]/g,'');
|
|
1409
|
-
if(!name)return;
|
|
1410
|
-
var tagline=prompt('Tagline (short description):');
|
|
1411
|
-
if(!tagline)return;
|
|
1412
|
-
var sysPrompt=prompt('System prompt (agent personality & instructions):');
|
|
1413
|
-
if(!sysPrompt)return;
|
|
1414
|
-
apiPost('/api/agents',{name:name,tagline:tagline,systemPrompt:sysPrompt}).then(function(r){
|
|
1415
|
-
if(r&&r.ok){showToast('success','Agent Created',name.toUpperCase()+' is ready to use');loadAgents().then(function(){renderAgents(document.getElementById('content'))});}
|
|
1416
|
-
else{alert('Error: '+(r&&r.error||'Unknown'));}
|
|
1417
|
-
});
|
|
1418
|
-
}
|
|
1419
|
-
|
|
1420
|
-
function editAgent(name){
|
|
1421
|
-
fetch('/api/agents/'+name).then(function(r){return r.json()}).then(function(data){
|
|
1422
|
-
var newTagline=prompt('Tagline:',data.tagline||'');
|
|
1423
|
-
if(newTagline===null)return;
|
|
1424
|
-
var newPrompt=prompt('System prompt:',data.systemPrompt||'');
|
|
1425
|
-
if(newPrompt===null)return;
|
|
1426
|
-
fetch('/api/agents/'+name,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({tagline:newTagline,systemPrompt:newPrompt,category:data.category||'custom'})}).then(function(r){return r.json()}).then(function(r){
|
|
1427
|
-
if(r&&r.ok){showToast('success','Agent Updated',name.toUpperCase()+' updated');loadAgents().then(function(){renderAgents(document.getElementById('content'))});}
|
|
1428
|
-
else{alert('Error: '+(r&&r.error||'Unknown'));}
|
|
1429
|
-
});
|
|
1430
|
-
});
|
|
1431
|
-
}
|
|
1432
|
-
|
|
1433
|
-
function deleteAgent(name){
|
|
1434
|
-
if(!confirm('Delete agent "'+name+'"? This cannot be undone.'))return;
|
|
1435
|
-
fetch('/api/agents/'+name,{method:'DELETE'}).then(function(r){return r.json()}).then(function(r){
|
|
1436
|
-
if(r&&r.ok){showToast('success','Agent Deleted',name+' removed');loadAgents().then(function(){renderAgents(document.getElementById('content'))});}
|
|
1437
|
-
else{alert('Error: '+(r&&r.error||'Unknown'));}
|
|
1438
|
-
});
|
|
1439
|
-
}
|
|
1440
|
-
|
|
1441
1172
|
function openAgent(name,display){
|
|
1442
1173
|
selectedAgent=name;
|
|
1443
1174
|
attachedFileContent=null;attachedFileName=null;
|
|
@@ -1520,26 +1251,9 @@ function renderSettings(el) {
|
|
|
1520
1251
|
['summary-time', 'Summary Time', '18:00'],
|
|
1521
1252
|
['meeting-alert', 'Meeting Alert (minutes)', '30'],
|
|
1522
1253
|
]) +
|
|
1523
|
-
'<div class="card" style="margin-top:16px"><div class="card__title">Google Account</div>' +
|
|
1524
|
-
'<div style="font-size:11px;color:var(--dim);margin-bottom:8px">Connect Gmail, Calendar, Drive, Contacts, and Tasks. Opens a browser window for Google sign-in.</div>' +
|
|
1525
|
-
(settingsData.hasGoogle ? '<div style="color:var(--green);font-size:12px;margin-bottom:8px">\\u2705 Connected</div>' : '') +
|
|
1526
|
-
'<button onclick="connectGoogle()" style="background:var(--green3);color:var(--bg);padding:8px 20px;border-radius:var(--r);font-weight:700;font-size:12px;cursor:pointer;border:none">' + (settingsData.hasGoogle ? 'Reconnect Google' : 'Connect Google') + '</button>' +
|
|
1527
|
-
'<div id="googleStatus" style="margin-top:8px;font-size:10px;color:var(--dim)"></div>' +
|
|
1528
|
-
'</div>' +
|
|
1529
1254
|
'</div>';
|
|
1530
1255
|
}
|
|
1531
1256
|
|
|
1532
|
-
function connectGoogle() {
|
|
1533
|
-
var s = document.getElementById('googleStatus');
|
|
1534
|
-
if (s) s.textContent = 'Starting Google sign-in...';
|
|
1535
|
-
apiPost('/api/google/auth', {}).then(function(r) {
|
|
1536
|
-
if (s) s.textContent = r.message || 'Check the browser window that opened.';
|
|
1537
|
-
if (s) s.style.color = 'var(--green)';
|
|
1538
|
-
}).catch(function(e) {
|
|
1539
|
-
if (s) { s.textContent = 'Error: ' + e.message; s.style.color = 'var(--red)'; }
|
|
1540
|
-
});
|
|
1541
|
-
}
|
|
1542
|
-
|
|
1543
1257
|
function settingsSection(id, title, desc, fields) {
|
|
1544
1258
|
var h = '<form class="card" style="margin-bottom:16px" id="settings-' + id + '" onsubmit="event.preventDefault();saveSettingsSection(\\x27' + id + '\\x27)">' +
|
|
1545
1259
|
'<div class="card__title" style="color:var(--green);font-size:14px;margin-bottom:4px">' + esc(title) + '</div>' +
|
|
@@ -1760,6 +1474,61 @@ function handleDaemonEvent(msg) {
|
|
|
1760
1474
|
showToast('plan', 'Daily Plan Ready', 'Your plan for ' + msg.data.date + ' has been generated.', 10000);
|
|
1761
1475
|
if (currentView === 'plan') renderPlan(document.getElementById('content'));
|
|
1762
1476
|
break;
|
|
1477
|
+
|
|
1478
|
+
case 'cron_result':
|
|
1479
|
+
showToast('cron', 'Scheduled Task', msg.data.prompt + '\\n' + (msg.data.result || '').slice(0, 100), 8000);
|
|
1480
|
+
break;
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
// ---- CANVAS PANEL ----
|
|
1485
|
+
var canvasVisible = false;
|
|
1486
|
+
function showCanvas(html, title) {
|
|
1487
|
+
var panel = document.getElementById('canvasPanel');
|
|
1488
|
+
if (!panel) {
|
|
1489
|
+
panel = document.createElement('div');
|
|
1490
|
+
panel.id = 'canvasPanel';
|
|
1491
|
+
panel.style.cssText = 'position:fixed;top:60px;right:12px;width:480px;max-height:calc(100vh - 80px);background:#0d0d0d;border:1px solid var(--green);border-radius:12px;z-index:1000;overflow:hidden;display:flex;flex-direction:column;box-shadow:0 0 30px rgba(0,255,65,0.1)';
|
|
1492
|
+
document.body.appendChild(panel);
|
|
1493
|
+
}
|
|
1494
|
+
var header = '<div style="display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border-bottom:1px solid var(--green);background:rgba(0,255,65,0.05)">' +
|
|
1495
|
+
'<span style="font-family:var(--mono);color:var(--green);font-size:12px">' + (title || 'Canvas') + '</span>' +
|
|
1496
|
+
'<div><button onclick="toggleCanvasSize()" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:14px;margin-right:8px" title="Resize">⤢</button>' +
|
|
1497
|
+
'<button onclick="closeCanvas()" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:14px" title="Close">×</button></div></div>';
|
|
1498
|
+
var iframe = '<iframe id="canvasFrame" sandbox="allow-scripts allow-same-origin" style="flex:1;border:none;background:#fff;min-height:300px;width:100%"></iframe>';
|
|
1499
|
+
panel.innerHTML = header + iframe;
|
|
1500
|
+
panel.style.display = 'flex';
|
|
1501
|
+
canvasVisible = true;
|
|
1502
|
+
|
|
1503
|
+
// Write HTML to iframe
|
|
1504
|
+
var frame = document.getElementById('canvasFrame');
|
|
1505
|
+
if (frame) {
|
|
1506
|
+
var doc = frame.contentDocument || frame.contentWindow.document;
|
|
1507
|
+
doc.open();
|
|
1508
|
+
doc.write(html);
|
|
1509
|
+
doc.close();
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
function closeCanvas() {
|
|
1514
|
+
var panel = document.getElementById('canvasPanel');
|
|
1515
|
+
if (panel) { panel.style.display = 'none'; canvasVisible = false; }
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
function toggleCanvasSize() {
|
|
1519
|
+
var panel = document.getElementById('canvasPanel');
|
|
1520
|
+
if (!panel) return;
|
|
1521
|
+
if (panel.style.width === '480px') {
|
|
1522
|
+
panel.style.width = '80vw';
|
|
1523
|
+
panel.style.height = '80vh';
|
|
1524
|
+
panel.style.top = '10vh';
|
|
1525
|
+
panel.style.right = '10vw';
|
|
1526
|
+
} else {
|
|
1527
|
+
panel.style.width = '480px';
|
|
1528
|
+
panel.style.height = '';
|
|
1529
|
+
panel.style.maxHeight = 'calc(100vh - 80px)';
|
|
1530
|
+
panel.style.top = '60px';
|
|
1531
|
+
panel.style.right = '12px';
|
|
1763
1532
|
}
|
|
1764
1533
|
}
|
|
1765
1534
|
|
|
@@ -1865,13 +1634,8 @@ init();
|
|
|
1865
1634
|
<div class="app">
|
|
1866
1635
|
<nav class="sidebar" id="sidebar">
|
|
1867
1636
|
<div class="sidebar__brand">
|
|
1868
|
-
<div
|
|
1869
|
-
|
|
1870
|
-
<span id="wsIndicator" style="color:var(--dim);font-size:8px" title="Daemon WebSocket">●</span>
|
|
1871
|
-
<span style="font-size:9px;color:var(--dim)">v${VERSION}</span>
|
|
1872
|
-
</div>
|
|
1873
|
-
<div id="sidebarPageTitle" style="font-size:11px;color:var(--bright);margin-top:4px;font-weight:600">Dashboard</div>
|
|
1874
|
-
<div class="sidebar__brand-sub" id="clock"></div>
|
|
1637
|
+
<div class="sidebar__brand-name">NHA</div>
|
|
1638
|
+
<div class="sidebar__brand-sub">Operations Console</div>
|
|
1875
1639
|
</div>
|
|
1876
1640
|
<div class="sidebar__section">
|
|
1877
1641
|
<div class="sidebar__label">Overview</div>
|
|
@@ -1908,30 +1672,17 @@ init();
|
|
|
1908
1672
|
<div class="sidebar__label">Config</div>
|
|
1909
1673
|
<div class="nav-item" data-view="settings" onclick="switchView('settings')"><span class="nav-item__icon">⚙</span> Settings</div>
|
|
1910
1674
|
</div>
|
|
1911
|
-
<div
|
|
1912
|
-
<div class="sidebar__label">Help</div>
|
|
1913
|
-
<a href="https://nothumanallowed.com/docs" target="_blank" class="nav-item" style="text-decoration:none"><span class="nav-item__icon">📖</span> Documentation</a>
|
|
1914
|
-
<a href="https://nothumanallowed.com/docs/agents" target="_blank" class="nav-item" style="text-decoration:none"><span class="nav-item__icon">🤖</span> Agents Guide</a>
|
|
1915
|
-
<a href="https://nothumanallowed.com/docs/mobile" target="_blank" class="nav-item" style="text-decoration:none"><span class="nav-item__icon">📱</span> Mobile App</a>
|
|
1916
|
-
</div>
|
|
1917
|
-
<div style="padding:12px 16px;margin-top:auto;border-top:1px solid var(--border);font-size:10px;color:var(--dim)">nothumanallowed.com</div>
|
|
1675
|
+
<div style="padding:12px 16px;margin-top:auto;border-top:1px solid var(--border);font-size:10px;color:var(--dim)">NHA v${VERSION}</div>
|
|
1918
1676
|
</nav>
|
|
1919
1677
|
|
|
1920
|
-
<
|
|
1678
|
+
<div class="header">
|
|
1679
|
+
<button class="header__burger" onclick="toggleSidebar()">☰</button>
|
|
1680
|
+
<span class="header__title" id="headerTitle">Dashboard</span>
|
|
1681
|
+
<span id="wsIndicator" style="color:var(--dim);font-size:8px;margin-right:4px" title="Daemon WebSocket">●</span>
|
|
1682
|
+
<span class="header__clock" id="clock"></span>
|
|
1683
|
+
</div>
|
|
1921
1684
|
|
|
1922
1685
|
<div class="content" id="content"></div>
|
|
1923
|
-
|
|
1924
|
-
<div class="browser-viewer" id="browserViewer">
|
|
1925
|
-
<div class="browser-viewer__header">
|
|
1926
|
-
<span class="browser-viewer__dot"></span>
|
|
1927
|
-
<span class="browser-viewer__title" id="bvTitle">Browser</span>
|
|
1928
|
-
<button class="browser-viewer__close" onclick="closeBrowserViewer()">×</button>
|
|
1929
|
-
</div>
|
|
1930
|
-
<div class="browser-viewer__frame" id="bvFrame">
|
|
1931
|
-
<span style="color:var(--dim);font-size:11px">Waiting...</span>
|
|
1932
|
-
</div>
|
|
1933
|
-
<div class="browser-viewer__status" id="bvStatus">Idle</div>
|
|
1934
|
-
</div>
|
|
1935
1686
|
</div>
|
|
1936
1687
|
|
|
1937
1688
|
<div class="modal-overlay" id="agentModal">
|