Files
Timmy-time-dashboard/src/dashboard/templates/creative.html
Alexander Whitestone 622a6a9204 polish: extract inline CSS, add connection status, panel macro, favicon, ollama cache, toast system (#164)
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>
2026-03-11 09:52:57 -04:00

200 lines
8.9 KiB
HTML

{% extends "base.html" %}
{% block title %}Creative Studio — Mission Control{% endblock %}
{% block extra_styles %}{% endblock %}
{% block content %}
<div class="creative-container py-3">
<div class="creative-header">
<div>
<div class="creative-title">CREATIVE STUDIO</div>
<div class="creative-subtitle">Image, music, and video generation &mdash; powered by Pixel, Lyra, and Reel</div>
</div>
<div class="creative-stats">
<div class="creative-stat-box">
<div class="creative-stat-val">{{ image_count }}</div>
<div class="creative-stat-label">IMAGES</div>
</div>
<div class="creative-stat-box">
<div class="creative-stat-val">{{ music_count }}</div>
<div class="creative-stat-label">TRACKS</div>
</div>
<div class="creative-stat-box">
<div class="creative-stat-val">{{ video_count }}</div>
<div class="creative-stat-label">CLIPS</div>
</div>
<div class="creative-stat-box">
<div class="creative-stat-val">{{ project_count }}</div>
<div class="creative-stat-label">PROJECTS</div>
</div>
</div>
</div>
<!-- Tab Navigation -->
<ul class="nav nav-tabs mb-3" role="tablist">
<li class="nav-item">
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#tab-images" type="button">Images</button>
</li>
<li class="nav-item">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#tab-music" type="button">Music</button>
</li>
<li class="nav-item">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#tab-video" type="button">Video</button>
</li>
<li class="nav-item">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#tab-director" type="button">Director</button>
</li>
</ul>
<div class="tab-content">
<!-- Images Tab -->
<div class="tab-pane fade show active" id="tab-images" role="tabpanel">
<div class="card mc-panel persona-card mb-3">
<div class="card-header mc-panel-header">
<strong>Pixel</strong> &mdash; Visual Architect (FLUX)
</div>
<div class="card-body">
<p>Generate images by sending a task to the swarm: <code>"Generate an image of ..."</code></p>
<p>Tools: <span class="badge badge-info">generate_image</span> <span class="badge badge-info">generate_storyboard</span> <span class="badge badge-info">image_variations</span></p>
</div>
</div>
{% if images %}
<div class="row g-3">
{% for img in images %}
<div class="col-6 col-md-3">
<div class="card">
<div class="card-body text-center">
<small style="color:var(--text-dim);">{{ img.name }}</small>
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div style="text-align:center; padding:24px; color:var(--text-dim); font-size:0.85rem;">No images generated yet. Send an image generation task to the swarm.</div>
{% endif %}
</div>
<!-- Music Tab -->
<div class="tab-pane fade" id="tab-music" role="tabpanel">
<div class="card mc-panel persona-card mb-3">
<div class="card-header mc-panel-header">
<strong>Lyra</strong> &mdash; Sound Weaver (ACE-Step 1.5)
</div>
<div class="card-body">
<p>Generate music by sending a task: <code>"Compose a pop song about ..."</code></p>
<p>Tools: <span class="badge badge-success">generate_song</span> <span class="badge badge-success">generate_instrumental</span> <span class="badge badge-success">generate_vocals</span> <span class="badge badge-success">list_genres</span></p>
<p style="margin-bottom:0;">Genres: pop, rock, hip-hop, r&amp;b, jazz, blues, country, electronic, classical, folk, reggae, metal, punk, soul, funk, latin, ambient, lo-fi, cinematic</p>
</div>
</div>
{% if music_files %}
<div class="d-flex flex-column gap-2">
{% for track in music_files %}
<div class="card">
<div class="card-body d-flex justify-content-between align-items-center flex-wrap gap-2" style="padding:10px 14px;">
<span style="color:var(--text-bright); font-size:0.85rem;">{{ track.name }}</span>
<audio controls preload="none" style="max-width:100%;"><source src="/static/{{ track.path }}" type="audio/wav"></audio>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div style="text-align:center; padding:24px; color:var(--text-dim); font-size:0.85rem;">No music tracks generated yet. Send a music generation task to the swarm.</div>
{% endif %}
</div>
<!-- Video Tab -->
<div class="tab-pane fade" id="tab-video" role="tabpanel">
<div class="card mc-panel persona-card mb-3">
<div class="card-header mc-panel-header">
<strong>Reel</strong> &mdash; Motion Director (Wan 2.1)
</div>
<div class="card-body">
<p>Generate video clips: <code>"Create a cinematic clip of ..."</code></p>
<p>Tools: <span class="badge badge-warning">generate_video_clip</span> <span class="badge badge-warning">image_to_video</span> <span class="badge badge-warning">stitch_clips</span> <span class="badge badge-warning">overlay_audio</span></p>
<p style="margin-bottom:0;">Resolutions: 480p, 720p | Styles: cinematic, anime, documentary, abstract, timelapse, slow-motion, music-video, vlog</p>
</div>
</div>
{% if videos %}
<div class="row g-3">
{% for vid in videos %}
<div class="col-6 col-md-4">
<div class="card">
<div class="card-body text-center">
<small style="color:var(--text-dim);">{{ vid.name }}</small>
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div style="text-align:center; padding:24px; color:var(--text-dim); font-size:0.85rem;">No video clips generated yet. Send a video generation task to the swarm.</div>
{% endif %}
</div>
<!-- Director Tab -->
<div class="tab-pane fade" id="tab-director" role="tabpanel">
<div class="card mc-panel persona-card mb-3">
<div class="card-header mc-panel-header">
<strong>Creative Director</strong> &mdash; Full Pipeline
</div>
<div class="card-body">
<p>Orchestrate all three creative personas to produce a 3+ minute music video or cinematic short.</p>
<p>Pipeline:
<span class="badge badge-info">Script</span> &rarr;
<span class="badge badge-info">Storyboard</span> &rarr;
<span class="badge badge-success">Music</span> &rarr;
<span class="badge badge-warning">Video</span> &rarr;
<span class="badge badge-danger">Assembly</span>
</p>
<p style="margin-bottom:0;">Tools:
<span class="badge badge-secondary">create_project</span>
<span class="badge badge-secondary">run_storyboard</span>
<span class="badge badge-secondary">run_music</span>
<span class="badge badge-secondary">run_video_generation</span>
<span class="badge badge-secondary">run_assembly</span>
<span class="badge badge-secondary">run_full_pipeline</span>
</p>
</div>
</div>
<div class="card mc-panel">
<div class="card-header mc-panel-header">// PROJECTS</div>
<div class="card-body">
{% if projects %}
<div class="row g-3">
{% for proj in projects %}
<div class="col-12 col-md-6">
<div class="card">
<div class="card-header d-flex justify-content-between">
<strong style="color:var(--text-bright);">{{ proj.title or proj.id }}</strong>
<span class="badge {% if proj.status == 'complete' %}badge-success{% elif proj.status == 'failed' %}badge-danger{% else %}badge-info{% endif %}">{{ proj.status }}</span>
</div>
<div class="card-body">
<div class="d-flex gap-3 flex-wrap" style="font-size:0.8rem; color:var(--text-dim);">
<span>Scenes: {{ proj.scene_count }}</span>
<span>Storyboard: {{ 'Yes' if proj.has_storyboard else 'No' }}</span>
<span>Music: {{ 'Yes' if proj.has_music else 'No' }}</span>
<span>Clips: {{ proj.clip_count }}</span>
<span>Final: {{ 'Yes' if proj.has_final else 'No' }}</span>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div style="text-align:center; padding:20px; color:var(--text-dim); font-size:0.85rem;">
No creative projects yet. Use the swarm to create one: <code>"Create a music video about sunrise over mountains"</code>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}