Add Checkpoints Gallery with per-checkpoint generation settings

- New Checkpoint model (slug, name, checkpoint_path, data JSON, image_path)
- sync_checkpoints() loads metadata from data/checkpoints/*.json and falls
  back to template defaults for models without a JSON file
- _apply_checkpoint_settings() applies per-checkpoint steps, CFG, sampler,
  base positive/negative prompts, and VAE (with dynamic VAELoader node
  injection for non-integrated VAEs) to the ComfyUI workflow
- Bulk Create from Checkpoints: scans Illustrious/Noob model directories,
  reads matching HTML files, uses LLM to populate metadata, falls back to
  template defaults when no HTML is present
- Gallery index with batch cover generation and WebSocket progress bar
- Detail page showing Generation Settings and Base Prompts cards
- Checkpoints nav link added to layout
- New data/prompts/checkpoint_system.txt LLM system prompt
- Updated README with all current galleries and file structure
- Also includes accumulated action/scene JSON updates, new actions, and
  other template/generator improvements from prior sessions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Aodhan Collins
2026-02-26 21:25:23 +00:00
parent 0d7d4d404f
commit 0b8802deb5
334 changed files with 9437 additions and 3772 deletions

View File

@@ -209,23 +209,37 @@
const clientId = 'style_detail_' + Math.random().toString(36).substring(2, 15);
// ComfyUI WebSocket
const socket = new WebSocket(`ws://127.0.0.1:8188/ws?clientId=${clientId}`);
const socket = new WebSocket('{{ COMFYUI_WS_URL }}?clientId=' + clientId);
const nodeNames = {
"3": "Sampling",
"11": "Face Detailing",
"13": "Hand Detailing",
"4": "Loading Models",
"16": "Character LoRA",
"17": "Outfit LoRA",
"18": "Action LoRA",
"19": "Style/Detailer LoRA",
"8": "Decoding Image",
"9": "Saving Image"
};
let currentPromptId = null;
let currentAction = null;
socket.addEventListener('message', (event) => {
if (!currentPromptId) return;
const msg = JSON.parse(event.data);
if (msg.type === 'status') {
const queueRemaining = msg.data.status.exec_info.queue_remaining;
if (queueRemaining > 0) {
progressLabel.textContent = `Queue position: ${queueRemaining}`;
if (!currentPromptId) {
const queueRemaining = msg.data.status.exec_info.queue_remaining;
if (queueRemaining > 0) {
progressLabel.textContent = `Queue position: ${queueRemaining}`;
}
}
}
else if (msg.type === 'progress') {
if (msg.data.prompt_id !== currentPromptId) return;
const value = msg.data.value;
const max = msg.data.max;
const percent = Math.round((value / max) * 100);
@@ -233,9 +247,16 @@
progressBar.textContent = `${percent}%`;
}
else if (msg.type === 'executing') {
if (msg.data.node === null && msg.data.prompt_id === currentPromptId) {
// Execution finished
if (msg.data.prompt_id !== currentPromptId) return;
const nodeId = msg.data.node;
if (nodeId === null) {
// Execution finished via WebSocket
console.log('Finished via WebSocket');
if (resolveCompletion) resolveCompletion();
} else {
const nodeName = nodeNames[nodeId] || `Processing...`;
progressLabel.textContent = nodeName;
}
}
});
@@ -249,7 +270,7 @@
};
resolveCompletion = checkResolve;
// Fallback polling
// Fallback polling in case WebSocket is blocked (403)
const pollInterval = setInterval(async () => {
try {
const resp = await fetch(`/check_status/${promptId}`);
@@ -294,6 +315,10 @@
currentPromptId = data.prompt_id;
progressLabel.textContent = 'Queued...';
progressBar.style.width = '100%';
progressBar.textContent = 'Queued';
progressBar.classList.add('progress-bar-striped', 'progress-bar-animated');
await waitForCompletion(currentPromptId);
finalizeGeneration(currentPromptId, currentAction);
currentPromptId = null;