mirror of
https://github.com/myronblair/jarvis
synced 2026-06-30 17:50:23 -05:00
Initial commit: JARVIS AI dashboard v2.3
- 4-tier chat: HA control → Ollama → Groq → Claude - Push-based agent system with heartbeat/metrics - Network monitoring, alerts, Proxmox, Home Assistant - Windows + Linux agent installers - Stats cache cron, facts collector, KB engine
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
class JarvisDB {
|
||||
private static ?PDO $pdo = null;
|
||||
|
||||
public static function get(): PDO {
|
||||
if (self::$pdo === null) {
|
||||
self::$pdo = new PDO(
|
||||
'mysql:host=' . DB_HOST . ';dbname=' . DB_NAME . ';charset=utf8mb4',
|
||||
DB_USER, DB_PASS,
|
||||
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false]
|
||||
);
|
||||
}
|
||||
return self::$pdo;
|
||||
}
|
||||
|
||||
public static function query(string $sql, array $params = []): array {
|
||||
$stmt = self::get()->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
public static function execute(string $sql, array $params = []): int {
|
||||
$stmt = self::get()->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
return $stmt->rowCount();
|
||||
}
|
||||
|
||||
public static function single(string $sql, array $params = []): ?array {
|
||||
$rows = self::query($sql, $params);
|
||||
return $rows[0] ?? null;
|
||||
}
|
||||
|
||||
public static function insert(string $sql, array $params = []): int {
|
||||
$stmt = self::get()->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
return (int)self::get()->lastInsertId();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
/**
|
||||
* JARVIS Knowledge Base + Intent Engine
|
||||
* Matches user input against intent patterns, substitutes live facts from kb_facts.
|
||||
* Returns a response if matched, or null to escalate to Ollama/Claude.
|
||||
*/
|
||||
|
||||
class KBEngine {
|
||||
|
||||
/**
|
||||
* Try to match the input against known intents.
|
||||
* Returns ['reply' => string, 'intent' => string] or null if no match.
|
||||
*/
|
||||
public static function match(string $input): ?array {
|
||||
$intents = JarvisDB::query(
|
||||
'SELECT * FROM kb_intents WHERE active=1 ORDER BY priority DESC, id ASC'
|
||||
);
|
||||
if (!$intents) return null;
|
||||
|
||||
foreach ($intents as $intent) {
|
||||
$pat = '~' . $intent['pattern'] . '~';
|
||||
if (@preg_match($pat, $input)) {
|
||||
$reply = self::fillTemplate(
|
||||
$intent['response_template'],
|
||||
$intent['fact_category']
|
||||
);
|
||||
return [
|
||||
'reply' => $reply,
|
||||
'intent' => $intent['intent_name'],
|
||||
'action' => $intent['action_type'],
|
||||
];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace {placeholder} tokens in a template with values from kb_facts,
|
||||
* plus built-in dynamic tokens like {current_time}.
|
||||
*/
|
||||
private static function fillTemplate(string $template, ?string $category): string {
|
||||
// Built-in tokens
|
||||
$builtins = [
|
||||
'current_time' => date('g:i A'),
|
||||
'current_date' => date('l, F j Y'),
|
||||
];
|
||||
|
||||
// Fetch all facts for this category (and null-category universal facts)
|
||||
$facts = [];
|
||||
if ($category) {
|
||||
$rows = JarvisDB::query(
|
||||
'SELECT fact_key, fact_value FROM kb_facts
|
||||
WHERE category = ?',
|
||||
[$category]
|
||||
);
|
||||
foreach ($rows as $r) {
|
||||
$facts[$r['fact_key']] = $r['fact_value'];
|
||||
}
|
||||
}
|
||||
// Also pull network facts for network tokens used in any template
|
||||
if (strpos($template, '{online_count}') !== false || strpos($template, '{total_count}') !== false) {
|
||||
$netRows = JarvisDB::query(
|
||||
"SELECT fact_key, fact_value FROM kb_facts
|
||||
WHERE category='network'"
|
||||
);
|
||||
foreach ($netRows as $r) {
|
||||
$facts[$r['fact_key']] = $r['fact_value'];
|
||||
}
|
||||
}
|
||||
|
||||
$allTokens = array_merge($builtins, $facts);
|
||||
|
||||
// Replace placeholders
|
||||
return preg_replace_callback('/\{([a-z0-9_]+)\}/', function ($m) use ($allTokens) {
|
||||
return $allTokens[$m[1]] ?? '[unknown]';
|
||||
}, $template);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a fact in kb_facts (upsert).
|
||||
*/
|
||||
public static function storeFact(
|
||||
string $category,
|
||||
string $key,
|
||||
string $value,
|
||||
string $host = 'local',
|
||||
?int $ttlSeconds = null
|
||||
): void {
|
||||
$expires = $ttlSeconds ? gmdate('Y-m-d H:i:s', time() + $ttlSeconds) : null;
|
||||
JarvisDB::execute(
|
||||
'INSERT INTO kb_facts (category, fact_key, fact_value, host, expires_at)
|
||||
VALUES (?,?,?,?,?)
|
||||
ON DUPLICATE KEY UPDATE fact_value=VALUES(fact_value), expires_at=VALUES(expires_at)',
|
||||
[$category, $key, $value, $host, $expires]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Learn from conversation — store interesting facts the user mentions.
|
||||
*/
|
||||
public static function learnFromConversation(string $input, string $reply): void {
|
||||
// Preference learning: user states a preference
|
||||
if (preg_match('/(?i)i (prefer|like|want|always)\s+(.+?)(?:\.|$)/', $input, $m)) {
|
||||
$pref = trim($m[2]);
|
||||
if (strlen($pref) < 120) {
|
||||
JarvisDB::execute(
|
||||
'INSERT INTO kb_preferences (pref_key, pref_value)
|
||||
VALUES (?,?)
|
||||
ON DUPLICATE KEY UPDATE pref_value=VALUES(pref_value)',
|
||||
['learned_' . md5($pref), $pref]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a summary of what the KB knows (for system prompt injection).
|
||||
*/
|
||||
public static function getContextSummary(): string {
|
||||
// Exclude entity_map — too large for Ollama 1B tokenizer
|
||||
$facts = JarvisDB::query(
|
||||
"SELECT category, fact_key, fact_value FROM kb_facts
|
||||
WHERE fact_key != 'entity_map'
|
||||
ORDER BY category, updated_at DESC"
|
||||
);
|
||||
if (!$facts) return '';
|
||||
|
||||
$byCategory = [];
|
||||
foreach ($facts as $f) {
|
||||
$byCategory[$f['category']][] = "{$f['fact_key']}={$f['fact_value']}";
|
||||
}
|
||||
|
||||
$lines = [];
|
||||
foreach ($byCategory as $cat => $items) {
|
||||
$lines[] = strtoupper($cat) . ': ' . implode(', ', array_slice($items, 0, 8));
|
||||
}
|
||||
return implode("\n", $lines);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user