Add support for simple tooltips (#3842)
* Add support for simple tooltips * Fix overflow * Add tooltips for nodes in the default workflow * new line * Prevent potential crash * PR feedback * Hide tooltip when clicking (e.g. combo widget) * Refactor tooltips, add node level support * Fix * move * Fix test (and undo last change) * Fixed indent * Fix dom widgets, dont show tooltip if not over canvas
This commit is contained in:
parent
a5af64d3ce
commit
e60e19b175
103
nodes.py
103
nodes.py
|
@ -47,11 +47,18 @@ MAX_RESOLUTION=16384
|
||||||
class CLIPTextEncode:
|
class CLIPTextEncode:
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
def INPUT_TYPES(s):
|
||||||
return {"required": {"text": ("STRING", {"multiline": True, "dynamicPrompts": True}), "clip": ("CLIP", )}}
|
return {
|
||||||
|
"required": {
|
||||||
|
"text": ("STRING", {"multiline": True, "dynamicPrompts": True, "tooltip": "The text to be encoded."}),
|
||||||
|
"clip": ("CLIP", {"tooltip": "The CLIP model used for encoding the text."})
|
||||||
|
}
|
||||||
|
}
|
||||||
RETURN_TYPES = ("CONDITIONING",)
|
RETURN_TYPES = ("CONDITIONING",)
|
||||||
|
OUTPUT_TOOLTIPS = ("A conditioning containing the embedded text used to guide the diffusion model.",)
|
||||||
FUNCTION = "encode"
|
FUNCTION = "encode"
|
||||||
|
|
||||||
CATEGORY = "conditioning"
|
CATEGORY = "conditioning"
|
||||||
|
DESCRIPTION = "Encodes a text prompt using a CLIP model into an embedding that can be used to guide the diffusion model towards generating specific images."
|
||||||
|
|
||||||
def encode(self, clip, text):
|
def encode(self, clip, text):
|
||||||
tokens = clip.tokenize(text)
|
tokens = clip.tokenize(text)
|
||||||
|
@ -260,11 +267,18 @@ class ConditioningSetTimestepRange:
|
||||||
class VAEDecode:
|
class VAEDecode:
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
def INPUT_TYPES(s):
|
||||||
return {"required": { "samples": ("LATENT", ), "vae": ("VAE", )}}
|
return {
|
||||||
|
"required": {
|
||||||
|
"samples": ("LATENT", {"tooltip": "The latent to be decoded."}),
|
||||||
|
"vae": ("VAE", {"tooltip": "The VAE model used for decoding the latent."})
|
||||||
|
}
|
||||||
|
}
|
||||||
RETURN_TYPES = ("IMAGE",)
|
RETURN_TYPES = ("IMAGE",)
|
||||||
|
OUTPUT_TOOLTIPS = ("The decoded image.",)
|
||||||
FUNCTION = "decode"
|
FUNCTION = "decode"
|
||||||
|
|
||||||
CATEGORY = "latent"
|
CATEGORY = "latent"
|
||||||
|
DESCRIPTION = "Decodes latent images back into pixel space images."
|
||||||
|
|
||||||
def decode(self, vae, samples):
|
def decode(self, vae, samples):
|
||||||
return (vae.decode(samples["samples"]), )
|
return (vae.decode(samples["samples"]), )
|
||||||
|
@ -506,12 +520,19 @@ class CheckpointLoader:
|
||||||
class CheckpointLoaderSimple:
|
class CheckpointLoaderSimple:
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
def INPUT_TYPES(s):
|
||||||
return {"required": { "ckpt_name": (folder_paths.get_filename_list("checkpoints"), ),
|
return {
|
||||||
}}
|
"required": {
|
||||||
|
"ckpt_name": (folder_paths.get_filename_list("checkpoints"), {"tooltip": "The name of the checkpoint (model) to load."}),
|
||||||
|
}
|
||||||
|
}
|
||||||
RETURN_TYPES = ("MODEL", "CLIP", "VAE")
|
RETURN_TYPES = ("MODEL", "CLIP", "VAE")
|
||||||
|
OUTPUT_TOOLTIPS = ("The model used for denoising latents.",
|
||||||
|
"The CLIP model used for encoding text prompts.",
|
||||||
|
"The VAE model used for encoding and decoding images to and from latent space.")
|
||||||
FUNCTION = "load_checkpoint"
|
FUNCTION = "load_checkpoint"
|
||||||
|
|
||||||
CATEGORY = "loaders"
|
CATEGORY = "loaders"
|
||||||
|
DESCRIPTION = "Loads a diffusion model checkpoint, diffusion models are used to denoise latents."
|
||||||
|
|
||||||
def load_checkpoint(self, ckpt_name):
|
def load_checkpoint(self, ckpt_name):
|
||||||
ckpt_path = folder_paths.get_full_path("checkpoints", ckpt_name)
|
ckpt_path = folder_paths.get_full_path("checkpoints", ckpt_name)
|
||||||
|
@ -582,16 +603,22 @@ class LoraLoader:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
def INPUT_TYPES(s):
|
||||||
return {"required": { "model": ("MODEL",),
|
return {
|
||||||
"clip": ("CLIP", ),
|
"required": {
|
||||||
"lora_name": (folder_paths.get_filename_list("loras"), ),
|
"model": ("MODEL", {"tooltip": "The diffusion model the LoRA will be applied to."}),
|
||||||
"strength_model": ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01}),
|
"clip": ("CLIP", {"tooltip": "The CLIP model the LoRA will be applied to."}),
|
||||||
"strength_clip": ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01}),
|
"lora_name": (folder_paths.get_filename_list("loras"), {"tooltip": "The name of the LoRA."}),
|
||||||
}}
|
"strength_model": ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01, "tooltip": "How strongly to modify the diffusion model. This value can be negative."}),
|
||||||
|
"strength_clip": ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01, "tooltip": "How strongly to modify the CLIP model. This value can be negative."}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RETURN_TYPES = ("MODEL", "CLIP")
|
RETURN_TYPES = ("MODEL", "CLIP")
|
||||||
|
OUTPUT_TOOLTIPS = ("The modified diffusion model.", "The modified CLIP model.")
|
||||||
FUNCTION = "load_lora"
|
FUNCTION = "load_lora"
|
||||||
|
|
||||||
CATEGORY = "loaders"
|
CATEGORY = "loaders"
|
||||||
|
DESCRIPTION = "LoRAs are used to modify diffusion and CLIP models, altering the way in which latents are denoised such as applying styles. Multiple LoRA nodes can be linked together."
|
||||||
|
|
||||||
def load_lora(self, model, clip, lora_name, strength_model, strength_clip):
|
def load_lora(self, model, clip, lora_name, strength_model, strength_clip):
|
||||||
if strength_model == 0 and strength_clip == 0:
|
if strength_model == 0 and strength_clip == 0:
|
||||||
|
@ -1033,13 +1060,19 @@ class EmptyLatentImage:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
def INPUT_TYPES(s):
|
||||||
return {"required": { "width": ("INT", {"default": 512, "min": 16, "max": MAX_RESOLUTION, "step": 8}),
|
return {
|
||||||
"height": ("INT", {"default": 512, "min": 16, "max": MAX_RESOLUTION, "step": 8}),
|
"required": {
|
||||||
"batch_size": ("INT", {"default": 1, "min": 1, "max": 4096})}}
|
"width": ("INT", {"default": 512, "min": 16, "max": MAX_RESOLUTION, "step": 8, "tooltip": "The width of the latent images in pixels."}),
|
||||||
|
"height": ("INT", {"default": 512, "min": 16, "max": MAX_RESOLUTION, "step": 8, "tooltip": "The height of the latent images in pixels."}),
|
||||||
|
"batch_size": ("INT", {"default": 1, "min": 1, "max": 4096, "tooltip": "The number of latent images in the batch."})
|
||||||
|
}
|
||||||
|
}
|
||||||
RETURN_TYPES = ("LATENT",)
|
RETURN_TYPES = ("LATENT",)
|
||||||
|
OUTPUT_TOOLTIPS = ("The empty latent image batch.",)
|
||||||
FUNCTION = "generate"
|
FUNCTION = "generate"
|
||||||
|
|
||||||
CATEGORY = "latent"
|
CATEGORY = "latent"
|
||||||
|
DESCRIPTION = "Create a new batch of empty latent images to be denoised via sampling."
|
||||||
|
|
||||||
def generate(self, width, height, batch_size=1):
|
def generate(self, width, height, batch_size=1):
|
||||||
latent = torch.zeros([batch_size, 4, height // 8, width // 8], device=self.device)
|
latent = torch.zeros([batch_size, 4, height // 8, width // 8], device=self.device)
|
||||||
|
@ -1359,24 +1392,27 @@ def common_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive,
|
||||||
class KSampler:
|
class KSampler:
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
def INPUT_TYPES(s):
|
||||||
return {"required":
|
return {
|
||||||
{"model": ("MODEL",),
|
"required": {
|
||||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
"model": ("MODEL", {"tooltip": "The model used for denoising the input latent."}),
|
||||||
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "The random seed used for creating the noise."}),
|
||||||
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step":0.1, "round": 0.01}),
|
"steps": ("INT", {"default": 20, "min": 1, "max": 10000, "tooltip": "The number of steps used in the denoising process."}),
|
||||||
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, ),
|
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step":0.1, "round": 0.01, "tooltip": "The Classifier-Free Guidance scale balances creativity and adherence to the prompt. Higher values result in images more closely matching the prompt however too high values will negatively impact quality."}),
|
||||||
"scheduler": (comfy.samplers.KSampler.SCHEDULERS, ),
|
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, {"tooltip": "The algorithm used when sampling, this can affect the quality, speed, and style of the generated output."}),
|
||||||
"positive": ("CONDITIONING", ),
|
"scheduler": (comfy.samplers.KSampler.SCHEDULERS, {"tooltip": "The scheduler controls how noise is gradually removed to form the image."}),
|
||||||
"negative": ("CONDITIONING", ),
|
"positive": ("CONDITIONING", {"tooltip": "The conditioning describing the attributes you want to include in the image."}),
|
||||||
"latent_image": ("LATENT", ),
|
"negative": ("CONDITIONING", {"tooltip": "The conditioning describing the attributes you want to exclude from the image."}),
|
||||||
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
"latent_image": ("LATENT", {"tooltip": "The latent image to denoise."}),
|
||||||
}
|
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": "The amount of denoising applied, lower values will maintain the structure of the initial image allowing for image to image sampling."}),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RETURN_TYPES = ("LATENT",)
|
RETURN_TYPES = ("LATENT",)
|
||||||
|
OUTPUT_TOOLTIPS = ("The denoised latent.",)
|
||||||
FUNCTION = "sample"
|
FUNCTION = "sample"
|
||||||
|
|
||||||
CATEGORY = "sampling"
|
CATEGORY = "sampling"
|
||||||
|
DESCRIPTION = "Uses the provided model, positive and negative conditioning to denoise the latent image."
|
||||||
|
|
||||||
def sample(self, model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise=1.0):
|
def sample(self, model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise=1.0):
|
||||||
return common_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise=denoise)
|
return common_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise=denoise)
|
||||||
|
@ -1424,11 +1460,15 @@ class SaveImage:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
def INPUT_TYPES(s):
|
||||||
return {"required":
|
return {
|
||||||
{"images": ("IMAGE", ),
|
"required": {
|
||||||
"filename_prefix": ("STRING", {"default": "ComfyUI"})},
|
"images": ("IMAGE", {"tooltip": "The images to save."}),
|
||||||
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
|
"filename_prefix": ("STRING", {"default": "ComfyUI", "tooltip": "The prefix for the file to save. This may include formatting information such as %date:yyyy-MM-dd% or %Empty Latent Image.width% to include values from nodes."})
|
||||||
}
|
},
|
||||||
|
"hidden": {
|
||||||
|
"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
RETURN_TYPES = ()
|
RETURN_TYPES = ()
|
||||||
FUNCTION = "save_images"
|
FUNCTION = "save_images"
|
||||||
|
@ -1436,6 +1476,7 @@ class SaveImage:
|
||||||
OUTPUT_NODE = True
|
OUTPUT_NODE = True
|
||||||
|
|
||||||
CATEGORY = "image"
|
CATEGORY = "image"
|
||||||
|
DESCRIPTION = "Saves the input images to your ComfyUI output directory."
|
||||||
|
|
||||||
def save_images(self, images, filename_prefix="ComfyUI", prompt=None, extra_pnginfo=None):
|
def save_images(self, images, filename_prefix="ComfyUI", prompt=None, extra_pnginfo=None):
|
||||||
filename_prefix += self.prefix_append
|
filename_prefix += self.prefix_append
|
||||||
|
|
|
@ -438,6 +438,9 @@ class PromptServer():
|
||||||
|
|
||||||
if hasattr(obj_class, 'CATEGORY'):
|
if hasattr(obj_class, 'CATEGORY'):
|
||||||
info['category'] = obj_class.CATEGORY
|
info['category'] = obj_class.CATEGORY
|
||||||
|
|
||||||
|
if hasattr(obj_class, 'OUTPUT_TOOLTIPS'):
|
||||||
|
info['output_tooltips'] = obj_class.OUTPUT_TOOLTIPS
|
||||||
return info
|
return info
|
||||||
|
|
||||||
@routes.get("/object_info")
|
@routes.get("/object_info")
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
import { app } from "../../scripts/app.js";
|
||||||
|
import { $el } from "../../scripts/ui.js";
|
||||||
|
|
||||||
|
// Adds support for tooltips
|
||||||
|
|
||||||
|
function getHoveredWidget() {
|
||||||
|
if (!app) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const node = app.canvas.node_over;
|
||||||
|
if (!node.widgets) return;
|
||||||
|
|
||||||
|
const graphPos = app.canvas.graph_mouse;
|
||||||
|
|
||||||
|
const x = graphPos[0] - node.pos[0];
|
||||||
|
const y = graphPos[1] - node.pos[1];
|
||||||
|
|
||||||
|
for (const w of node.widgets) {
|
||||||
|
let widgetWidth, widgetHeight;
|
||||||
|
if (w.computeSize) {
|
||||||
|
const sz = w.computeSize();
|
||||||
|
widgetWidth = sz[0];
|
||||||
|
widgetHeight = sz[1];
|
||||||
|
} else {
|
||||||
|
widgetWidth = w.width || node.size[0];
|
||||||
|
widgetHeight = LiteGraph.NODE_WIDGET_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (w.last_y !== undefined && x >= 6 && x <= widgetWidth - 12 && y >= w.last_y && y <= w.last_y + widgetHeight) {
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.registerExtension({
|
||||||
|
name: "Comfy.Tooltips",
|
||||||
|
setup() {
|
||||||
|
const tooltipEl = $el("div.comfy-graph-tooltip", {
|
||||||
|
parent: document.body,
|
||||||
|
});
|
||||||
|
let idleTimeout;
|
||||||
|
|
||||||
|
const hideTooltip = () => {
|
||||||
|
tooltipEl.style.display = "none";
|
||||||
|
};
|
||||||
|
const showTooltip = (tooltip) => {
|
||||||
|
if (!tooltip) return;
|
||||||
|
|
||||||
|
tooltipEl.textContent = tooltip;
|
||||||
|
tooltipEl.style.display = "block";
|
||||||
|
tooltipEl.style.left = app.canvas.mouse[0] + "px";
|
||||||
|
tooltipEl.style.top = app.canvas.mouse[1] + "px";
|
||||||
|
const rect = tooltipEl.getBoundingClientRect();
|
||||||
|
if (rect.right > window.innerWidth) {
|
||||||
|
tooltipEl.style.left = app.canvas.mouse[0] - rect.width + "px";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rect.top < 0) {
|
||||||
|
tooltipEl.style.top = app.canvas.mouse[1] + rect.height + "px";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const getInputTooltip = (nodeData, name) => {
|
||||||
|
const inputDef = nodeData.input?.required?.[name] ?? nodeData.input?.optional?.[name];
|
||||||
|
return inputDef?.[1]?.tooltip;
|
||||||
|
};
|
||||||
|
const onIdle = () => {
|
||||||
|
const { canvas } = app;
|
||||||
|
const node = canvas.node_over;
|
||||||
|
if (!node) return;
|
||||||
|
|
||||||
|
const nodeData = node.constructor.nodeData ?? {};
|
||||||
|
|
||||||
|
if (node.constructor.title_mode !== LiteGraph.NO_TITLE && canvas.graph_mouse[1] < node.pos[1]) {
|
||||||
|
return showTooltip(nodeData.description);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.flags?.collapsed) return;
|
||||||
|
|
||||||
|
const inputSlot = canvas.isOverNodeInput(node, canvas.graph_mouse[0], canvas.graph_mouse[1], [0, 0]);
|
||||||
|
if (inputSlot !== -1) {
|
||||||
|
const inputName = node.inputs[inputSlot].name;
|
||||||
|
return showTooltip(getInputTooltip(nodeData, inputName));
|
||||||
|
}
|
||||||
|
|
||||||
|
const outputSlot = canvas.isOverNodeOutput(node, canvas.graph_mouse[0], canvas.graph_mouse[1], [0, 0]);
|
||||||
|
if (outputSlot !== -1) {
|
||||||
|
return showTooltip(nodeData.output_tooltips?.[outputSlot]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const widget = getHoveredWidget();
|
||||||
|
// Dont show for DOM widgets, these use native browser tooltips as we dont get proper mouse events on these
|
||||||
|
if (widget && !widget.element) {
|
||||||
|
return showTooltip(widget.tooltip ?? getInputTooltip(nodeData, widget.name));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseMove = (e) => {
|
||||||
|
hideTooltip();
|
||||||
|
clearTimeout(idleTimeout);
|
||||||
|
|
||||||
|
if(e.target.nodeName !== "CANVAS") return
|
||||||
|
idleTimeout = setTimeout(onIdle, 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
app.ui.settings.addSetting({
|
||||||
|
id: "Comfy.EnableTooltips",
|
||||||
|
name: "Enable Tooltips",
|
||||||
|
type: "boolean",
|
||||||
|
defaultValue: true,
|
||||||
|
onChange(value) {
|
||||||
|
if (value) {
|
||||||
|
window.addEventListener("mousemove", onMouseMove);
|
||||||
|
window.addEventListener("click", hideTooltip);
|
||||||
|
} else {
|
||||||
|
window.removeEventListener("mousemove", onMouseMove);
|
||||||
|
window.removeEventListener("click", hideTooltip);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
|
@ -181,7 +181,7 @@ export function mergeIfValid(output, config2, forceUpdate, recreateWidget, confi
|
||||||
|
|
||||||
const isNumber = config1[0] === "INT" || config1[0] === "FLOAT";
|
const isNumber = config1[0] === "INT" || config1[0] === "FLOAT";
|
||||||
for (const k of keys.values()) {
|
for (const k of keys.values()) {
|
||||||
if (k !== "default" && k !== "forceInput" && k !== "defaultInput" && k !== "control_after_generate" && k !== "multiline") {
|
if (k !== "default" && k !== "forceInput" && k !== "defaultInput" && k !== "control_after_generate" && k !== "multiline" && k !== "tooltip") {
|
||||||
let v1 = config1[1][k];
|
let v1 = config1[1][k];
|
||||||
let v2 = config2[1]?.[k];
|
let v2 = config2[1]?.[k];
|
||||||
|
|
||||||
|
|
|
@ -1713,9 +1713,10 @@ export class ComfyApp {
|
||||||
for (const o in nodeData["output"]) {
|
for (const o in nodeData["output"]) {
|
||||||
let output = nodeData["output"][o];
|
let output = nodeData["output"][o];
|
||||||
if(output instanceof Array) output = "COMBO";
|
if(output instanceof Array) output = "COMBO";
|
||||||
|
const outputTooltip = nodeData["output_tooltips"]?.[o];
|
||||||
const outputName = nodeData["output_name"][o] || output;
|
const outputName = nodeData["output_name"][o] || output;
|
||||||
const outputShape = nodeData["output_is_list"][o] ? LiteGraph.GRID_SHAPE : LiteGraph.CIRCLE_SHAPE ;
|
const outputShape = nodeData["output_is_list"][o] ? LiteGraph.GRID_SHAPE : LiteGraph.CIRCLE_SHAPE ;
|
||||||
this.addOutput(outputName, output, { shape: outputShape });
|
this.addOutput(outputName, output, { shape: outputShape, tooltip: outputTooltip });
|
||||||
}
|
}
|
||||||
|
|
||||||
const s = this.computeSize();
|
const s = this.computeSize();
|
||||||
|
|
|
@ -223,6 +223,12 @@ LGraphNode.prototype.addDOMWidget = function (name, type, element, options) {
|
||||||
document.addEventListener("mousedown", mouseDownHandler);
|
document.addEventListener("mousedown", mouseDownHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { nodeData } = this.constructor;
|
||||||
|
const tooltip = (nodeData?.input.required?.[name] ?? nodeData?.input.optional?.[name])?.[1]?.tooltip;
|
||||||
|
if (tooltip && !element.title) {
|
||||||
|
element.title = tooltip;
|
||||||
|
}
|
||||||
|
|
||||||
const widget = {
|
const widget = {
|
||||||
type,
|
type,
|
||||||
name,
|
name,
|
||||||
|
|
|
@ -75,6 +75,7 @@ export function addValueControlWidgets(node, targetWidget, defaultValue = "rando
|
||||||
serialize: false, // Don't include this in prompt.
|
serialize: false, // Don't include this in prompt.
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
valueControl.tooltip = "Allows the linked widget to be changed automatically, for example randomizing the noise seed.";
|
||||||
valueControl[IS_CONTROL_WIDGET] = true;
|
valueControl[IS_CONTROL_WIDGET] = true;
|
||||||
updateControlWidgetLabel(valueControl);
|
updateControlWidgetLabel(valueControl);
|
||||||
widgets.push(valueControl);
|
widgets.push(valueControl);
|
||||||
|
@ -95,6 +96,7 @@ export function addValueControlWidgets(node, targetWidget, defaultValue = "rando
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
updateControlWidgetLabel(comboFilter);
|
updateControlWidgetLabel(comboFilter);
|
||||||
|
comboFilter.tooltip = "Allows for filtering the list of values when changing the value via the control generate mode. Allows for RegEx matches in the format /abc/ to only filter to values containing 'abc'."
|
||||||
|
|
||||||
widgets.push(comboFilter);
|
widgets.push(comboFilter);
|
||||||
}
|
}
|
||||||
|
|
|
@ -645,3 +645,20 @@ dialog::backdrop {
|
||||||
audio.comfy-audio.empty-audio-widget {
|
audio.comfy-audio.empty-audio-widget {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.comfy-graph-tooltip {
|
||||||
|
background: var(--comfy-input-bg);
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0 0 5px rgba(0, 0, 0, 0.4);
|
||||||
|
color: var(--input-text);
|
||||||
|
display: none;
|
||||||
|
font-family: sans-serif;
|
||||||
|
left: 0;
|
||||||
|
max-width: 30vw;
|
||||||
|
padding: 4px 8px;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(5px, calc(-100% - 5px));
|
||||||
|
white-space: pre-wrap;
|
||||||
|
z-index: 99999;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue