Add danbooru-mcp auto-start, git sync, status API endpoints, navbar status indicators, and LLM format retry

- app.py: add subprocess import; add _ensure_mcp_repo() to clone/pull
  danbooru-mcp from https://git.liveaodh.com/aodhan/danbooru-mcp into
  tools/danbooru-mcp/ at startup; add ensure_mcp_server_running() which
  calls _ensure_mcp_repo() then starts the Docker container if not running;
  add GET /api/status/comfyui and GET /api/status/mcp health endpoints;
  fix call_llm() to retry up to 3 times on unexpected response format
  (KeyError/IndexError), logging the raw response and prompting the LLM
  to respond with valid JSON before each retry
- templates/layout.html: add ComfyUI and MCP status dot indicators to
  navbar; add polling JS that checks both endpoints on load and every 30s
- static/style.css: add .service-status, .status-dot, .status-ok,
  .status-error, .status-checking styles and status-pulse keyframe animation
- .gitignore: add tools/ to exclude the cloned danbooru-mcp repo
This commit is contained in:
Aodhan Collins
2026-03-03 00:57:27 +00:00
parent 0b8802deb5
commit ae7ba961c1
1194 changed files with 17475 additions and 3268 deletions

View File

@@ -3,19 +3,19 @@
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Detailer Gallery</h2>
<div class="d-flex">
<button id="batch-generate-btn" class="btn btn-outline-success me-2">Generate Missing Covers</button>
<button id="regenerate-all-btn" class="btn btn-outline-danger me-2">Regenerate All Covers</button>
<form action="{{ url_for('bulk_create_detailers_from_loras') }}" method="post" class="me-2">
<button type="submit" class="btn btn-primary">Bulk Create from LoRAs</button>
<div class="d-flex gap-1 align-items-center">
<button id="batch-generate-btn" class="btn btn-sm btn-outline-success btn-icon" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Generate cover images for detailers without one"><img src="{{ url_for('static', filename='icons/new-cover-batch.png') }}"></button>
<button id="regenerate-all-btn" class="btn btn-sm btn-outline-danger btn-icon" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Regenerate cover images for all detailers"><img src="{{ url_for('static', filename='icons/new-cover-batch.png') }}"></button>
<form action="{{ url_for('bulk_create_detailers_from_loras') }}" method="post" class="d-contents">
<button type="submit" class="btn btn-sm btn-primary btn-icon" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Create new detailer entries from all LoRA files"><img src="{{ url_for('static', filename='icons/new-file.png') }}"></button>
</form>
<form action="{{ url_for('bulk_create_detailers_from_loras') }}" method="post" class="me-2">
<form action="{{ url_for('bulk_create_detailers_from_loras') }}" method="post" class="d-contents">
<input type="hidden" name="overwrite" value="true">
<button type="submit" class="btn btn-danger" onclick="return confirm('WARNING: This will re-run LLM generation for ALL detailer LoRAs, consuming significant API credits and overwriting ALL existing detailer metadata. Are you absolutely sure?')">Bulk Overwrite from LoRAs</button>
<button type="submit" class="btn btn-sm btn-danger btn-icon" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Overwrite all detailer metadata from LoRA files (uses API credits)" onclick="return confirm('WARNING: This will re-run LLM generation for ALL detailer LoRAs, consuming significant API credits and overwriting ALL existing detailer metadata. Are you absolutely sure?')"><img src="{{ url_for('static', filename='icons/new-file.png') }}"></button>
</form>
<a href="{{ url_for('create_detailer') }}" class="btn btn-success me-2">Create New Detailer</a>
<form action="{{ url_for('rescan_detailers') }}" method="post">
<button type="submit" class="btn btn-outline-primary">Rescan Detailer Files</button>
<a href="{{ url_for('create_detailer') }}" class="btn btn-sm btn-success">Create New Detailer</a>
<form action="{{ url_for('rescan_detailers') }}" method="post" class="d-contents">
<button type="submit" class="btn btn-sm btn-outline-primary btn-icon" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Rescan detailer files from disk"><img src="{{ url_for('static', filename='icons/refresh.png') }}"></button>
</form>
</div>
</div>
@@ -63,15 +63,28 @@
<div class="card-body">
<h5 class="card-title text-center">{{ detailer.name }}</h5>
<p class="card-text small text-center text-muted">
{{ detailer.data.prompt }}
{% set ns = namespace(parts=[]) %}
{% if detailer.data.prompt is string %}
{% if detailer.data.prompt %}{% set ns.parts = ns.parts + [detailer.data.prompt] %}{% endif %}
{% elif detailer.data.prompt %}
{% for v in detailer.data.prompt %}
{% if v %}{% set ns.parts = ns.parts + [v] %}{% endif %}
{% endfor %}
{% endif %}
{% if detailer.data.lora and detailer.data.lora.lora_triggers %}
{% set ns.parts = ns.parts + [detailer.data.lora.lora_triggers] %}
{% endif %}
{{ ns.parts | join(', ') }}
</p>
</div>
{% if detailer.data.lora and detailer.data.lora.lora_name %}
{% set lora_name = detailer.data.lora.lora_name.split('/')[-1].replace('.safetensors', '') %}
<div class="card-footer text-center p-1">
<small class="text-muted" title="{{ detailer.data.lora.lora_name }}">{{ lora_name }}</small>
<div class="card-footer d-flex justify-content-between align-items-center p-1">
{% if detailer.data.lora and detailer.data.lora.lora_name %}
{% set lora_name = detailer.data.lora.lora_name.split('/')[-1].replace('.safetensors', '') %}
<small class="text-muted text-truncate" title="{{ detailer.data.lora.lora_name }}">{{ lora_name }}</small>
{% else %}<span></span>{% endif %}
<button class="btn btn-sm btn-outline-danger py-0 px-1 flex-shrink-0 ms-1 resource-delete-btn" title="Delete"
data-category="detailers" data-slug="{{ detailer.slug }}" data-name="{{ detailer.name | e }}">🗑</button>
</div>
{% endif %}
</div>
</div>
{% endfor %}