Files
character-browser/models.py
Aodhan Collins ae7ba961c1 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
2026-03-03 00:57:27 +00:00

138 lines
5.8 KiB
Python

from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Character(db.Model):
id = db.Column(db.Integer, primary_key=True)
character_id = db.Column(db.String(100), unique=True, nullable=False)
slug = db.Column(db.String(100), unique=True, nullable=False)
filename = db.Column(db.String(255), nullable=True)
name = db.Column(db.String(100), nullable=False)
data = db.Column(db.JSON, nullable=False)
default_fields = db.Column(db.JSON, nullable=True)
image_path = db.Column(db.String(255), nullable=True)
active_outfit = db.Column(db.String(100), default='default')
def get_active_wardrobe(self):
"""Get the currently active wardrobe outfit."""
wardrobe = self.data.get('wardrobe', {})
# Check if wardrobe is nested (new format) or flat (legacy)
if 'default' in wardrobe and isinstance(wardrobe.get('default'), dict):
# New nested format - return active outfit
return wardrobe.get(self.active_outfit or 'default', wardrobe.get('default', {}))
else:
# Legacy flat format - return as-is
return wardrobe
def get_available_outfits(self):
"""Get list of available outfit names."""
wardrobe = self.data.get('wardrobe', {})
if 'default' in wardrobe and isinstance(wardrobe.get('default'), dict):
return list(wardrobe.keys())
return ['default']
def __repr__(self):
return f'<Character {self.character_id}>'
class Look(db.Model):
id = db.Column(db.Integer, primary_key=True)
look_id = db.Column(db.String(100), unique=True, nullable=False)
slug = db.Column(db.String(100), unique=True, nullable=False)
filename = db.Column(db.String(255), nullable=True)
name = db.Column(db.String(100), nullable=False)
character_id = db.Column(db.String(100), nullable=True) # linked character
data = db.Column(db.JSON, nullable=False)
default_fields = db.Column(db.JSON, nullable=True)
image_path = db.Column(db.String(255), nullable=True)
def __repr__(self):
return f'<Look {self.look_id}>'
class Outfit(db.Model):
id = db.Column(db.Integer, primary_key=True)
outfit_id = db.Column(db.String(100), unique=True, nullable=False)
slug = db.Column(db.String(100), unique=True, nullable=False)
filename = db.Column(db.String(255), nullable=True)
name = db.Column(db.String(100), nullable=False)
data = db.Column(db.JSON, nullable=False)
default_fields = db.Column(db.JSON, nullable=True)
image_path = db.Column(db.String(255), nullable=True)
def __repr__(self):
return f'<Outfit {self.outfit_id}>'
class Action(db.Model):
id = db.Column(db.Integer, primary_key=True)
action_id = db.Column(db.String(100), unique=True, nullable=False)
slug = db.Column(db.String(100), unique=True, nullable=False)
filename = db.Column(db.String(255), nullable=True)
name = db.Column(db.String(100), nullable=False)
data = db.Column(db.JSON, nullable=False)
default_fields = db.Column(db.JSON, nullable=True)
image_path = db.Column(db.String(255), nullable=True)
def __repr__(self):
return f'<Action {self.action_id}>'
class Style(db.Model):
id = db.Column(db.Integer, primary_key=True)
style_id = db.Column(db.String(100), unique=True, nullable=False)
slug = db.Column(db.String(100), unique=True, nullable=False)
filename = db.Column(db.String(255), nullable=True)
name = db.Column(db.String(100), nullable=False)
data = db.Column(db.JSON, nullable=False)
default_fields = db.Column(db.JSON, nullable=True)
image_path = db.Column(db.String(255), nullable=True)
def __repr__(self):
return f'<Style {self.style_id}>'
class Scene(db.Model):
id = db.Column(db.Integer, primary_key=True)
scene_id = db.Column(db.String(100), unique=True, nullable=False)
slug = db.Column(db.String(100), unique=True, nullable=False)
filename = db.Column(db.String(255), nullable=True)
name = db.Column(db.String(100), nullable=False)
data = db.Column(db.JSON, nullable=False)
default_fields = db.Column(db.JSON, nullable=True)
image_path = db.Column(db.String(255), nullable=True)
def __repr__(self):
return f'<Scene {self.scene_id}>'
class Detailer(db.Model):
id = db.Column(db.Integer, primary_key=True)
detailer_id = db.Column(db.String(100), unique=True, nullable=False)
slug = db.Column(db.String(100), unique=True, nullable=False)
filename = db.Column(db.String(255), nullable=True)
name = db.Column(db.String(100), nullable=False)
data = db.Column(db.JSON, nullable=False)
default_fields = db.Column(db.JSON, nullable=True)
image_path = db.Column(db.String(255), nullable=True)
def __repr__(self):
return f'<Detailer {self.detailer_id}>'
class Checkpoint(db.Model):
id = db.Column(db.Integer, primary_key=True)
checkpoint_id = db.Column(db.String(255), unique=True, nullable=False)
slug = db.Column(db.String(255), unique=True, nullable=False)
name = db.Column(db.String(255), nullable=False)
checkpoint_path = db.Column(db.String(255), nullable=False) # e.g. "Illustrious/model.safetensors"
data = db.Column(db.JSON, nullable=True)
image_path = db.Column(db.String(255), nullable=True)
def __repr__(self):
return f'<Checkpoint {self.checkpoint_id}>'
class Settings(db.Model):
id = db.Column(db.Integer, primary_key=True)
llm_provider = db.Column(db.String(50), default='openrouter') # 'openrouter', 'ollama', 'lmstudio'
openrouter_api_key = db.Column(db.String(255), nullable=True)
openrouter_model = db.Column(db.String(100), default='google/gemini-2.0-flash-001')
local_base_url = db.Column(db.String(255), nullable=True)
local_model = db.Column(db.String(100), nullable=True)
def __repr__(self):
return '<Settings>'