Model tab sort dropdown
This commit is contained in:
10
README.md
10
README.md
@@ -6,7 +6,7 @@ Browse models in ComfyUI. (Downloading and deleting are WIP.)
|
||||
|
||||
## About this fork
|
||||
|
||||
I made this fork because the original repo was inactive and missing many things I needed to make the ComfyUI usable. Also, many other custom nodes bundle unrelated features together or search the internet in the background.
|
||||
I made this fork because the original repo was inactive and missing many things I needed to make ComfyUI more usable. Also, many other custom nodes bundle unrelated features together or search the internet in the background.
|
||||
|
||||
Currently it is still missing some features it should have.
|
||||
|
||||
@@ -17,6 +17,7 @@ Currently it is still missing some features it should have.
|
||||
- Search `/`subdirectories of model directories based on your file structure (for example, `/0/1.5/styles/clothing`).
|
||||
- Add `/` at the start of the search bar to see auto-complete suggestions.
|
||||
- Include models listed in ComfyUI's `extra_model_paths.yaml` or added in `ComfyUI/models`.
|
||||
- Sort for models (Date Created, Date Modified, Name).
|
||||
- Button to copy a model to the ComfyUI clipboard or embedding to system clipboard. (Embedding copying requires secure http connection.)
|
||||
- Button to add model to ComfyUI graph or embedding to selected nodes. (For small screens/low resolution.)
|
||||
- Right, left, top and bottom toggleable sidebar modes.
|
||||
@@ -65,12 +66,7 @@ Currently it is still missing some features it should have.
|
||||
- ☐ Directory dropdown
|
||||
- ☐ Use always filter to filter directory content auto-suggest dropdown
|
||||
- ☐ Generalize model list filtering code to reuse approach
|
||||
- ☐ Highlight selection
|
||||
- ☐ Key shortcuts
|
||||
- ☐ Down & Up arrows to select dropdown option
|
||||
- ☐ Right arrow or enter to add selection
|
||||
- ☐ Escape to loose focus on dropdown
|
||||
- ☐ Generalize search dropdown for download location selection
|
||||
- ☐ Generalize search dropdown for download location selection
|
||||
- ☐ Filters dropdown
|
||||
- ☐ Stable Diffusion model version/Clip/Upscale/?
|
||||
- ☐ Favorites
|
||||
|
||||
22
__init__.py
22
__init__.py
@@ -1,4 +1,5 @@
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
import copy
|
||||
import hashlib
|
||||
@@ -268,7 +269,7 @@ async def load_download_models(request):
|
||||
models = {}
|
||||
for model_type in model_types:
|
||||
model_extensions = tuple(folder_paths_get_supported_pt_extensions(model_type))
|
||||
file_names = []
|
||||
file_infos = []
|
||||
for base_path_index, model_base_path in enumerate(folder_paths_get_folder_paths(model_type)):
|
||||
if not os.path.exists(model_base_path): # Bug in main code?
|
||||
continue
|
||||
@@ -290,17 +291,26 @@ async def load_download_models(request):
|
||||
if model_name == image_name:
|
||||
image = end_swap_and_pop(dir_images, iImage)
|
||||
break
|
||||
abs_path = os.path.join(cwd, model)
|
||||
stats = pathlib.Path(abs_path).stat()
|
||||
date_modified = stats.st_mtime_ns
|
||||
date_created = stats.st_ctime_ns
|
||||
rel_path = "" if cwd == model_base_path else os.path.relpath(cwd, model_base_path)
|
||||
file_names.append((model, image, base_path_index, rel_path))
|
||||
file_names.sort(key=lambda tup: tup[0].lower())
|
||||
info = (model, image, base_path_index, rel_path, date_modified, date_created)
|
||||
file_infos.append(info)
|
||||
file_infos.sort(key=lambda tup: tup[4], reverse=True) # TODO: remove sort; sorted on client
|
||||
|
||||
model_items = []
|
||||
for model, image, base_path_index, rel_path in file_names:
|
||||
# TODO: Stop sending redundant information
|
||||
for model, image, base_path_index, rel_path, date_modified, date_created in file_infos:
|
||||
# TODO: Stop sending redundant path information
|
||||
item = {
|
||||
"name": model,
|
||||
"search-path": "/" + os.path.join(model_type, str(base_path_index), rel_path, model).replace(os.path.sep, "/"), # TODO: Remove hack
|
||||
"searchPath": "/" + os.path.join(model_type, str(base_path_index), rel_path, model).replace(os.path.sep, "/"), # TODO: Remove hack
|
||||
"path": os.path.join(rel_path, model),
|
||||
"dateModified": date_modified,
|
||||
"dateCreated": date_created,
|
||||
#"dateLastUsed": "", # TODO: track server-side, send increment client-side
|
||||
#"countUsed": 0, # TODO: track server-side, send increment client-side
|
||||
}
|
||||
if image is not None:
|
||||
raw_post = os.path.join(model_type, str(base_path_index), rel_path, image)
|
||||
|
||||
@@ -342,7 +342,7 @@
|
||||
|
||||
.model-manager .search-text-area,
|
||||
.model-manager .source-text-area,
|
||||
.model-manager .model-type-dropdown {
|
||||
.model-manager .model-select-dropdown {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,10 @@ const modelNodeType = {
|
||||
|
||||
const DROPDOWN_DIRECTORY_SELECTION_CLASS = "search-dropdown-selected";
|
||||
|
||||
const MODEL_SORT_DATE_CREATED = "dateCreated";
|
||||
const MODEL_SORT_DATE_MODIFIED = "dateModified";
|
||||
const MODEL_SORT_DATE_NAME = "name";
|
||||
|
||||
/**
|
||||
* @typedef {Object} DirectoryItem
|
||||
* @param {string} name
|
||||
@@ -594,7 +598,7 @@ class ModelGrid {
|
||||
static #filter(list, searchString) {
|
||||
/** @type {string[]} */
|
||||
const keywords = searchString
|
||||
.replace("*", " ")
|
||||
//.replace("*", " ") // TODO: this is wrong for wildcards
|
||||
.split(/(-?".*?"|[^\s"]+)+/g)
|
||||
.map((item) => item
|
||||
.trim()
|
||||
@@ -603,7 +607,7 @@ class ModelGrid {
|
||||
.filter(Boolean);
|
||||
|
||||
const regexSHA256 = /^[a-f0-9]{64}$/gi;
|
||||
const fields = ["name", "search-path"]; // TODO: Remove "search-path" hack.
|
||||
const fields = ["name", "searchPath"]; // TODO: Remove "searchPath" hack.
|
||||
return list.filter((element) => {
|
||||
const text = fields
|
||||
.reduce((memo, field) => memo + " " + element[field], "")
|
||||
@@ -621,6 +625,33 @@ class ModelGrid {
|
||||
}, true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* In-place sort. Returns an arrat alias.
|
||||
* @param {Array} list
|
||||
* @param {string} sortBy
|
||||
* @param {bool} [reverse=false]
|
||||
* @returns {Array}
|
||||
*/
|
||||
static #sort(list, sortBy, reverse = false) {
|
||||
let compareFn = undefined;
|
||||
switch (sortBy) {
|
||||
case MODEL_SORT_DATE_NAME:
|
||||
compareFn = (a, b) => { return a[MODEL_SORT_DATE_NAME].localeCompare(b[MODEL_SORT_DATE_NAME]); };
|
||||
break;
|
||||
case MODEL_SORT_DATE_MODIFIED:
|
||||
compareFn = (a, b) => { return b[MODEL_SORT_DATE_MODIFIED] - a[MODEL_SORT_DATE_MODIFIED]; };
|
||||
break;
|
||||
case MODEL_SORT_DATE_CREATED:
|
||||
compareFn = (a, b) => { return b[MODEL_SORT_DATE_CREATED] - a[MODEL_SORT_DATE_CREATED]; };
|
||||
break;
|
||||
default:
|
||||
console.warn("Invalid filter sort value: '" + sortBy + "'");
|
||||
return list;
|
||||
}
|
||||
const sorted = list.sort(compareFn);
|
||||
return reverse ? sorted.reverse() : sorted;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Event} event
|
||||
@@ -836,10 +867,12 @@ class ModelGrid {
|
||||
* @param {HTMLSelectElement} modelSelect
|
||||
* @param {Object.<{value: string}>} previousModelType
|
||||
* @param {Object} settings
|
||||
* @param {string} sortBy
|
||||
* @param {boolean} reverseSort
|
||||
* @param {Array} previousModelFilters
|
||||
* @param {HTMLInputElement} modelFilter
|
||||
*/
|
||||
static update(modelGrid, models, modelSelect, previousModelType, settings, previousModelFilters, modelFilter) {
|
||||
static update(modelGrid, models, modelSelect, previousModelType, settings, sortBy, reverseSort, previousModelFilters, modelFilter) {
|
||||
let modelType = modelSelect.value;
|
||||
if (models[modelType] === undefined) {
|
||||
modelType = "checkpoints"; // TODO: magic value
|
||||
@@ -870,6 +903,7 @@ class ModelGrid {
|
||||
const searchAppend = settings["model-search-always-append"].value;
|
||||
const searchText = modelFilter.value + " " + searchAppend;
|
||||
const modelList = ModelGrid.#filter(models[modelType], searchText);
|
||||
ModelGrid.#sort(modelList, sortBy, reverseSort);
|
||||
|
||||
modelGrid.innerHTML = "";
|
||||
const modelGridModels = ModelGrid.#generateInnerHtml(modelList, modelType, settings);
|
||||
@@ -927,6 +961,7 @@ class ModelManager extends ComfyDialog {
|
||||
|
||||
/** @type {HTMLDivElement} */ modelGrid: null,
|
||||
/** @type {HTMLSelectElement} */ modelTypeSelect: null,
|
||||
/** @type {HTMLSelectElement} */ modelSortSelect: null,
|
||||
/** @type {HTMLDivElement} */ searchDirectoryDropdown: null,
|
||||
/** @type {HTMLInputElement} */ modelContentFilter: null,
|
||||
|
||||
@@ -1181,11 +1216,25 @@ class ModelManager extends ComfyDialog {
|
||||
textContent: "⟳",
|
||||
onclick: () => this.#modelTab_updateModels(),
|
||||
}),
|
||||
$el("select.model-type-dropdown", {
|
||||
$el("select.model-select-dropdown", {
|
||||
$: (el) => (this.#el.modelTypeSelect = el),
|
||||
name: "model-type",
|
||||
onchange: () => this.#modelTab_updateModelGrid(),
|
||||
}),
|
||||
$el("select.model-select-dropdown",
|
||||
{
|
||||
$: (el) => (this.#el.modelSortSelect = el),
|
||||
onchange: () => this.#modelTab_updateModelGrid(),
|
||||
},
|
||||
[
|
||||
$el("option", { value: MODEL_SORT_DATE_CREATED }, ["Date Created (newest to oldest)"]),
|
||||
$el("option", { value: "-" + MODEL_SORT_DATE_CREATED }, ["Date Created (oldest to newest)"]),
|
||||
$el("option", { value: MODEL_SORT_DATE_MODIFIED }, ["Date Modified (newest to oldest)"]),
|
||||
$el("option", { value: "-" + MODEL_SORT_DATE_MODIFIED }, ["Date Modified (oldest to newest)"]),
|
||||
$el("option", { value: MODEL_SORT_DATE_NAME }, ["Name (A-Z)"]),
|
||||
$el("option", { value: "-" + MODEL_SORT_DATE_NAME }, ["Name (Z-A)"]),
|
||||
],
|
||||
),
|
||||
]),
|
||||
$el("div.row.tab-header-flex-block", [
|
||||
$el("div.search-models", [
|
||||
@@ -1203,15 +1252,22 @@ class ModelManager extends ComfyDialog {
|
||||
];
|
||||
}
|
||||
|
||||
#modelTab_updateModelGrid = () => ModelGrid.update(
|
||||
this.#el.modelGrid,
|
||||
this.#data.models,
|
||||
this.#el.modelTypeSelect,
|
||||
this.#data.previousModelType,
|
||||
this.#el.settings,
|
||||
this.#data.previousModelFilters,
|
||||
this.#el.modelContentFilter
|
||||
);
|
||||
#modelTab_updateModelGrid = () => {
|
||||
const sortValue = this.#el.modelSortSelect.value;
|
||||
const reverseSort = sortValue[0] === "-";
|
||||
const sortBy = reverseSort ? sortValue.substring(1) : sortValue;
|
||||
ModelGrid.update(
|
||||
this.#el.modelGrid,
|
||||
this.#data.models,
|
||||
this.#el.modelTypeSelect,
|
||||
this.#data.previousModelType,
|
||||
this.#el.settings,
|
||||
sortBy,
|
||||
reverseSort,
|
||||
this.#data.previousModelFilters,
|
||||
this.#el.modelContentFilter
|
||||
);
|
||||
}
|
||||
|
||||
async #modelTab_updateModels() {
|
||||
this.#data.models = await request("/model-manager/models");
|
||||
|
||||
Reference in New Issue
Block a user