Files
JasonHoku-ComfyUI-Ultimate-…/resources/logic_pipeline.js
T

353 lines
13 KiB
JavaScript

/**
* OPTIMIZED DATA PIPELINE
* Fixed: New items appear immediately when prepended to top
* Added: Prompt, Size, Seed filters, and Search Filters
*/
let isPipelinePending = false;
let filterCache = new Map();
let lastFilterKey = null;
// Helper to update index map
function refreshIndices() {
if (!activeData) return;
const sorted = activeData.slice().sort((a, b) => a.id - b.id);
idToIndexMap = new Map(sorted.map((item, index) => [item.id, index + 1]));
}
// Generate cache key from current filters (including search filters)
function getFilterKey() {
const parts = [
currentSort,
[...filters.sampler].sort().join(','),
[...filters.scheduler].sort().join(','),
[...filters.denoise].sort().join(','),
[...filters.lora].sort().join(','),
[...filters.model].sort().join(','),
[...filters.positive].sort().join(','),
[...filters.negative].sort().join(','),
[...filters.size].sort().join(','),
[...filters.seed].sort().join(','),
// Include search filters in cache key
searchFilters.map(f => `${f.type}:${f.term}`).join('|')
];
return parts.join('|');
}
// INCREMENTAL FILTERING: Only filter new items
function incrementalFilter(newItems) {
const hasButtonFilters = filters.sampler.size > 0 ||
filters.scheduler.size > 0 ||
filters.denoise.size > 0 ||
filters.lora.size > 0 ||
filters.model.size > 0 ||
filters.positive.size > 0 ||
filters.negative.size > 0 ||
filters.size.size > 0 ||
filters.seed.size > 0;
return newItems.filter(d => {
if (d.rejected) return false;
// Apply button filters
if (hasButtonFilters) {
if (filters.sampler.size > 0 && !filters.sampler.has(d.sampler)) return false;
if (filters.scheduler.size > 0 && !filters.scheduler.has(d.scheduler)) return false;
if (filters.denoise.size > 0 && !filters.denoise.has(d.denoise)) return false;
if (filters.lora.size > 0 && !filters.lora.has(d.lora)) return false;
if (filters.model.size > 0 && !filters.model.has(d.model || meta.model || "Default")) return false;
// Prompt filters
if (filters.positive.size > 0 && !filters.positive.has(d.positive || meta.positive || "")) return false;
if (filters.negative.size > 0 && !filters.negative.has(d.negative || meta.negative || "")) return false;
// Size filter
if (filters.size.size > 0) {
const sizeStr = `${d.width}x${d.height}`;
if (!filters.size.has(sizeStr)) return false;
}
// Seed filter
if (filters.seed.size > 0 && !filters.seed.has(d.seed)) return false;
}
// Apply search filters (if function is available)
if (typeof matchesSearchFilters === 'function') {
if (!matchesSearchFilters(d)) return false;
}
return true;
});
}
// MAIN TRIGGER: Debounced and cached
function updateDataPipeline() {
if (isPipelinePending) return;
isPipelinePending = true;
requestAnimationFrame(() => {
executePipeline();
isPipelinePending = false;
});
}
function executePipeline() {
const startTime = performance.now();
const currentFilterKey = getFilterKey();
const filtersChanged = currentFilterKey !== lastFilterKey;
if (filtersChanged) {
console.log('[Pipeline] Filters changed, full reprocess');
lastFilterKey = currentFilterKey;
processedData = activeData.filter(d => {
if (d.rejected) return false;
// Button filters
if (filters.sampler.size > 0 && !filters.sampler.has(d.sampler)) return false;
if (filters.scheduler.size > 0 && !filters.scheduler.has(d.scheduler)) return false;
if (filters.denoise.size > 0 && !filters.denoise.has(d.denoise)) return false;
if (filters.lora.size > 0 && !filters.lora.has(d.lora)) return false;
if (filters.model.size > 0 && !filters.model.has(d.model || meta.model || "Default")) return false;
// Prompt filters
if (filters.positive.size > 0 && !filters.positive.has(d.positive || meta.positive || "")) return false;
if (filters.negative.size > 0 && !filters.negative.has(d.negative || meta.negative || "")) return false;
// Size filter
if (filters.size.size > 0) {
const sizeStr = `${d.width}x${d.height}`;
if (!filters.size.has(sizeStr)) return false;
}
// Seed filter
if (filters.seed.size > 0 && !filters.seed.has(d.seed)) return false;
// Search filters
if (typeof matchesSearchFilters === 'function') {
if (!matchesSearchFilters(d)) return false;
}
return true;
});
} else {
processedData = processedData.filter(d => !d.rejected);
}
// Sort
switch (currentSort) {
case 'newest':
processedData.sort((a, b) => b.id - a.id);
break;
case 'fastest':
processedData.sort((a, b) => a.duration - b.duration);
break;
case 'favorited':
// Favorited first, then by ID
processedData.sort((a, b) => {
const aFav = a.favorited ? 1 : 0;
const bFav = b.favorited ? 1 : 0;
if (bFav !== aFav) return bFav - aFav;
return a.id - b.id;
});
break;
case 'model':
processedData.sort((a, b) => {
const aModel = (a.model || meta.model || "Default").toLowerCase();
const bModel = (b.model || meta.model || "Default").toLowerCase();
return aModel.localeCompare(bModel);
});
break;
case 'prompt':
processedData.sort((a, b) => {
const aPrompt = (a.positive || meta.positive || "").toLowerCase();
const bPrompt = (b.positive || meta.positive || "").toLowerCase();
return aPrompt.localeCompare(bPrompt);
});
break;
case 'cfg':
processedData.sort((a, b) => a.cfg - b.cfg);
break;
case 'denoise':
processedData.sort((a, b) => a.denoise - b.denoise);
break;
case 'lora':
processedData.sort((a, b) => {
const aLora = (a.lora || "None").toLowerCase();
const bLora = (b.lora || "None").toLowerCase();
return aLora.localeCompare(bLora);
});
break;
case 'sampler':
processedData.sort((a, b) => {
const aSampler = (a.sampler || "").toLowerCase();
const bSampler = (b.sampler || "").toLowerCase();
return aSampler.localeCompare(bSampler);
});
break;
default: // oldest
processedData.sort((a, b) => a.id - b.id);
}
const elapsed = performance.now() - startTime;
console.log(`[Pipeline] ✅ Processed ${processedData.length} items in ${elapsed.toFixed(1)}ms`);
updateJSONs(processedData);
// Only full re-render if filters changed
if (filtersChanged) {
renderDOM();
} else {
if (typeof updateVisibleItems === 'function') {
updateVisibleItems();
}
}
}
// OPTIMIZED: Process new data incrementally
function processNewData(newItems) {
if (!newItems || newItems.length === 0) return;
console.log(`[Pipeline] ⚡ Processing ${newItems.length} new items incrementally`);
const filtered = incrementalFilter(newItems);
// Track if we're prepending (affects visible range)
let prependedToTop = false;
// Add to correct position based on sort mode
if (currentSort === 'newest') {
// Prepend to beginning
processedData.unshift(...filtered);
prependedToTop = true;
} else if (currentSort === 'oldest') {
// Append to end
processedData.push(...filtered);
} else {
// All other sort modes: add items and re-sort entire array
processedData.push(...filtered);
switch (currentSort) {
case 'fastest':
processedData.sort((a, b) => a.duration - b.duration);
break;
case 'favorited':
processedData.sort((a, b) => {
const aFav = a.favorited ? 1 : 0;
const bFav = b.favorited ? 1 : 0;
if (bFav !== aFav) return bFav - aFav;
return a.id - b.id;
});
break;
case 'model':
processedData.sort((a, b) => {
const aModel = (a.model || meta.model || "Default").toLowerCase();
const bModel = (b.model || meta.model || "Default").toLowerCase();
return aModel.localeCompare(bModel);
});
break;
case 'prompt':
processedData.sort((a, b) => {
const aPrompt = (a.positive || meta.positive || "").toLowerCase();
const bPrompt = (b.positive || meta.positive || "").toLowerCase();
return aPrompt.localeCompare(bPrompt);
});
break;
case 'cfg':
processedData.sort((a, b) => a.cfg - b.cfg);
break;
case 'denoise':
processedData.sort((a, b) => a.denoise - b.denoise);
break;
case 'lora':
processedData.sort((a, b) => {
const aLora = (a.lora || "None").toLowerCase();
const bLora = (b.lora || "None").toLowerCase();
return aLora.localeCompare(bLora);
});
break;
case 'sampler':
processedData.sort((a, b) => {
const aSampler = (a.sampler || "").toLowerCase();
const bSampler = (b.sampler || "").toLowerCase();
return aSampler.localeCompare(bSampler);
});
break;
}
}
console.log(`[Pipeline] Now have ${processedData.length} items`);
updateJSONs(processedData);
// CRITICAL FIX: If prepended to top, force visible range recalc
if (prependedToTop && typeof forceVisibleRangeUpdate === 'function') {
forceVisibleRangeUpdate(filtered.length);
} else if (typeof updateVisibleItems === 'function') {
updateVisibleItems();
}
}
// Change Sort Order with dropdown
function changeSort() {
const select = document.getElementById('sort-select');
if (!select) return;
currentSort = select.value;
// Save to localStorage
localStorage.setItem('ultimate_grid_sort', currentSort);
console.log(`[Pipeline] Sort changed to: ${currentSort}`);
updateDataPipeline();
}
// Load sort order from localStorage
function loadSortPreference() {
const savedSort = localStorage.getItem('ultimate_grid_sort');
const select = document.getElementById('sort-select');
const validSortOptions = ['oldest', 'newest', 'fastest', 'favorited', 'model', 'prompt', 'cfg', 'denoise', 'lora', 'sampler'];
if (savedSort && validSortOptions.includes(savedSort)) {
currentSort = savedSort;
if (select) {
select.value = currentSort;
}
console.log(`[Pipeline] Loaded sort preference: ${currentSort}`);
}
}
// Update Filters when new data arrives
function updateFiltersForNewData(newItems) {
let changed = false;
['model', 'sampler', 'scheduler', 'denoise', 'lora', 'positive', 'negative', 'size', 'seed'].forEach(key => {
newItems.forEach(d => {
let val;
if (key === 'model') {
val = d.model || meta.model || "Default";
} else if (key === 'positive') {
val = d.positive || meta.positive || "";
} else if (key === 'negative') {
val = d.negative || meta.negative || "";
} else if (key === 'size') {
val = `${d.width}x${d.height}`;
} else {
val = d[key];
}
if (!filters[key].has(val)) {
changed = true;
filters[key].add(val);
}
});
});
if (changed && typeof initFilters === 'function') {
console.log('[Pipeline] New filter options detected, rebuilding filter UI');
initFilters();
}
}