feat: Self-Coding Dashboard HTMX Templates
Add complete UI for self-coding dashboard: Templates: - self_coding.html - Main dashboard page with layout - partials/self_coding_stats.html - Stats cards (total, success rate, etc) - partials/journal_entries.html - List of modification attempts - partials/journal_entry_detail.html - Expanded view of single attempt - partials/execute_form.html - Task execution form - partials/execute_result.html - Execution result display - partials/error.html - Error message display Features: - HTMX-powered dynamic updates - Real-time journal filtering (all/success/failure) - Modal dialog for task execution - Responsive Bootstrap 5 styling - Automatic refresh after successful execution
This commit is contained in:
7
src/dashboard/templates/partials/error.html
Normal file
7
src/dashboard/templates/partials/error.html
Normal file
@@ -0,0 +1,7 @@
|
||||
{# Error partial #}
|
||||
<div class="alert alert-danger mb-0">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span>⚠️</span>
|
||||
<span>{{ message }}</span>
|
||||
</div>
|
||||
</div>
|
||||
45
src/dashboard/templates/partials/execute_form.html
Normal file
45
src/dashboard/templates/partials/execute_form.html
Normal file
@@ -0,0 +1,45 @@
|
||||
{# Execute task form partial #}
|
||||
<form hx-post="/self-coding/execute" hx-target="#execute-result" hx-indicator="#execute-loading">
|
||||
<div class="mb-3">
|
||||
<label for="task-description" class="form-label">Task Description</label>
|
||||
<textarea
|
||||
class="form-control form-control-sm bg-dark text-light border-secondary"
|
||||
id="task-description"
|
||||
name="task_description"
|
||||
rows="4"
|
||||
placeholder="Describe what you want Timmy to do...
|
||||
|
||||
Example: Add error handling to the /health endpoint that returns 503 when Ollama is unreachable."
|
||||
required
|
||||
></textarea>
|
||||
<div class="form-text">
|
||||
Be specific. Include what to change and what the expected behavior should be.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning d-flex align-items-start gap-2 py-2">
|
||||
<span>⚠️</span>
|
||||
<small>
|
||||
<strong>Warning:</strong> This will modify source code. Changes will be tested and committed.
|
||||
Safety constraints: max 3 files, only files with tests, protected files cannot be modified.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="document.getElementById('execute-modal').close()">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" class="btn btn-sm btn-primary" onclick="document.getElementById('execute-modal').close()">
|
||||
Execute Task
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div id="execute-loading" class="htmx-indicator mt-3">
|
||||
<div class="d-flex align-items-center gap-2 text-muted">
|
||||
<div class="spinner-border spinner-border-sm" role="status"></div>
|
||||
<small>Executing self-edit task... This may take a few minutes.</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="execute-result" class="mt-3"></div>
|
||||
58
src/dashboard/templates/partials/execute_result.html
Normal file
58
src/dashboard/templates/partials/execute_result.html
Normal file
@@ -0,0 +1,58 @@
|
||||
{# Execute task result partial #}
|
||||
<div class="alert {% if result.success %}alert-success{% else %}alert-danger{% endif %} mb-0">
|
||||
<div class="d-flex align-items-start gap-2">
|
||||
<span class="fs-5">{% if result.success %}✅{% else %}❌{% endif %}</span>
|
||||
<div>
|
||||
<h6 class="alert-heading mb-1">
|
||||
{% if result.success %}Success!{% else %}Failed{% endif %}
|
||||
</h6>
|
||||
<p class="mb-0 small">{{ result.message }}</p>
|
||||
|
||||
{% if result.success %}
|
||||
{% if result.files_modified %}
|
||||
<div class="mt-2">
|
||||
<small class="text-muted">Files modified:</small>
|
||||
<ul class="list-unstyled mb-0 small">
|
||||
{% for file in result.files_modified %}
|
||||
<li><code>{{ file }}</code></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if result.commit_hash %}
|
||||
<div class="mt-2 small">
|
||||
<span class="text-muted">Commit:</span>
|
||||
<code>{{ result.commit_hash[:8] }}</code>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if result.attempt_id %}
|
||||
<div class="mt-2">
|
||||
<a href="/self-coding#journal-{{ result.attempt_id }}" class="btn btn-sm btn-outline-success">
|
||||
View in Journal
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if result.test_results %}
|
||||
<div class="mt-2">
|
||||
<small class="text-muted">Test output:</small>
|
||||
<pre class="small bg-black bg-opacity-25 p-2 rounded mb-0 mt-1 overflow-auto" style="max-height: 150px;"><code>{{ result.test_results[:500] }}{% if result.test_results|length > 500 %}...{% endif %}</code></pre>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Refresh journal and stats after execution #}
|
||||
{% if result.success %}
|
||||
<script>
|
||||
// Refresh journal and stats after successful execution
|
||||
setTimeout(() => {
|
||||
htmx.ajax('GET', '/self-coding/journal', { target: '#journal-container' });
|
||||
htmx.ajax('GET', '/self-coding/stats', { target: '#stats-container' });
|
||||
}, 500);
|
||||
</script>
|
||||
{% endif %}
|
||||
64
src/dashboard/templates/partials/journal_entries.html
Normal file
64
src/dashboard/templates/partials/journal_entries.html
Normal file
@@ -0,0 +1,64 @@
|
||||
{# Journal entries list partial #}
|
||||
{% if entries %}
|
||||
<div class="list-group list-group-flush">
|
||||
{% for entry in entries %}
|
||||
<div class="list-group-item journal-entry {{ entry.outcome.value }} p-3"
|
||||
hx-get="/self-coding/journal/{{ entry.id }}/detail"
|
||||
hx-target="#journal-detail-{{ entry.id }}"
|
||||
hx-swap="innerHTML"
|
||||
style="cursor: pointer;">
|
||||
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
{# Outcome icon #}
|
||||
{% if entry.outcome.value == 'success' %}
|
||||
<span class="badge bg-success">✓</span>
|
||||
{% elif entry.outcome.value == 'failure' %}
|
||||
<span class="badge bg-danger">✗</span>
|
||||
{% else %}
|
||||
<span class="badge bg-warning text-dark">↩</span>
|
||||
{% endif %}
|
||||
|
||||
<span class="text-muted small">
|
||||
#{{ entry.id }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<small class="text-muted">
|
||||
{{ entry.timestamp.strftime('%Y-%m-%d %H:%M') if entry.timestamp else 'Unknown' }}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<p class="mb-1 fw-medium">{{ entry.task_description }}</p>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="small text-muted">
|
||||
{% if entry.files_modified %}
|
||||
<span class="me-2">📁 {{ entry.files_modified|length }} file(s)</span>
|
||||
{% endif %}
|
||||
|
||||
{% if entry.retry_count > 0 %}
|
||||
<span class="me-2">🔄 {{ entry.retry_count }} retries</span>
|
||||
{% endif %}
|
||||
|
||||
{% if entry.reflection %}
|
||||
<span title="Has reflection">💡</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<span class="badge {% if entry.outcome.value == 'success' %}bg-success{% elif entry.outcome.value == 'failure' %}bg-danger{% else %}bg-warning text-dark{% endif %}">
|
||||
{{ entry.outcome.value|upper }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{# Detail container - populated on click #}
|
||||
<div id="journal-detail-{{ entry.id }}" class="mt-3"></div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-5 text-muted">
|
||||
<p class="mb-0">No journal entries found.</p>
|
||||
<small>Self-edit attempts will appear here.</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
54
src/dashboard/templates/partials/journal_entry_detail.html
Normal file
54
src/dashboard/templates/partials/journal_entry_detail.html
Normal file
@@ -0,0 +1,54 @@
|
||||
{# Journal entry detail partial #}
|
||||
<div class="card mt-3 bg-dark-subtle border-0">
|
||||
<div class="card-body">
|
||||
<h6 class="card-subtitle mb-3 text-muted">Attempt Details</h6>
|
||||
|
||||
{% if entry.approach %}
|
||||
<div class="mb-3">
|
||||
<small class="text-muted">Approach:</small>
|
||||
<p class="mb-0">{{ entry.approach }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if entry.files_modified %}
|
||||
<div class="mb-3">
|
||||
<small class="text-muted">Files Modified:</small>
|
||||
<ul class="list-unstyled mb-0">
|
||||
{% for file in entry.files_modified %}
|
||||
<li><code class="small">{{ file }}</code></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if entry.diff %}
|
||||
<div class="mb-3">
|
||||
<small class="text-muted">Diff:</small>
|
||||
<pre class="small bg-black p-2 rounded overflow-auto" style="max-height: 200px;"><code>{{ entry.diff[:500] }}{% if entry.diff|length > 500 %}...{% endif %}</code></pre>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if entry.test_results %}
|
||||
<div class="mb-3">
|
||||
<small class="text-muted">Test Results:</small>
|
||||
<pre class="small bg-black p-2 rounded overflow-auto" style="max-height: 150px;"><code>{{ entry.test_results[:500] }}{% if entry.test_results|length > 500 %}...{% endif %}</code></pre>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if entry.failure_analysis %}
|
||||
<div class="mb-3">
|
||||
<small class="text-danger">Failure Analysis:</small>
|
||||
<p class="mb-0 text-danger-emphasis">{{ entry.failure_analysis }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if entry.reflection %}
|
||||
<div class="mb-0">
|
||||
<small class="text-info">Reflection:</small>
|
||||
<div class="p-2 bg-info-subtle rounded">
|
||||
{{ entry.reflection|markdown }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
71
src/dashboard/templates/partials/self_coding_stats.html
Normal file
71
src/dashboard/templates/partials/self_coding_stats.html
Normal file
@@ -0,0 +1,71 @@
|
||||
{# Stats cards partial for self-coding dashboard #}
|
||||
<div class="row g-3">
|
||||
<!-- Total Attempts -->
|
||||
<div class="col-md-3 col-6">
|
||||
<div class="card border-0 shadow-sm stat-card h-100">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<h6 class="text-muted small mb-1">Total Attempts</h6>
|
||||
<h3 class="mb-0">{{ metrics.total }}</h3>
|
||||
</div>
|
||||
<span class="fs-4">📝</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Success Rate -->
|
||||
<div class="col-md-3 col-6">
|
||||
<div class="card border-0 shadow-sm stat-card h-100">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<h6 class="text-muted small mb-1">Success Rate</h6>
|
||||
<h3 class="mb-0 {% if metrics.overall >= 0.7 %}text-success{% elif metrics.overall >= 0.4 %}text-warning{% else %}text-danger{% endif %}">
|
||||
{{ "%.0f"|format(metrics.overall * 100) }}%
|
||||
</h3>
|
||||
</div>
|
||||
<span class="fs-4">📊</span>
|
||||
</div>
|
||||
<div class="progress mt-2" style="height: 4px;">
|
||||
<div class="progress-bar {% if metrics.overall >= 0.7 %}bg-success{% elif metrics.overall >= 0.4 %}bg-warning{% else %}bg-danger{% endif %}"
|
||||
style="width: {{ metrics.overall * 100 }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Successes -->
|
||||
<div class="col-md-3 col-6">
|
||||
<div class="card border-0 shadow-sm stat-card h-100 border-start border-3 border-success">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<h6 class="text-muted small mb-1">Successes</h6>
|
||||
<h3 class="mb-0 text-success">{{ metrics.success }}</h3>
|
||||
</div>
|
||||
<span class="fs-4">✅</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Failures -->
|
||||
<div class="col-md-3 col-6">
|
||||
<div class="card border-0 shadow-sm stat-card h-100 border-start border-3 border-danger">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<h6 class="text-muted small mb-1">Failures</h6>
|
||||
<h3 class="mb-0 text-danger">{{ metrics.failure + metrics.rollback }}</h3>
|
||||
</div>
|
||||
<span class="fs-4">❌</span>
|
||||
</div>
|
||||
<small class="text-muted">
|
||||
{{ metrics.failure }} fail / {{ metrics.rollback }} rollback
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
184
src/dashboard/templates/self_coding.html
Normal file
184
src/dashboard/templates/self_coding.html
Normal file
@@ -0,0 +1,184 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Self-Coding — Timmy Time{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
<!-- Header -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h1 class="h3 mb-0">Self-Coding</h1>
|
||||
<p class="text-muted small mb-0">Timmy's ability to modify its own source code</p>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-sm btn-outline-info" hx-get="/self-coding/stats" hx-target="#stats-container" hx-indicator="#stats-loading">
|
||||
Refresh Stats
|
||||
</button>
|
||||
<button class="btn btn-sm btn-primary" hx-get="/self-coding/execute-form" hx-target="#execute-modal-content" onclick="document.getElementById('execute-modal').showModal()">
|
||||
+ New Task
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stats Cards -->
|
||||
<div id="stats-container" hx-get="/self-coding/stats" hx-trigger="load">
|
||||
<div id="stats-loading" class="htmx-indicator">
|
||||
<div class="d-flex justify-content-center py-4">
|
||||
<div class="spinner-border text-info" role="status">
|
||||
<span class="visually-hidden">Loading stats...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content Grid -->
|
||||
<div class="row g-4 mt-2">
|
||||
<!-- Left Column: Journal -->
|
||||
<div class="col-lg-8">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header bg-transparent border-secondary d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">Modification Journal</h5>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button class="btn btn-outline-secondary active" hx-get="/self-coding/journal" hx-target="#journal-container">All</button>
|
||||
<button class="btn btn-outline-secondary" hx-get="/self-coding/journal?outcome=success" hx-target="#journal-container">Success</button>
|
||||
<button class="btn btn-outline-secondary" hx-get="/self-coding/journal?outcome=failure" hx-target="#journal-container">Failed</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div id="journal-container" hx-get="/self-coding/journal" hx-trigger="load" class="journal-list">
|
||||
<div class="d-flex justify-content-center py-5">
|
||||
<div class="spinner-border text-info" role="status">
|
||||
<span class="visually-hidden">Loading journal...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Quick Actions & Info -->
|
||||
<div class="col-lg-4">
|
||||
<!-- Quick Actions -->
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-header bg-transparent border-secondary">
|
||||
<h5 class="mb-0">Quick Actions</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-grid gap-2">
|
||||
<button class="btn btn-outline-info" hx-post="/self-coding/api/codebase/reindex" hx-swap="none" hx-confirm="Reindex codebase? This may take a moment.">
|
||||
🔄 Reindex Codebase
|
||||
</button>
|
||||
<a href="/self-coding/api/codebase/summary" target="_blank" class="btn btn-outline-secondary">
|
||||
📄 View Codebase Summary
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Safety Info -->
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-header bg-transparent border-secondary">
|
||||
<h5 class="mb-0">Safety Constraints</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="list-unstyled small mb-0">
|
||||
<li class="mb-2">✓ Max 3 files per commit</li>
|
||||
<li class="mb-2">✓ Max 100 lines changed</li>
|
||||
<li class="mb-2">✓ Only files with test coverage</li>
|
||||
<li class="mb-2">✓ Max 3 retries on failure</li>
|
||||
<li class="mb-2">✓ Protected files cannot be modified</li>
|
||||
<li>✓ All changes on feature branches</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- How It Works -->
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header bg-transparent border-secondary">
|
||||
<h5 class="mb-0">How It Works</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ol class="small mb-0">
|
||||
<li class="mb-2">Receive task description</li>
|
||||
<li class="mb-2">Find relevant files via indexer</li>
|
||||
<li class="mb-2">Check journal for similar attempts</li>
|
||||
<li class="mb-2">Create feature branch</li>
|
||||
<li class="mb-2">Plan edit with LLM</li>
|
||||
<li class="mb-2">Execute via Aider or direct edit</li>
|
||||
<li class="mb-2">Run tests</li>
|
||||
<li class="mb-2">Commit on success, rollback on failure</li>
|
||||
<li>Log attempt and reflect</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Execute Modal -->
|
||||
<dialog id="execute-modal" class="rounded border-0 shadow-lg" style="max-width: 600px; width: 90%; background: var(--bs-body-bg);">
|
||||
<div class="p-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h5 class="mb-0">Execute Self-Edit Task</h5>
|
||||
<button type="button" class="btn-close" onclick="document.getElementById('execute-modal').close()"></button>
|
||||
</div>
|
||||
<div id="execute-modal-content">
|
||||
<!-- Form loaded via HTMX -->
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<style>
|
||||
.journal-list {
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.journal-entry {
|
||||
border-left: 3px solid transparent;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.journal-entry:hover {
|
||||
background-color: rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
.journal-entry.success {
|
||||
border-left-color: #198754;
|
||||
}
|
||||
|
||||
.journal-entry.failure {
|
||||
border-left-color: #dc3545;
|
||||
}
|
||||
|
||||
.journal-entry.rollback {
|
||||
border-left-color: #fd7e14;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* Custom scrollbar for journal */
|
||||
.journal-list::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.journal-list::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.journal-list::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.journal-list::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user