mirror of
https://github.com/myronblair/jarvis
synced 2026-06-30 17:50:23 -05:00
Add JARVIS improvements: mobile UI, sparklines, suggestions, multi-step commands, Arc Reactor health, tier badges
- Mobile UI: 3-button bottom nav with panel switcher - Chat history search: search modal with keyword query - News filtering: category filter with localStorage persistence - Proactive reminders: planner/appointment alerts at login and every 5 min - Proactive alerts: polls every 60s, speaks new critical/warning alerts - Agent sparklines: 2h CPU+MEM sparkline on each online agent card - Tier source badge: KB/GROQ/CLAUDE/OLLAMA pill shown after each reply - VM suggestions: 24h resource analysis via voice command - HA scene control: fuzzy-match scene activation via voice - Jellyfin control: pause/stop/next/previous via voice and KB - Pattern suggestions: usage_patterns table + proactive chips every 30 min - Multi-step commands: compound "X and Y" command parsing (Tier 0.5) - Arc Reactor health: warning=amber/1.2s, critical=red/0.6s pulse encoding - Cross-session history: last 6 turns loaded from prior session - Restart agent: voice command to restart any JARVIS agent - New endpoints: history.php, metrics.php, suggestions.php, jellyfin.php Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1632,6 +1632,77 @@ if (!$reply) {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Tier 0.5: Multi-step command detection ──────────────────────────────────
|
||||
// Detect "do X and Y" or "X then Y" compound commands (only when no reply yet)
|
||||
if (!$reply) {
|
||||
$compoundParts = preg_split('/\s+(?:and|then|also)\s+/i', $message, 3);
|
||||
if (count($compoundParts) >= 2) {
|
||||
$multiReplies = [];
|
||||
$multiActionIntents = [];
|
||||
foreach ($compoundParts as $part) {
|
||||
$part = trim($part);
|
||||
if (!$part) continue;
|
||||
$mMatch = KBEngine::match($part);
|
||||
if ($mMatch && ($mMatch['action'] ?? '') === 'action') {
|
||||
$multiActionIntents[] = ['match' => $mMatch, 'part' => $part];
|
||||
}
|
||||
}
|
||||
|
||||
if (count($multiActionIntents) >= 2) {
|
||||
foreach ($multiActionIntents as $ma) {
|
||||
$mIntent = $ma['match']['intent'];
|
||||
$mPart = $ma['part'];
|
||||
$mReply = null;
|
||||
|
||||
if ($mIntent === 'ha_scene') {
|
||||
$haStates = @json_decode(@file_get_contents(HA_URL.'/api/states', false,
|
||||
stream_context_create(['http'=>['timeout'=>4,'header'=>'Authorization: Bearer '.HA_TOKEN]])), true) ?? [];
|
||||
$haScenes = array_filter($haStates, fn($s) => str_starts_with($s['entity_id']??'','scene.'));
|
||||
$mLow = strtolower($mPart); $best=null; $bestS=0;
|
||||
foreach ($haScenes as $s) {
|
||||
$name=strtolower($s['attributes']['friendly_name']??'');
|
||||
$id=strtolower(str_replace(['scene.','_'],['',''],$s['entity_id']));
|
||||
$score=0; foreach(array_filter(explode(' ',"$name $id")) as $tok)
|
||||
if(strlen($tok)>2&&str_contains($mLow,$tok))$score++;
|
||||
if($name&&str_contains($mLow,$name))$score+=5;
|
||||
if($score>$bestS){$bestS=$score;$best=$s;}
|
||||
}
|
||||
if ($best && $bestS >= 1) {
|
||||
@file_get_contents(HA_URL.'/api/services/scene/turn_on', false,
|
||||
stream_context_create(['http'=>['method'=>'POST','timeout'=>4,
|
||||
'header'=>"Authorization: Bearer ".HA_TOKEN."
|
||||
Content-Type: application/json",
|
||||
'content'=>json_encode(['entity_id'=>$best['entity_id']])]]));
|
||||
$mReply = ($best['attributes']['friendly_name'] ?? $best['entity_id']) . ' activated';
|
||||
}
|
||||
} elseif (in_array($mIntent, ['jellyfin_pause','jellyfin_stop','jellyfin_next','jellyfin_previous'])) {
|
||||
$jCmdMap = ['jellyfin_pause'=>'TogglePause','jellyfin_stop'=>'Stop','jellyfin_next'=>'NextTrack','jellyfin_previous'=>'PreviousTrack'];
|
||||
$jCmd = $jCmdMap[$mIntent];
|
||||
$jSessions = @json_decode(@file_get_contents(JELLYFIN_URL.'/Sessions?api_key='.JELLYFIN_API_KEY,false,
|
||||
stream_context_create(['http'=>['timeout'=>4]])),true)??[];
|
||||
foreach ($jSessions as $js) { if(isset($js['NowPlayingItem'])){
|
||||
@file_get_contents(JELLYFIN_URL.'/Sessions/'.rawurlencode($js['Id']).'/Command/'.$jCmd.'?api_key='.JELLYFIN_API_KEY,
|
||||
false,stream_context_create(['http'=>['method'=>'POST','timeout'=>4,'content'=>'{}','header'=>'Content-Type: application/json']]));
|
||||
$mReply = 'Jellyfin ' . strtolower(str_replace('Track','',$jCmd)); break;
|
||||
}}
|
||||
} elseif ($mIntent === 'focus_mode') { $uiAction = 'focus_mode'; $mReply = 'focus mode on'; }
|
||||
elseif ($mIntent === 'show_panels') { $uiAction = 'show_panels'; $mReply = 'panels shown'; }
|
||||
elseif ($mIntent === 'network_scan') {
|
||||
$devCount = JarvisDB::query("SELECT COUNT(*) as c FROM network_devices WHERE last_seen > DATE_SUB(NOW(),INTERVAL 15 MINUTE)")[0]['c'] ?? 0;
|
||||
$mReply = "network scan queued ({$devCount} devices online)";
|
||||
}
|
||||
|
||||
if ($mReply) $multiReplies[] = $mReply;
|
||||
}
|
||||
|
||||
if (count($multiReplies) >= 2) {
|
||||
$reply = "Done, {$userAddr}. " . implode(', and ', $multiReplies) . '.';
|
||||
$source = 'intent:multi_step';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Tier 1: Intent Engine (instant, no LLM) ───────────────────────────────
|
||||
if (!$reply) {
|
||||
$matched = KBEngine::match($message);
|
||||
@@ -2206,6 +2277,17 @@ JarvisDB::insert(
|
||||
);
|
||||
KBEngine::learnFromConversation($message, $reply);
|
||||
|
||||
// Track usage pattern for action intents
|
||||
if ($source && str_starts_with($source, 'intent:')) {
|
||||
$intentKey = str_replace('intent:', '', $source);
|
||||
JarvisDB::query(
|
||||
"INSERT INTO usage_patterns (intent_name, hour, dow, hit_count)
|
||||
VALUES (?, HOUR(NOW()), DAYOFWEEK(NOW())-1, 1)
|
||||
ON DUPLICATE KEY UPDATE hit_count=hit_count+1, last_seen=NOW()",
|
||||
[$intentKey]
|
||||
);
|
||||
}
|
||||
|
||||
// Memory Core — async extraction for LLM responses (don't extract from intent/KB/fallback)
|
||||
if ($reply && !in_array(explode(':', $source)[0], ['intent', 'kb', 'fallback', 'memory', 'arc'])) {
|
||||
memoryExtractAsync($message, $reply, $sessionId);
|
||||
|
||||
Reference in New Issue
Block a user