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>
200 lines
8.9 KiB
HTML
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 — 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> — 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> — 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&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> — 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> — 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> →
|
|
<span class="badge badge-info">Storyboard</span> →
|
|
<span class="badge badge-success">Music</span> →
|
|
<span class="badge badge-warning">Video</span> →
|
|
<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 %}
|