added embedding autocomplete
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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(' ');
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user