forked from Rockachopa/Timmy-time-dashboard
Major:
- Extract all inline <style> blocks from 22 Jinja2 templates into
static/css/mission-control.css — single cacheable stylesheet
- Add tox lint check that fails on inline <style> in templates
Minor:
1. Connection status indicator in topbar (green/amber/red dot) reflecting
WebSocket + Ollama reachability, with auto-reconnect
2. Jinja2 {% macro panel(title) %} in macros.html — eliminates repeated
.card.mc-panel markup; index.html converted as example
3. SVG favicon (purple T + orange dot)
4. 30-second TTL cache on _check_ollama() to avoid blocking the event loop
on every health poll (asyncio.to_thread was already in place)
5. Toast notification system (McToast.show) for transient status messages —
wired into connection status for Ollama/WebSocket state changes
Enforcement:
- CLAUDE.md updated with conventions 11-14 (no inline CSS, use panel macro,
use toasts, never block the event loop)
- tox lint + pre-push environments now fail on inline <style> blocks
https://claude.ai/code/session_014FQ785MQdyJQ4BAXrRSo9w
Co-authored-by: Claude <noreply@anthropic.com>
207 lines
9.3 KiB
HTML
207 lines
9.3 KiB
HTML
{% extends "base.html" %}
|
||
|
||
{% block title %}Timmy Time — Spark Intelligence{% endblock %}
|
||
|
||
{% block extra_styles %}{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="container-fluid spark-container py-3">
|
||
|
||
<!-- Header -->
|
||
<div class="spark-header">
|
||
<div class="spark-title">SPARK INTELLIGENCE</div>
|
||
<div class="spark-subtitle">
|
||
Self-evolving cognitive layer —
|
||
<span class="spark-status-val">{{ status.events_captured }}</span> events captured,
|
||
<span class="spark-status-val">{{ status.memories_stored }}</span> memories,
|
||
<span class="spark-status-val">{{ status.predictions.evaluated }}</span> predictions evaluated
|
||
</div>
|
||
</div>
|
||
|
||
<div class="row g-3">
|
||
|
||
<!-- Left column: Status + Advisories -->
|
||
<div class="col-12 col-lg-4 d-flex flex-column gap-3">
|
||
|
||
<div class="card mc-panel">
|
||
<div class="card-header mc-panel-header">// EIDOS LOOP</div>
|
||
<div class="card-body p-3">
|
||
<div class="spark-stat-grid">
|
||
<div class="spark-stat">
|
||
<span class="spark-stat-label">PREDICTIONS</span>
|
||
<span class="spark-stat-value">{{ status.predictions.total_predictions }}</span>
|
||
</div>
|
||
<div class="spark-stat">
|
||
<span class="spark-stat-label">EVALUATED</span>
|
||
<span class="spark-stat-value">{{ status.predictions.evaluated }}</span>
|
||
</div>
|
||
<div class="spark-stat">
|
||
<span class="spark-stat-label">PENDING</span>
|
||
<span class="spark-stat-value">{{ status.predictions.pending }}</span>
|
||
</div>
|
||
<div class="spark-stat">
|
||
<span class="spark-stat-label">ACCURACY</span>
|
||
<span class="spark-stat-value {% if status.predictions.avg_accuracy >= 0.7 %}text-success{% elif status.predictions.avg_accuracy < 0.4 %}text-danger{% else %}text-warning{% endif %}">
|
||
{{ "%.0f"|format(status.predictions.avg_accuracy * 100) }}%
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card mc-panel">
|
||
<div class="card-header mc-panel-header">// EVENT PIPELINE</div>
|
||
<div class="card-body p-3">
|
||
{% for event_type, count in status.event_types.items() %}
|
||
<div class="spark-event-row">
|
||
<span class="spark-event-type-badge spark-type-{{ event_type }}">{{ event_type | replace("_", " ") | upper }}</span>
|
||
<span class="spark-event-count">{{ count }}</span>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card mc-panel"
|
||
hx-get="/spark/insights"
|
||
hx-trigger="load, every 30s"
|
||
hx-target="#spark-insights-body"
|
||
hx-swap="innerHTML">
|
||
<div class="card-header mc-panel-header d-flex justify-content-between align-items-center">
|
||
<span>// ADVISORIES</span>
|
||
<span class="badge badge-info">{{ advisories | length }}</span>
|
||
</div>
|
||
<div class="card-body p-3" id="spark-insights-body">
|
||
{% if advisories %}
|
||
{% for adv in advisories %}
|
||
<div class="spark-advisory priority-{{ 'high' if adv.priority >= 0.7 else ('medium' if adv.priority >= 0.4 else 'low') }}">
|
||
<div class="spark-advisory-header">
|
||
<span class="spark-advisory-cat">{{ adv.category | replace("_", " ") | upper }}</span>
|
||
<span class="spark-advisory-priority">{{ "%.0f"|format(adv.priority * 100) }}%</span>
|
||
</div>
|
||
<div class="spark-advisory-title">{{ adv.title }}</div>
|
||
<div class="spark-advisory-detail">{{ adv.detail }}</div>
|
||
<div class="spark-advisory-action">{{ adv.suggested_action }}</div>
|
||
</div>
|
||
{% endfor %}
|
||
{% else %}
|
||
<div style="text-align:center; color:var(--text-dim); padding:16px; font-size:0.85rem;">No advisories yet. Run more tasks to build intelligence.</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Middle column: Predictions -->
|
||
<div class="col-12 col-lg-4 d-flex flex-column gap-3">
|
||
|
||
<div class="card mc-panel">
|
||
<div class="card-header mc-panel-header">// EIDOS PREDICTIONS</div>
|
||
<div class="card-body p-3">
|
||
{% if predictions %}
|
||
{% for pred in predictions %}
|
||
<div class="spark-prediction {% if pred.evaluated_at %}evaluated{% else %}pending{% endif %}">
|
||
<div class="spark-pred-header">
|
||
<span class="spark-pred-task">{{ pred.task_id[:8] }}...</span>
|
||
{% if pred.accuracy is not none %}
|
||
<span class="spark-pred-accuracy {% if pred.accuracy >= 0.7 %}text-success{% elif pred.accuracy < 0.4 %}text-danger{% else %}text-warning{% endif %}">
|
||
{{ "%.0f"|format(pred.accuracy * 100) }}%
|
||
</span>
|
||
{% else %}
|
||
<span class="spark-pred-pending-badge">PENDING</span>
|
||
{% endif %}
|
||
</div>
|
||
<div class="spark-pred-detail">
|
||
{% if pred.predicted %}
|
||
<div class="spark-pred-item">
|
||
<span class="spark-pred-label">Winner:</span>
|
||
{{ (pred.predicted.likely_winner or "?")[:8] }}
|
||
</div>
|
||
<div class="spark-pred-item">
|
||
<span class="spark-pred-label">Success:</span>
|
||
{{ "%.0f"|format((pred.predicted.success_probability or 0) * 100) }}%
|
||
</div>
|
||
<div class="spark-pred-item">
|
||
<span class="spark-pred-label">Bid range:</span>
|
||
{{ pred.predicted.estimated_bid_range | join("–") }} sats
|
||
</div>
|
||
{% endif %}
|
||
{% if pred.actual %}
|
||
<div class="spark-pred-actual">
|
||
<span class="spark-pred-label">Actual:</span>
|
||
{% if pred.actual.succeeded %}completed{% else %}failed{% endif %}
|
||
by {{ (pred.actual.winner or "?")[:8] }}
|
||
{% if pred.actual.winning_bid %} at {{ pred.actual.winning_bid }} sats{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
<div class="spark-pred-time">{{ pred.created_at[:19] }}</div>
|
||
</div>
|
||
{% endfor %}
|
||
{% else %}
|
||
<div style="text-align:center; color:var(--text-dim); padding:16px; font-size:0.85rem;">No predictions yet. Post tasks to activate the EIDOS loop.</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card mc-panel">
|
||
<div class="card-header mc-panel-header">// MEMORIES</div>
|
||
<div class="card-body p-3">
|
||
{% if memories %}
|
||
{% for mem in memories %}
|
||
<div class="spark-memory-card mem-{{ mem.memory_type }}">
|
||
<div class="spark-mem-header">
|
||
<span class="spark-mem-type">{{ mem.memory_type | upper }}</span>
|
||
<span class="spark-mem-confidence">{{ "%.0f"|format(mem.confidence * 100) }}% conf</span>
|
||
</div>
|
||
<div class="spark-mem-content">{{ mem.content }}</div>
|
||
<div class="spark-mem-meta">
|
||
{{ mem.source_events }} events • {{ mem.created_at[:10] }}
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
{% else %}
|
||
<div style="text-align:center; color:var(--text-dim); padding:16px; font-size:0.85rem;">Memories will form as patterns emerge.</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Right column: Event Timeline -->
|
||
<div class="col-12 col-lg-4 d-flex flex-column gap-3">
|
||
|
||
<div class="card mc-panel"
|
||
hx-get="/spark/timeline"
|
||
hx-trigger="load, every 15s"
|
||
hx-target="#spark-timeline-body"
|
||
hx-swap="innerHTML">
|
||
<div class="card-header mc-panel-header d-flex justify-content-between align-items-center">
|
||
<span>// EVENT TIMELINE</span>
|
||
<span class="badge badge-secondary">{{ status.events_captured }} total</span>
|
||
</div>
|
||
<div class="card-body p-3 spark-timeline-scroll" id="spark-timeline-body">
|
||
{% if timeline %}
|
||
{% for ev in timeline %}
|
||
<div class="spark-event spark-type-{{ ev.event_type }}">
|
||
<div class="spark-event-header">
|
||
<span class="spark-event-type-badge">{{ ev.event_type | replace("_", " ") | upper }}</span>
|
||
<span class="spark-event-importance" title="Importance: {{ ev.importance }}">
|
||
{% if ev.importance >= 0.8 %}●●●{% elif ev.importance >= 0.5 %}●●{% else %}●{% endif %}
|
||
</span>
|
||
</div>
|
||
<div class="spark-event-desc">{{ ev.description }}</div>
|
||
{% if ev.task_id %}
|
||
<div class="spark-event-meta">task: {{ ev.task_id[:8] }}{% if ev.agent_id %} • agent: {{ ev.agent_id[:8] }}{% endif %}</div>
|
||
{% endif %}
|
||
<div class="spark-event-time">{{ ev.created_at[:19] }}</div>
|
||
</div>
|
||
{% endfor %}
|
||
{% else %}
|
||
<div style="text-align:center; color:var(--text-dim); padding:16px; font-size:0.85rem;">No events captured yet.</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|