mirror of
https://github.com/myronblair/jarvis
synced 2026-06-30 17:50:23 -05:00
feat: proactive alerts, Jellyfin control, agent sparklines, CCR roster fix
- Proactive alerts (#1): polls every 60s; baselines on load so old alerts are silent; speaks new critical/warning alerts aloud if voice active; adds chat bubble for all new alerts - Jellyfin control (#2): pause/stop/next/previous via voice — auto-detects active session; 12 KB intents; jellyfin.php control action uses Jellyfin General Commands API - Agent sparklines (#6): metrics.php returns 2h CPU+MEM history per agent; SVG polyline sparklines rendered in each agent card (cyan=CPU, green=MEM); non-blocking fetch so existing view shows instantly - Agent health CCR (#7): updated hourly cloud routine to current 13-agent roster, removed ollama-ai and alien-pc, added all active agents with correct IPs/IDs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1722,6 +1722,46 @@ if (!$reply) {
|
||||
$source = 'intent:network_scan';
|
||||
break;
|
||||
|
||||
case 'jellyfin_pause':
|
||||
case 'jellyfin_stop':
|
||||
case 'jellyfin_next':
|
||||
case 'jellyfin_previous': {
|
||||
$intentCmd = [
|
||||
'jellyfin_pause' => 'TogglePause',
|
||||
'jellyfin_stop' => 'Stop',
|
||||
'jellyfin_next' => 'NextTrack',
|
||||
'jellyfin_previous' => 'PreviousTrack',
|
||||
][$matched['intent']];
|
||||
// Get first active session
|
||||
$jfSessions = @json_decode(@file_get_contents(
|
||||
JELLYFIN_URL . '/Sessions?api_key=' . JELLYFIN_API_KEY, false,
|
||||
stream_context_create(['http' => ['timeout' => 4]])
|
||||
), true) ?? [];
|
||||
$activeSession = null;
|
||||
foreach ($jfSessions as $s) {
|
||||
if (isset($s['NowPlayingItem'])) { $activeSession = $s; break; }
|
||||
}
|
||||
if (!$activeSession) {
|
||||
$reply = "Nothing is currently playing on Jellyfin, {$userAddr}.";
|
||||
} else {
|
||||
$sid = $activeSession['Id'];
|
||||
$url = JELLYFIN_URL . '/Sessions/' . rawurlencode($sid) . '/Command/' . $intentCmd
|
||||
. '?api_key=' . JELLYFIN_API_KEY;
|
||||
$ctx = stream_context_create(['http' => ['method' => 'POST', 'timeout' => 5,
|
||||
'header' => 'Content-Type: application/json', 'content' => '{}', 'ignore_errors' => true]]);
|
||||
@file_get_contents($url, false, $ctx);
|
||||
$titles = [
|
||||
'TogglePause' => 'Playback toggled, {$userAddr}.',
|
||||
'Stop' => 'Playback stopped, {$userAddr}.',
|
||||
'NextTrack' => 'Skipping to next track, {$userAddr}.',
|
||||
'PreviousTrack' => 'Going back to previous, {$userAddr}.',
|
||||
];
|
||||
$reply = strtr($titles[$intentCmd], ['{$userAddr}' => $userAddr]);
|
||||
}
|
||||
$source = 'intent:jellyfin_control';
|
||||
break;
|
||||
}
|
||||
|
||||
case 'jellyfin_now_playing':
|
||||
$jSessions = @json_decode(@file_get_contents(
|
||||
JELLYFIN_URL . '/Sessions?api_key=' . JELLYFIN_API_KEY, false,
|
||||
|
||||
@@ -76,6 +76,34 @@ switch ($action) {
|
||||
echo json_encode(['recent' => $out]);
|
||||
break;
|
||||
|
||||
case 'control':
|
||||
$jfSessionId = $_GET['session_id'] ?? '';
|
||||
$jfCommand = $_GET['command'] ?? '';
|
||||
// General commands supported by Jellyfin session control
|
||||
$allowed = ['TogglePause', 'Stop', 'NextTrack', 'PreviousTrack', 'VolumeUp', 'VolumeDown'];
|
||||
if (!$jfSessionId || !in_array($jfCommand, $allowed, true)) {
|
||||
// No session_id: get the first active session automatically
|
||||
if (!$jfSessionId && in_array($jfCommand, $allowed, true)) {
|
||||
$sessions = jf_get('/Sessions');
|
||||
foreach ($sessions as $s) {
|
||||
if (isset($s['NowPlayingItem'])) { $jfSessionId = $s['Id']; break; }
|
||||
}
|
||||
}
|
||||
if (!$jfSessionId) { echo json_encode(['error' => 'No active session or invalid command']); break; }
|
||||
}
|
||||
$url = JELLYFIN_URL . '/Sessions/' . rawurlencode($jfSessionId) . '/Command/' . $jfCommand
|
||||
. '?api_key=' . JELLYFIN_API_KEY;
|
||||
$ctx = stream_context_create(['http' => [
|
||||
'method' => 'POST',
|
||||
'timeout' => 5,
|
||||
'header' => 'Content-Type: application/json',
|
||||
'content' => '{}',
|
||||
'ignore_errors' => true,
|
||||
]]);
|
||||
@file_get_contents($url, false, $ctx);
|
||||
echo json_encode(['success' => true, 'command' => $jfCommand, 'session_id' => $jfSessionId]);
|
||||
break;
|
||||
|
||||
default:
|
||||
echo json_encode(['error' => 'Unknown action']);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
// Agent metrics history — returns CPU/RAM samples for sparklines
|
||||
require_once __DIR__ . '/../config.php';
|
||||
require_once __DIR__ . '/../../includes/auth.php';
|
||||
header('Content-Type: application/json');
|
||||
AuthMiddleware::requireAuth();
|
||||
|
||||
$agentId = $_GET['agent_id'] ?? '';
|
||||
$hours = min((int)($_GET['hours'] ?? 2), 24);
|
||||
|
||||
if (!$agentId) {
|
||||
// Return all online agents' last 2h in one shot
|
||||
$agents = JarvisDB::query(
|
||||
"SELECT DISTINCT agent_id FROM agent_metrics WHERE recorded_at > DATE_SUB(NOW(), INTERVAL ? HOUR)",
|
||||
[$hours]
|
||||
) ?? [];
|
||||
|
||||
$out = [];
|
||||
foreach ($agents as $a) {
|
||||
$rows = JarvisDB::query(
|
||||
"SELECT CAST(JSON_EXTRACT(metric_data, '$.cpu_percent') AS DECIMAL(5,1)) as cpu,
|
||||
CAST(JSON_EXTRACT(metric_data, '$.memory.percent') AS DECIMAL(5,1)) as mem,
|
||||
recorded_at
|
||||
FROM agent_metrics
|
||||
WHERE agent_id = ? AND recorded_at > DATE_SUB(NOW(), INTERVAL ? HOUR)
|
||||
ORDER BY recorded_at ASC",
|
||||
[$a['agent_id'], $hours]
|
||||
) ?? [];
|
||||
if ($rows) {
|
||||
$out[$a['agent_id']] = array_map(fn($r) => [
|
||||
'cpu' => (float)($r['cpu'] ?? 0),
|
||||
'mem' => (float)($r['mem'] ?? 0),
|
||||
], $rows);
|
||||
}
|
||||
}
|
||||
echo json_encode($out);
|
||||
} else {
|
||||
$rows = JarvisDB::query(
|
||||
"SELECT CAST(JSON_EXTRACT(metric_data, '$.cpu_percent') AS DECIMAL(5,1)) as cpu,
|
||||
CAST(JSON_EXTRACT(metric_data, '$.memory.percent') AS DECIMAL(5,1)) as mem,
|
||||
recorded_at
|
||||
FROM agent_metrics
|
||||
WHERE agent_id = ? AND recorded_at > DATE_SUB(NOW(), INTERVAL ? HOUR)
|
||||
ORDER BY recorded_at ASC",
|
||||
[$agentId, $hours]
|
||||
) ?? [];
|
||||
echo json_encode(array_map(fn($r) => [
|
||||
'cpu' => (float)($r['cpu'] ?? 0),
|
||||
'mem' => (float)($r['mem'] ?? 0),
|
||||
], $rows));
|
||||
}
|
||||
Reference in New Issue
Block a user