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
|
## 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.
|
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`).
|
- 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.
|
- 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`.
|
- 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 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.)
|
- 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.
|
- Right, left, top and bottom toggleable sidebar modes.
|
||||||
@@ -65,12 +66,7 @@ Currently it is still missing some features it should have.
|
|||||||
- ☐ Directory dropdown
|
- ☐ Directory dropdown
|
||||||
- ☐ Use always filter to filter directory content auto-suggest dropdown
|
- ☐ Use always filter to filter directory content auto-suggest dropdown
|
||||||
- ☐ Generalize model list filtering code to reuse approach
|
- ☐ Generalize model list filtering code to reuse approach
|
||||||
- ☐ Highlight selection
|
- ☐ Generalize search dropdown for download location 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
|
|
||||||
- ☐ Filters dropdown
|
- ☐ Filters dropdown
|
||||||
- ☐ Stable Diffusion model version/Clip/Upscale/?
|
- ☐ Stable Diffusion model version/Clip/Upscale/?
|
||||||
- ☐ Favorites
|
- ☐ Favorites
|
||||||
|
|||||||
22
__init__.py
22
__init__.py
@@ -1,4 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
|
import pathlib
|
||||||
import sys
|
import sys
|
||||||
import copy
|
import copy
|
||||||
import hashlib
|
import hashlib
|
||||||
@@ -268,7 +269,7 @@ async def load_download_models(request):
|
|||||||
models = {}
|
models = {}
|
||||||
for model_type in model_types:
|
for model_type in model_types:
|
||||||
model_extensions = tuple(folder_paths_get_supported_pt_extensions(model_type))
|
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)):
|
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?
|
if not os.path.exists(model_base_path): # Bug in main code?
|
||||||
continue
|
continue
|
||||||
@@ -290,17 +291,26 @@ async def load_download_models(request):
|
|||||||
if model_name == image_name:
|
if model_name == image_name:
|
||||||
image = end_swap_and_pop(dir_images, iImage)
|
image = end_swap_and_pop(dir_images, iImage)
|
||||||
break
|
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)
|
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))
|
info = (model, image, base_path_index, rel_path, date_modified, date_created)
|
||||||
file_names.sort(key=lambda tup: tup[0].lower())
|
file_infos.append(info)
|
||||||
|
file_infos.sort(key=lambda tup: tup[4], reverse=True) # TODO: remove sort; sorted on client
|
||||||
|
|
||||||
model_items = []
|
model_items = []
|
||||||
for model, image, base_path_index, rel_path in file_names:
|
for model, image, base_path_index, rel_path, date_modified, date_created in file_infos:
|
||||||
# TODO: Stop sending redundant information
|
# TODO: Stop sending redundant path information
|
||||||
item = {
|
item = {
|
||||||
"name": model,
|
"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),
|
"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:
|
if image is not None:
|
||||||
raw_post = os.path.join(model_type, str(base_path_index), rel_path, image)
|
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 .search-text-area,
|
||||||
.model-manager .source-text-area,
|
.model-manager .source-text-area,
|
||||||
.model-manager .model-type-dropdown {
|
.model-manager .model-select-dropdown {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,10 @@ const modelNodeType = {
|
|||||||
|
|
||||||
const DROPDOWN_DIRECTORY_SELECTION_CLASS = "search-dropdown-selected";
|
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
|
* @typedef {Object} DirectoryItem
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
@@ -594,7 +598,7 @@ class ModelGrid {
|
|||||||
static #filter(list, searchString) {
|
static #filter(list, searchString) {
|
||||||
/** @type {string[]} */
|
/** @type {string[]} */
|
||||||
const keywords = searchString
|
const keywords = searchString
|
||||||
.replace("*", " ")
|
//.replace("*", " ") // TODO: this is wrong for wildcards
|
||||||
.split(/(-?".*?"|[^\s"]+)+/g)
|
.split(/(-?".*?"|[^\s"]+)+/g)
|
||||||
.map((item) => item
|
.map((item) => item
|
||||||
.trim()
|
.trim()
|
||||||
@@ -603,7 +607,7 @@ class ModelGrid {
|
|||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
|
||||||
const regexSHA256 = /^[a-f0-9]{64}$/gi;
|
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) => {
|
return list.filter((element) => {
|
||||||
const text = fields
|
const text = fields
|
||||||
.reduce((memo, field) => memo + " " + element[field], "")
|
.reduce((memo, field) => memo + " " + element[field], "")
|
||||||
@@ -622,6 +626,33 @@ class ModelGrid {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
* @param {Event} event
|
||||||
* @param {string} modelType
|
* @param {string} modelType
|
||||||
@@ -836,10 +867,12 @@ class ModelGrid {
|
|||||||
* @param {HTMLSelectElement} modelSelect
|
* @param {HTMLSelectElement} modelSelect
|
||||||
* @param {Object.<{value: string}>} previousModelType
|
* @param {Object.<{value: string}>} previousModelType
|
||||||
* @param {Object} settings
|
* @param {Object} settings
|
||||||
|
* @param {string} sortBy
|
||||||
|
* @param {boolean} reverseSort
|
||||||
* @param {Array} previousModelFilters
|
* @param {Array} previousModelFilters
|
||||||
* @param {HTMLInputElement} modelFilter
|
* @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;
|
let modelType = modelSelect.value;
|
||||||
if (models[modelType] === undefined) {
|
if (models[modelType] === undefined) {
|
||||||
modelType = "checkpoints"; // TODO: magic value
|
modelType = "checkpoints"; // TODO: magic value
|
||||||
@@ -870,6 +903,7 @@ class ModelGrid {
|
|||||||
const searchAppend = settings["model-search-always-append"].value;
|
const searchAppend = settings["model-search-always-append"].value;
|
||||||
const searchText = modelFilter.value + " " + searchAppend;
|
const searchText = modelFilter.value + " " + searchAppend;
|
||||||
const modelList = ModelGrid.#filter(models[modelType], searchText);
|
const modelList = ModelGrid.#filter(models[modelType], searchText);
|
||||||
|
ModelGrid.#sort(modelList, sortBy, reverseSort);
|
||||||
|
|
||||||
modelGrid.innerHTML = "";
|
modelGrid.innerHTML = "";
|
||||||
const modelGridModels = ModelGrid.#generateInnerHtml(modelList, modelType, settings);
|
const modelGridModels = ModelGrid.#generateInnerHtml(modelList, modelType, settings);
|
||||||
@@ -927,6 +961,7 @@ class ModelManager extends ComfyDialog {
|
|||||||
|
|
||||||
/** @type {HTMLDivElement} */ modelGrid: null,
|
/** @type {HTMLDivElement} */ modelGrid: null,
|
||||||
/** @type {HTMLSelectElement} */ modelTypeSelect: null,
|
/** @type {HTMLSelectElement} */ modelTypeSelect: null,
|
||||||
|
/** @type {HTMLSelectElement} */ modelSortSelect: null,
|
||||||
/** @type {HTMLDivElement} */ searchDirectoryDropdown: null,
|
/** @type {HTMLDivElement} */ searchDirectoryDropdown: null,
|
||||||
/** @type {HTMLInputElement} */ modelContentFilter: null,
|
/** @type {HTMLInputElement} */ modelContentFilter: null,
|
||||||
|
|
||||||
@@ -1181,11 +1216,25 @@ class ModelManager extends ComfyDialog {
|
|||||||
textContent: "⟳",
|
textContent: "⟳",
|
||||||
onclick: () => this.#modelTab_updateModels(),
|
onclick: () => this.#modelTab_updateModels(),
|
||||||
}),
|
}),
|
||||||
$el("select.model-type-dropdown", {
|
$el("select.model-select-dropdown", {
|
||||||
$: (el) => (this.#el.modelTypeSelect = el),
|
$: (el) => (this.#el.modelTypeSelect = el),
|
||||||
name: "model-type",
|
name: "model-type",
|
||||||
onchange: () => this.#modelTab_updateModelGrid(),
|
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.row.tab-header-flex-block", [
|
||||||
$el("div.search-models", [
|
$el("div.search-models", [
|
||||||
@@ -1203,15 +1252,22 @@ class ModelManager extends ComfyDialog {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
#modelTab_updateModelGrid = () => ModelGrid.update(
|
#modelTab_updateModelGrid = () => {
|
||||||
this.#el.modelGrid,
|
const sortValue = this.#el.modelSortSelect.value;
|
||||||
this.#data.models,
|
const reverseSort = sortValue[0] === "-";
|
||||||
this.#el.modelTypeSelect,
|
const sortBy = reverseSort ? sortValue.substring(1) : sortValue;
|
||||||
this.#data.previousModelType,
|
ModelGrid.update(
|
||||||
this.#el.settings,
|
this.#el.modelGrid,
|
||||||
this.#data.previousModelFilters,
|
this.#data.models,
|
||||||
this.#el.modelContentFilter
|
this.#el.modelTypeSelect,
|
||||||
);
|
this.#data.previousModelType,
|
||||||
|
this.#el.settings,
|
||||||
|
sortBy,
|
||||||
|
reverseSort,
|
||||||
|
this.#data.previousModelFilters,
|
||||||
|
this.#el.modelContentFilter
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async #modelTab_updateModels() {
|
async #modelTab_updateModels() {
|
||||||
this.#data.models = await request("/model-manager/models");
|
this.#data.models = await request("/model-manager/models");
|
||||||
|
|||||||
Reference in New Issue
Block a user