Files
DemonGatanjieu-Anomalous_Mo…/web/modules/ui_model_search.js
T
DemonGatanjieuandClaude Opus 5.5 c51fd11e03 Show a model's guessed words, its author's settings and the files it needs in its details
- ui_model_guide.js (above the description, out of ui_detail.js): Civitai's trigger words,
  else the words the LoRA's file suggests (dashed, said to be a guess), the settings its
  author's example pictures used, and for a diffusion models file of a known family the text
  encoder and VAE it needs with the file of that name found here; one GET
  /anomalous/model_guide per page, a newer page cancels it.
- The models page's search finds the header words too.
- Checked on the isolated test ComfyUI with stand-in files: words read now and after an
  offline scan, three example pictures summed up, Anima's two parts found by name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 22:52:33 +08:00

73 lines
2.7 KiB
JavaScript

/**
* The models page's search, in the header where it is seen, instead of inside the folder list.
* It narrows the grid of the type being shown to the models whose name, file name, folder,
* trigger words, base model or notes hold every word typed (ui_grid.js applies it through
* `owner.modelQuery`); Esc or the field's ✕ clears it. Shown on the models page only (CSS,
* by the shell's `data-page`).
*/
import { translate as t } from './locales.js';
const WAIT_MS = 180;
/** Whether a listed model matches every word of `query` (any case). */
export function matchesModelQuery(model, query) {
const words = String(query || '').toLowerCase().split(/\s+/).filter(Boolean);
if (!words.length) return true;
const meta = model?.metadata || {};
const text = [
model?.filename, model?.subfolder, meta.custom_name, meta.name, meta.baseModel, meta.custom_notes,
...(Array.isArray(meta.trainedWords) ? meta.trainedWords : []),
...(Array.isArray(meta.headerWords) ? meta.headerWords : []),
].filter(Boolean).join('\n').toLowerCase();
return words.every(word => text.includes(word));
}
/** Back from a model's page to the grid, where the results are. */
function leaveDetail(owner) {
if (!owner.currentDetailModel) return;
owner.stopMediaInContainer?.(owner.detailPanel);
owner.detailPanel.replaceChildren();
owner.detailPanel.style.display = 'none';
owner.currentDetailModel = null;
owner.grid.style.display = 'grid';
}
export function createModelSearch(owner) {
const box = document.createElement('label');
box.className = 'anomalous-model-search';
const icon = document.createElement('span');
icon.className = 'anomalous-model-search-icon';
icon.setAttribute('aria-hidden', 'true');
icon.textContent = '⌕';
const input = document.createElement('input');
input.type = 'search';
input.spellcheck = false;
input.placeholder = t('modelSearchPlaceholder');
input.setAttribute('aria-label', t('modelSearchPlaceholder'));
let timer = 0;
const apply = () => {
clearTimeout(timer);
const query = input.value.trim();
if (query === (owner.modelQuery || '')) return;
owner.modelQuery = query;
leaveDetail(owner);
owner.loadModels();
};
input.oninput = () => {
clearTimeout(timer);
timer = setTimeout(apply, WAIT_MS);
};
input.onkeydown = (event) => {
if (event.key === 'Enter') apply();
if (event.key === 'Escape' && input.value) {
event.stopPropagation(); // clears the search, does not close the browser
input.value = '';
apply();
}
};
box.append(icon, input);
owner.modelSearchInput = input;
return box;
}