nothumanallowed 9.5.2 → 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
-
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
203
 
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?page=0&pageSize=25').then(function(r){dash.emails=(r&&r.emails)||[];dash._emailHasMore=r&&r.hasMore;dashLoaded.emails=true;emailPage=0;updateBadges();render()});
254
+ return Promise.all([apiGet('/api/status'),apiGet('/api/emails'),apiGet('/api/calendar'),apiGet('/api/tasks')]).then(function(r){
255
+ dash.status=r[0];dash.emails=(r[1]&&r[1].emails)||[];dash.events=(r[2]&&r[2].events)||[];dash.tasks=(r[3]&&r[3].tasks)||[];
256
+ updateBadges();
257
+ });
322
258
  }
323
259
  function loadAgents(){return apiGet('/api/agents').then(function(r){agentsList=(r&&r.agents)||[]})}
324
260
  function updateBadges(){
@@ -332,7 +268,6 @@ function updateBadges(){
332
268
  // ---- HELPERS ----
333
269
  function esc(s){return s?String(s).replace(/&/g,'&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;}
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(){});
520
378
  }
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();}});
601
- }
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,64 +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
- // Load More button
686
- if(dash._emailHasMore!==false){
687
- h+='<button id="loadMoreEmails" onclick="loadMoreEmails()" style="width:100%;padding:12px;margin-top:8px;background:var(--bg3);border:1px solid var(--border);border-radius:var(--r);color:var(--cyan);font-family:var(--font);font-size:12px;cursor:pointer;font-weight:700">Load More Emails</button>';
688
- }
689
455
  el.innerHTML=h;
690
456
  }
691
- var emailPage=0;
692
- function loadMoreEmails(){
693
- var btn=document.getElementById('loadMoreEmails');
694
- if(btn){btn.textContent='Loading...';btn.disabled=true;}
695
- emailPage++;
696
- apiGet('/api/emails?page='+emailPage+'&pageSize=25').then(function(r){
697
- if(r&&r.emails){
698
- for(var i=0;i<r.emails.length;i++){
699
- if(!dash.emails.find(function(e){return e.id===r.emails[i].id})){
700
- dash.emails.push(r.emails[i]);
701
- }
702
- }
703
- dash._emailHasMore=r.hasMore;
704
- updateBadges();
705
- render();
706
- }
707
- });
708
- }
709
- function markAllEmailsRead(){
710
- apiPost('/api/email/mark-all-read',{}).then(function(r){
711
- if(r&&r.ok){
712
- dash.emails.forEach(function(e){e.isUnread=false});
713
- updateBadges();
714
- renderEmails(document.getElementById('content'));
715
- showToast('success','All Read','Marked '+( r.count||0)+' emails as read');
716
- }else{
717
- showToast('error','Error',r&&r.error||'Failed');
718
- }
719
- });
720
- }
721
457
  var openEmailId=null;
722
458
  function openEmail(id){
723
459
  openEmailId=id;
724
- // Mark as read locally + on server
725
- var emailObj=dash.emails.find(function(e){return e.id===id});
726
- if(emailObj&&emailObj.isUnread){
727
- emailObj.isUnread=false;
728
- updateBadges();
729
- apiPost('/api/email/mark-read',{messageId:id}).catch(function(){});
730
- }
731
460
  var el=document.getElementById('content');
732
461
  el.innerHTML='<div style="text-align:center;padding:40px"><div class="spinner"></div><div style="color:var(--dim)">Loading email...</div></div>';
733
462
  apiPost('/api/email/read',{messageId:id}).then(function(r){
@@ -1376,68 +1105,22 @@ function renderAgents(el){
1376
1105
 
1377
1106
  var filtered=agentFilter?agentsList.filter(function(a){return a.category===agentFilter}):agentsList;
1378
1107
 
1379
- h+='<div style="margin-bottom:10px"><button class="btn btn--primary" style="font-size:11px" onclick="showCreateAgentForm()">+ Create Agent</button></div>';
1380
1108
  h+='<div class="agents-grid">';
1381
1109
  filtered.forEach(function(a){
1382
1110
  var name=a.name||a.agentName;
1383
1111
  var display=a.displayName||name;
1384
1112
  var icon=AGENT_ICONS[name.toLowerCase()]||'\\u{1F916}';
1385
1113
  var desc=AGENT_DESCRIPTIONS[name.toLowerCase()]||a.tagline||a.description||'';
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">'+
1114
+ h+='<div class="card agent-card" onclick="openAgent(\\''+esc(name)+'\\',\\''+esc(display)+'\\')">'+
1390
1115
  '<div class="agent-card__icon">'+icon+'</div>'+
1391
1116
  '<div class="agent-card__body"><div class="agent-card__name">'+esc(display)+'</div>'+
1392
1117
  '<div class="agent-card__tagline">'+esc(desc)+'</div></div>'+
1393
- '</div></div>'+
1394
- '<div style="display:flex;gap:4px;flex-shrink:0">'+
1395
- '<button onclick="editAgent(\\''+esc(name)+'\\')" style="background:none;border:none;cursor:pointer;font-size:12px;padding:2px" title="Edit">\\u{270F}\\u{FE0F}</button>'+
1396
- '<button onclick="deleteAgent(\\''+esc(name)+'\\')" style="background:none;border:none;cursor:pointer;font-size:12px;padding:2px;color:#f44" title="Delete">\\u{1F5D1}</button>'+
1397
- '</div>'+
1398
1118
  '</div>';
1399
1119
  });
1400
1120
  h+='</div>';
1401
1121
  el.innerHTML=h;
1402
1122
  }
1403
1123
  var agentFilter=null;
1404
-
1405
- function showCreateAgentForm(){
1406
- var name=prompt('Agent name (lowercase, no spaces):');
1407
- if(!name)return;
1408
- name=name.toLowerCase().replace(/[^a-z0-9_-]/g,'');
1409
- if(!name)return;
1410
- var tagline=prompt('Tagline (short description):');
1411
- if(!tagline)return;
1412
- var sysPrompt=prompt('System prompt (agent personality & instructions):');
1413
- if(!sysPrompt)return;
1414
- apiPost('/api/agents',{name:name,tagline:tagline,systemPrompt:sysPrompt}).then(function(r){
1415
- if(r&&r.ok){showToast('success','Agent Created',name.toUpperCase()+' is ready to use');loadAgents().then(function(){renderAgents(document.getElementById('content'))});}
1416
- else{alert('Error: '+(r&&r.error||'Unknown'));}
1417
- });
1418
- }
1419
-
1420
- function editAgent(name){
1421
- fetch('/api/agents/'+name).then(function(r){return r.json()}).then(function(data){
1422
- var newTagline=prompt('Tagline:',data.tagline||'');
1423
- if(newTagline===null)return;
1424
- var newPrompt=prompt('System prompt:',data.systemPrompt||'');
1425
- if(newPrompt===null)return;
1426
- fetch('/api/agents/'+name,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({tagline:newTagline,systemPrompt:newPrompt,category:data.category||'custom'})}).then(function(r){return r.json()}).then(function(r){
1427
- if(r&&r.ok){showToast('success','Agent Updated',name.toUpperCase()+' updated');loadAgents().then(function(){renderAgents(document.getElementById('content'))});}
1428
- else{alert('Error: '+(r&&r.error||'Unknown'));}
1429
- });
1430
- });
1431
- }
1432
-
1433
- function deleteAgent(name){
1434
- if(!confirm('Delete agent "'+name+'"? This cannot be undone.'))return;
1435
- fetch('/api/agents/'+name,{method:'DELETE'}).then(function(r){return r.json()}).then(function(r){
1436
- if(r&&r.ok){showToast('success','Agent Deleted',name+' removed');loadAgents().then(function(){renderAgents(document.getElementById('content'))});}
1437
- else{alert('Error: '+(r&&r.error||'Unknown'));}
1438
- });
1439
- }
1440
-
1441
1124
  function openAgent(name,display){
1442
1125
  selectedAgent=name;
1443
1126
  attachedFileContent=null;attachedFileName=null;
@@ -1520,26 +1203,9 @@ function renderSettings(el) {
1520
1203
  ['summary-time', 'Summary Time', '18:00'],
1521
1204
  ['meeting-alert', 'Meeting Alert (minutes)', '30'],
1522
1205
  ]) +
1523
- '<div class="card" style="margin-top:16px"><div class="card__title">Google Account</div>' +
1524
- '<div style="font-size:11px;color:var(--dim);margin-bottom:8px">Connect Gmail, Calendar, Drive, Contacts, and Tasks. Opens a browser window for Google sign-in.</div>' +
1525
- (settingsData.hasGoogle ? '<div style="color:var(--green);font-size:12px;margin-bottom:8px">\\u2705 Connected</div>' : '') +
1526
- '<button onclick="connectGoogle()" style="background:var(--green3);color:var(--bg);padding:8px 20px;border-radius:var(--r);font-weight:700;font-size:12px;cursor:pointer;border:none">' + (settingsData.hasGoogle ? 'Reconnect Google' : 'Connect Google') + '</button>' +
1527
- '<div id="googleStatus" style="margin-top:8px;font-size:10px;color:var(--dim)"></div>' +
1528
- '</div>' +
1529
1206
  '</div>';
1530
1207
  }
1531
1208
 
1532
- function connectGoogle() {
1533
- var s = document.getElementById('googleStatus');
1534
- if (s) s.textContent = 'Starting Google sign-in...';
1535
- apiPost('/api/google/auth', {}).then(function(r) {
1536
- if (s) s.textContent = r.message || 'Check the browser window that opened.';
1537
- if (s) s.style.color = 'var(--green)';
1538
- }).catch(function(e) {
1539
- if (s) { s.textContent = 'Error: ' + e.message; s.style.color = 'var(--red)'; }
1540
- });
1541
- }
1542
-
1543
1209
  function settingsSection(id, title, desc, fields) {
1544
1210
  var h = '<form class="card" style="margin-bottom:16px" id="settings-' + id + '" onsubmit="event.preventDefault();saveSettingsSection(\\x27' + id + '\\x27)">' +
1545
1211
  '<div class="card__title" style="color:var(--green);font-size:14px;margin-bottom:4px">' + esc(title) + '</div>' +
@@ -1760,6 +1426,61 @@ function handleDaemonEvent(msg) {
1760
1426
  showToast('plan', 'Daily Plan Ready', 'Your plan for ' + msg.data.date + ' has been generated.', 10000);
1761
1427
  if (currentView === 'plan') renderPlan(document.getElementById('content'));
1762
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';
1763
1484
  }
1764
1485
  }
1765
1486
 
@@ -1865,13 +1586,8 @@ init();
1865
1586
  <div class="app">
1866
1587
  <nav class="sidebar" id="sidebar">
1867
1588
  <div class="sidebar__brand">
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">&#9679;</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>
1589
+ <div class="sidebar__brand-name">NHA</div>
1590
+ <div class="sidebar__brand-sub">Operations Console</div>
1875
1591
  </div>
1876
1592
  <div class="sidebar__section">
1877
1593
  <div class="sidebar__label">Overview</div>
@@ -1908,30 +1624,17 @@ init();
1908
1624
  <div class="sidebar__label">Config</div>
1909
1625
  <div class="nav-item" data-view="settings" onclick="switchView('settings')"><span class="nav-item__icon">&#9881;</span> Settings</div>
1910
1626
  </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">&#128214;</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">&#129302;</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">&#128241;</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>
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>
1918
1628
  </nav>
1919
1629
 
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">&#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>
1921
1636
 
1922
1637
  <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()">&times;</button>
1929
- </div>
1930
- <div class="browser-viewer__frame" id="bvFrame">
1931
- <span style="color:var(--dim);font-size:11px">Waiting...</span>
1932
- </div>
1933
- <div class="browser-viewer__status" id="bvStatus">Idle</div>
1934
- </div>
1935
1638
  </div>
1936
1639
 
1937
1640
  <div class="modal-overlay" id="agentModal">