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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 MiB

BIN
static/icons/gaze-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
static/icons/new-file.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
static/icons/refresh.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
static/icons/trash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

613
static/style.css Normal file
View File

@@ -0,0 +1,613 @@
/* ============================================================
Character Browser — Dark Theme (Indigo / Violet)
============================================================ */
/* --- Variables ------------------------------------------------ */
:root {
--bg-base: #0f0f1a;
--bg-card: #1a1a2e;
--bg-raised: #222240;
--bg-input: #16162a;
--accent: #6c63ff;
--accent-dim: #4f46e5;
--accent-glow: rgba(108, 99, 255, 0.22);
--border: #2d2d5e;
--border-light: #3a3a6a;
--text: #e2e2f0;
--text-muted: #8888aa;
--text-dim: #5555aa;
--success: #22c55e;
--danger: #ef4444;
--warning: #f59e0b;
--info: #38bdf8;
--radius: 10px;
}
/* --- Base ----------------------------------------------------- */
body {
background-color: var(--bg-base);
color: var(--text);
font-family: 'Inter', system-ui, -apple-system, sans-serif;
min-height: 100vh;
}
a { color: var(--accent); }
a:hover { color: #9d98ff; }
/* Scrollbar */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: var(--bg-card); }
::-webkit-scrollbar-thumb { background: var(--border-light); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: var(--accent-dim); }
/* ============================================================
Navbar
============================================================ */
.navbar {
background: rgba(15, 15, 26, 0.95) !important;
backdrop-filter: blur(12px);
border-bottom: 1px solid var(--border);
padding: 0.55rem 0;
}
.navbar-brand {
font-weight: 700;
font-size: 1.05rem;
letter-spacing: 0.12em;
text-transform: uppercase;
background: linear-gradient(90deg, #a78bfa, #6c63ff, #c084fc);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.navbar-brand:hover {
background: linear-gradient(90deg, #c4b5fd, #818cf8, #d8b4fe);
-webkit-background-clip: text;
background-clip: text;
}
.navbar-logo {
height: 28px;
width: auto;
}
/* All nav outline-light buttons become understated link-style items */
.navbar .btn-outline-light {
border: none;
color: var(--text-muted) !important;
background: transparent;
font-size: 0.82rem;
padding: 0.28rem 0.6rem;
border-radius: 6px;
transition: color 0.15s, background 0.15s;
}
.navbar .btn-outline-light:hover,
.navbar .btn-outline-light:focus {
color: var(--text) !important;
background: rgba(255, 255, 255, 0.07);
box-shadow: none;
}
/* Generator and Gallery get accent tint */
.navbar .btn-outline-light[href="/generator"],
.navbar .btn-outline-light[href="/gallery"] {
color: #9d98ff !important;
}
.navbar .btn-outline-light[href="/generator"]:hover,
.navbar .btn-outline-light[href="/gallery"]:hover {
background: var(--accent-glow);
color: #b8b4ff !important;
}
/* Create Character */
.navbar .btn-outline-success {
border: 1px solid rgba(34, 197, 94, 0.5);
color: var(--success) !important;
font-size: 0.82rem;
padding: 0.28rem 0.7rem;
border-radius: 6px;
transition: all 0.15s;
}
.navbar .btn-outline-success:hover {
background: rgba(34, 197, 94, 0.12);
border-color: var(--success);
box-shadow: none;
color: var(--success) !important;
}
/* Vertical divider in navbar */
.navbar .vr {
background-color: var(--border);
opacity: 1;
}
/* ============================================================
Cards
============================================================ */
.card {
background-color: var(--bg-card) !important;
border: 1px solid var(--border) !important;
border-radius: var(--radius) !important;
color: var(--text);
}
.card-header {
background-color: var(--bg-raised) !important;
border-bottom: 1px solid var(--border) !important;
color: var(--text) !important;
font-weight: 600;
font-size: 0.88rem;
}
/* Override Bootstrap bg utility classes on card-header */
.card-header.bg-primary,
.card-header.bg-dark,
.card-header.bg-secondary,
.card-header.bg-success,
.card-header.bg-info {
background-color: var(--bg-raised) !important;
}
.card-footer {
background-color: var(--bg-raised) !important;
border-top: 1px solid var(--border) !important;
color: var(--text-muted);
}
.card-body { color: var(--text); }
/* Character / category card hover */
.character-card {
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s, border-color 0.2s;
}
.character-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 32px var(--accent-glow);
border-color: var(--accent) !important;
}
/* ============================================================
Image container
============================================================ */
.img-container {
height: 300px;
overflow: hidden;
background-color: var(--bg-raised);
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--radius) var(--radius) 0 0;
}
.img-container img {
width: 100%;
height: 100%;
object-fit: cover;
}
/* Generator result container */
#result-container {
background-color: var(--bg-raised) !important;
border-radius: 0 0 var(--radius) var(--radius);
}
/* ============================================================
Forms
============================================================ */
.form-control,
.form-select {
background-color: var(--bg-input) !important;
border-color: var(--border) !important;
color: var(--text) !important;
border-radius: 6px;
transition: border-color 0.15s, box-shadow 0.15s;
}
.form-control:focus,
.form-select:focus {
border-color: var(--accent) !important;
box-shadow: 0 0 0 3px var(--accent-glow) !important;
background-color: var(--bg-input) !important;
color: var(--text) !important;
}
.form-control::placeholder { color: var(--text-dim); }
.form-control:disabled,
.form-select:disabled {
background-color: var(--bg-raised) !important;
color: var(--text-muted) !important;
}
.form-label { color: var(--text-muted); font-size: 0.82rem; font-weight: 500; }
.form-text { color: var(--text-dim) !important; }
.form-check-input {
background-color: var(--bg-input);
border-color: var(--border-light);
}
.form-check-input:checked {
background-color: var(--accent);
border-color: var(--accent);
}
.form-check-input:focus { box-shadow: 0 0 0 3px var(--accent-glow); }
.form-check-label { color: var(--text); }
option { background-color: var(--bg-raised); color: var(--text); }
/* ============================================================
Buttons
============================================================ */
.btn-primary {
background-color: var(--accent);
border-color: var(--accent-dim);
color: #fff;
}
.btn-primary:hover,
.btn-primary:active,
.btn-primary:focus {
background-color: var(--accent-dim);
border-color: #3730a3;
box-shadow: 0 0 0 3px var(--accent-glow);
color: #fff;
}
.btn-outline-primary {
border-color: var(--accent);
color: var(--accent);
}
.btn-outline-primary:hover,
.btn-outline-primary:active {
background-color: var(--accent);
border-color: var(--accent);
color: #fff;
}
.btn-secondary {
background-color: var(--bg-raised);
border-color: var(--border-light);
color: var(--text);
}
.btn-secondary:hover {
background-color: #2a2a50;
border-color: var(--border-light);
color: var(--text);
}
.btn-outline-secondary {
border-color: var(--border-light);
color: var(--text-muted);
}
.btn-outline-secondary:hover,
.btn-outline-secondary:active {
background-color: var(--bg-raised);
border-color: var(--border-light);
color: var(--text);
}
/* Active resolution preset */
.btn-secondary.preset-btn {
background-color: var(--accent);
border-color: var(--accent-dim);
color: #fff;
}
.btn-outline-success { border-color: var(--success); color: var(--success); }
.btn-outline-success:hover { background-color: rgba(34,197,94,.12); border-color: var(--success); color: var(--success); }
.btn-success { background-color: var(--success); border-color: #16a34a; color: #fff; }
.btn-success:hover { background-color: #16a34a; border-color: #15803d; color: #fff; }
.btn-outline-danger { border-color: var(--danger); color: var(--danger); }
.btn-outline-danger:hover { background-color: rgba(239,68,68,.12); border-color: var(--danger); color: var(--danger); }
.btn-danger { background-color: var(--danger); border-color: #dc2626; color: #fff; }
.btn-danger:hover { background-color: #dc2626; border-color: #b91c1c; color: #fff; }
.btn-outline-warning { border-color: var(--warning); color: var(--warning); }
.btn-outline-warning:hover { background-color: rgba(245,158,11,.12); border-color: var(--warning); color: var(--warning); }
.btn-light, .btn-outline-light {
background-color: rgba(255,255,255,.08);
border-color: var(--border-light);
color: var(--text);
}
.btn-light:hover, .btn-outline-light:hover {
background-color: rgba(255,255,255,.14);
color: var(--text);
}
/* Close button override (visible on dark bg) */
.btn-close { filter: invert(1) brightness(0.8); }
/* ============================================================
Accordion
============================================================ */
.accordion-item {
background-color: var(--bg-card) !important;
border-color: var(--border) !important;
}
.accordion-button {
background-color: var(--bg-raised) !important;
color: var(--text) !important;
font-size: 0.88rem;
}
.accordion-button:not(.collapsed) {
background-color: #252450 !important;
color: var(--text) !important;
box-shadow: inset 0 -1px 0 var(--border);
}
/* Make the chevron arrow visible on dark bg */
.accordion-button::after,
.accordion-button:not(.collapsed)::after {
filter: invert(1) brightness(0.75);
}
.accordion-button:focus { box-shadow: none; }
.accordion-body { background-color: var(--bg-card); padding: 0.5rem; }
/* Mix & match list items */
.mix-item { user-select: none; border-radius: 6px; }
.mix-item:hover { background-color: rgba(108, 99, 255, 0.12) !important; }
/* N/A placeholder for items without images */
.mix-item .bg-light {
background-color: var(--bg-raised) !important;
color: var(--text-dim) !important;
}
/* ============================================================
Progress bars
============================================================ */
.progress {
background-color: var(--bg-raised);
border: 1px solid var(--border);
border-radius: 8px;
}
.progress-bar {
background-color: var(--accent);
border-radius: 8px;
transition: width 0.4s ease-in-out;
}
.progress-bar.bg-success { background-color: var(--success) !important; }
.progress-bar.bg-info { background-color: var(--info) !important; }
/* ============================================================
Badges
============================================================ */
.badge.bg-primary { background-color: var(--accent) !important; }
.badge.bg-secondary { background-color: #2e2e52 !important; color: var(--text-muted) !important; }
.badge.bg-info { background-color: var(--info) !important; color: #0f172a !important; }
.badge.bg-success { background-color: var(--success) !important; }
.badge.bg-danger { background-color: var(--danger) !important; }
.badge.bg-warning { background-color: var(--warning) !important; color: #0f172a !important; }
.badge.bg-light { background-color: var(--bg-raised) !important; color: var(--text-muted) !important; border-color: var(--border) !important; }
/* ============================================================
Modals
============================================================ */
.modal-content {
background-color: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius);
color: var(--text);
}
.modal-header {
border-bottom: 1px solid var(--border);
background-color: var(--bg-raised);
border-radius: var(--radius) var(--radius) 0 0;
}
.modal-footer {
border-top: 1px solid var(--border);
background-color: var(--bg-raised);
border-radius: 0 0 var(--radius) var(--radius);
}
.modal-title { color: var(--text); }
/* ============================================================
Alerts
============================================================ */
.alert-info {
background-color: rgba(56, 189, 248, 0.1);
border-color: rgba(56, 189, 248, 0.3);
color: var(--info);
}
.alert-success {
background-color: rgba(34, 197, 94, 0.1);
border-color: rgba(34, 197, 94, 0.3);
color: var(--success);
}
.alert-danger {
background-color: rgba(239, 68, 68, 0.1);
border-color: rgba(239, 68, 68, 0.3);
color: var(--danger);
}
/* ============================================================
Pagination
============================================================ */
.page-link {
background-color: var(--bg-card);
border-color: var(--border);
color: var(--text-muted);
}
.page-link:hover {
background-color: var(--bg-raised);
border-color: var(--border-light);
color: var(--text);
}
.page-item.active .page-link {
background-color: var(--accent);
border-color: var(--accent);
color: #fff;
}
.page-item.disabled .page-link {
background-color: var(--bg-card);
border-color: var(--border);
color: var(--text-dim);
}
/* ============================================================
Text / utility overrides
============================================================ */
.text-muted { color: var(--text-muted) !important; }
h1, h2, h3, h4, h5, h6 { color: var(--text); }
.border-bottom { border-color: var(--border) !important; }
.border-top { border-color: var(--border) !important; }
.border { border-color: var(--border) !important; }
hr { border-color: var(--border); }
small { color: var(--text-muted); }
.font-monospace { color: var(--text); }
/* Spinner on dark bg */
.spinner-border.text-secondary { color: var(--text-muted) !important; }
/* ============================================================
Gallery page
============================================================ */
.gallery-card {
position: relative;
overflow: hidden;
border-radius: var(--radius);
background: var(--bg-raised);
cursor: pointer;
border: 1px solid var(--border);
transition: border-color 0.2s, box-shadow 0.2s;
}
.gallery-card:hover {
border-color: var(--accent);
box-shadow: 0 0 20px var(--accent-glow);
}
.gallery-card img {
width: 100%;
aspect-ratio: 1;
object-fit: cover;
display: block;
transition: transform 0.2s;
}
.gallery-card:hover img { transform: scale(1.04); }
.gallery-card .overlay {
position: absolute; bottom: 0; left: 0; right: 0;
background: linear-gradient(transparent, rgba(0,0,0,0.88));
padding: 28px 8px 8px;
opacity: 0;
transition: opacity 0.2s;
}
.gallery-card:hover .overlay { opacity: 1; }
.gallery-card .cat-badge {
position: absolute; top: 6px; left: 6px;
font-size: 0.65rem; text-transform: uppercase; letter-spacing: .04em;
}
.gallery-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 8px;
}
@media (min-width: 768px) { .gallery-grid { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); } }
@media (min-width: 1200px) { .gallery-grid { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); } }
/* Lightbox */
#lightbox {
display: none; position: fixed; inset: 0;
background: rgba(0, 0, 0, 0.94); z-index: 9999;
align-items: center; justify-content: center; flex-direction: column;
}
#lightbox.active { display: flex; }
#lightbox-img-wrap { position: relative; }
#lightbox-img {
max-width: 90vw; max-height: 80vh; object-fit: contain;
border-radius: 8px;
box-shadow: 0 0 60px rgba(108, 99, 255, 0.3);
cursor: zoom-in; display: block;
}
#lightbox-meta { color: #eee; margin-top: 10px; text-align: center; font-size: .85rem; }
#lightbox-hint { color: rgba(255,255,255,.38); font-size: .75rem; margin-top: 3px; }
#lightbox-close { position: fixed; top: 16px; right: 20px; font-size: 2rem; color: #fff; cursor: pointer; z-index: 10000; line-height: 1; }
#lightbox-actions { position: fixed; bottom: 20px; right: 20px; z-index: 10000; display: flex; gap: 8px; }
/* Prompt modal metadata */
.meta-grid { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; font-size: .85rem; }
.meta-grid .meta-label { color: var(--text-muted); white-space: nowrap; font-weight: 600; }
.meta-grid .meta-value { font-family: monospace; word-break: break-all; color: var(--text); }
.lora-chip {
display: inline-flex; align-items: center; gap: 4px;
background: var(--bg-raised);
border: 1px solid var(--border-light);
border-radius: 4px; padding: 2px 8px;
font-size: .8rem; font-family: monospace; margin: 2px;
color: var(--text);
}
.lora-chip .lora-strength { color: var(--accent); }
/* ============================================================
Misc
============================================================ */
/* Vertical rule in navbar */
.vr { opacity: 1; background-color: var(--border); }
/* Inline icons inside buttons */
.btn img { height: 1em; vertical-align: -0.125em; }
/* Icon-only square button */
.btn-icon {
width: 34px;
height: 34px;
padding: 0;
display: inline-flex !important;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.btn-icon img {
width: 18px;
height: 18px;
filter: brightness(0) invert(1);
}
/* Make forms invisible to flex layout so buttons flow naturally */
.d-contents { display: contents !important; }
/* Tags display */
.badge.rounded-pill { font-weight: 400; }
/* Textarea read-only */
textarea[readonly] {
background-color: var(--bg-raised) !important;
color: var(--text) !important;
border-color: var(--border) !important;
resize: vertical;
}
/* ============================================================
Service status indicators (navbar)
============================================================ */
.service-status {
display: inline-flex;
align-items: center;
gap: 4px;
cursor: default;
user-select: none;
opacity: 0.85;
}
.service-status:hover { opacity: 1; }
.status-dot {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
transition: background-color 0.4s ease;
}
.status-dot.status-ok { background-color: #3dd68c; box-shadow: 0 0 4px #3dd68c88; }
.status-dot.status-error { background-color: #f06080; box-shadow: 0 0 4px #f0608088; }
.status-dot.status-checking { background-color: #888; animation: status-pulse 1.2s ease-in-out infinite; }
.status-label {
font-size: 0.7rem;
font-weight: 500;
color: rgba(255,255,255,0.65);
letter-spacing: 0.02em;
}
@keyframes status-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}