Files
arcum42-ComfyUI_SageUtils/js/reports/modelSorting.js
T
2025-09-14 23:59:37 -07:00

228 lines
9.4 KiB
JavaScript

/**
* Model Sorting and Grouping Utilities for SageUtils Reports
* Handles sorting models by various criteria and grouping by Civitai ID
*/
import {
hasUpdateAvailable
} from '../shared/civitai.js';
/**
* Sort models array based on the specified criteria
* @param {Array} models - Array of model objects
* @param {string} sortBy - Sort criteria
* @returns {Array} - Sorted array of models
*/
export function sortModels(models, sortBy) {
return models.sort((a, b) => {
const infoA = a.info || {};
const infoB = b.info || {};
switch (sortBy) {
case 'name': {
const nameA = (infoA.model && infoA.model.name) || infoA.name || a.filePath.split('/').pop() || '';
const nameB = (infoB.model && infoB.model.name) || infoB.name || b.filePath.split('/').pop() || '';
return nameA.localeCompare(nameB);
}
case 'name-desc': {
const nameA = (infoA.model && infoA.model.name) || infoA.name || a.filePath.split('/').pop() || '';
const nameB = (infoB.model && infoB.model.name) || infoB.name || b.filePath.split('/').pop() || '';
return nameB.localeCompare(nameA);
}
case 'lastused': {
const lastUsedA = (infoA.lastUsed || infoA.last_accessed) ? new Date(infoA.lastUsed || infoA.last_accessed) : new Date(0);
const lastUsedB = (infoB.lastUsed || infoB.last_accessed) ? new Date(infoB.lastUsed || infoB.last_accessed) : new Date(0);
return lastUsedB - lastUsedA; // Recent first
}
case 'lastused-desc': {
const lastUsedA = (infoA.lastUsed || infoA.last_accessed) ? new Date(infoA.lastUsed || infoA.last_accessed) : new Date(0);
const lastUsedB = (infoB.lastUsed || infoB.last_accessed) ? new Date(infoB.lastUsed || infoB.last_accessed) : new Date(0);
return lastUsedA - lastUsedB; // Oldest first
}
case 'size': {
const sizeA = infoA.file_size || 0;
const sizeB = infoB.file_size || 0;
return sizeA - sizeB; // Small to large
}
case 'size-desc': {
const sizeA = infoA.file_size || 0;
const sizeB = infoB.file_size || 0;
return sizeB - sizeA; // Large to small
}
case 'type': {
const typeA = (infoA.model && infoA.model.type) || infoA.model_type || 'ZZZ';
const typeB = (infoB.model && infoB.model.type) || infoB.model_type || 'ZZZ';
if (typeA !== typeB) {
return typeA.localeCompare(typeB);
}
// Secondary sort by name
const nameA = (infoA.model && infoA.model.name) || infoA.name || a.filePath.split('/').pop() || '';
const nameB = (infoB.model && infoB.model.name) || infoB.name || b.filePath.split('/').pop() || '';
return nameA.localeCompare(nameB);
}
case 'type-desc': {
const typeA = (infoA.model && infoA.model.type) || infoA.model_type || 'ZZZ';
const typeB = (infoB.model && infoB.model.type) || infoB.model_type || 'ZZZ';
if (typeA !== typeB) {
return typeB.localeCompare(typeA); // Reversed for descending
}
// Secondary sort by name (also reversed for descending)
const nameA = (infoA.model && infoA.model.name) || infoA.name || a.filePath.split('/').pop() || '';
const nameB = (infoB.model && infoB.model.name) || infoB.name || b.filePath.split('/').pop() || '';
return nameB.localeCompare(nameA);
}
default: {
// Default to name sorting
const nameA = (infoA.model && infoA.model.name) || infoA.name || a.filePath.split('/').pop() || '';
const nameB = (infoB.model && infoB.model.name) || infoB.name || b.filePath.split('/').pop() || '';
return nameA.localeCompare(nameB);
}
}
});
}
/**
* Group models by Civitai ID, keeping different versions of the same model together
* @param {Array} models - Array of model objects
* @param {string} sortBy - Sort criteria to apply within groups
* @returns {Array} - Array of models grouped and sorted
*/
export function groupModelsByCivitaiId(models, sortBy) {
// First, group models by Civitai ID
const groupedModels = new Map();
const ungroupedModels = [];
models.forEach(model => {
const modelId = model.info && (model.info.modelId || model.info.model_id);
if (modelId && modelId !== 'Unknown' && modelId !== null) {
if (!groupedModels.has(modelId)) {
groupedModels.set(modelId, []);
}
groupedModels.get(modelId).push(model);
} else {
ungroupedModels.push(model);
}
});
// Sort models within each group
for (const [modelId, group] of groupedModels) {
groupedModels.set(modelId, sortModels(group, sortBy));
}
// Sort ungrouped models
const sortedUngrouped = sortModels(ungroupedModels, sortBy);
// Combine grouped and ungrouped models
// First, get all groups and sort them by the first model in each group
const sortedGroups = Array.from(groupedModels.entries()).sort((a, b) => {
const firstModelA = a[1][0];
const firstModelB = b[1][0];
// Use the same sorting logic as the main sort
const tempModels = [firstModelA, firstModelB];
const sorted = sortModels(tempModels, sortBy);
return sorted[0] === firstModelA ? -1 : 1;
});
// Flatten the result: grouped models first, then ungrouped models
const result = [];
sortedGroups.forEach(([modelId, group]) => {
result.push(...group);
});
result.push(...sortedUngrouped);
return result;
}
/**
* Generate grouping information for models based on Civitai ID
* @param {Array} models - Array of models (already grouped and sorted)
* @returns {Array} - Array of group info objects for each model
*/
export function generateGroupInfo(models) {
const groupInfo = [];
let currentModelId = null;
let groupStartIndex = -1;
let groupSize = 0;
models.forEach((model, index) => {
const modelId = model.info && (model.info.modelId || model.info.model_id);
const effectiveModelId = (modelId && modelId !== 'Unknown' && modelId !== null) ? modelId : null;
if (effectiveModelId && effectiveModelId === currentModelId) {
// Continue current group
groupSize++;
groupInfo[index] = {
isGroupMember: true,
isGroupFirst: false,
isGroupLast: false,
groupSize: groupSize,
modelId: effectiveModelId
};
} else {
// End previous group if it had more than one model
if (groupSize > 1 && groupStartIndex !== -1) {
groupInfo[groupStartIndex].isGroupFirst = true;
groupInfo[index - 1].isGroupLast = true;
// Check if any model in the group doesn't have an update available
// If so, we likely already have the latest version
let groupHasLatestVersion = false;
for (let i = groupStartIndex; i < index; i++) {
const modelInGroup = models[i];
if (modelInGroup && modelInGroup.info && !hasUpdateAvailable(modelInGroup.info)) {
groupHasLatestVersion = true;
break;
}
}
// Mark all models in the group
for (let i = groupStartIndex; i < index; i++) {
groupInfo[i].isGroupMember = true;
groupInfo[i].groupHasLatestVersion = groupHasLatestVersion;
}
}
// Start new group or single model
currentModelId = effectiveModelId;
groupStartIndex = effectiveModelId ? index : -1;
groupSize = effectiveModelId ? 1 : 0;
groupInfo[index] = {
isGroupMember: effectiveModelId ? true : false,
isGroupFirst: false,
isGroupLast: false,
groupSize: groupSize,
modelId: effectiveModelId
};
}
});
// Handle the last group if needed
if (groupSize > 1 && groupStartIndex !== -1) {
groupInfo[groupStartIndex].isGroupFirst = true;
groupInfo[models.length - 1].isGroupLast = true;
// Check if any model in the final group doesn't have an update available
let groupHasLatestVersion = false;
for (let i = groupStartIndex; i < models.length; i++) {
const modelInGroup = models[i];
if (modelInGroup && modelInGroup.info && !hasUpdateAvailable(modelInGroup.info)) {
groupHasLatestVersion = true;
break;
}
}
// Mark all models in the final group
for (let i = groupStartIndex; i < models.length; i++) {
groupInfo[i].isGroupMember = true;
groupInfo[i].groupHasLatestVersion = groupHasLatestVersion;
}
}
return groupInfo;
}