forked from Rockachopa/Timmy-time-dashboard
feat: add Grok (xAI) as opt-in premium backend with monetization
- Add GrokBackend class in src/timmy/backends.py with full sync/async support, health checks, usage stats, and cost estimation in sats - Add consult_grok tool to Timmy's toolkit for proactive Grok queries - Extend cascade router with Grok provider type for failover chain - Add Grok Mode toggle card to Mission Control dashboard (HTMX live) - Add "Ask Grok" button on chat input for direct Grok queries - Add /grok/* routes: status, toggle, chat, stats endpoints - Integrate Lightning invoice generation for Grok usage monetization - Add GROK_ENABLED, XAI_API_KEY, GROK_DEFAULT_MODEL, GROK_MAX_SATS_PER_QUERY, GROK_FREE config settings via pydantic-settings - Update .env.example and docker-compose.yml with Grok env vars - Add 21 tests covering backend, tools, and route endpoints (all green) Local-first ethos preserved: Grok is premium augmentation only, disabled by default, and Lightning-payable when enabled. https://claude.ai/code/session_01FygwN8wS8J6WGZ8FPb7XGV
This commit is contained in:
@@ -59,10 +59,61 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Grok Mode Toggle -->
|
||||
<div class="card" style="margin-top: 24px;">
|
||||
<div class="card-header">
|
||||
<h2 class="card-title">Grok Mode</h2>
|
||||
<div>
|
||||
<span class="badge" id="grok-badge" style="background: #666;">STANDBY</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="grok-toggle-card"
|
||||
hx-get="/grok/status"
|
||||
hx-trigger="load"
|
||||
hx-target="#grok-toggle-card"
|
||||
hx-swap="innerHTML">
|
||||
<div style="border: 2px solid #666; border-radius: 12px; padding: 16px;
|
||||
background: var(--bg-secondary);">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div>
|
||||
<div style="font-weight: 700; font-size: 1.1rem; color: #666;">
|
||||
GROK MODE: LOADING...
|
||||
</div>
|
||||
<div style="font-size: 0.8rem; color: var(--text-muted); margin-top: 4px;">
|
||||
xAI frontier reasoning augmentation
|
||||
</div>
|
||||
</div>
|
||||
<button hx-post="/grok/toggle"
|
||||
hx-target="#grok-toggle-card"
|
||||
hx-swap="outerHTML"
|
||||
style="background: #666; color: #000; border: none;
|
||||
border-radius: 8px; padding: 8px 20px; cursor: pointer;
|
||||
font-weight: 700; font-family: inherit;">
|
||||
ACTIVATE
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-3" style="margin-top: 12px;">
|
||||
<div class="stat">
|
||||
<div class="stat-value" id="grok-requests">0</div>
|
||||
<div class="stat-label">Grok Queries</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-value" id="grok-tokens">0</div>
|
||||
<div class="stat-label">Tokens Used</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-value" id="grok-cost">0</div>
|
||||
<div class="stat-label">Est. Cost (sats)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Heartbeat Monitor -->
|
||||
<div class="card" style="margin-top: 24px;">
|
||||
<div class="card-header">
|
||||
<h2 class="card-title">💓 Heartbeat Monitor</h2>
|
||||
<h2 class="card-title">Heartbeat Monitor</h2>
|
||||
<div>
|
||||
<span class="badge" id="heartbeat-status">Checking...</span>
|
||||
</div>
|
||||
@@ -318,11 +369,40 @@ async function loadChatHistory() {
|
||||
}
|
||||
}
|
||||
|
||||
// Load Grok stats
|
||||
async function loadGrokStats() {
|
||||
try {
|
||||
const response = await fetch('/grok/status');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.stats) {
|
||||
document.getElementById('grok-requests').textContent = data.stats.total_requests || 0;
|
||||
document.getElementById('grok-tokens').textContent =
|
||||
(data.stats.total_prompt_tokens || 0) + (data.stats.total_completion_tokens || 0);
|
||||
document.getElementById('grok-cost').textContent = data.stats.estimated_cost_sats || 0;
|
||||
}
|
||||
|
||||
const badge = document.getElementById('grok-badge');
|
||||
if (data.active) {
|
||||
badge.textContent = 'ACTIVE';
|
||||
badge.style.background = '#00ff88';
|
||||
badge.style.color = '#000';
|
||||
} else {
|
||||
badge.textContent = 'STANDBY';
|
||||
badge.style.background = '#666';
|
||||
badge.style.color = '#fff';
|
||||
}
|
||||
} catch (error) {
|
||||
// Grok endpoint may not respond — silent fallback
|
||||
}
|
||||
}
|
||||
|
||||
// Initial load
|
||||
loadSovereignty();
|
||||
loadHealth();
|
||||
loadSwarmStats();
|
||||
loadLightningStats();
|
||||
loadGrokStats();
|
||||
loadChatHistory();
|
||||
|
||||
// Periodic updates
|
||||
@@ -330,5 +410,6 @@ setInterval(loadSovereignty, 30000); // Every 30s
|
||||
setInterval(loadHealth, 10000); // Every 10s
|
||||
setInterval(loadSwarmStats, 5000); // Every 5s
|
||||
setInterval(updateHeartbeat, 5000); // Heartbeat every 5s
|
||||
setInterval(loadGrokStats, 10000); // Grok stats every 10s
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user