Compare commits

..
4 changed files with 72 additions and 76 deletions
+11 -19
View File
@@ -235,16 +235,7 @@ class Downloader:
def _check_new_csv_from_hf_dataset(self, dataset_meta: dict, now_utc: datetime, force_check: bool = False): def _check_new_csv_from_hf_dataset(self, dataset_meta: dict, now_utc: datetime, force_check: bool = False):
"""Checks HuggingFace for file updates and updates metadata.""" """Checks HuggingFace for file updates and updates metadata."""
perform_hf_check = True perform_hf_check = force_check
if not force_check and dataset_meta.get("last_remote_check_timestamp"):
try:
last_check_dt = datetime.fromisoformat(dataset_meta["last_remote_check_timestamp"])
if now_utc - last_check_dt < timedelta(days=7):
perform_hf_check = False
except (ValueError, KeyError, TypeError):
print(
"[Autocomplete-Plus] Invalid or missing timestamp for last_remote_check_timestamp. Will perform remote check."
)
if perform_hf_check: if perform_hf_check:
huggingface_dataset_id = dataset_meta["hf_dataset_id"] huggingface_dataset_id = dataset_meta["hf_dataset_id"]
@@ -303,15 +294,16 @@ class Downloader:
return f"File {file_name} is missing or empty locally." return f"File {file_name} is missing or empty locally."
# Check if the last modified date on HuggingFace is newer than the last download date # Check if the last modified date on HuggingFace is newer than the last download date
try: # Temporarily comment out until changes to remote files can be detected
last_download_dt = datetime.fromisoformat(file_meta_entry["last_download"]) # try:
hf_modified_dt = datetime.fromisoformat(file_meta_entry["last_modified_on_hf"]) # last_download_dt = datetime.fromisoformat(file_meta_entry["last_download"])
if hf_modified_dt > last_download_dt: # hf_modified_dt = datetime.fromisoformat(file_meta_entry["last_modified_on_hf"])
return f"Remote file {file_name} is newer (HF: {last_download_dt}, Local Download: {hf_modified_dt})." # if hf_modified_dt > last_download_dt:
except (ValueError, TypeError): # return f"Remote file {file_name} is newer (HF: {hf_modified_dt}, Local Download: {last_download_dt})."
file_meta_entry["last_download"] = None # except (ValueError, TypeError):
file_meta_entry["last_modified_on_hf"] = None # file_meta_entry["last_download"] = None
return f"Invalid timestamp format for {file_name}. Forcing download to ensure integrity." # file_meta_entry["last_modified_on_hf"] = None
# return f"Invalid timestamp format for {file_name}. Forcing download to ensure integrity."
# If the file is missing or empty, but the last download timestamp exists, we need to retry. # If the file is missing or empty, but the last download timestamp exists, we need to retry.
if not check_file_valid(local_file_path) and file_meta_entry.get("last_download") is not None: if not check_file_valid(local_file_path) and file_meta_entry.get("last_download") is not None:
+1 -1
View File
@@ -1,7 +1,7 @@
[project] [project]
name = "comfyui-autocomplete-plus" name = "comfyui-autocomplete-plus"
description = "Autocomplete and Related Tag display for ComfyUI" description = "Autocomplete and Related Tag display for ComfyUI"
version = "1.4.0" version = "1.4.1"
license = {file = "LICENSE"} license = {file = "LICENSE"}
dependencies = ["",] dependencies = ["",]
+26 -23
View File
@@ -154,6 +154,7 @@ async function loadTags(csvUrl, siteName) {
} }
const csvText = await response.text(); const csvText = await response.text();
const lines = csvText.split('\n').filter(line => line.trim().length > 0); const lines = csvText.split('\n').filter(line => line.trim().length > 0);
const totalLines = lines.length;
const startIndex = lines[0].toLowerCase().startsWith(TAGS_CSV_HEADER) ? 1 : 0; const startIndex = lines[0].toLowerCase().startsWith(TAGS_CSV_HEADER) ? 1 : 0;
@@ -183,8 +184,18 @@ async function loadTags(csvUrl, siteName) {
updateMaxTagLength(tag.length); updateMaxTagLength(tag.length);
autoCompleteData[siteName].sortedTags.push(tagData); autoCompleteData[siteName].sortedTags.push(tagData);
} else {
console.warn(`[Autocomplete-Plus] Invalid CSV format in line ${i + 1} of ${csvUrl}: ${line}. Expected ${TAGS_CSV_HEADER_COLUMNS.length} columns, but got ${columns.length}.`);
continue;
}
}
// Set the tag and its alias in the maps // Sort by count in descending order
autoCompleteData[siteName].sortedTags.sort((a, b) => b.count - a.count);
// Build maps as before, but ensure not to overwrite if already processed from extra files
autoCompleteData[siteName].sortedTags.forEach(tagData => {
if (!autoCompleteData[siteName].tagMap.has(tagData.tag)) {
autoCompleteData[siteName].tagMap.set(tagData.tag, tagData); autoCompleteData[siteName].tagMap.set(tagData.tag, tagData);
if (tagData.alias && Array.isArray(tagData.alias)) { if (tagData.alias && Array.isArray(tagData.alias)) {
tagData.alias.forEach(alias => { tagData.alias.forEach(alias => {
@@ -193,11 +204,8 @@ async function loadTags(csvUrl, siteName) {
} }
}); });
} }
} else {
console.warn(`[Autocomplete-Plus] Invalid CSV format in line ${i + 1} of ${csvUrl}: ${line}. Expected ${TAGS_CSV_HEADER_COLUMNS.length} columns, but got ${columns.length}.`);
continue;
} }
} });
} catch (error) { } catch (error) {
console.error(`[Autocomplete-Plus] Failed to fetch or process tags from ${csvUrl}:`, error); console.error(`[Autocomplete-Plus] Failed to fetch or process tags from ${csvUrl}:`, error);
@@ -268,17 +276,17 @@ async function loadCooccurrence(csvUrl, siteName) {
const startIndex = lines[0].startsWith('tag_a,tag_b,count') ? 1 : 0; const startIndex = lines[0].startsWith('tag_a,tag_b,count') ? 1 : 0;
await processCooccurrenceInChunks(lines, startIndex, autoCompleteData[siteName].cooccurrenceMap, csvUrl, siteName); await processInChunks(lines, startIndex, autoCompleteData[siteName].cooccurrenceMap, csvUrl, siteName);
} catch (error) { } catch (error) {
console.error(`[Autocomplete-Plus] Failed to fetch or process cooccurrence data from ${csvUrl}:`, error); console.error(`[Autocomplete-Plus] Failed to fetch or process cooccurrence data from ${csvUrl}:`, error);
} }
} }
/** /**
* Process Co-Occurrence CSV data in chunks to avoid blocking the UI. * Process CSV data in chunks to avoid blocking the UI.
* Modifies the targetMap directly. * Modifies the targetMap directly.
*/ */
function processCooccurrenceInChunks(lines, startIndex, targetMap, csvUrl, siteName) { function processInChunks(lines, startIndex, targetMap, csvUrl, siteName) {
return new Promise((resolve) => { return new Promise((resolve) => {
const CHUNK_SIZE = 10000; const CHUNK_SIZE = 10000;
let i = startIndex; let i = startIndex;
@@ -289,7 +297,7 @@ function processCooccurrenceInChunks(lines, startIndex, targetMap, csvUrl, siteN
for (; i < endIndex; i++) { for (; i < endIndex; i++) {
const line = lines[i]; const line = lines[i];
const columns = line.split(","); const columns = parseCSVLine(line);
if (columns.length >= 3) { if (columns.length >= 3) {
const tagA = columns[0].trim(); const tagA = columns[0].trim();
@@ -299,19 +307,17 @@ function processCooccurrenceInChunks(lines, startIndex, targetMap, csvUrl, siteN
if (!tagA || !tagB || isNaN(count)) continue; if (!tagA || !tagB || isNaN(count)) continue;
// Add tagA -> tagB relationship // Add tagA -> tagB relationship
let subMapA = targetMap.get(tagA); if (!targetMap.has(tagA)) {
if (!subMapA) { targetMap.set(tagA, new Map());
subMapA = new Map();
targetMap.set(tagA, subMapA);
} }
subMapA.set(tagB, count); targetMap.get(tagA).set(tagB, count);
let subMapB = targetMap.get(tagB);
if (!subMapB) { // Add tagB -> tagA relationship (bidirectional)
subMapB = new Map(); if (!targetMap.has(tagB)) {
targetMap.set(tagB, subMapB); targetMap.set(tagB, new Map());
} }
subMapB.set(tagA, count); targetMap.get(tagB).set(tagA, count);
pairCount++; pairCount++;
} }
@@ -444,9 +450,6 @@ async function initializeDataFromCSV(csvListData, source) {
await Promise.all([ await Promise.all([
Promise.all(tagsLoadPromiseFactories.map(factory => factory())) Promise.all(tagsLoadPromiseFactories.map(factory => factory()))
.then(() => { .then(() => {
// Sort by count in descending order
autoCompleteData[source].sortedTags.sort((a, b) => b.count - a.count);
// Build FlexSearch index after tags are loaded // Build FlexSearch index after tags are loaded
return buildFlexSearchIndex(source); return buildFlexSearchIndex(source);
}) })
@@ -543,7 +546,7 @@ async function loadLoras() {
} }
/** /**
* Load all data sources in parallel. * Load all data sources asynchronously.
*/ */
export async function loadDataAsync() { export async function loadDataAsync() {
return Promise.all([ return Promise.all([
+34 -33
View File
@@ -190,40 +190,41 @@ async function addExtraSettings() {
} }
// Add extra setting for checking new CSV updates // Add extra setting for checking new CSV updates
app.ui.settings.addSetting({ // Note: Temporarily comment out until changes to remote files can be detected
id: id + ".check_new_csv", // app.ui.settings.addSetting({
defaultValue: null, // id: id + ".check_new_csv",
name: "Check CSV updates", // defaultValue: null,
category: [name, "Misc", "Check new CSV"], // name: "Check CSV updates",
type: () => { // category: [name, "Misc", "Check new CSV"],
const lastCheckSpan = $el("span", { // type: () => {
textContent: lastCheckTimeText, // const lastCheckSpan = $el("span", {
className: "text-sm text-gray-500", // textContent: lastCheckTimeText,
style: { // className: "text-sm text-gray-500",
marginRight: "16px" // style: {
} // marginRight: "16px"
}); // }
// });
const checkButton = $el("button", { // const checkButton = $el("button", {
textContent: "Check now", // textContent: "Check now",
className: "p-button p-component p-button-primary", // className: "p-button p-component p-button-primary",
onclick: async () => { // onclick: async () => {
await performUpdateCheck(checkButton, lastCheckSpan); // await performUpdateCheck(checkButton, lastCheckSpan);
} // }
}); // });
return $el("div", { // return $el("div", {
className: "flex-row items-center gap-2", // className: "flex-row items-center gap-2",
}, [ // }, [
$el("div", { // $el("div", {
className: "p-component", // className: "p-component",
}, [ // }, [
lastCheckSpan, // lastCheckSpan,
checkButton, // checkButton,
]), // ]),
]); // ]);
} // }
}); // });
} }
/** /**
@@ -240,7 +241,7 @@ app.registerExtension({
let rootPath = import.meta.url.replace("js/main.js", ""); let rootPath = import.meta.url.replace("js/main.js", "");
loadCSS(rootPath + "css/autocomplete-plus.css"); // Load CSS for autocomplete loadCSS(rootPath + "css/autocomplete-plus.css"); // Load CSS for autocomplete
loadDataAsync(); await loadDataAsync();
}, },
// One the Settings Screen, displays reverse order in same category // One the Settings Screen, displays reverse order in same category