nothumanallowed 9.5.1 → 9.6.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.
@@ -46,7 +46,10 @@ input:focus,textarea:focus{border-color:var(--green3)}
46
46
 
47
47
  /* ---- LAYOUT: mobile-first ---- */
48
48
  .app{display:flex;flex-direction:column;height:100vh;height:100dvh}
49
- /* header removed info moved to sidebar brand */
49
+ .header{display:flex;align-items:center;gap:12px;padding:10px 16px;border-bottom:1px solid var(--border);background:var(--bg);position:relative;z-index:60;flex-shrink:0}
50
+ .header__burger{background:none;color:var(--green);font-size:22px;padding:4px 8px;line-height:1}
51
+ .header__title{font-size:14px;color:var(--bright);font-weight:700;flex:1}
52
+ .header__clock{font-size:10px;color:var(--dim)}
50
53
 
51
54
  .sidebar{display:none;position:fixed;top:0;left:0;width:260px;height:100vh;height:100dvh;background:var(--bg2);border-right:1px solid var(--border);z-index:200;flex-direction:column;overflow-y:auto;box-shadow:4px 0 20px rgba(0,0,0,0.8)}
52
55
  .sidebar--open{display:flex}
@@ -84,9 +87,8 @@ input:focus,textarea:focus{border-color:var(--green3)}
84
87
  .section-title{font-size:12px;color:var(--cyan);text-transform:uppercase;letter-spacing:1px;margin-bottom:10px}
85
88
 
86
89
  /* ---- CHAT ---- */
87
- .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}}
90
+ .chat{display:flex;flex-direction:column;height:calc(100vh - 52px);height:calc(100dvh - 52px)}
91
+ @media(min-width:901px){.chat{height:calc(100vh - 52px)}}
90
92
  .chat__messages{flex:1;overflow-y:auto;padding-bottom:12px;-webkit-overflow-scrolling:touch}
91
93
  .chat__empty{text-align:center;padding:60px 16px;color:var(--dim)}
92
94
  .chat__empty-title{font-size:28px;color:var(--green);margin-bottom:12px}
@@ -96,30 +98,10 @@ input:focus,textarea:focus{border-color:var(--green3)}
96
98
  .msg--assistant .msg__bubble{background:var(--greendim);border:1px solid var(--green3);border-radius:8px 8px 8px 2px;padding:10px 14px;max-width:85%;color:var(--text);white-space:pre-wrap;word-wrap:break-word}
97
99
  .msg__label{font-size:10px;color:var(--dim);margin-bottom:2px}
98
100
  .msg--thinking{color:var(--dim);font-style:italic}
99
- .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}
101
+ .chat__bar{display:flex;gap:8px;padding:10px 0 0 0;border-top:1px solid var(--border);flex-shrink:0}
118
102
  .chat__input{flex:1;resize:none;min-height:40px;max-height:100px;padding:10px 14px}
119
103
  .chat__send{background:var(--green3);color:var(--bg);padding:10px 16px;border-radius:var(--r);font-weight:700;font-size:12px}
120
104
  .chat__send:disabled{opacity:.4}
121
- .chat__stop{background:var(--red);color:var(--bright);padding:10px 16px;border-radius:var(--r);font-weight:700;font-size:12px;display:none}
122
- .chat__stop--visible{display:block}
123
105
 
124
106
  /* ---- TASKS ---- */
125
107
  .task-bar{display:flex;gap:8px;margin-bottom:12px;flex-wrap:wrap}
@@ -216,52 +198,11 @@ input:focus,textarea:focus{border-color:var(--green3)}
216
198
  const JS = `
217
199
  var API = '';
218
200
  var currentView = 'dashboard';
219
- var chatHistory = [];
220
- var activeConvId = null;
221
- var convList = [];
201
+ var chatHistory = (function(){try{var s=localStorage.getItem('nha_chat_history');return s?JSON.parse(s):[];}catch(e){return [];}})();
222
202
  var dash = {emails:[],events:[],tasks:[],plan:null,status:null};
223
- var dashLoaded = {emails:false,events:false,tasks:false,contacts:false,notes:false,drive:false,github:false,notion:false,slack:false};
224
- var chatStreaming = false;
225
- var chatAbortController = null;
226
-
227
- function endStreaming(){
228
- chatStreaming=false;chatAbortController=null;
229
- var stopBtn=document.getElementById('chatStop');if(stopBtn)stopBtn.classList.remove('chat__stop--visible');
230
- var sendBtn=document.getElementById('chatSend');if(sendBtn)sendBtn.style.display='';
231
- }
232
- function stopChat(){
233
- if(chatAbortController){try{chatAbortController.abort()}catch(e){}}
234
- endStreaming();
235
- if(chatHistory.length>0){
236
- var last=chatHistory[chatHistory.length-1];
237
- if(last.role==='assistant'&&(!last.content||last.content===''))last.content='[Stopped]';
238
- }
239
- renderMessages();
240
- }
241
203
 
242
- // ---- 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()}
204
+ function saveChatToStorage(){try{localStorage.setItem('nha_chat_history',JSON.stringify(chatHistory.slice(-40)));}catch(e){}}
205
+ function clearChatHistory(){chatHistory=[];saveChatToStorage();renderMessages();}
265
206
  var agentsList = [];
266
207
  var selectedAgent = null;
267
208
 
@@ -278,11 +219,7 @@ function switchView(v) {
278
219
  if(el.dataset.view===v){el.classList.add('nav-item--active')}else{el.classList.remove('nav-item--active')}
279
220
  });
280
221
  var titles = {dashboard:'Dashboard',chat:'Chat',plan:'Daily Plan',tasks:'Tasks',emails:'Emails',calendar:'Calendar',drive:'Drive',contacts:'Contacts',notes:'Notes',onedrive:'OneDrive',mstodo:'Microsoft To Do',agents:'Agents',settings:'Settings'};
281
- 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')}}
222
+ document.getElementById('headerTitle').textContent = titles[v]||v;
286
223
  closeSidebar();
287
224
  render();
288
225
  }
@@ -314,11 +251,10 @@ function apiPatch(p){return fetch(API+p,{method:'PATCH'}).then(function(r){retur
314
251
 
315
252
  // ---- LOAD DATA ----
316
253
  function loadDash(){
317
- // 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').then(function(r){dash.emails=(r&&r.emails)||[];dashLoaded.emails=true;updateBadges();render()});
254
+ return Promise.all([apiGet('/api/status'),apiGet('/api/emails'),apiGet('/api/calendar'),apiGet('/api/tasks')]).then(function(r){
255
+ dash.status=r[0];dash.emails=(r[1]&&r[1].emails)||[];dash.events=(r[2]&&r[2].events)||[];dash.tasks=(r[3]&&r[3].tasks)||[];
256
+ updateBadges();
257
+ });
322
258
  }
323
259
  function loadAgents(){return apiGet('/api/agents').then(function(r){agentsList=(r&&r.agents)||[]})}
324
260
  function updateBadges(){
@@ -332,7 +268,6 @@ function updateBadges(){
332
268
  // ---- HELPERS ----
333
269
  function esc(s){return s?String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'):''}
334
270
  function fmtTime(iso){if(!iso)return '';try{return new Date(iso).toLocaleTimeString('en',{hour:'2-digit',minute:'2-digit',hour12:true})}catch(e){return iso}}
335
- function loadingHTML(label){return '<div style="text-align:center;padding:40px"><div class="spinner"></div><div style="color:var(--dim);margin-top:8px;font-size:12px">Loading '+esc(label)+'...</div></div>'}
336
271
 
337
272
  // ---- RENDER ----
338
273
  function render(){
@@ -361,14 +296,13 @@ function render(){
361
296
 
362
297
  // ---- DASHBOARD ----
363
298
  function renderDash(el){
364
- if(!dashLoaded.tasks&&!dashLoaded.events&&!dashLoaded.emails){el.innerHTML=loadingHTML('dashboard');return}
365
299
  var t=dash.tasks,e=dash.emails,ev=dash.events;
366
300
  var done=t.filter(function(x){return x.status==='done'}).length;
367
301
  var pend=t.length-done;
368
302
  var pct=t.length>0?Math.round(done/t.length*100):0;
369
303
  var h='<div class="dash-grid">'+
370
304
  '<div class="card"><div class="card__title">Tasks</div><div class="card__value">'+pend+'</div><div class="card__sub">'+done+'/'+t.length+' done ('+pct+'%)</div></div>'+
371
- '<div class="card"><div class="card__title">Emails</div><div class="card__value">'+(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>'+
305
+ '<div class="card"><div class="card__title">Emails</div><div class="card__value">'+e.length+'</div><div class="card__sub">'+(e.length>0?esc(e[0].from):'Inbox zero')+'</div></div>'+
372
306
  '<div class="card"><div class="card__title">Events</div><div class="card__value">'+ev.length+'</div><div class="card__sub">'+(ev.length>0?esc(ev[0].summary):'No events')+'</div></div>'+
373
307
  '<div class="card"><div class="card__title">Agents</div><div class="card__value">38</div><div class="card__sub">Ready</div></div>'+
374
308
  '</div>';
@@ -382,225 +316,67 @@ function renderDash(el){
382
316
  var chatReady=false;
383
317
  function renderChat(el){
384
318
  if(!chatReady||!document.getElementById('chatMessages')){
385
- el.innerHTML='<div style="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">&#128172;</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">&times;</button></div>'+
399
- '<div class="chat__bar"><button class="chat__mic" id="chatMic" onclick="toggleVoiceInput()" title="Voice input">&#127908;</button><button onclick="document.getElementById(\\x27chatFileInput\\x27).click()" style="background:none;border:none;cursor:pointer;font-size:16px;padding:4px" title="Attach file">&#128206;</button><button onclick="document.getElementById(\\x27chatImageInput\\x27).click()" style="background:none;border:none;cursor:pointer;font-size:16px;padding:4px" title="Attach image">&#128247;</button><input type="file" id="chatFileInput" style="display:none" onchange="handleChatFile(this)"><input type="file" id="chatImageInput" accept="image/*" style="display:none" onchange="handleChatImage(this)"><textarea class="chat__input" id="chatInput" placeholder="Ask anything... (or attach file/image first)" rows="1"></textarea><button class="chat__send" id="chatSend">Send</button><button class="chat__stop" id="chatStop" onclick="stopChat()">Stop</button></div>'+
400
- '</div>'+
401
- '</div>'+
402
- '</div>';
319
+ el.innerHTML='<div class="chat"><div class="chat__messages" id="chatMessages"></div><div class="chat__bar"><button class="chat__mic" id="chatMic" onclick="toggleVoiceInput()" title="Voice input">&#127908;</button><textarea class="chat__input" id="chatInput" placeholder="Ask anything..." rows="1"></textarea><button class="chat__send" id="chatSend">Send</button><button onclick="clearChatHistory()" style="background:none;color:var(--dim);font-size:10px;padding:4px 8px" title="Clear chat history">Clear</button></div></div>';
403
320
  chatReady=true;
404
321
  document.getElementById('chatSend').onclick=sendChat;
405
322
  document.getElementById('chatInput').onkeydown=function(e){if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();sendChat()}};
406
- loadConvList().then(function(){
407
- if(!activeConvId&&convList.length>0){loadConv(convList[0].id)}
408
- else if(!activeConvId){createNewConv()}
409
- else{loadConv(activeConvId)}
410
- });
323
+ renderMessages();
411
324
  setTimeout(function(){var i=document.getElementById('chatInput');if(i)i.focus()},100);
412
325
  }
413
326
  }
414
- function toggleConvSidebar(){var s=document.getElementById('convSidebar');if(!s)return;var hide=s.style.display!=='none';s.style.display=hide?'none':'';try{localStorage.setItem('nha_conv_sidebar',hide?'hidden':'visible')}catch(e){}}
415
- function renderConvSidebar(){
416
- var el=document.getElementById('convList');if(!el)return;
417
- var h='';convList.forEach(function(c){
418
- var active=c.id===activeConvId;
419
- var turns=Math.floor(c.messageCount/2);
420
- h+='<div onclick="loadConv(\\x27'+c.id+'\\x27)" style="padding:8px 12px;cursor:pointer;border-left:3px solid '+(active?'var(--green)':'transparent')+';background:'+(active?'var(--bg2)':'transparent')+'" onmouseover="this.style.background=\\x27var(--bg2)\\x27" onmouseout="this.style.background='+(active?"\\x27var(--bg2)\\x27":"\\x27transparent\\x27")+'">'+
421
- '<div style="font-size:11px;color:var(--fg);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">'+esc(c.title)+'</div>'+
422
- '<div style="font-size:9px;color:var(--dim);display:flex;gap:6px;margin-top:2px"><span>'+turns+' turns</span>'+(active?'':'<span onclick="event.stopPropagation();deleteConv(\\x27'+c.id+'\\x27)" style="color:var(--red);cursor:pointer">del</span>')+'</div>'+
423
- '</div>';
424
- });
425
- el.innerHTML=h;
426
- var t=document.getElementById('convTitle');
427
- if(t){var ac=convList.find(function(c){return c.id===activeConvId});t.textContent=ac?ac.title:'New Chat';}
428
- }
429
- function exportConvMd(){if(!activeConvId)return;window.open(API+'/api/conversations/'+activeConvId+'/export?format=md','_blank');}
430
327
  function renderMessages(){
431
328
  var el=document.getElementById('chatMessages');if(!el)return;
432
329
  if(chatHistory.length===0){
433
- el.innerHTML='<div class="chat__empty"><div class="chat__empty-title">NHA Chat</div><div>Personal Operations Assistant — 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>';
330
+ el.innerHTML='<div class="chat__empty"><div class="chat__empty-title">NHA Chat</div><div>Personal Operations Assistant</div><div class="chat__empty-hint">Try: Show my unread emails / What is on my calendar? / Add a task</div></div>';
434
331
  return;
435
332
  }
436
333
  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>';
334
+ var content = m.content || '';
335
+ // Handle canvas render markers
336
+ var canvasMatch = content.match(/\\[CANVAS_RENDER\\](.*?)\\[\\/CANVAS_RENDER\\]/s);
337
+ if (canvasMatch) {
338
+ try { var cd = JSON.parse(canvasMatch[1]); showCanvas(cd.html, cd.title); } catch(e){}
339
+ content = content.replace(/\\[CANVAS_RENDER\\].*?\\[\\/CANVAS_RENDER\\]/s, '').trim();
340
+ }
341
+ if (content.indexOf('[CANVAS_CLEAR]') !== -1) {
342
+ closeCanvas();
343
+ content = content.replace(/\\[CANVAS_CLEAR\\].*?\\[\\/CANVAS_CLEAR\\]/s, '').trim();
344
+ }
345
+ // Handle screenshot markers
346
+ var screenshotMatch = content.match(/\\[SCREENSHOT\\](.*?)\\[\\/SCREENSHOT\\]/);
347
+ if (screenshotMatch) {
348
+ var imgPath = screenshotMatch[1];
349
+ content = content.replace(/\\[SCREENSHOT\\].*?\\[\\/SCREENSHOT\\]/, '');
350
+ content = '<img src="/api/screenshots/' + encodeURIComponent(imgPath.split('/').pop()) + '" style="max-width:100%;border-radius:8px;margin:8px 0" />' + content;
351
+ }
352
+ var bubbleContent = m.role === 'assistant' ? content : esc(content);
353
+ 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></div>';
447
354
  });
448
355
  el.innerHTML=h;el.scrollTop=el.scrollHeight;
449
356
  }
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
- }
477
- }
478
-
479
- function handleChatImage(input){
480
- var file=input.files&&input.files[0];if(!file)return;
481
- var reader=new FileReader();
482
- reader.onload=function(e){
483
- var base64=e.target.result.split(',')[1];
484
- chatAttachedImage={name:file.name,size:file.size,base64:base64,mimeType:file.type||'image/jpeg'};
485
- chatAttachedFile=null;
486
- document.getElementById('chatAttachInfo').style.display='';
487
- document.getElementById('chatAttachName').textContent='📷 '+file.name+' ('+Math.round(file.size/1024)+' KB)';
488
- };
489
- reader.readAsDataURL(file);
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='';
497
- }
498
-
499
357
  function sendChat(){
500
358
  var inp=document.getElementById('chatInput');if(!inp)return;
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;}
520
- }
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![Screenshot](/api/screenshots/'+ssf[fi]+')\\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();}});
359
+ var msg=inp.value.trim();if(!msg)return;
360
+ chatHistory.push({role:'user',content:msg});
361
+ inp.value='';saveChatToStorage();renderMessages();
362
+ chatHistory.push({role:'assistant',content:'Thinking...'});renderMessages();
363
+ apiPost('/api/chat',{message:msg,history:chatHistory.slice(0,-1)}).then(function(r){
364
+ chatHistory.pop();
365
+ if(r&&r.response){chatHistory.push({role:'assistant',content:r.response})}
366
+ else if(r&&r.error){chatHistory.push({role:'assistant',content:'Error: '+r.error})}
367
+ else{chatHistory.push({role:'assistant',content:'Error: no response from server'})}
368
+ saveChatToStorage();renderMessages();
369
+ // Refresh ALL data after any tool execution
370
+ if(r&&((r.actions&&r.actions.length>0)||(r.toolResults&&r.toolResults.length>0))){
371
+ calEventsCache={};
372
+ contactsData=null;
373
+ notesData=null;
374
+ driveData=null;
375
+ onedriveData=null;
376
+ mstodoData=null;
377
+ loadDash().then(function(){render()}).catch(function(){});
601
378
  }
602
- pump();
603
- }).catch(function(e){endStreaming();if(e.name!=='AbortError'){chatHistory[streamIdx].content='Error: '+e.message;renderMessages();}});
379
+ });
604
380
  }
605
381
 
606
382
  // ---- TASKS ----
@@ -670,42 +446,17 @@ function refreshPlan(){
670
446
 
671
447
  // ---- EMAILS ----
672
448
  function renderEmails(el){
673
- if(!dashLoaded.emails){el.innerHTML=loadingHTML('emails');return}
674
449
  var e=dash.emails;
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){
450
+ if(e.length===0){el.innerHTML='<div class="card" style="text-align:center;color:var(--dim);padding:30px">No unread emails</div>';return}
451
+ var h='';e.forEach(function(x){
682
452
  var unreadStyle=x.isUnread?'border-left:3px solid var(--green);font-weight:700':'border-left:3px solid transparent;opacity:0.7';
683
453
  h+='<div class="card email" style="cursor:pointer;'+unreadStyle+'" onclick="openEmail(\\x27'+esc(x.id)+'\\x27)"><div class="email__header"><span class="email__from">'+esc(x.from)+'</span><span class="email__date">'+esc(x.date)+(x.isUnread?' <span style="color:var(--green);font-size:9px">NEW</span>':'')+'</span></div><div class="email__subject">'+esc(x.subject)+'</div><div class="email__snippet" style="font-weight:400">'+esc((x.snippet||'').slice(0,150))+'</div></div>';
684
454
  });
685
455
  el.innerHTML=h;
686
456
  }
687
- function markAllEmailsRead(){
688
- apiPost('/api/email/mark-all-read',{}).then(function(r){
689
- if(r&&r.ok){
690
- dash.emails.forEach(function(e){e.isUnread=false});
691
- updateBadges();
692
- renderEmails(document.getElementById('content'));
693
- showToast('success','All Read','Marked '+( r.count||0)+' emails as read');
694
- }else{
695
- showToast('error','Error',r&&r.error||'Failed');
696
- }
697
- });
698
- }
699
457
  var openEmailId=null;
700
458
  function openEmail(id){
701
459
  openEmailId=id;
702
- // Mark as read locally + on server
703
- var emailObj=dash.emails.find(function(e){return e.id===id});
704
- if(emailObj&&emailObj.isUnread){
705
- emailObj.isUnread=false;
706
- updateBadges();
707
- apiPost('/api/email/mark-read',{messageId:id}).catch(function(){});
708
- }
709
460
  var el=document.getElementById('content');
710
461
  el.innerHTML='<div style="text-align:center;padding:40px"><div class="spinner"></div><div style="color:var(--dim)">Loading email...</div></div>';
711
462
  apiPost('/api/email/read',{messageId:id}).then(function(r){
@@ -1354,68 +1105,22 @@ function renderAgents(el){
1354
1105
 
1355
1106
  var filtered=agentFilter?agentsList.filter(function(a){return a.category===agentFilter}):agentsList;
1356
1107
 
1357
- h+='<div style="margin-bottom:10px"><button class="btn btn--primary" style="font-size:11px" onclick="showCreateAgentForm()">+ Create Agent</button></div>';
1358
1108
  h+='<div class="agents-grid">';
1359
1109
  filtered.forEach(function(a){
1360
1110
  var name=a.name||a.agentName;
1361
1111
  var display=a.displayName||name;
1362
1112
  var icon=AGENT_ICONS[name.toLowerCase()]||'\\u{1F916}';
1363
1113
  var desc=AGENT_DESCRIPTIONS[name.toLowerCase()]||a.tagline||a.description||'';
1364
- var isCustom=a.category==='custom';
1365
- h+='<div class="card agent-card" style="position:relative">'+
1366
- '<div style="flex:1;cursor:pointer" onclick="openAgent(\\''+esc(name)+'\\',\\''+esc(display)+'\\')">'+
1367
- '<div style="display:flex;align-items:center;gap:8px">'+
1114
+ h+='<div class="card agent-card" onclick="openAgent(\\''+esc(name)+'\\',\\''+esc(display)+'\\')">'+
1368
1115
  '<div class="agent-card__icon">'+icon+'</div>'+
1369
1116
  '<div class="agent-card__body"><div class="agent-card__name">'+esc(display)+'</div>'+
1370
1117
  '<div class="agent-card__tagline">'+esc(desc)+'</div></div>'+
1371
- '</div></div>'+
1372
- '<div style="display:flex;gap:4px;flex-shrink:0">'+
1373
- '<button onclick="editAgent(\\''+esc(name)+'\\')" style="background:none;border:none;cursor:pointer;font-size:12px;padding:2px" title="Edit">\\u{270F}\\u{FE0F}</button>'+
1374
- '<button onclick="deleteAgent(\\''+esc(name)+'\\')" style="background:none;border:none;cursor:pointer;font-size:12px;padding:2px;color:#f44" title="Delete">\\u{1F5D1}</button>'+
1375
- '</div>'+
1376
1118
  '</div>';
1377
1119
  });
1378
1120
  h+='</div>';
1379
1121
  el.innerHTML=h;
1380
1122
  }
1381
1123
  var agentFilter=null;
1382
-
1383
- function showCreateAgentForm(){
1384
- var name=prompt('Agent name (lowercase, no spaces):');
1385
- if(!name)return;
1386
- name=name.toLowerCase().replace(/[^a-z0-9_-]/g,'');
1387
- if(!name)return;
1388
- var tagline=prompt('Tagline (short description):');
1389
- if(!tagline)return;
1390
- var sysPrompt=prompt('System prompt (agent personality & instructions):');
1391
- if(!sysPrompt)return;
1392
- apiPost('/api/agents',{name:name,tagline:tagline,systemPrompt:sysPrompt}).then(function(r){
1393
- if(r&&r.ok){showToast('success','Agent Created',name.toUpperCase()+' is ready to use');loadAgents().then(function(){renderAgents(document.getElementById('content'))});}
1394
- else{alert('Error: '+(r&&r.error||'Unknown'));}
1395
- });
1396
- }
1397
-
1398
- function editAgent(name){
1399
- fetch('/api/agents/'+name).then(function(r){return r.json()}).then(function(data){
1400
- var newTagline=prompt('Tagline:',data.tagline||'');
1401
- if(newTagline===null)return;
1402
- var newPrompt=prompt('System prompt:',data.systemPrompt||'');
1403
- if(newPrompt===null)return;
1404
- 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){
1405
- if(r&&r.ok){showToast('success','Agent Updated',name.toUpperCase()+' updated');loadAgents().then(function(){renderAgents(document.getElementById('content'))});}
1406
- else{alert('Error: '+(r&&r.error||'Unknown'));}
1407
- });
1408
- });
1409
- }
1410
-
1411
- function deleteAgent(name){
1412
- if(!confirm('Delete agent "'+name+'"? This cannot be undone.'))return;
1413
- fetch('/api/agents/'+name,{method:'DELETE'}).then(function(r){return r.json()}).then(function(r){
1414
- if(r&&r.ok){showToast('success','Agent Deleted',name+' removed');loadAgents().then(function(){renderAgents(document.getElementById('content'))});}
1415
- else{alert('Error: '+(r&&r.error||'Unknown'));}
1416
- });
1417
- }
1418
-
1419
1124
  function openAgent(name,display){
1420
1125
  selectedAgent=name;
1421
1126
  attachedFileContent=null;attachedFileName=null;
@@ -1498,26 +1203,9 @@ function renderSettings(el) {
1498
1203
  ['summary-time', 'Summary Time', '18:00'],
1499
1204
  ['meeting-alert', 'Meeting Alert (minutes)', '30'],
1500
1205
  ]) +
1501
- '<div class="card" style="margin-top:16px"><div class="card__title">Google Account</div>' +
1502
- '<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>' +
1503
- (settingsData.hasGoogle ? '<div style="color:var(--green);font-size:12px;margin-bottom:8px">\\u2705 Connected</div>' : '') +
1504
- '<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>' +
1505
- '<div id="googleStatus" style="margin-top:8px;font-size:10px;color:var(--dim)"></div>' +
1506
- '</div>' +
1507
1206
  '</div>';
1508
1207
  }
1509
1208
 
1510
- function connectGoogle() {
1511
- var s = document.getElementById('googleStatus');
1512
- if (s) s.textContent = 'Starting Google sign-in...';
1513
- apiPost('/api/google/auth', {}).then(function(r) {
1514
- if (s) s.textContent = r.message || 'Check the browser window that opened.';
1515
- if (s) s.style.color = 'var(--green)';
1516
- }).catch(function(e) {
1517
- if (s) { s.textContent = 'Error: ' + e.message; s.style.color = 'var(--red)'; }
1518
- });
1519
- }
1520
-
1521
1209
  function settingsSection(id, title, desc, fields) {
1522
1210
  var h = '<form class="card" style="margin-bottom:16px" id="settings-' + id + '" onsubmit="event.preventDefault();saveSettingsSection(\\x27' + id + '\\x27)">' +
1523
1211
  '<div class="card__title" style="color:var(--green);font-size:14px;margin-bottom:4px">' + esc(title) + '</div>' +
@@ -1738,6 +1426,61 @@ function handleDaemonEvent(msg) {
1738
1426
  showToast('plan', 'Daily Plan Ready', 'Your plan for ' + msg.data.date + ' has been generated.', 10000);
1739
1427
  if (currentView === 'plan') renderPlan(document.getElementById('content'));
1740
1428
  break;
1429
+
1430
+ case 'cron_result':
1431
+ showToast('cron', 'Scheduled Task', msg.data.prompt + '\\n' + (msg.data.result || '').slice(0, 100), 8000);
1432
+ break;
1433
+ }
1434
+ }
1435
+
1436
+ // ---- CANVAS PANEL ----
1437
+ var canvasVisible = false;
1438
+ function showCanvas(html, title) {
1439
+ var panel = document.getElementById('canvasPanel');
1440
+ if (!panel) {
1441
+ panel = document.createElement('div');
1442
+ panel.id = 'canvasPanel';
1443
+ 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)';
1444
+ document.body.appendChild(panel);
1445
+ }
1446
+ 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)">' +
1447
+ '<span style="font-family:var(--mono);color:var(--green);font-size:12px">' + (title || 'Canvas') + '</span>' +
1448
+ '<div><button onclick="toggleCanvasSize()" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:14px;margin-right:8px" title="Resize">&#x2922;</button>' +
1449
+ '<button onclick="closeCanvas()" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:14px" title="Close">&times;</button></div></div>';
1450
+ var iframe = '<iframe id="canvasFrame" sandbox="allow-scripts allow-same-origin" style="flex:1;border:none;background:#fff;min-height:300px;width:100%"></iframe>';
1451
+ panel.innerHTML = header + iframe;
1452
+ panel.style.display = 'flex';
1453
+ canvasVisible = true;
1454
+
1455
+ // Write HTML to iframe
1456
+ var frame = document.getElementById('canvasFrame');
1457
+ if (frame) {
1458
+ var doc = frame.contentDocument || frame.contentWindow.document;
1459
+ doc.open();
1460
+ doc.write(html);
1461
+ doc.close();
1462
+ }
1463
+ }
1464
+
1465
+ function closeCanvas() {
1466
+ var panel = document.getElementById('canvasPanel');
1467
+ if (panel) { panel.style.display = 'none'; canvasVisible = false; }
1468
+ }
1469
+
1470
+ function toggleCanvasSize() {
1471
+ var panel = document.getElementById('canvasPanel');
1472
+ if (!panel) return;
1473
+ if (panel.style.width === '480px') {
1474
+ panel.style.width = '80vw';
1475
+ panel.style.height = '80vh';
1476
+ panel.style.top = '10vh';
1477
+ panel.style.right = '10vw';
1478
+ } else {
1479
+ panel.style.width = '480px';
1480
+ panel.style.height = '';
1481
+ panel.style.maxHeight = 'calc(100vh - 80px)';
1482
+ panel.style.top = '60px';
1483
+ panel.style.right = '12px';
1741
1484
  }
1742
1485
  }
1743
1486
 
@@ -1843,13 +1586,8 @@ init();
1843
1586
  <div class="app">
1844
1587
  <nav class="sidebar" id="sidebar">
1845
1588
  <div class="sidebar__brand">
1846
- <div style="display:flex;align-items:center;gap:8px">
1847
- <div class="sidebar__brand-name">NHA</div>
1848
- <span id="wsIndicator" style="color:var(--dim);font-size:8px" title="Daemon WebSocket">&#9679;</span>
1849
- <span style="font-size:9px;color:var(--dim)">v${VERSION}</span>
1850
- </div>
1851
- <div id="sidebarPageTitle" style="font-size:11px;color:var(--bright);margin-top:4px;font-weight:600">Dashboard</div>
1852
- <div class="sidebar__brand-sub" id="clock"></div>
1589
+ <div class="sidebar__brand-name">NHA</div>
1590
+ <div class="sidebar__brand-sub">Operations Console</div>
1853
1591
  </div>
1854
1592
  <div class="sidebar__section">
1855
1593
  <div class="sidebar__label">Overview</div>
@@ -1886,30 +1624,17 @@ init();
1886
1624
  <div class="sidebar__label">Config</div>
1887
1625
  <div class="nav-item" data-view="settings" onclick="switchView('settings')"><span class="nav-item__icon">&#9881;</span> Settings</div>
1888
1626
  </div>
1889
- <div class="sidebar__section">
1890
- <div class="sidebar__label">Help</div>
1891
- <a href="https://nothumanallowed.com/docs" target="_blank" class="nav-item" style="text-decoration:none"><span class="nav-item__icon">&#128214;</span> Documentation</a>
1892
- <a href="https://nothumanallowed.com/docs/agents" target="_blank" class="nav-item" style="text-decoration:none"><span class="nav-item__icon">&#129302;</span> Agents Guide</a>
1893
- <a href="https://nothumanallowed.com/docs/mobile" target="_blank" class="nav-item" style="text-decoration:none"><span class="nav-item__icon">&#128241;</span> Mobile App</a>
1894
- </div>
1895
- <div style="padding:12px 16px;margin-top:auto;border-top:1px solid var(--border);font-size:10px;color:var(--dim)">nothumanallowed.com</div>
1627
+ <div style="padding:12px 16px;margin-top:auto;border-top:1px solid var(--border);font-size:10px;color:var(--dim)">NHA v${VERSION}</div>
1896
1628
  </nav>
1897
1629
 
1898
- <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">&#9776;</button>
1630
+ <div class="header">
1631
+ <button class="header__burger" onclick="toggleSidebar()">&#9776;</button>
1632
+ <span class="header__title" id="headerTitle">Dashboard</span>
1633
+ <span id="wsIndicator" style="color:var(--dim);font-size:8px;margin-right:4px" title="Daemon WebSocket">&#9679;</span>
1634
+ <span class="header__clock" id="clock"></span>
1635
+ </div>
1899
1636
 
1900
1637
  <div class="content" id="content"></div>
1901
-
1902
- <div class="browser-viewer" id="browserViewer">
1903
- <div class="browser-viewer__header">
1904
- <span class="browser-viewer__dot"></span>
1905
- <span class="browser-viewer__title" id="bvTitle">Browser</span>
1906
- <button class="browser-viewer__close" onclick="closeBrowserViewer()">&times;</button>
1907
- </div>
1908
- <div class="browser-viewer__frame" id="bvFrame">
1909
- <span style="color:var(--dim);font-size:11px">Waiting...</span>
1910
- </div>
1911
- <div class="browser-viewer__status" id="bvStatus">Idle</div>
1912
- </div>
1913
1638
  </div>
1914
1639
 
1915
1640
  <div class="modal-overlay" id="agentModal">