Metaphysix2's picture
Upload folder using huggingface_hub
3e5f61c
// Imports specific objects from other modules.
import { app } from "../../scripts/app.js";
import { ttN_CreateDropdown, ttN_RemoveDropdown } from "./ttN.js";
// Initialize some global lists and objects.
let embeddingsList = [];
let embeddingFiles = [];
let embeddingsHierarchy = {};
// Convert a list of strings into a hierarchical structure.
function convertListToHierarchy(list) {
const hierarchy = {}; // Initialize an empty hierarchy object.
// Iterate over each item in the list.
for (var item of list) {
item = item.replace("embedding:", ""); // Remove any "embedding:" prefix from the item.
const parts = item.split(/:\\|\\/); // Split the item by either ':\' or '\'.
let currentNode = hierarchy; // Start at the root of the hierarchy.
// For each part of the split item...
parts.forEach((part, index) => {
// If it's the last part, set its value to null in the hierarchy.
if (index === parts.length - 1) {
currentNode[part] = null;
} else {
// Otherwise, initialize the node if it doesn't exist yet and move deeper into the hierarchy.
currentNode[part] = currentNode[part] || {};
currentNode = currentNode[part];
}
});
}
return hierarchy; // Return the filled hierarchy.
}
// Register an extension to the app.
app.registerExtension({
name: "comfy.ttN.embeddingAC",
// Before a node definition is registered...
async beforeRegisterNodeDef(nodeType, nodeData, app) {
// If the node name matches a specific type...
if (nodeData.name === "ttN pipeKSampler") {
initializeEmbeddingData(nodeData.input.hidden.embeddingsList[0]);
}
},
// When a node is created...
nodeCreated(node) {
// If the node has widgets and its title isn't "xyPlot"...
if (node.widgets && node.getTitle() !== "xyPlot") {
const relevantWidgets = filterRelevantWidgets(node.widgets); // Filter out the relevant widgets.
addInputListenersToWidgets(relevantWidgets); // Add input listeners to these widgets.
}
}
});
// Returns a list of widgets that either have type "customtext" with dynamic prompts or just have dynamic prompts.
function filterRelevantWidgets(widgets) {
return widgets.filter(widget => (widget.type === "customtext" && widget.dynamicPrompts !== false) || widget.dynamicPrompts);
}
// Adds input listeners to the given widgets.
function addInputListenersToWidgets(widgets) {
widgets.forEach(widget => {
const inputHandler = createWidgetInputHandler(widget); // Create an input handler specific for this widget.
setWidgetInputHandler(widget, inputHandler); // Set this handler to the widget.
});
}
// Returns a function that will handle the widget's input.
function createWidgetInputHandler(widget) {
return function handleInput() {
const currentWord = getCurrentWordFromInput(widget); // Get the word at the current cursor position in the widget's input.
// Check if the current word should trigger embedding suggestions...
if (shouldProvideEmbeddingSuggestion(currentWord)) {
const suggestions = filterEmbeddingsForInput(currentWord); // Get suggestions for the current word.
if (suggestions.length > 0) { // If there are suggestions...
// Convert the suggestions to a hierarchy and create a dropdown with these suggestions.
embeddingsHierarchy = convertListToHierarchy(suggestions);
ttN_CreateDropdown(widget.inputEl, embeddingsHierarchy, selectedSuggestion => {
// Update the widget's input value with the selected suggestion when one is chosen.
widget.inputEl.value = updateInputWithSuggestion(widget.inputEl.value, selectedSuggestion, widget);
}, true);
return;
}
}
// If no suggestions, remove any existing dropdown.
ttN_RemoveDropdown();
};
}
// Adds or replaces event listeners for the widget's input.
function setWidgetInputHandler(widget, handler) {
['input', 'mousedown'].forEach(event => {
// Remove any existing listeners and then add the new handler.
widget.inputEl.removeEventListener(event, handler);
widget.inputEl.addEventListener(event, handler);
});
}
// Returns the word at the current cursor position from the widget's input.
function getCurrentWordFromInput(widget) {
const cursorPosition = widget.inputEl.selectionStart;
const segments = widget.inputEl.value.split(' ');
return segments[widget.inputEl.value.substring(0, cursorPosition).split(' ').length - 1].toLowerCase();
}
// Determines if the current word should trigger embedding suggestions.
function shouldProvideEmbeddingSuggestion(word) {
const suggestionPrefix = 'embedding:';
return suggestionPrefix.startsWith(word) && word.length > 2 || word.startsWith(suggestionPrefix);
}
// Filters embeddings based on a specific word.
function filterEmbeddingsForInput(input) {
const prefixes = ['embedding', 'embeddin', 'embeddi', 'embedd', 'embed', 'embe', 'emb']
let inputLowered = input.toLowerCase();
let cleanedInput = inputLowered.replace('embedding:', '');
prefixes.forEach(prefix => {
if (inputLowered.startsWith(prefix)) {
cleanedInput = cleanedInput.replace(prefix, '');
}
})
cleanedInput = cleanedInput.replace(/\//g, "\\");
return embeddingsList.filter(embedding => {
const embeddingName = getFileName(embedding).toLowerCase();
embedding = embedding.replace('embedding:', '').toLowerCase();
if (embeddingName.startsWith(cleanedInput) || embedding.startsWith(cleanedInput) || prefixes.includes(cleanedInput)) {
return true;
}
return false
});
}
function getFileName(path) {
const parts = path.split(/[\/:\\]/); // Split the path by '/' or ':'
const fileName = parts[parts.length - 1]; // Get the last part (filename with extension)
return fileName;
}
// Updates the widget's input text with a selected suggestion.
function updateInputWithSuggestion(inputText, selectedSuggestion, widget) {
const cursorPosition = widget.inputEl.selectionStart;
const inputSegments = inputText.split(' ');
const cursorSegmentIndex = inputText.substring(0, cursorPosition).split(' ').length - 1;
if (inputSegments[cursorSegmentIndex].startsWith('emb')) {
inputSegments[cursorSegmentIndex] = 'embedding:' + selectedSuggestion;
}
return inputSegments.join(' ');
}
// Initializes data related to embeddings.
function initializeEmbeddingData(initialEmbeddingsList) {
embeddingsList = initialEmbeddingsList;
embeddingsList.forEach(embedding => {
const fileName = embedding.split('\\').slice(-1)[0];
embeddingFiles.push(fileName);
});
embeddingsList = embeddingsList.map(embedding => {
const segments = embedding.split('/');
return segments.map((segment, index) => "embedding:" + segments.slice(0, index + 1).join('/'));
}).flat();
}