Files
AEmotionStudio-ComfyUI-Magn…/src/info-panel/NodeDataExtractor.ts
T

260 lines
8.8 KiB
TypeScript

/**
* ComfyUI MagnifyGlass - Node Data Extractor
*
* Utility functions for extracting specific information from nodes.
* Extracted from UIManager.ts for better modularity.
*/
import { formatWidgetValue } from './ValueFormatter';
import type { NodeInfo } from '../types/comfyui';
/**
* Interface for extracted parameter items.
*/
export interface ParameterItem {
label: string;
value: string;
}
/**
* Get checkpoint/model info from a node.
* @param nodeInfo - Node information object
* @returns Model filename or null
*/
export function getCheckpointInfo(nodeInfo: NodeInfo): string | null {
if (nodeInfo.type && (
nodeInfo.type.includes('CheckpointLoader') ||
nodeInfo.type.includes('LoadCheckpoint') ||
nodeInfo.type.includes('ModelLoader') ||
nodeInfo.type.includes('UNETLoader') ||
nodeInfo.type.includes('VAELoader') ||
nodeInfo.type.includes('LoraLoader')
)) {
if (nodeInfo.widgets && nodeInfo.widgets.length > 0) {
for (const widget of nodeInfo.widgets) {
if (widget.name && (
widget.name.toLowerCase().includes('model') ||
widget.name.toLowerCase().includes('checkpoint') ||
widget.name.toLowerCase().includes('ckpt') ||
widget.name.toLowerCase().includes('lora') ||
widget.name.toLowerCase().includes('vae') ||
widget.name.toLowerCase().includes('file')
)) {
const value = String(widget.value);
const filename = value.split(/[\/\\]/).pop();
return filename || value;
}
}
}
}
return null;
}
/**
* Image info result type.
*/
export interface ImageInfoResult {
width: string | number;
height: string | number;
src: string;
}
/**
* Get image info from a node.
* @param nodeInfo - Node information object
* @returns Image info string, object, or null
*/
export function getImageInfo(nodeInfo: NodeInfo): string | ImageInfoResult | null {
if (nodeInfo.type && (
nodeInfo.type.includes('SaveImage') ||
nodeInfo.type.includes('PreviewImage') ||
nodeInfo.type.includes('VisionOutput') ||
nodeInfo.type.includes('ImageOutput') ||
nodeInfo.type.includes('LoadImage') ||
nodeInfo.type.includes('Display')
)) {
if (nodeInfo.widgets) {
for (const widget of nodeInfo.widgets) {
if (widget.name && (
widget.name.toLowerCase().includes('image') ||
widget.name.toLowerCase().includes('filename') ||
widget.name.toLowerCase().includes('file')
)) {
return String(widget.value);
}
}
}
if (nodeInfo.properties && (nodeInfo.properties as Record<string, unknown>).img) {
const img = (nodeInfo.properties as Record<string, unknown>).img as Record<string, unknown>;
return {
width: (img.width as string | number) || 'unknown',
height: (img.height as string | number) || 'unknown',
src: img.src ? String(img.src).split(/[\/\\]/).pop() || 'Preview available' : 'Preview available'
};
}
if (nodeInfo.outputs) {
for (const output of nodeInfo.outputs) {
if (output.links && output.links.length > 0) {
return 'Image connected to ' + output.links.length + ' node(s)';
}
}
}
return 'Image node';
}
return null;
}
/**
* Get text box content from a node.
* @param nodeInfo - Node information object
* @returns Text content or null
*/
export function getTextBoxContent(nodeInfo: NodeInfo): string | null {
if (nodeInfo.widgets && nodeInfo.widgets.length > 0) {
if (nodeInfo.type && nodeInfo.type.includes('CLIPTextEncode')) {
for (const widget of nodeInfo.widgets) {
if (widget.name === 'text' && typeof widget.value === 'string') {
return widget.value;
}
}
}
for (const widget of nodeInfo.widgets) {
if ((widget.name.toLowerCase().includes('prompt') ||
widget.name.toLowerCase().includes('conditioning')) &&
typeof widget.value === 'string' && widget.value.length > 0) {
return widget.value;
}
}
for (const widget of nodeInfo.widgets) {
if ((widget.type === 'text' || widget.type === 'textarea' ||
widget.type === 'string' || widget.name.toLowerCase().includes('text')) &&
typeof widget.value === 'string' && widget.value.length > 0) {
return widget.value;
}
}
}
return null;
}
/**
* List of important parameter names for complex nodes.
*/
const COMPLEX_NODE_PARAMS = [
'seed', 'steps', 'cfg', 'scale', 'sampler', 'scheduler',
'positive', 'negative', 'width', 'height', 'denoise', 'strength',
'noise', 'count', 'batch', 'size', 'phase', 'color', 'intensity',
'control_after_generate', 'control', 'after', 'generate',
'start_at_step', 'end_at_step', 'start', 'end',
'return_with_leftover_noise', 'leftover', 'noise_return',
'model', 'vae', 'clip', 'lora', 'checkpoint',
'latent', 'image', 'mask', 'filename', 'directory',
'prompt', 'conditioning', 'filename_prefix',
'resolution', 'num_chunks', 'seconds', 'aspect_ratio',
'style_type', 'background', 'n', 'human', 'raw', 'guidance',
'skip_preprocessing', 'movement_amplitude', 'animation',
'material_type', 'b1', 'b2', 's1', 's2', 'type', 'channel',
'sigma', 'rho', 'alpha', 'base_shift', 'shift', 'stretch', 'terminal',
'spacing', 'style', 'eta', 'norm_threshold', 'momentum',
'hypernetwork_name', 'reuse_threshold', 'verbose', 'layers',
'set_cond_area', 'audioui', 'camera_pose', 'fx', 'cx', 'fy', 'cy'
];
/**
* Parameters for save nodes.
*/
const SAVE_NODE_PARAMS = [
'filename_prefix', 'filename', 'directory', 'path',
'format', 'quality', 'extension'
];
/**
* Generic widget names to skip.
*/
const SKIP_WIDGET_NAMES = ['title', 'node', 'id', 'type', 'mode'];
/**
* Check if a node type should show all widgets.
* @param nodeType - The node type string
* @returns True if all widgets should be shown
*/
function shouldShowAllWidgets(nodeType: string): boolean {
const type = nodeType.toLowerCase();
const isSaveNode = type.includes('save') &&
!type.includes('checkpoint') &&
!type.includes('model') &&
!type.includes('preview');
return (
type.includes('ksampler') ||
type.includes('sampler') ||
type.includes('k_samplers') ||
type.includes('checkpoint') ||
type.includes('model') ||
type.includes('lora') ||
type.includes('controlnet') ||
type.includes('advanced') ||
type.includes('detailer') ||
type.includes('inpaint') ||
type.includes('upscale') ||
type.includes('clip') ||
type.includes('text') ||
type.includes('encode')
) && !isSaveNode;
}
/**
* Get important node parameters based on node type.
* @param nodeInfo - Node information object
* @returns Array of parameter items
*/
export function getImportantNodeParameters(nodeInfo: NodeInfo): ParameterItem[] {
const parameters: ParameterItem[] = [];
if (!nodeInfo.widgets || nodeInfo.widgets.length === 0) {
return parameters;
}
const nodeType = nodeInfo.type || '';
const typeLower = nodeType.toLowerCase();
const isSaveNode = typeLower.includes('save') &&
!typeLower.includes('checkpoint') &&
!typeLower.includes('model') &&
!typeLower.includes('preview');
// For complex nodes, show ALL widgets
if (nodeType && shouldShowAllWidgets(nodeType)) {
for (const widget of nodeInfo.widgets) {
if (widget.name && widget.name !== '') {
const widgetName = widget.name.toLowerCase();
if (!SKIP_WIDGET_NAMES.some(skip => widgetName.includes(skip))) {
parameters.push({
label: widget.name,
value: formatWidgetValue(widget.value)
});
}
}
}
return parameters;
}
// For other nodes, use filtered parameter lists
const importantParams = isSaveNode ? SAVE_NODE_PARAMS : COMPLEX_NODE_PARAMS;
for (const widget of nodeInfo.widgets) {
const paramName = widget.name.toLowerCase();
if (importantParams.some(param => paramName.includes(param))) {
parameters.push({
label: widget.name,
value: formatWidgetValue(widget.value)
});
}
}
return parameters;
}