added embedding autocomplete

This commit is contained in:
TinyTerra
2023-06-06 21:09:02 +02:00
parent 0ecd7c0b09
commit d8bb4faffa
5 changed files with 329 additions and 13 deletions
+13 -13
View File
@@ -7,20 +7,20 @@ app.registerExtension({
if (nodeData.name === "ttN textDebug") {
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
const r = onNodeCreated?.apply(this, arguments);
const w = ComfyWidgets["STRING"](this, "text", ["STRING", { multiline: true }], app).widget;
w.inputEl.readOnly = true;
w.inputEl.style.opacity = 0.7;
return r;
const r = onNodeCreated?.apply(this, arguments);
const w = ComfyWidgets["STRING"](this, "text", ["STRING", { multiline: true }], app).widget;
w.inputEl.readOnly = true;
w.inputEl.style.opacity = 0.7;
return r;
};
const onExecuted = nodeType.prototype.onExecuted;
nodeType.prototype.onExecuted = function (message) {
onExecuted?.apply(this, arguments);
onExecuted?.apply(this, arguments);
this.widgets[1].value = message.text.join('');
this.widgets[1].value = message.text.join('');
this.onResize?.(this.size);
this.onResize?.(this.size);
};
}
},
@@ -33,10 +33,10 @@ app.registerExtension({
nodeData.name === "ttN pipeKSampler" ||
nodeData.name === "ttN imageOutput" ||
nodeData.name === "ttN imageREMBG"
) {
) {
const onNodeCreated = nodeType.prototype.onNodeCreated;
// Simple date formatter
// Simple date formatter
const parts = {
d: (d) => d.getDate(),
M: (d) => d.getMonth() + 1,
@@ -61,7 +61,7 @@ app.registerExtension({
});
}
// When the SaveImage node is created we want to override the serialization of the output name widget to run our S&R
// When the SaveImage node is created we want to override the serialization of the output name widget to run our S&R
nodeType.prototype.onNodeCreated = function () {
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
@@ -70,7 +70,7 @@ app.registerExtension({
return widget.value.replace(/%([^%]+)%/g, function (match, text) {
const split = text.split(".");
if (split.length !== 2) {
// Special handling for dates
// Special handling for dates
if (split[0].startsWith("date:")) {
return formatDate(split[0].substring(5), new Date());
}
@@ -112,7 +112,7 @@ app.registerExtension({
return r;
};
} else {
// When any other node is created add a property to alias the node
// When any other node is created add a property to alias the node
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
+26
View File
@@ -0,0 +1,26 @@
.autocomplete-dropdown {
position: absolute;
box-sizing: border-box;
background-color: #121212;
border-radius: 7px;
box-shadow: 0 2px 4px rgba(255, 255, 255, .25);
padding: 0;
margin: 0;
list-style: none;
z-index: 1000;
overflow: auto;
max-height: 200px;
}
.autocomplete-dropdown li {
padding: 4px 10px;
cursor: pointer;
font-family: system-ui;
font-size: 0.7rem;
}
.autocomplete-dropdown li:hover,
.autocomplete-dropdown li.selected {
background-color: #e5e5e5;
border-radius: 7px;
}
+208
View File
@@ -0,0 +1,208 @@
import { app } from "/scripts/app.js";
// Create a link element for the CSS file
const cssFile = document.createElement('link');
cssFile.rel = 'stylesheet';
cssFile.type = "text/css";
cssFile.href = 'extensions/tinyterraNodes/ttNembedAC.css';
// Append the link element to the document's head
document.head.appendChild(cssFile);
let embeddingsList = [];
fetch('extensions/tinyterraNodes/embeddingsList.json')
.then(response => response.json())
.then(data => {
// Use the JSON data as a constant
embeddingsList = data
.filter(embedding => embedding !== 'put_embeddings_or_textual_inversion_concepts_here')
.map(embedding => "embedding:" + embedding); // Add "embedding:" to each element;
})
.catch(error => {
console.error('Error:', error);
});
app.registerExtension({
name: "comfy.ttN.embeddingAC",
nodeCreated(node) {
if (node.widgets) {
// Locate dynamic prompt text widgets
// Include any widgets with dynamicPrompts set to true, and customtext
const widgets = node.widgets.filter(
(n) => (n.type === "customtext" && n.dynamicPrompts !== false) || n.dynamicPrompts
);
for (const w of widgets) {
let autocompleteActive = false;
let selectedSuggestionIndex = -1;
const autocompleteId = 'autocomplete-dropdown';
const onInput = function () {
const inputText = w.inputEl.value;
const cursorPosition = w.inputEl.selectionStart;
const inputSegments = inputText.split(' ');
const cursorSegmentIndex = inputText.substring(0, cursorPosition).split(' ').length - 1;
const currentSegment = inputSegments[cursorSegmentIndex];
const currentSegmentLower = currentSegment.toLowerCase();
const suggestionkey = 'embedding:';
if (suggestionkey.startsWith(currentSegmentLower) && currentSegmentLower.length > 2 || currentSegmentLower.startsWith(suggestionkey)) {
const filteredEmbeddingsList = embeddingsList.filter(s => s.includes(currentSegment));
if (filteredEmbeddingsList.length > 0) {
displayEmbeddingsList(filteredEmbeddingsList);
autocompleteActive = true;
} else {
hideEmbeddingsList();
autocompleteActive = false;
}
} else {
hideEmbeddingsList();
autocompleteActive = false;
}
};
w.inputEl.removeEventListener('input', onInput);
w.inputEl.addEventListener('input', onInput);
const onKeyDown = function (event) {
const tabKeyCode = 9;
const enterKeyCode = 13;
const escKeyCode = 27;
const arrowUpKeyCode = 38;
const arrowDownKeyCode = 40;
if (event.keyCode === tabKeyCode && autocompleteActive) {
event.preventDefault();
if (selectedSuggestionIndex !== -1) {
const selectedSuggestion = document.getElementById('autocomplete-item-' + selectedSuggestionIndex);
const newText = replaceLastEmbeddingSegment(w.inputEl.value, selectedSuggestion.textContent);
w.inputEl.value = newText;
}
hideEmbeddingsList();
shouldRemoveDropdown = false;
} else if (event.keyCode === enterKeyCode && autocompleteActive) {
event.preventDefault();
if (selectedSuggestionIndex !== -1) {
const selectedSuggestion = document.getElementById('autocomplete-item-' + selectedSuggestionIndex);
const newText = replaceLastEmbeddingSegment(w.inputEl.value, selectedSuggestion.textContent);
w.inputEl.value = newText;
}
hideEmbeddingsList();
shouldRemoveDropdown = false;
} else if (event.keyCode === arrowUpKeyCode && autocompleteActive) {
event.preventDefault();
if (selectedSuggestionIndex > 0) {
selectedSuggestionIndex--;
highlightSuggestion(selectedSuggestionIndex);
}
} else if (event.keyCode === arrowDownKeyCode && autocompleteActive) {
event.preventDefault();
if (selectedSuggestionIndex < embeddingsList.length - 1) {
selectedSuggestionIndex++;
highlightSuggestion(selectedSuggestionIndex);
}
} else if (event.keyCode === escKeyCode && autocompleteActive) {
event.preventDefault();
hideEmbeddingsList();
}
};
w.inputEl.removeEventListener('keydown', onKeyDown);
w.inputEl.addEventListener('keydown', onKeyDown);
let shouldRemoveDropdown = false;
let autocompleteDropdown = null;
function displayEmbeddingsList(filteredEmbeddingsList) {
hideEmbeddingsList();
const dropdown = document.createElement('ul');
dropdown.setAttribute('id', autocompleteId);
dropdown.setAttribute('role', 'listbox');
dropdown.classList.add('autocomplete-dropdown');
filteredEmbeddingsList.forEach((suggestion, index) => {
const listItem = document.createElement('li');
listItem.setAttribute('id', 'autocomplete-item-' + index);
listItem.setAttribute('role', 'option');
listItem.textContent = suggestion;
listItem.addEventListener('mouseover', function () {
highlightSuggestion(index);
});
listItem.addEventListener('mousedown', function (event) {
event.preventDefault();
const newText = replaceLastEmbeddingSegment(w.inputEl.value, suggestion);
w.inputEl.value = newText;
hideEmbeddingsList();
shouldRemoveDropdown = false;
});
dropdown.appendChild(listItem);
});
const inputRect = w.inputEl.getBoundingClientRect();
dropdown.style.top = (inputRect.top + inputRect.height) + 'px';
dropdown.style.left = inputRect.left + 'px';
dropdown.style.width = inputRect.width + 'px';
document.body.appendChild(dropdown);
autocompleteDropdown = dropdown;
shouldRemoveDropdown = true;
}
document.addEventListener('click', function (event) {
if (shouldRemoveDropdown && autocompleteDropdown && !autocompleteDropdown.contains(event.target)) {
hideEmbeddingsList();
shouldRemoveDropdown = false;
autocompleteDropdown = null;
}
});
function hideEmbeddingsList() {
const dropdown = document.getElementById(autocompleteId);
if (dropdown) {
dropdown.remove();
}
selectedSuggestionIndex = -1;
}
let previousSuggestionIndex = -1;
function highlightSuggestion(index) {
const selectedSuggestion = document.getElementById('autocomplete-item-' + index);
const previousSelectedSuggestion = document.getElementById('autocomplete-item-' + previousSuggestionIndex);
if (previousSelectedSuggestion) {
previousSelectedSuggestion.classList.remove('selected');
}
if (selectedSuggestion) {
selectedSuggestion.classList.add('selected');
previousSuggestionIndex = index;
selectedSuggestionIndex = index;
}
}
function replaceLastEmbeddingSegment(inputText, selectedSuggestion) {
const cursorPosition = w.inputEl.selectionStart;
const inputSegments = inputText.split(' ');
const cursorSegmentIndex = inputText.substring(0, cursorPosition).split(' ').length - 1;
if (inputSegments[cursorSegmentIndex].startsWith('emb')) {
inputSegments[cursorSegmentIndex] = selectedSuggestion;
}
return inputSegments.join(' ');
}
}
}
}
});
+10
View File
@@ -34,5 +34,15 @@ app.registerExtension({
return r;
};
}
if (nodeData.name == "ttN hiresfixScale") {
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
const r = onNodeCreated?.apply(this, arguments);
this.color=LGraphCanvas.node_colors.cyan.color;
this.bgcolor=LGraphCanvas.node_colors.cyan.bgcolor;
this.groupcolor = LGraphCanvas.node_colors.cyan.groupcolor;
return r;
};
}
}
});
+72
View File
@@ -0,0 +1,72 @@
# in_dev - likely broken
class ttN_debugInput:
@classmethod
def INPUT_TYPES(s):
return {"required": {"console_title": ("STRING", {"default": "ttN INPUT DEBUG"}),},
"optional": {"debug": ("", {"default": None}),}
}
RETURN_TYPES = tuple()
RETURN_NAMES = tuple()
FUNCTION = "debug"
CATEGORY = "ttN/dev"
OUTPUT_NODE = True
def debug(_, **kwargs):
for key, value in kwargs.items():
if key == "console_title":
print(value)
else:
print(f"{key}: {value}")
return tuple()
class ttN_busIN:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"lane_0": ("",),
}}
RETURN_TYPES = ("BUS_LINE",)
RETURN_NAMES = ("bus_line",)
FUNCTION = "roundnround"
CATEGORY = "ttN/dev"
@staticmethod
def roundnround(*args, **kwargs):
bus_line = []
for key, value in kwargs.items():
bus_line.append(value)
print("busIN + kw:--",tuple(bus_line))
return (tuple(bus_line),)
class ttN_busOUT:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"bus_line": ("BUS_LINE",),
}}
RETURN_TYPES = ()
FUNCTION = "roundnround"
CATEGORY = "ttN/dev"
@staticmethod
def roundnround(bus_line):
print("busOUT:--",bus_line)
return (bus_line,)
NODE_CLASS_MAPPINGS = {
"ttN debugInput": ttN_debugInput,
"ttN busIN": ttN_busIN,
"ttN busOUT": ttN_busOUT
}
NODE_DISPLAY_NAME_MAPPINGS = {
"ttN debugInput": "debugInput",
"ttN busIN": "busIN",
"ttN busOUT": "busOUT"
}