@@ -1713,6 +1756,93 @@ function _drawTopo() {
})();
+// ── SLEEP MODE ────────────────────────────────────────────────────────────────
+var isAsleep = false;
+var _sleepRefreshTimer = null;
+
+var SLEEP_CMDS = /\b(good\s*night|go\s*to\s*sleep|sleep\s*(mode|now)?|shut\s*(down|off)\s*(jarvis|for\s*the\s*night)?|offline|stand\s*by|power\s*down|hibernate|signing\s*off)\b/i;
+
+function enterSleepMode() {
+ if (isAsleep) return;
+ isAsleep = true;
+
+ // Pause voice mode
+ voiceMode = false;
+ voiceMuted = false;
+ updateMicBtn();
+
+ // Slow or pause the refresh loop — keep mic alive for wake word
+ clearInterval(refreshTimer);
+ refreshTimer = null;
+ // Light polling every 2 min just to stay alive
+ _sleepRefreshTimer = setInterval(function() {
+ // heartbeat only — keep session alive without hammering APIs
+ try { fetch('/api/auth', {method:'GET', headers:{'Authorization':'Bearer '+sessionToken}}); } catch(e) {}
+ }, 120000);
+
+ // Dim the UI
+ var app = document.getElementById('app');
+ if (app) app.classList.add('sleeping');
+
+ // Flash title to confirm
+ document.title = 'JARVIS — STANDBY';
+
+ addMessage('jarvis', 'Understood. Going offline. Say "wake up JARVIS" when you need me.');
+}
+
+function wakeFromSleep() {
+ if (!isAsleep) return;
+ isAsleep = false;
+
+ // Restore full polling
+ clearInterval(_sleepRefreshTimer);
+ _sleepRefreshTimer = null;
+ refreshAll();
+ refreshTimer = setInterval(refreshAll, 10000);
+
+ // Remove dim overlay
+ var app = document.getElementById('app');
+ if (app) app.classList.remove('sleeping');
+ document.title = 'JARVIS — Integrated Defense and Logistics System';
+
+ // Boot sequence
+ var topBar=document.getElementById('topBar'), lp=document.getElementById('leftPanel');
+ var rp=document.getElementById('rightPanel'), cp=document.getElementById('centerPanel');
+ [topBar,lp,rp,cp].forEach(function(el){if(el){el.style.opacity='0';}});
+ requestAnimationFrame(function(){
+ setTimeout(function(){if(topBar){topBar.style.opacity='';topBar.classList.add('boot-top');}},0);
+ setTimeout(function(){if(lp){lp.style.opacity='';lp.classList.add('boot-left');}},140);
+ setTimeout(function(){if(rp){rp.style.opacity='';rp.classList.add('boot-right');}},200);
+ setTimeout(function(){if(cp){cp.style.opacity='';cp.classList.add('boot-center');}},260);
+ setTimeout(function(){[topBar,lp,rp,cp].forEach(function(el){if(el)el.classList.remove('boot-top','boot-left','boot-right','boot-center');});},1400);
+ });
+
+ // Enter voice mode and greet
+ enterVoiceMode('wake');
+}
+
+function _focusWindow() {
+ // Attempt to bring browser window to front
+ try { window.focus(); } catch(e) {}
+
+ // Flash title to grab attention if tab is backgrounded
+ var _origTitle = 'JARVIS — Integrated Defense and Logistics System';
+ var _flashCount = 0;
+ var _titleFlash = setInterval(function() {
+ document.title = _flashCount % 2 === 0 ? '⚡ JARVIS — ONLINE' : _origTitle;
+ if (++_flashCount >= 8) { clearInterval(_titleFlash); document.title = _origTitle; }
+ }, 400);
+
+ // Browser Notification API — fires even when window is minimized
+ if ('Notification' in window) {
+ if (Notification.permission === 'granted') {
+ new Notification('JARVIS', { body: 'Wake word detected — system online.', icon: '/favicon.ico', tag: 'jarvis-wake', requireInteraction: false });
+ } else if (Notification.permission !== 'denied') {
+ Notification.requestPermission();
+ }
+ }
+}
+
// ── NETWORK MAP ──────────────────────────────────────────────────────────────
var _nmNodes=[], _nmEdges=[], _nmParticles=[], _nmRaf=null, _nmT=0, _nmHoverNode=null;
var _nmRot=[0,0,0,0,0];
@@ -2059,7 +2189,7 @@ function showApp(name, greeting, silent = false) {
refreshAll();
refreshTimer = setInterval(refreshAll, 10000); // every 10s
setInterval(() => {
- if (Date.now() - lastActivity > IDLE_RELOAD_MS) {
+ if (!isAsleep && Date.now() - lastActivity > IDLE_RELOAD_MS) {
sessionStorage.setItem('jarvis_autoreload', '1');
location.reload();
}
@@ -2085,6 +2215,10 @@ function showApp(name, greeting, silent = false) {
}
}, 12000);
startListening();
+ // Request notification permission for wake-word alerts when minimized
+ if ('Notification' in window && Notification.permission === 'default') {
+ setTimeout(() => Notification.requestPermission(), 3000);
+ }
loadNetwork();
loadHA();
checkAgentStatus();
@@ -2869,6 +3003,15 @@ async function sendMessage() {
// Local commands — no API round-trip
var t2 = text.toLowerCase();
+
+ // Sleep command
+ if (SLEEP_CMDS.test(t2)) {
+ input.value = '';
+ addMessage('user', text);
+ enterSleepMode();
+ return;
+ }
+
if (NM_OPEN_RE.test(t2)) {
input.value=''; addMessage('user',text);
addMessage('jarvis','Launching network topology display.');
@@ -2948,16 +3091,28 @@ function initVoice() {
const transcript = (e.results[0][0].transcript || '').trim();
if (!transcript) return;
const lc = transcript.toLowerCase();
+
+ // Sleeping: ONLY respond to master wake phrases
+ if (isAsleep) {
+ if (WAKE_PHRASES.some(p => lc.includes(p))) wakeFromSleep();
+ return;
+ }
+
if (!voiceMode) {
if (WAKE_PHRASES.some(p => lc.includes(p))) enterVoiceMode();
} else if (!voiceMuted) {
- // Awake — any speech is a command; strip optional "jarvis" prefix
voiceLastCmd = Date.now();
voiceActive = Date.now();
const cmd = lc.startsWith(CMD_PREFIX)
? transcript.substring(CMD_PREFIX.length).trim()
: transcript;
if (cmd) {
+ // Check for sleep command by voice
+ if (SLEEP_CMDS.test(cmd)) {
+ addMessage('user', transcript);
+ enterSleepMode();
+ return;
+ }
_showTranscript(cmd);
document.getElementById('textInput').value = cmd;
sendMessage();
@@ -2991,21 +3146,21 @@ function _showTranscript(text) {
if (el) { el.placeholder = '▶ ' + text.substring(0, 60); setTimeout(() => { el.placeholder = 'Enter command or speak to JARVIS...'; }, 3000); }
}
-function enterVoiceMode() {
+function enterVoiceMode(source) {
voiceMode = true;
voiceMuted = false;
voiceLastCmd = Date.now();
voiceActive = Date.now();
updateMicBtn();
- speak('Yes, ' + (sessionUser || 'Sir') + '?');
- // Bring window to front and maximize when JARVIS wakes
- try {
- window.focus();
- if (!document.fullscreenElement && window.screen) {
- window.moveTo(0, 0);
- window.resizeTo(window.screen.availWidth, window.screen.availHeight);
- }
- } catch(e) {}
+ // Focus/notify when woken from minimized or sleep
+ _focusWindow();
+ if (source === 'wake') {
+ const g = 'All systems back online, ' + (sessionUser || 'Sir') + '. Good to have you back.';
+ addMessage('jarvis', g);
+ speak(g);
+ } else {
+ speak('Yes, ' + (sessionUser || 'Sir') + '?');
+ }
}
function exitVoiceMode() {