diff --git a/resources/logic_init.js b/resources/logic_init.js index e384b17..73da025 100644 --- a/resources/logic_init.js +++ b/resources/logic_init.js @@ -58,6 +58,9 @@ function init() { // Initialize search filter UI if (typeof renderSearchFilters === 'function') renderSearchFilters(); + // Initialize logic filter UI + if (typeof renderLogicFilters === 'function') renderLogicFilters(); + // IMPORTANT: Calculate grid dimensions BEFORE running pipeline if (typeof updateGridDimensions === 'function') { updateGridDimensions(); diff --git a/resources/logic_pipeline.js b/resources/logic_pipeline.js index 8ca5642..964f071 100644 --- a/resources/logic_pipeline.js +++ b/resources/logic_pipeline.js @@ -16,6 +16,69 @@ function refreshIndices() { idToIndexMap = new Map(sorted.map((item, index) => [item.id, index + 1])); } +// ============================================================ +// LOGIC FILTER HELPERS (numeric field extraction + comparison) +// ============================================================ + +/** + * Extract a numeric field value from a manifest item. + * Returns null if the field is missing or not parseable as a number. + */ +function _extractFieldValue(item, field) { + if (field === 'lora_strength') { + // Parse first lora's model strength from "name:0.5:0.7" format + const lora = item.lora; + if (!lora || lora === 'None') return null; + const firstPart = String(lora).split(' + ')[0]; + const parts = firstPart.split(':'); + if (parts.length < 2) return null; + const v = parseFloat(parts[1]); + return isNaN(v) ? null : v; + } + // Simple top-level numeric fields + const v = item[field]; + if (typeof v === 'number') return v; + if (typeof v === 'string') { + const n = parseFloat(v); + return isNaN(n) ? null : n; + } + return null; +} + +/** + * Evaluate a comparison: itemValue op filterValue. + * Returns true if the item passes the comparison, false otherwise. + */ +function _evalOp(itemValue, op, filterValue) { + const a = parseFloat(itemValue); + const b = parseFloat(filterValue); + if (isNaN(a) || isNaN(b)) return false; + switch (op) { + case '>': return a > b; + case '<': return a < b; + case '>=': return a >= b; + case '<=': return a <= b; + case '=': return a === b; + case '!=': return a !== b; + default: return true; + } +} + +/** + * Check if an item passes all active logic filters (AND logic). + * Returns true if no logic filters are active or all pass. + */ +function matchesLogicFilters(item) { + if (typeof logicFilters === 'undefined' || logicFilters.length === 0) return true; + for (const f of logicFilters) { + const itemValue = _extractFieldValue(item, f.field); + // If field is missing/null on the item, skip this filter (don't exclude) + if (itemValue === null || itemValue === undefined) continue; + if (!_evalOp(itemValue, f.op, f.value)) return false; + } + return true; +} + // Generate cache key from current filters function getFilterKey() { const parts = [ @@ -37,7 +100,8 @@ function getFilterKey() { [...filters.cfg].sort().join(','), [...filters.upscaleMethod].sort().join(','), [...filters.mediaType].sort().join(','), - searchFilters.map(f => `${f.type}:${f.term}`).join('|') + searchFilters.map(f => `${f.type}:${f.term}`).join('|'), + (typeof logicFilters !== 'undefined' ? logicFilters.map(f => `${f.field}${f.op}${f.value}`).join('|') : '') ]; return parts.join('|'); } @@ -280,6 +344,9 @@ function incrementalFilter(newItems) { if (!matchesSearchFilters(d)) return false; } + // Apply logic filters (numeric comparisons) + if (!matchesLogicFilters(d)) return false; + return true; }); } @@ -371,6 +438,9 @@ function executePipeline() { if (hasSearchFilters && !matchesSearchFilters(d)) return false; + // Apply logic filters (numeric comparisons) + if (!matchesLogicFilters(d)) return false; + return true; }); diff --git a/resources/logic_state.js b/resources/logic_state.js index 8410de5..e41e6f7 100644 --- a/resources/logic_state.js +++ b/resources/logic_state.js @@ -60,6 +60,11 @@ const sortOptionsList = [ // --- SEARCH FILTER STATE --- // Array of search filter objects: { type: 'model', term: 'mountain' } +// --- LOGIC FILTER STATE --- +// Array of numeric comparison filter objects: { field: 'cfg', op: '>', value: 7 } +// Multiple filters are applied with AND logic. OR is a future extension. +var logicFilters = logicFilters || []; + // --- VIRTUALIZATION & PIPELINE --- let visibleSlice = { start: -1, end: -1 }; diff --git a/resources/logic_ui.js b/resources/logic_ui.js index 827bcc5..b987c28 100644 --- a/resources/logic_ui.js +++ b/resources/logic_ui.js @@ -1766,6 +1766,108 @@ function matchesSearchFilters(item) { } +// ============================================================ +// LOGIC FILTER FUNCTIONS (numeric comparison filters) +// ============================================================ + +// Add a logic filter +function addLogicFilter() { + const fieldSelect = document.getElementById('logic-filter-field'); + const opSelect = document.getElementById('logic-filter-op'); + const valueInput = document.getElementById('logic-filter-value'); + + if (!fieldSelect || !opSelect || !valueInput) return; + + const field = fieldSelect.value; + const op = opSelect.value; + const rawValue = valueInput.value.trim(); + + if (!field) { alert('Please select a field'); return; } + if (rawValue === '') { alert('Please enter a value'); return; } + + const value = parseFloat(rawValue); + if (isNaN(value)) { alert('Value must be a number'); return; } + + // Add to logicFilters array + logicFilters.push({ field, op, value }); + + // Clear inputs + fieldSelect.value = ''; + valueInput.value = ''; + + renderLogicFilters(); + updateDataPipeline(); + + console.log(`[Logic Filter] Added: ${field} ${op} ${value}`); +} + +// Remove a logic filter by index +function removeLogicFilter(index) { + if (index >= 0 && index < logicFilters.length) { + const removed = logicFilters.splice(index, 1)[0]; + console.log(`[Logic Filter] Removed: ${removed.field} ${removed.op} ${removed.value}`); + renderLogicFilters(); + updateDataPipeline(); + } +} + +// Clear all logic filters +function clearAllLogicFilters() { + if (logicFilters.length === 0) return; + logicFilters = []; + renderLogicFilters(); + updateDataPipeline(); + console.log('[Logic Filter] All logic filters cleared'); +} + +// Render logic filter chips +function renderLogicFilters() { + const container = document.getElementById('active-logic-filters'); + if (!container) return; + + container.innerHTML = ''; + + if (logicFilters.length === 0) { + container.innerHTML = '