mirror of
https://github.com/myronblair/jarvis
synced 2026-06-30 17:50:23 -05:00
feat: voice agent commands, cross-session memory, proactive reminders
- Voice commands (#3): say "restart the homebridge agent" or "restart mediastack agent" → queues restart_service to that agent; lists options if no hostname matched; 6 KB intents added - Persistent context (#4): chat.php now loads last 6 turns from most recent prior session before current session history, giving JARVIS memory across page reloads - Proactive reminders (#5): 3s after login, auto-announces overdue tasks / tasks due today / upcoming appointments; 5-min interval checks for appointments starting within 15 min and speaks alert once Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+54
-2
@@ -72,13 +72,27 @@ JarvisDB::insert(
|
||||
[$sessionId, 'user', $message]
|
||||
);
|
||||
|
||||
// Conversation history
|
||||
// Conversation history — current session
|
||||
$history = JarvisDB::query(
|
||||
'SELECT role, content FROM conversations WHERE session_id=? ORDER BY created_at DESC LIMIT 10',
|
||||
[$sessionId]
|
||||
);
|
||||
) ?? [];
|
||||
$history = array_reverse($history);
|
||||
|
||||
// Cross-session memory: last 6 turns from the most recent prior session
|
||||
$priorSession = JarvisDB::query(
|
||||
"SELECT session_id FROM conversations WHERE session_id != ? AND role='user'
|
||||
ORDER BY created_at DESC LIMIT 1",
|
||||
[$sessionId]
|
||||
);
|
||||
if ($priorSession && !empty($priorSession[0]['session_id'])) {
|
||||
$priorHistory = JarvisDB::query(
|
||||
'SELECT role, content FROM conversations WHERE session_id=? ORDER BY created_at DESC LIMIT 6',
|
||||
[$priorSession[0]['session_id']]
|
||||
) ?? [];
|
||||
$history = array_merge(array_reverse($priorHistory), $history);
|
||||
}
|
||||
|
||||
$reply = null;
|
||||
$source = 'unknown';
|
||||
|
||||
@@ -1633,6 +1647,44 @@ if (!$reply) {
|
||||
if ($matched && $matched['action'] === 'action') {
|
||||
switch ($matched['intent']) {
|
||||
|
||||
case 'restart_agent':
|
||||
// Extract target hostname from message
|
||||
$msgLow = strtolower($message);
|
||||
$agentMap = [
|
||||
'homebridge' => 'homebridge_b57cbaea',
|
||||
'jellyfin' => 'jellyfin_7e386833',
|
||||
'networkbackup' => 'networkbackup_NetworkB',
|
||||
'network backup'=> 'networkbackup_NetworkB',
|
||||
'novacpx' => 'novacpx_e3b07264',
|
||||
'nova' => 'novacpx_e3b07264',
|
||||
'mediastack' => 'MediaStack_2c00b1b8',
|
||||
'media stack' => 'MediaStack_2c00b1b8',
|
||||
'homeassistant' => 'homeassistant_ha',
|
||||
'home assistant'=> 'homeassistant_ha',
|
||||
];
|
||||
$targetAgentId = null;
|
||||
$targetName = null;
|
||||
foreach ($agentMap as $keyword => $agentId) {
|
||||
if (str_contains($msgLow, $keyword)) {
|
||||
$targetAgentId = $agentId;
|
||||
$targetName = ucfirst($keyword);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($targetAgentId) {
|
||||
JarvisDB::insert(
|
||||
"INSERT INTO agent_commands (agent_id, command_type, command_data, status, created_at)
|
||||
VALUES (?, 'restart_service', ?, 'pending', NOW())",
|
||||
[$targetAgentId, json_encode(['service' => 'jarvis-agent'])]
|
||||
);
|
||||
$reply = "Restart command sent to the {$targetName} agent, {$userAddr}. It should come back online within 15 seconds.";
|
||||
} else {
|
||||
// Fall back to listing restartable agents
|
||||
$reply = "Which agent should I restart, {$userAddr}? I can restart: HomeAssistant, Homebridge, Jellyfin, MediaStack, NetworkBackup, or NovaCPX.";
|
||||
}
|
||||
$source = 'intent:restart_agent';
|
||||
break;
|
||||
|
||||
case 'focus_mode':
|
||||
$uiAction = 'focus_mode';
|
||||
$reply = "Focus mode activated, {$userAddr}. Side panels hidden.";
|
||||
|
||||
@@ -2584,6 +2584,8 @@ function showApp(name, greeting, silent = false) {
|
||||
loadWeather();
|
||||
loadNews();
|
||||
initMobile();
|
||||
setTimeout(checkPlannerReminder, 3000);
|
||||
setInterval(checkUpcomingAppts, 300000);
|
||||
// Guardian Mode — badge refresh + proactive chat
|
||||
setTimeout(() => {
|
||||
_refreshGuardianBadge();
|
||||
@@ -3157,6 +3159,52 @@ async function toggleHA(entityId, domain, currentState) {
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
// ── PROACTIVE REMINDERS ──────────────────────────────────────────────────────
|
||||
let _reminderShown = false;
|
||||
async function checkPlannerReminder() {
|
||||
if (_reminderShown || sessionStorage.getItem('reminderShown')) return;
|
||||
_reminderShown = true;
|
||||
sessionStorage.setItem('reminderShown', '1');
|
||||
const d = await api('planner/today').catch(() => null);
|
||||
if (!d) return;
|
||||
const tasks = [...(d.tasks_overdue||[]), ...(d.tasks_today||[])];
|
||||
const appts = d.appts_today || [];
|
||||
const overdue = d.tasks_overdue?.length || 0;
|
||||
if (!tasks.length && !appts.length) return;
|
||||
|
||||
const parts = [];
|
||||
if (overdue) parts.push(overdue + ' overdue task' + (overdue > 1 ? 's' : ''));
|
||||
if (tasks.length - overdue > 0) parts.push((tasks.length - overdue) + ' task' + (tasks.length - overdue > 1 ? 's' : '') + ' due today');
|
||||
if (appts.length) {
|
||||
const nextAppt = appts[0];
|
||||
const t = nextAppt.start_at ? new Date(nextAppt.start_at).toLocaleTimeString('en-US',{hour:'2-digit',minute:'2-digit',hour12:true}) : '';
|
||||
parts.push((t ? 'appointment at ' + t : appts.length + ' appointment' + (appts.length > 1 ? 's' : '') + ' today'));
|
||||
}
|
||||
|
||||
const msg = 'Heads up, ' + (sessionUser||'Sir') + '. You have ' + parts.join(' and ') + '.';
|
||||
addMessage('jarvis', msg);
|
||||
if (typeof speak === 'function' && isVoiceActive) speak(msg);
|
||||
}
|
||||
|
||||
// Check for upcoming appointments (fires every 5 min after load)
|
||||
let _apptAlerted = new Set();
|
||||
async function checkUpcomingAppts() {
|
||||
const d = await api('planner/today').catch(() => null);
|
||||
if (!d) return;
|
||||
const now = Date.now();
|
||||
for (const a of (d.appts_today||[])) {
|
||||
if (!a.start_at || _apptAlerted.has(a.id)) continue;
|
||||
const start = new Date(a.start_at).getTime();
|
||||
const minsUntil = (start - now) / 60000;
|
||||
if (minsUntil > 0 && minsUntil <= 15) {
|
||||
_apptAlerted.add(a.id);
|
||||
const msg = 'Reminder: ' + a.title + ' starts in ' + Math.round(minsUntil) + ' minutes' + (a.location ? ' at ' + a.location : '') + '.';
|
||||
addMessage('jarvis', msg);
|
||||
if (typeof speak === 'function' && isVoiceActive) speak(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── PLANNER SUMMARY (top bar badge only) ─────────────────────────────────
|
||||
async function loadPlannerSummary() {
|
||||
const d = await api('planner/today');
|
||||
|
||||
Reference in New Issue
Block a user