nothumanallowed 9.7.2 → 9.8.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/package.json +1 -1
- package/src/commands/ask.mjs +206 -18
- package/src/commands/chat.mjs +482 -64
- package/src/commands/ui.mjs +843 -89
- package/src/constants.mjs +1 -1
- package/src/services/browser-engine.mjs +1240 -0
- package/src/services/conversations.mjs +277 -0
- package/src/services/tool-executor.mjs +384 -59
- package/src/services/web-tools.mjs +430 -0
- package/src/services/web-ui.mjs +422 -175
package/src/services/web-ui.mjs
CHANGED
|
@@ -46,10 +46,7 @@ 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
|
-
|
|
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)}
|
|
49
|
+
/* header removed — info moved to sidebar brand */
|
|
53
50
|
|
|
54
51
|
.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)}
|
|
55
52
|
.sidebar--open{display:flex}
|
|
@@ -87,8 +84,9 @@ input:focus,textarea:focus{border-color:var(--green3)}
|
|
|
87
84
|
.section-title{font-size:12px;color:var(--cyan);text-transform:uppercase;letter-spacing:1px;margin-bottom:10px}
|
|
88
85
|
|
|
89
86
|
/* ---- CHAT ---- */
|
|
90
|
-
.chat{display:flex;flex-direction:column
|
|
91
|
-
|
|
87
|
+
.content--chat{overflow:hidden!important;padding:0!important;display:flex;flex-direction:column}
|
|
88
|
+
.chat{display:flex;flex-direction:column;flex:1;min-height:0;padding:16px;padding-bottom:0}
|
|
89
|
+
@media(min-width:901px){.content--chat{padding:0!important}}
|
|
92
90
|
.chat__messages{flex:1;overflow-y:auto;padding-bottom:12px;-webkit-overflow-scrolling:touch}
|
|
93
91
|
.chat__empty{text-align:center;padding:60px 16px;color:var(--dim)}
|
|
94
92
|
.chat__empty-title{font-size:28px;color:var(--green);margin-bottom:12px}
|
|
@@ -96,14 +94,32 @@ input:focus,textarea:focus{border-color:var(--green3)}
|
|
|
96
94
|
.msg{margin-bottom:12px}
|
|
97
95
|
.msg--user .msg__bubble{background:var(--bg3);border:1px solid var(--border2);border-radius:8px 8px 2px 8px;padding:10px 14px;max-width:85%;margin-left:auto;color:var(--bright)}
|
|
98
96
|
.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}
|
|
99
|
-
.msg__actions{opacity:0.15;transition:opacity 0.2s}
|
|
100
|
-
.msg:hover .msg__actions{opacity:1}
|
|
101
97
|
.msg__label{font-size:10px;color:var(--dim);margin-bottom:2px}
|
|
102
98
|
.msg--thinking{color:var(--dim);font-style:italic}
|
|
103
|
-
.
|
|
99
|
+
.tool-indicator{display:inline-block;padding:2px 8px;margin:2px 0;border-radius:4px;font-size:11px;background:var(--bg3);border:1px solid var(--border)}
|
|
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}
|
|
104
118
|
.chat__input{flex:1;resize:none;min-height:40px;max-height:100px;padding:10px 14px}
|
|
105
119
|
.chat__send{background:var(--green3);color:var(--bg);padding:10px 16px;border-radius:var(--r);font-weight:700;font-size:12px}
|
|
106
120
|
.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}
|
|
107
123
|
|
|
108
124
|
/* ---- TASKS ---- */
|
|
109
125
|
.task-bar{display:flex;gap:8px;margin-bottom:12px;flex-wrap:wrap}
|
|
@@ -200,11 +216,52 @@ input:focus,textarea:focus{border-color:var(--green3)}
|
|
|
200
216
|
const JS = `
|
|
201
217
|
var API = '';
|
|
202
218
|
var currentView = 'dashboard';
|
|
203
|
-
var chatHistory =
|
|
219
|
+
var chatHistory = [];
|
|
220
|
+
var activeConvId = null;
|
|
221
|
+
var convList = [];
|
|
204
222
|
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
|
+
}
|
|
205
241
|
|
|
206
|
-
|
|
207
|
-
function
|
|
242
|
+
// ---- BROWSER VIEWER (live preview of headless Chrome) ----
|
|
243
|
+
function showBrowserViewer(title,status){
|
|
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()}
|
|
208
265
|
var agentsList = [];
|
|
209
266
|
var selectedAgent = null;
|
|
210
267
|
|
|
@@ -221,7 +278,11 @@ function switchView(v) {
|
|
|
221
278
|
if(el.dataset.view===v){el.classList.add('nav-item--active')}else{el.classList.remove('nav-item--active')}
|
|
222
279
|
});
|
|
223
280
|
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'};
|
|
224
|
-
document.getElementById('
|
|
281
|
+
var spt=document.getElementById('sidebarPageTitle');
|
|
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')}}
|
|
225
286
|
closeSidebar();
|
|
226
287
|
render();
|
|
227
288
|
}
|
|
@@ -253,10 +314,11 @@ function apiPatch(p){return fetch(API+p,{method:'PATCH'}).then(function(r){retur
|
|
|
253
314
|
|
|
254
315
|
// ---- LOAD DATA ----
|
|
255
316
|
function loadDash(){
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
});
|
|
317
|
+
// Load each API independently — render as each arrives (emails are slow)
|
|
318
|
+
apiGet('/api/status').then(function(r){dash.status=r;render()});
|
|
319
|
+
apiGet('/api/tasks').then(function(r){dash.tasks=(r&&r.tasks)||[];dashLoaded.tasks=true;updateBadges();render()});
|
|
320
|
+
apiGet('/api/calendar').then(function(r){dash.events=(r&&r.events)||[];dashLoaded.events=true;updateBadges();render()});
|
|
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()});
|
|
260
322
|
}
|
|
261
323
|
function loadAgents(){return apiGet('/api/agents').then(function(r){agentsList=(r&&r.agents)||[]})}
|
|
262
324
|
function updateBadges(){
|
|
@@ -270,6 +332,7 @@ function updateBadges(){
|
|
|
270
332
|
// ---- HELPERS ----
|
|
271
333
|
function esc(s){return s?String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'):''}
|
|
272
334
|
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>'}
|
|
273
336
|
|
|
274
337
|
// ---- RENDER ----
|
|
275
338
|
function render(){
|
|
@@ -298,13 +361,14 @@ function render(){
|
|
|
298
361
|
|
|
299
362
|
// ---- DASHBOARD ----
|
|
300
363
|
function renderDash(el){
|
|
364
|
+
if(!dashLoaded.tasks&&!dashLoaded.events&&!dashLoaded.emails){el.innerHTML=loadingHTML('dashboard');return}
|
|
301
365
|
var t=dash.tasks,e=dash.emails,ev=dash.events;
|
|
302
366
|
var done=t.filter(function(x){return x.status==='done'}).length;
|
|
303
367
|
var pend=t.length-done;
|
|
304
368
|
var pct=t.length>0?Math.round(done/t.length*100):0;
|
|
305
369
|
var h='<div class="dash-grid">'+
|
|
306
370
|
'<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>'+
|
|
307
|
-
'<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>'+
|
|
371
|
+
'<div class="card"><div class="card__title">Emails</div><div class="card__value">'+(dashLoaded.emails?e.length:'<span class="spinner" style="width:14px;height:14px;display:inline-block;vertical-align:middle"></span>')+'</div><div class="card__sub">'+(dashLoaded.emails?(e.length>0?esc(e[0].from):'Inbox zero'):'Loading...')+'</div></div>'+
|
|
308
372
|
'<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>'+
|
|
309
373
|
'<div class="card"><div class="card__title">Agents</div><div class="card__value">38</div><div class="card__sub">Ready</div></div>'+
|
|
310
374
|
'</div>';
|
|
@@ -318,115 +382,225 @@ function renderDash(el){
|
|
|
318
382
|
var chatReady=false;
|
|
319
383
|
function renderChat(el){
|
|
320
384
|
if(!chatReady||!document.getElementById('chatMessages')){
|
|
321
|
-
el.innerHTML='<div
|
|
385
|
+
el.innerHTML='<div style="display:flex;height:calc(100vh - 56px)">'+
|
|
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>';
|
|
322
403
|
chatReady=true;
|
|
323
404
|
document.getElementById('chatSend').onclick=sendChat;
|
|
324
405
|
document.getElementById('chatInput').onkeydown=function(e){if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();sendChat()}};
|
|
325
|
-
|
|
406
|
+
loadConvList().then(function(){
|
|
407
|
+
if(!activeConvId&&convList.length>0){loadConv(convList[0].id)}
|
|
408
|
+
else if(!activeConvId){createNewConv()}
|
|
409
|
+
else{loadConv(activeConvId)}
|
|
410
|
+
});
|
|
326
411
|
setTimeout(function(){var i=document.getElementById('chatInput');if(i)i.focus()},100);
|
|
327
412
|
}
|
|
328
413
|
}
|
|
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');}
|
|
329
430
|
function renderMessages(){
|
|
330
431
|
var el=document.getElementById('chatMessages');if(!el)return;
|
|
331
432
|
if(chatHistory.length===0){
|
|
332
|
-
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 /
|
|
433
|
+
el.innerHTML='<div class="chat__empty"><div class="chat__empty-title">NHA Chat</div><div>Personal Operations Assistant — Streaming + Web Search + Browser</div><div class="chat__empty-hint">Try: Show my unread emails / Search the web for React 19 / Open google.com and take a screenshot</div></div>';
|
|
333
434
|
return;
|
|
334
435
|
}
|
|
335
|
-
var h='';chatHistory.forEach(function(m
|
|
336
|
-
var
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
}
|
|
347
|
-
if (content.indexOf('[CANVAS_CLEAR]') !== -1) {
|
|
348
|
-
closeCanvas();
|
|
349
|
-
content = content.replace(/\\[CANVAS_CLEAR\\][\\s\\S]*?\\[\\/CANVAS_CLEAR\\]/, '').trim();
|
|
350
|
-
}
|
|
351
|
-
// Handle screenshot file markers
|
|
352
|
-
var ssMatch = content.match(/\\[SCREENSHOT_FILE\\](.*?)\\[\\/SCREENSHOT_FILE\\]/);
|
|
353
|
-
if (ssMatch) {
|
|
354
|
-
var fname = ssMatch[1].split('/').pop();
|
|
355
|
-
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)" />';
|
|
356
|
-
content = content.replace(/\\[SCREENSHOT_FILE\\].*?\\[\\/SCREENSHOT_FILE\\]/, '').trim();
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
var bubbleContent = isAssistant ? extraHtml + esc(content).replace(/\\n/g, '<br>') : esc(content).replace(/\\n/g, '<br>');
|
|
361
|
-
|
|
362
|
-
// Action buttons for each message
|
|
363
|
-
var actions = '<div class="msg__actions" style="display:flex;gap:6px;margin-top:4px">';
|
|
364
|
-
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>';
|
|
365
|
-
if (isAssistant) {
|
|
366
|
-
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>';
|
|
367
|
-
} else {
|
|
368
|
-
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>';
|
|
369
|
-
}
|
|
370
|
-
actions += '</div>';
|
|
371
|
-
|
|
372
|
-
h+='<div class="msg msg--'+esc(m.role)+'"><div class="msg__label">'+esc(m.role==='user'?'You':'NHA')+'</div><div class="msg__bubble">'+bubbleContent+'</div>'+actions+'</div>';
|
|
436
|
+
var h='';chatHistory.forEach(function(m){
|
|
437
|
+
var raw=m.content||'';
|
|
438
|
+
// Strip any raw base64 data that leaked into content (from LLM hallucinations)
|
|
439
|
+
raw=raw.replace(/data:image\\/[a-z]+;base64,[A-Za-z0-9+\\/=]{200,}/g,'[image]');
|
|
440
|
+
raw=raw.replace(/[A-Za-z0-9+\\/=]{500,}/g,'');
|
|
441
|
+
var imgs=[];var idx=0;
|
|
442
|
+
// Match both /api/screenshots/ URLs and data:image (short ones only, for inline display)
|
|
443
|
+
var safe=raw.replace(/!\\[([^\\]]*)\\]\\((\\/api\\/screenshots\\/[a-zA-Z0-9._-]+)\\)/g,function(_,alt,src){var ph='__IMG'+idx+'__';imgs.push({ph:ph,alt:alt,src:src});idx++;return ph;});
|
|
444
|
+
var content=esc(safe);
|
|
445
|
+
for(var i=0;i<imgs.length;i++){content=content.replace(imgs[i].ph,'<img class="screenshot-preview" alt="'+esc(imgs[i].alt)+'" src="'+imgs[i].src+'">');}
|
|
446
|
+
h+='<div class="msg msg--'+esc(m.role)+'"><div class="msg__label">'+esc(m.role==='user'?'You':'NHA')+'</div><div class="msg__bubble">'+content+'</div></div>';
|
|
373
447
|
});
|
|
374
448
|
el.innerHTML=h;el.scrollTop=el.scrollHeight;
|
|
375
449
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
450
|
+
var chatAttachedFile=null;
|
|
451
|
+
var chatAttachedImage=null;
|
|
452
|
+
|
|
453
|
+
function handleChatFile(input){
|
|
454
|
+
var file=input.files&&input.files[0];if(!file)return;
|
|
455
|
+
var isPDF=file.name.toLowerCase().endsWith('.pdf')||file.type==='application/pdf';
|
|
456
|
+
if(isPDF){
|
|
457
|
+
// PDF: read as base64 and send as document to LLM
|
|
458
|
+
var reader=new FileReader();
|
|
459
|
+
reader.onload=function(e){
|
|
460
|
+
var base64=e.target.result.split(',')[1];
|
|
461
|
+
chatAttachedFile={name:file.name,size:file.size,content:null,base64:base64,mimeType:'application/pdf',isPDF:true};
|
|
462
|
+
chatAttachedImage=null;
|
|
463
|
+
document.getElementById('chatAttachInfo').style.display='';
|
|
464
|
+
document.getElementById('chatAttachName').textContent='📎 '+file.name+' ('+Math.round(file.size/1024)+' KB)';
|
|
465
|
+
};
|
|
466
|
+
reader.readAsDataURL(file);
|
|
467
|
+
}else{
|
|
468
|
+
var reader=new FileReader();
|
|
469
|
+
reader.onload=function(e){
|
|
470
|
+
chatAttachedFile={name:file.name,size:file.size,content:e.target.result};
|
|
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
|
+
}
|
|
397
477
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
var
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function clearChatAttach(){
|
|
493
|
+
chatAttachedFile=null;chatAttachedImage=null;
|
|
494
|
+
document.getElementById('chatAttachInfo').style.display='none';
|
|
495
|
+
document.getElementById('chatFileInput').value='';
|
|
496
|
+
document.getElementById('chatImageInput').value='';
|
|
406
497
|
}
|
|
498
|
+
|
|
407
499
|
function sendChat(){
|
|
408
500
|
var inp=document.getElementById('chatInput');if(!inp)return;
|
|
409
|
-
var msg=inp.value.trim();
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
501
|
+
var msg=inp.value.trim();
|
|
502
|
+
var hasAttach=!!chatAttachedFile||!!chatAttachedImage;
|
|
503
|
+
if(!msg&&!hasAttach)return;
|
|
504
|
+
if(chatStreaming)return;
|
|
505
|
+
|
|
506
|
+
var displayMsg=msg;
|
|
507
|
+
if(chatAttachedFile)displayMsg=(msg?msg+' ':'')+'[File: '+chatAttachedFile.name+']';
|
|
508
|
+
if(chatAttachedImage)displayMsg=(msg?msg+' ':'')+'[Image: '+chatAttachedImage.name+']';
|
|
509
|
+
|
|
510
|
+
chatHistory.push({role:'user',content:displayMsg});
|
|
511
|
+
inp.value='';renderMessages();
|
|
512
|
+
|
|
513
|
+
// If attachment, use regular (non-streaming) endpoint
|
|
514
|
+
if(chatAttachedFile||chatAttachedImage){
|
|
515
|
+
chatHistory.push({role:'assistant',content:'Thinking...'});renderMessages();
|
|
516
|
+
var payload={message:msg||'Analyze this attachment',history:chatHistory.slice(0,-1)};
|
|
517
|
+
if(chatAttachedFile){
|
|
518
|
+
if(chatAttachedFile.isPDF&&chatAttachedFile.base64){payload.pdfBase64=chatAttachedFile.base64;payload.pdfName=chatAttachedFile.name;}
|
|
519
|
+
else{payload.fileContent=chatAttachedFile.content;payload.fileName=chatAttachedFile.name;}
|
|
428
520
|
}
|
|
429
|
-
|
|
521
|
+
if(chatAttachedImage){payload.imageBase64=chatAttachedImage.base64;payload.imageMimeType=chatAttachedImage.mimeType;}
|
|
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();}});
|
|
430
604
|
}
|
|
431
605
|
|
|
432
606
|
// ---- TASKS ----
|
|
@@ -496,17 +670,64 @@ function refreshPlan(){
|
|
|
496
670
|
|
|
497
671
|
// ---- EMAILS ----
|
|
498
672
|
function renderEmails(el){
|
|
673
|
+
if(!dashLoaded.emails){el.innerHTML=loadingHTML('emails');return}
|
|
499
674
|
var e=dash.emails;
|
|
500
|
-
if(e.length===0){el.innerHTML='<div class="card" style="text-align:center;color:var(--dim);padding:30px">
|
|
501
|
-
var
|
|
675
|
+
if(e.length===0){el.innerHTML='<div class="card" style="text-align:center;color:var(--dim);padding:30px">Inbox zero — no emails</div>';return}
|
|
676
|
+
var unreadCount=e.filter(function(x){return x.isUnread}).length;
|
|
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){
|
|
502
682
|
var unreadStyle=x.isUnread?'border-left:3px solid var(--green);font-weight:700':'border-left:3px solid transparent;opacity:0.7';
|
|
503
683
|
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>';
|
|
504
684
|
});
|
|
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
|
+
}
|
|
505
689
|
el.innerHTML=h;
|
|
506
690
|
}
|
|
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
|
+
}
|
|
507
721
|
var openEmailId=null;
|
|
508
722
|
function openEmail(id){
|
|
509
723
|
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
|
+
}
|
|
510
731
|
var el=document.getElementById('content');
|
|
511
732
|
el.innerHTML='<div style="text-align:center;padding:40px"><div class="spinner"></div><div style="color:var(--dim)">Loading email...</div></div>';
|
|
512
733
|
apiPost('/api/email/read',{messageId:id}).then(function(r){
|
|
@@ -1155,22 +1376,68 @@ function renderAgents(el){
|
|
|
1155
1376
|
|
|
1156
1377
|
var filtered=agentFilter?agentsList.filter(function(a){return a.category===agentFilter}):agentsList;
|
|
1157
1378
|
|
|
1379
|
+
h+='<div style="margin-bottom:10px"><button class="btn btn--primary" style="font-size:11px" onclick="showCreateAgentForm()">+ Create Agent</button></div>';
|
|
1158
1380
|
h+='<div class="agents-grid">';
|
|
1159
1381
|
filtered.forEach(function(a){
|
|
1160
1382
|
var name=a.name||a.agentName;
|
|
1161
1383
|
var display=a.displayName||name;
|
|
1162
1384
|
var icon=AGENT_ICONS[name.toLowerCase()]||'\\u{1F916}';
|
|
1163
1385
|
var desc=AGENT_DESCRIPTIONS[name.toLowerCase()]||a.tagline||a.description||'';
|
|
1164
|
-
|
|
1386
|
+
var isCustom=a.category==='custom';
|
|
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">'+
|
|
1165
1390
|
'<div class="agent-card__icon">'+icon+'</div>'+
|
|
1166
1391
|
'<div class="agent-card__body"><div class="agent-card__name">'+esc(display)+'</div>'+
|
|
1167
1392
|
'<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>'+
|
|
1168
1398
|
'</div>';
|
|
1169
1399
|
});
|
|
1170
1400
|
h+='</div>';
|
|
1171
1401
|
el.innerHTML=h;
|
|
1172
1402
|
}
|
|
1173
1403
|
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
|
+
|
|
1174
1441
|
function openAgent(name,display){
|
|
1175
1442
|
selectedAgent=name;
|
|
1176
1443
|
attachedFileContent=null;attachedFileName=null;
|
|
@@ -1253,9 +1520,26 @@ function renderSettings(el) {
|
|
|
1253
1520
|
['summary-time', 'Summary Time', '18:00'],
|
|
1254
1521
|
['meeting-alert', 'Meeting Alert (minutes)', '30'],
|
|
1255
1522
|
]) +
|
|
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>' +
|
|
1256
1529
|
'</div>';
|
|
1257
1530
|
}
|
|
1258
1531
|
|
|
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
|
+
|
|
1259
1543
|
function settingsSection(id, title, desc, fields) {
|
|
1260
1544
|
var h = '<form class="card" style="margin-bottom:16px" id="settings-' + id + '" onsubmit="event.preventDefault();saveSettingsSection(\\x27' + id + '\\x27)">' +
|
|
1261
1545
|
'<div class="card__title" style="color:var(--green);font-size:14px;margin-bottom:4px">' + esc(title) + '</div>' +
|
|
@@ -1476,61 +1760,6 @@ function handleDaemonEvent(msg) {
|
|
|
1476
1760
|
showToast('plan', 'Daily Plan Ready', 'Your plan for ' + msg.data.date + ' has been generated.', 10000);
|
|
1477
1761
|
if (currentView === 'plan') renderPlan(document.getElementById('content'));
|
|
1478
1762
|
break;
|
|
1479
|
-
|
|
1480
|
-
case 'cron_result':
|
|
1481
|
-
showToast('cron', 'Scheduled Task', msg.data.prompt + '\\n' + (msg.data.result || '').slice(0, 100), 8000);
|
|
1482
|
-
break;
|
|
1483
|
-
}
|
|
1484
|
-
}
|
|
1485
|
-
|
|
1486
|
-
// ---- CANVAS PANEL ----
|
|
1487
|
-
var canvasVisible = false;
|
|
1488
|
-
function showCanvas(html, title) {
|
|
1489
|
-
var panel = document.getElementById('canvasPanel');
|
|
1490
|
-
if (!panel) {
|
|
1491
|
-
panel = document.createElement('div');
|
|
1492
|
-
panel.id = 'canvasPanel';
|
|
1493
|
-
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)';
|
|
1494
|
-
document.body.appendChild(panel);
|
|
1495
|
-
}
|
|
1496
|
-
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)">' +
|
|
1497
|
-
'<span style="font-family:var(--mono);color:var(--green);font-size:12px">' + (title || 'Canvas') + '</span>' +
|
|
1498
|
-
'<div><button onclick="toggleCanvasSize()" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:14px;margin-right:8px" title="Resize">⤢</button>' +
|
|
1499
|
-
'<button onclick="closeCanvas()" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:14px" title="Close">×</button></div></div>';
|
|
1500
|
-
var iframe = '<iframe id="canvasFrame" sandbox="allow-scripts allow-same-origin" style="flex:1;border:none;background:#fff;min-height:300px;width:100%"></iframe>';
|
|
1501
|
-
panel.innerHTML = header + iframe;
|
|
1502
|
-
panel.style.display = 'flex';
|
|
1503
|
-
canvasVisible = true;
|
|
1504
|
-
|
|
1505
|
-
// Write HTML to iframe
|
|
1506
|
-
var frame = document.getElementById('canvasFrame');
|
|
1507
|
-
if (frame) {
|
|
1508
|
-
var doc = frame.contentDocument || frame.contentWindow.document;
|
|
1509
|
-
doc.open();
|
|
1510
|
-
doc.write(html);
|
|
1511
|
-
doc.close();
|
|
1512
|
-
}
|
|
1513
|
-
}
|
|
1514
|
-
|
|
1515
|
-
function closeCanvas() {
|
|
1516
|
-
var panel = document.getElementById('canvasPanel');
|
|
1517
|
-
if (panel) { panel.style.display = 'none'; canvasVisible = false; }
|
|
1518
|
-
}
|
|
1519
|
-
|
|
1520
|
-
function toggleCanvasSize() {
|
|
1521
|
-
var panel = document.getElementById('canvasPanel');
|
|
1522
|
-
if (!panel) return;
|
|
1523
|
-
if (panel.style.width === '480px') {
|
|
1524
|
-
panel.style.width = '80vw';
|
|
1525
|
-
panel.style.height = '80vh';
|
|
1526
|
-
panel.style.top = '10vh';
|
|
1527
|
-
panel.style.right = '10vw';
|
|
1528
|
-
} else {
|
|
1529
|
-
panel.style.width = '480px';
|
|
1530
|
-
panel.style.height = '';
|
|
1531
|
-
panel.style.maxHeight = 'calc(100vh - 80px)';
|
|
1532
|
-
panel.style.top = '60px';
|
|
1533
|
-
panel.style.right = '12px';
|
|
1534
1763
|
}
|
|
1535
1764
|
}
|
|
1536
1765
|
|
|
@@ -1636,8 +1865,13 @@ init();
|
|
|
1636
1865
|
<div class="app">
|
|
1637
1866
|
<nav class="sidebar" id="sidebar">
|
|
1638
1867
|
<div class="sidebar__brand">
|
|
1639
|
-
<div
|
|
1640
|
-
|
|
1868
|
+
<div style="display:flex;align-items:center;gap:8px">
|
|
1869
|
+
<div class="sidebar__brand-name">NHA</div>
|
|
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>
|
|
1641
1875
|
</div>
|
|
1642
1876
|
<div class="sidebar__section">
|
|
1643
1877
|
<div class="sidebar__label">Overview</div>
|
|
@@ -1674,17 +1908,30 @@ init();
|
|
|
1674
1908
|
<div class="sidebar__label">Config</div>
|
|
1675
1909
|
<div class="nav-item" data-view="settings" onclick="switchView('settings')"><span class="nav-item__icon">⚙</span> Settings</div>
|
|
1676
1910
|
</div>
|
|
1677
|
-
<div
|
|
1911
|
+
<div class="sidebar__section">
|
|
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>
|
|
1678
1918
|
</nav>
|
|
1679
1919
|
|
|
1680
|
-
<
|
|
1681
|
-
<button class="header__burger" onclick="toggleSidebar()">☰</button>
|
|
1682
|
-
<span class="header__title" id="headerTitle">Dashboard</span>
|
|
1683
|
-
<span id="wsIndicator" style="color:var(--dim);font-size:8px;margin-right:4px" title="Daemon WebSocket">●</span>
|
|
1684
|
-
<span class="header__clock" id="clock"></span>
|
|
1685
|
-
</div>
|
|
1920
|
+
<button onclick="openSidebar()" style="position:fixed;top:8px;left:8px;z-index:100;background:var(--bg2);border:1px solid var(--border);border-radius:var(--r);color:var(--green);font-size:20px;padding:4px 10px;cursor:pointer;line-height:1" id="mobileBurger">☰</button>
|
|
1686
1921
|
|
|
1687
1922
|
<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>
|
|
1688
1935
|
</div>
|
|
1689
1936
|
|
|
1690
1937
|
<div class="modal-overlay" id="agentModal">
|