Comfy Apps: 12-workflow image-gen web GUI + payment frontend

- Flask frontend + serialized GPU worker + BTCPay invoice/webhook
- 12 example images rendered from the real ComfyUI workflows
- fix: ipadapter workflow uses modern IPAdapter node (was broken IPAdapterApply)
- helper scripts: generate/optimize examples
- README with deploy recipe
This commit is contained in:
drjones
2026-08-27 20:37:11 -07:00
commit 55bea5757c
23 changed files with 800 additions and 0 deletions

137
templates/app.html Normal file
View File

@@ -0,0 +1,137 @@
<!doctype html>
<html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>{{ name.replace('_',' ').title() }} — Comfy Apps</title>
<link rel="stylesheet" href="/static/style.css"></head>
<body>
<div class="wrap">
<header>
<div class="logo"><a href="/">Comfy<span>Apps</span></a></div>
<div class="nav"><a href="/jobs">Gallery</a></div>
</header>
<div class="hero" style="padding-bottom:10px">
<h1>{{ name.replace('_',' ').title() }}</h1>
<p>{{ meta.desc }} — <b style="color:var(--gold)">{{ meta.price_sats }} sats</b></p>
</div>
<div class="example">
<img src="/static/examples/{{ name }}.jpg" alt="{{ name }} example" onerror="this.parentElement.style.display='none'">
<div class="example-caption">Example output — generated with this workflow</div>
</div>
<form class="panel" action="/run" method="post" enctype="multipart/form-data">
<input type="hidden" name="app" value="{{ name }}">
{% set is_t2i = name in ['logo','hero','article','portrait'] %}
{% set is_img = name in ['upscale','inpaint','outpaint','restore_face','ipadapter','controlnet','face_swap','banner'] %}
{% set is_flux = name in ['hero','banner','inpaint','outpaint'] %}
{% if is_img %}
<div class="field">
<label>{% if name in ['ipadapter'] %}Style image{% elif name in ['controlnet'] %}Control image (pose/edge/depth){% elif name in ['face_swap'] %}Face to swap{% elif name == 'banner' %}Image to extend{% else %}Source image{% endif %}</label>
<input type="file" name="image" accept="image/*" required>
{% if name == 'inpaint' %}
<label style="margin-top:12px">Mask (white = edit this region)</label>
<input type="file" name="mask" accept="image/*" required>
{% endif %}
</div>
{% endif %}
<div class="field">
<label>Prompt</label>
<textarea name="prompt" placeholder="Describe what you want..."></textarea>
</div>
{% if is_t2i or name in ['controlnet','ipadapter','face_swap'] %}
<div class="field">
<label>Negative prompt <span class="muted">(what to avoid)</span></label>
<textarea name="negative" placeholder="blurry, low quality, extra fingers..." style="min-height:56px"></textarea>
</div>
{% endif %}
{% if is_t2i %}
<div class="row">
<div class="field">
<label>Width</label>
<input type="number" name="w" value="{{ 1024 if name=='logo' else (1344 if name=='hero' else (1152 if name=='article' else 768)) }}" min="256" max="2048" step="64">
</div>
<div class="field">
<label>Height</label>
<input type="number" name="h" value="{{ 1024 if name=='logo' else (768 if name=='hero' else (768 if name=='article' else 1024)) }}" min="256" max="2048" step="64">
</div>
</div>
{% endif %}
{% if name == 'banner' or name == 'outpaint' %}
<div class="row">
<div class="field"><label>Extend left</label><input type="number" name="left" value="192" min="0" max="1024" step="64"></div>
<div class="field"><label>Extend right</label><input type="number" name="right" value="192" min="0" max="1024" step="64"></div>
<div class="field"><label>Extend top</label><input type="number" name="top" value="0" min="0" max="1024" step="64"></div>
<div class="field"><label>Extend bottom</label><input type="number" name="bottom" value="0" min="0" max="1024" step="64"></div>
</div>
{% endif %}
{% if name == 'upscale' or name == 'restore_face' or name == 'logo' %}
<div class="field">
<label>Upscaler</label>
<select name="upscaler">
<option>4x-UltraSharp.pth</option><option>RealESRGAN_x4.pth</option>
<option>4x_foolhardy_Remacri.pth</option><option>4x_NMKD-Siax_200k.pth</option>
<option>4x_NMKD-Superscale-SP_178000_G.pth</option>
</select>
</div>
{% endif %}
{% if name == 'controlnet' %}
<div class="field">
<label>Control type</label>
<select name="control_type">
<option>openpose</option><option>canny</option><option>depth</option>
<option>lineart</option><option>tile</option>
</select>
</div>
{% endif %}
<div class="row">
<div class="field">
<label>Steps</label>
<input type="number" name="steps" value="{% if is_flux or name in ['controlnet','ipadapter','face_swap'] %}20{% else %}8{% endif %}" min="1" max="60">
</div>
<div class="field">
<label>Seed <span class="muted">(blank = random)</span></label>
<input type="number" name="seed" placeholder="random">
</div>
</div>
{% if name in ['inpaint','outpaint'] %}
<div class="field">
<label>Denoise <span class="muted">(how much to change — {{ '0.7' if name=='inpaint' else '0.85' }} default)</span></label>
<input type="number" name="denoise" value="{{ '0.7' if name=='inpaint' else '0.85' }}" min="0" max="1" step="0.05">
</div>
{% endif %}
{% if name in ['controlnet','ipadapter'] %}
<div class="field">
<label>CFG scale <span class="muted">(prompt adherence)</span></label>
<input type="number" name="cfg" value="7.0" min="1" max="15" step="0.5">
</div>
{% endif %}
{% if name == 'controlnet' %}
<div class="field">
<label>Control strength</label>
<input type="number" name="strength" value="1.0" min="0" max="2" step="0.1">
</div>
{% endif %}
{% if name in ['ipadapter','face_swap'] %}
<div class="field">
<label>{% if name=='ipadapter' %}Style weight{% else %}Identity weight{% endif %}</label>
<input type="number" name="weight" value="{{ '1.0' if name=='ipadapter' else '0.8' }}" min="0" max="1.5" step="0.05">
</div>
{% endif %}
<button class="btn" type="submit">Pay {{ meta.price_sats }} sats &amp; generate</button>
</form>
</div>
</body></html>