nothumanallowed 9.7.1 → 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 -173
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}
|
|
@@ -98,10 +96,30 @@ input:focus,textarea:focus{border-color:var(--green3)}
|
|
|
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
97
|
.msg__label{font-size:10px;color:var(--dim);margin-bottom:2px}
|
|
100
98
|
.msg--thinking{color:var(--dim);font-style:italic}
|
|
101
|
-
.
|
|
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}
|
|
102
118
|
.chat__input{flex:1;resize:none;min-height:40px;max-height:100px;padding:10px 14px}
|
|
103
119
|
.chat__send{background:var(--green3);color:var(--bg);padding:10px 16px;border-radius:var(--r);font-weight:700;font-size:12px}
|
|
104
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}
|
|
105
123
|
|
|
106
124
|
/* ---- TASKS ---- */
|
|
107
125
|
.task-bar{display:flex;gap:8px;margin-bottom:12px;flex-wrap:wrap}
|
|
@@ -198,11 +216,52 @@ input:focus,textarea:focus{border-color:var(--green3)}
|
|
|
198
216
|
const JS = `
|
|
199
217
|
var API = '';
|
|
200
218
|
var currentView = 'dashboard';
|
|
201
|
-
var chatHistory =
|
|
219
|
+
var chatHistory = [];
|
|
220
|
+
var activeConvId = null;
|
|
221
|
+
var convList = [];
|
|
202
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
|
+
}
|
|
203
241
|
|
|
204
|
-
|
|
205
|
-
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()}
|
|
206
265
|
var agentsList = [];
|
|
207
266
|
var selectedAgent = null;
|
|
208
267
|
|
|
@@ -219,7 +278,11 @@ function switchView(v) {
|
|
|
219
278
|
if(el.dataset.view===v){el.classList.add('nav-item--active')}else{el.classList.remove('nav-item--active')}
|
|
220
279
|
});
|
|
221
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'};
|
|
222
|
-
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')}}
|
|
223
286
|
closeSidebar();
|
|
224
287
|
render();
|
|
225
288
|
}
|
|
@@ -251,10 +314,11 @@ function apiPatch(p){return fetch(API+p,{method:'PATCH'}).then(function(r){retur
|
|
|
251
314
|
|
|
252
315
|
// ---- LOAD DATA ----
|
|
253
316
|
function loadDash(){
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
});
|
|
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()});
|
|
258
322
|
}
|
|
259
323
|
function loadAgents(){return apiGet('/api/agents').then(function(r){agentsList=(r&&r.agents)||[]})}
|
|
260
324
|
function updateBadges(){
|
|
@@ -268,6 +332,7 @@ function updateBadges(){
|
|
|
268
332
|
// ---- HELPERS ----
|
|
269
333
|
function esc(s){return s?String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'):''}
|
|
270
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>'}
|
|
271
336
|
|
|
272
337
|
// ---- RENDER ----
|
|
273
338
|
function render(){
|
|
@@ -296,13 +361,14 @@ function render(){
|
|
|
296
361
|
|
|
297
362
|
// ---- DASHBOARD ----
|
|
298
363
|
function renderDash(el){
|
|
364
|
+
if(!dashLoaded.tasks&&!dashLoaded.events&&!dashLoaded.emails){el.innerHTML=loadingHTML('dashboard');return}
|
|
299
365
|
var t=dash.tasks,e=dash.emails,ev=dash.events;
|
|
300
366
|
var done=t.filter(function(x){return x.status==='done'}).length;
|
|
301
367
|
var pend=t.length-done;
|
|
302
368
|
var pct=t.length>0?Math.round(done/t.length*100):0;
|
|
303
369
|
var h='<div class="dash-grid">'+
|
|
304
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>'+
|
|
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>'+
|
|
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>'+
|
|
306
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>'+
|
|
307
373
|
'<div class="card"><div class="card__title">Agents</div><div class="card__value">38</div><div class="card__sub">Ready</div></div>'+
|
|
308
374
|
'</div>';
|
|
@@ -316,115 +382,225 @@ function renderDash(el){
|
|
|
316
382
|
var chatReady=false;
|
|
317
383
|
function renderChat(el){
|
|
318
384
|
if(!chatReady||!document.getElementById('chatMessages')){
|
|
319
|
-
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>';
|
|
320
403
|
chatReady=true;
|
|
321
404
|
document.getElementById('chatSend').onclick=sendChat;
|
|
322
405
|
document.getElementById('chatInput').onkeydown=function(e){if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();sendChat()}};
|
|
323
|
-
|
|
406
|
+
loadConvList().then(function(){
|
|
407
|
+
if(!activeConvId&&convList.length>0){loadConv(convList[0].id)}
|
|
408
|
+
else if(!activeConvId){createNewConv()}
|
|
409
|
+
else{loadConv(activeConvId)}
|
|
410
|
+
});
|
|
324
411
|
setTimeout(function(){var i=document.getElementById('chatInput');if(i)i.focus()},100);
|
|
325
412
|
}
|
|
326
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');}
|
|
327
430
|
function renderMessages(){
|
|
328
431
|
var el=document.getElementById('chatMessages');if(!el)return;
|
|
329
432
|
if(chatHistory.length===0){
|
|
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 /
|
|
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>';
|
|
331
434
|
return;
|
|
332
435
|
}
|
|
333
|
-
var h='';chatHistory.forEach(function(m
|
|
334
|
-
var
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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>';
|
|
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>';
|
|
371
447
|
});
|
|
372
448
|
el.innerHTML=h;el.scrollTop=el.scrollHeight;
|
|
373
449
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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
|
+
}
|
|
395
477
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
var
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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='';
|
|
404
497
|
}
|
|
498
|
+
|
|
405
499
|
function sendChat(){
|
|
406
500
|
var inp=document.getElementById('chatInput');if(!inp)return;
|
|
407
|
-
var msg=inp.value.trim();
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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;}
|
|
426
520
|
}
|
|
427
|
-
|
|
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();}});
|
|
428
604
|
}
|
|
429
605
|
|
|
430
606
|
// ---- TASKS ----
|
|
@@ -494,17 +670,64 @@ function refreshPlan(){
|
|
|
494
670
|
|
|
495
671
|
// ---- EMAILS ----
|
|
496
672
|
function renderEmails(el){
|
|
673
|
+
if(!dashLoaded.emails){el.innerHTML=loadingHTML('emails');return}
|
|
497
674
|
var e=dash.emails;
|
|
498
|
-
if(e.length===0){el.innerHTML='<div class="card" style="text-align:center;color:var(--dim);padding:30px">
|
|
499
|
-
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){
|
|
500
682
|
var unreadStyle=x.isUnread?'border-left:3px solid var(--green);font-weight:700':'border-left:3px solid transparent;opacity:0.7';
|
|
501
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>';
|
|
502
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
|
+
}
|
|
503
689
|
el.innerHTML=h;
|
|
504
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
|
+
}
|
|
505
721
|
var openEmailId=null;
|
|
506
722
|
function openEmail(id){
|
|
507
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
|
+
}
|
|
508
731
|
var el=document.getElementById('content');
|
|
509
732
|
el.innerHTML='<div style="text-align:center;padding:40px"><div class="spinner"></div><div style="color:var(--dim)">Loading email...</div></div>';
|
|
510
733
|
apiPost('/api/email/read',{messageId:id}).then(function(r){
|
|
@@ -1153,22 +1376,68 @@ function renderAgents(el){
|
|
|
1153
1376
|
|
|
1154
1377
|
var filtered=agentFilter?agentsList.filter(function(a){return a.category===agentFilter}):agentsList;
|
|
1155
1378
|
|
|
1379
|
+
h+='<div style="margin-bottom:10px"><button class="btn btn--primary" style="font-size:11px" onclick="showCreateAgentForm()">+ Create Agent</button></div>';
|
|
1156
1380
|
h+='<div class="agents-grid">';
|
|
1157
1381
|
filtered.forEach(function(a){
|
|
1158
1382
|
var name=a.name||a.agentName;
|
|
1159
1383
|
var display=a.displayName||name;
|
|
1160
1384
|
var icon=AGENT_ICONS[name.toLowerCase()]||'\\u{1F916}';
|
|
1161
1385
|
var desc=AGENT_DESCRIPTIONS[name.toLowerCase()]||a.tagline||a.description||'';
|
|
1162
|
-
|
|
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">'+
|
|
1163
1390
|
'<div class="agent-card__icon">'+icon+'</div>'+
|
|
1164
1391
|
'<div class="agent-card__body"><div class="agent-card__name">'+esc(display)+'</div>'+
|
|
1165
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>'+
|
|
1166
1398
|
'</div>';
|
|
1167
1399
|
});
|
|
1168
1400
|
h+='</div>';
|
|
1169
1401
|
el.innerHTML=h;
|
|
1170
1402
|
}
|
|
1171
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
|
+
|
|
1172
1441
|
function openAgent(name,display){
|
|
1173
1442
|
selectedAgent=name;
|
|
1174
1443
|
attachedFileContent=null;attachedFileName=null;
|
|
@@ -1251,9 +1520,26 @@ function renderSettings(el) {
|
|
|
1251
1520
|
['summary-time', 'Summary Time', '18:00'],
|
|
1252
1521
|
['meeting-alert', 'Meeting Alert (minutes)', '30'],
|
|
1253
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>' +
|
|
1254
1529
|
'</div>';
|
|
1255
1530
|
}
|
|
1256
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
|
+
|
|
1257
1543
|
function settingsSection(id, title, desc, fields) {
|
|
1258
1544
|
var h = '<form class="card" style="margin-bottom:16px" id="settings-' + id + '" onsubmit="event.preventDefault();saveSettingsSection(\\x27' + id + '\\x27)">' +
|
|
1259
1545
|
'<div class="card__title" style="color:var(--green);font-size:14px;margin-bottom:4px">' + esc(title) + '</div>' +
|
|
@@ -1474,61 +1760,6 @@ function handleDaemonEvent(msg) {
|
|
|
1474
1760
|
showToast('plan', 'Daily Plan Ready', 'Your plan for ' + msg.data.date + ' has been generated.', 10000);
|
|
1475
1761
|
if (currentView === 'plan') renderPlan(document.getElementById('content'));
|
|
1476
1762
|
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';
|
|
1532
1763
|
}
|
|
1533
1764
|
}
|
|
1534
1765
|
|
|
@@ -1634,8 +1865,13 @@ init();
|
|
|
1634
1865
|
<div class="app">
|
|
1635
1866
|
<nav class="sidebar" id="sidebar">
|
|
1636
1867
|
<div class="sidebar__brand">
|
|
1637
|
-
<div
|
|
1638
|
-
|
|
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>
|
|
1639
1875
|
</div>
|
|
1640
1876
|
<div class="sidebar__section">
|
|
1641
1877
|
<div class="sidebar__label">Overview</div>
|
|
@@ -1672,17 +1908,30 @@ init();
|
|
|
1672
1908
|
<div class="sidebar__label">Config</div>
|
|
1673
1909
|
<div class="nav-item" data-view="settings" onclick="switchView('settings')"><span class="nav-item__icon">⚙</span> Settings</div>
|
|
1674
1910
|
</div>
|
|
1675
|
-
<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>
|
|
1676
1918
|
</nav>
|
|
1677
1919
|
|
|
1678
|
-
<
|
|
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>
|
|
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>
|
|
1684
1921
|
|
|
1685
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>
|
|
1686
1935
|
</div>
|
|
1687
1936
|
|
|
1688
1937
|
<div class="modal-overlay" id="agentModal">
|