feat: tier source badge + VM resource suggestions

- Tier badge (#9): addMessage() gains source param; sourceBadge() maps source string to KB/GROQ/CLAUDE/OLLAMA pill rendered after typing finishes; sendMessage() passes data.source through; CSS badges styled with domain colors
- VM suggestions (#10): vm_suggestions intent queries 24h avg CPU+MEM per agent, flags hosts with >80% avg CPU, <5% CPU on 2GB+ RAM, >85% avg MEM, or <25% MEM on 2GB+ RAM; 8 KB intents; say "VM resource suggestions" or "optimize VMs"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-17 02:38:24 +00:00
parent b024e51f3d
commit bde8909490
2 changed files with 76 additions and 3 deletions
+27 -2
View File
@@ -586,6 +586,15 @@ body::after{
.device-item.ctx-active{background:rgba(0,212,255,0.1);border-color:rgba(0,212,255,0.4)}
.alert-item{cursor:pointer}
.alert-item.ctx-active{border-color:var(--cyan) !important;box-shadow:0 0 8px rgba(0,212,255,0.15)}
.tier-badge{
display:inline-block;font-family:var(--font-display);font-size:0.42rem;
letter-spacing:1.5px;padding:1px 5px;border-radius:2px;margin-top:4px;
opacity:0.7;border:1px solid;vertical-align:middle;
}
.tier-badge.kb {color:#00d4ff;border-color:rgba(0,212,255,0.4);background:rgba(0,212,255,0.06)}
.tier-badge.groq {color:#f5a623;border-color:rgba(245,166,35,0.4);background:rgba(245,166,35,0.06)}
.tier-badge.claude{color:#b57cf5;border-color:rgba(181,124,245,0.4);background:rgba(181,124,245,0.06)}
.tier-badge.ollama{color:#7ef55a;border-color:rgba(126,245,90,0.4);background:rgba(126,245,90,0.06)}
.news-item{cursor:pointer;transition:background 0.15s}
.news-item:hover{background:rgba(0,212,255,0.04)}
.news-item.ctx-active{background:rgba(0,212,255,0.08);border-color:rgba(0,212,255,0.4)}
@@ -3439,7 +3448,21 @@ function switchTab(name) {
}
// ── CHAT ──────────────────────────────────────────────────────────────
function addMessage(role, text) {
function sourceBadge(source) {
if (!source) return '';
let cls, label;
if (/^intent:|^planner:|^kb:/.test(source)) { cls = 'kb'; label = 'KB'; }
else if (/^groq:/.test(source)) { cls = 'groq'; label = 'GROQ'; }
else if (source === 'claude' || /^claude/.test(source)) { cls = 'claude'; label = 'CLAUDE'; }
else if (/^ollama/.test(source)) { cls = 'ollama'; label = 'LOCAL AI'; }
else return '';
const s = document.createElement('div');
s.style.cssText = 'margin-top:4px;text-align:right';
s.innerHTML = `<span class="tier-badge ${cls}">${label}</span>`;
return s;
}
function addMessage(role, text, source=null) {
const log = document.getElementById('chatLog');
const div = document.createElement('div');
div.className = 'msg ' + role;
@@ -3459,6 +3482,8 @@ function addMessage(role, text) {
setTimeout(type, msPerChar + (text[i-1] === '.' || text[i-1] === ',' ? msPerChar * 4 : 0));
} else {
cursor.remove();
const badge = sourceBadge(source);
if (badge) div.appendChild(badge);
}
};
setTimeout(type, 0);
@@ -3556,7 +3581,7 @@ async function sendMessage() {
if (bubble) bubble.remove();
if (data.reply) {
addMessage('jarvis', data.reply);
addMessage('jarvis', data.reply, data.source || null);
speak(data.reply);
}
if (data.open_network_map) { openNetMap(); }