cleanup changes
This commit is contained in:
+1
-1
@@ -45,7 +45,7 @@ from .nodes.masks import *
|
||||
|
||||
# Import API endpoints to register routes
|
||||
from . import api
|
||||
from .utility.rgthree_api import routes_model_info
|
||||
from .utility.wanvid_api import routes_model_info
|
||||
from .nodes import trigger_ref_refresh # Register PrepareRefs backend trigger endpoint
|
||||
|
||||
NODE_CONFIG = {}
|
||||
|
||||
@@ -447,11 +447,24 @@ async def rename_lora(request) -> web.Response:
|
||||
if os.path.exists(preview_folder):
|
||||
renamed_files.extend(_rename_preview_files(old_base_name, new_name, preview_folder))
|
||||
|
||||
# Compute the ComfyUI-relative name (e.g. "subdir/name.safetensors") for
|
||||
# UI use - widgets store partial paths, not full filesystem paths.
|
||||
new_relative_path = new_lora_path
|
||||
for folder in folders_to_check:
|
||||
try:
|
||||
rel = os.path.relpath(new_lora_path, folder)
|
||||
if not rel.startswith('..'):
|
||||
new_relative_path = rel.replace(os.sep, '/')
|
||||
break
|
||||
except ValueError:
|
||||
# Different drive letters on Windows, skip this folder
|
||||
continue
|
||||
|
||||
return web.json_response({
|
||||
"success": True,
|
||||
"message": f"Successfully renamed LoRA and {len(renamed_files)-1} associated files",
|
||||
"new_high_path": new_lora_path,
|
||||
"new_low_path": new_lora_path, # For high variant, low path is the same
|
||||
"new_high_path": new_relative_path,
|
||||
"new_low_path": new_relative_path, # For high variant, low path is the same
|
||||
"renamed_files": renamed_files
|
||||
})
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ class LoraReduceRank:
|
||||
EXPERIMENTAL = True
|
||||
DESCRIPTION = "Resize a LoRA model by reducing it's rank. Based on kohya's sd-scripts."
|
||||
|
||||
CATEGORY = "KJNodes/lora"
|
||||
CATEGORY = "QQ/lora"
|
||||
|
||||
def save(self, lora_name, new_rank, output_dtype, dynamic_method, dynamic_param, verbose):
|
||||
|
||||
@@ -110,7 +110,7 @@ class LoraRemapStrength:
|
||||
RETURN_TYPES = ()
|
||||
FUNCTION = "save"
|
||||
OUTPUT_NODE = True
|
||||
CATEGORY = "KJNodes/lora"
|
||||
CATEGORY = "QQ/lora"
|
||||
|
||||
def save(self, lora_name, reference_strength, target_strength, create_missing_alpha, output_dtype):
|
||||
print(f"[LoraRemapStrength DEBUG] Starting with inputs:")
|
||||
|
||||
@@ -271,7 +271,7 @@ class LoraSmartCombine:
|
||||
RETURN_TYPES = ()
|
||||
FUNCTION = "combine"
|
||||
OUTPUT_NODE = True
|
||||
CATEGORY = "KJNodes/lora"
|
||||
CATEGORY = "QQ/lora"
|
||||
DESCRIPTION = "Smartly combine multiple LoRAs with rank extension using SVD decomposition"
|
||||
|
||||
def combine(self, lora_1_name, lora_2_name, lora_3_name, lora_4_name,
|
||||
|
||||
@@ -379,7 +379,7 @@ class LoraExtractKJv2:
|
||||
RETURN_TYPES = ()
|
||||
FUNCTION = "save"
|
||||
OUTPUT_NODE = True
|
||||
CATEGORY = "KJNodes/lora"
|
||||
CATEGORY = "QQ/lora"
|
||||
|
||||
def save(self, finetuned_model, original_model, filename_prefix, rank, lora_type, algorithm,
|
||||
lowrank_iters, output_dtype, bias_diff, adaptive_param, clamp_quantile,
|
||||
|
||||
@@ -46,7 +46,7 @@ class LoadModelMeta:
|
||||
|
||||
RETURN_TYPES = ("MODEL",)
|
||||
FUNCTION = "load_checkpoint"
|
||||
CATEGORY = "KJNodes/ltxv"
|
||||
CATEGORY = "QQ/ltxv"
|
||||
|
||||
def load_checkpoint(self, checkpoint_name):
|
||||
"""
|
||||
@@ -156,7 +156,7 @@ class LTX2LoRASelect:
|
||||
|
||||
RETURN_TYPES = ("MODEL",)
|
||||
FUNCTION = "apply_lora"
|
||||
CATEGORY = "KJNodes/ltxv"
|
||||
CATEGORY = "QQ/ltxv"
|
||||
|
||||
def apply_lora(
|
||||
self,
|
||||
@@ -357,7 +357,7 @@ class LoraMergeLTX:
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("merged_lora_path",)
|
||||
FUNCTION = "merge_loras"
|
||||
CATEGORY = "KJNodes/ltxv"
|
||||
CATEGORY = "QQ/ltxv"
|
||||
OUTPUT_NODE = True
|
||||
|
||||
def merge_loras(self, model_with_loras, output_prefix, rank, output_dir, merge_on_model):
|
||||
@@ -622,7 +622,7 @@ class LoraApplyOnLTX:
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("saved_checkpoint_path",)
|
||||
FUNCTION = "apply_lora_to_model"
|
||||
CATEGORY = "KJNodes/ltxv"
|
||||
CATEGORY = "QQ/ltxv"
|
||||
OUTPUT_NODE = True
|
||||
|
||||
def apply_lora_to_model(self, model_with_loras, output_prefix, output_dir):
|
||||
|
||||
@@ -90,7 +90,7 @@ class PowerCompareVideo:
|
||||
"""
|
||||
Playback preview node: receives an IMAGE batch (video frames) and sends
|
||||
them to the client UI for timeline-based playback with a sliding A/B
|
||||
comparison (rgthree Image Comparer style).
|
||||
comparison (Image Comparer style).
|
||||
|
||||
Inputs:
|
||||
images - IMAGE tensor [frame_count, height, width, 3] (e.g. from PowerLoadVideo).
|
||||
|
||||
+8
-38
@@ -59,7 +59,7 @@ any_type = AnyType("*")
|
||||
|
||||
|
||||
def get_lora_by_filename_basic(file_path, lora_paths=None, log_node=None):
|
||||
"""Basic implementation of LoRA path resolution based on rgthree-comfy's get_lora_by_filename."""
|
||||
"""Basic LoRA path resolution helper."""
|
||||
if lora_paths is None:
|
||||
lora_paths = folder_paths.get_filename_list("loras")
|
||||
|
||||
@@ -124,7 +124,7 @@ def get_lora_by_filename_basic(file_path, lora_paths=None, log_node=None):
|
||||
|
||||
|
||||
class WanVideoPowerLoraLoader:
|
||||
"""A power lora loader for WanVideo that supports unlimited loras with rgthree-style UI."""
|
||||
"""A power lora loader for WanVideo that supports unlimited loras with a custom-styled UI."""
|
||||
|
||||
def __init__(self):
|
||||
self.properties = {
|
||||
@@ -150,7 +150,7 @@ class WanVideoPowerLoraLoader:
|
||||
RETURN_NAMES = ("selected_lora", "low_lora")
|
||||
FUNCTION = "load_loras"
|
||||
CATEGORY = "WanVideoWrapper_QQ/utils"
|
||||
DESCRIPTION = "A powerful, flexible node to load multiple WanVideo loras with rgthree-style UI"
|
||||
DESCRIPTION = "A powerful, flexible node to load multiple WanVideo loras with a custom-styled UI"
|
||||
|
||||
def load_loras(self, prev_lora=None, blocks=None, **kwargs):
|
||||
"""Loops over the provided loras in kwargs and applies valid ones."""
|
||||
@@ -188,13 +188,7 @@ class WanVideoPowerLoraLoader:
|
||||
if not lora_name or lora_name.lower() == "none":
|
||||
continue
|
||||
|
||||
# Use the sophisticated LoRA path resolution from rgthree-comfy
|
||||
try:
|
||||
from rgthree_comfy.py.power_prompt_utils import get_lora_by_filename
|
||||
lora_filename = get_lora_by_filename(lora_name, log_node=self.__class__.__name__)
|
||||
except ImportError:
|
||||
# Fallback to basic path resolution if rgthree-comfy is not available
|
||||
lora_filename = get_lora_by_filename_basic(lora_name, log_node=self.__class__.__name__)
|
||||
lora_filename = get_lora_by_filename_basic(lora_name, log_node=self.__class__.__name__)
|
||||
|
||||
if lora_filename is None:
|
||||
print(f"[WanVideoPowerLoraLoader] LoRA '{lora_name}' not found, skipping.")
|
||||
@@ -314,13 +308,7 @@ class WanVideoPowerLoraLoader:
|
||||
if low_strength is None:
|
||||
low_strength = 0.0
|
||||
|
||||
# Use the sophisticated LoRA path resolution for low variant
|
||||
try:
|
||||
from rgthree_comfy.py.power_prompt_utils import get_lora_by_filename
|
||||
low_variant_filename = get_lora_by_filename(low_variant_name, log_node=self.__class__.__name__)
|
||||
except ImportError:
|
||||
# Fallback to basic path resolution if rgthree-comfy is not available
|
||||
low_variant_filename = get_lora_by_filename_basic(low_variant_name, log_node=self.__class__.__name__)
|
||||
low_variant_filename = get_lora_by_filename_basic(low_variant_name, log_node=self.__class__.__name__)
|
||||
|
||||
if low_variant_filename is None:
|
||||
print(f"[WanVideoPowerLoraLoader] Low variant LoRA '{low_variant_name}' not found, skipping low variant.")
|
||||
@@ -426,13 +414,7 @@ class PowerLoraLoaderV2:
|
||||
if not lora_name or lora_name.lower() == "none":
|
||||
continue
|
||||
|
||||
# Use the sophisticated LoRA path resolution from rgthree-comfy
|
||||
try:
|
||||
from rgthree_comfy.py.power_prompt_utils import get_lora_by_filename
|
||||
lora_filename = get_lora_by_filename(lora_name, log_node=self.__class__.__name__)
|
||||
except ImportError:
|
||||
# Fallback to basic path resolution if rgthree-comfy is not available
|
||||
lora_filename = get_lora_by_filename_basic(lora_name, log_node=self.__class__.__name__)
|
||||
lora_filename = get_lora_by_filename_basic(lora_name, log_node=self.__class__.__name__)
|
||||
|
||||
if lora_filename is None:
|
||||
# Try to get the full path directly from ComfyUI as a fallback
|
||||
@@ -573,13 +555,7 @@ class PowerLoraLoaderV2:
|
||||
if low_strength is None:
|
||||
low_strength = 0.0
|
||||
|
||||
# Use the sophisticated LoRA path resolution for low variant
|
||||
try:
|
||||
from rgthree_comfy.py.power_prompt_utils import get_lora_by_filename
|
||||
low_variant_filename = get_lora_by_filename(low_variant_name, log_node=self.__class__.__name__)
|
||||
except ImportError:
|
||||
# Fallback to basic path resolution if rgthree-comfy is not available
|
||||
low_variant_filename = get_lora_by_filename_basic(low_variant_name, log_node=self.__class__.__name__)
|
||||
low_variant_filename = get_lora_by_filename_basic(low_variant_name, log_node=self.__class__.__name__)
|
||||
|
||||
if low_variant_filename is None:
|
||||
print(f"[PowerLoraLoaderV2] Low variant LoRA '{low_variant_name}' not found, skipping low variant.")
|
||||
@@ -689,13 +665,7 @@ class SimplePowerLoraLoader:
|
||||
if not lora_name or lora_name.lower() == "none":
|
||||
continue
|
||||
|
||||
# Use the sophisticated LoRA path resolution from rgthree-comfy
|
||||
try:
|
||||
from rgthree_comfy.py.power_prompt_utils import get_lora_by_filename
|
||||
lora_filename = get_lora_by_filename(lora_name, log_node=self.__class__.__name__)
|
||||
except ImportError:
|
||||
# Fallback to basic path resolution if rgthree-comfy is not available
|
||||
lora_filename = get_lora_by_filename_basic(lora_name, log_node=self.__class__.__name__)
|
||||
lora_filename = get_lora_by_filename_basic(lora_name, log_node=self.__class__.__name__)
|
||||
|
||||
if lora_filename is None:
|
||||
# Try to get the full path directly from ComfyUI as a fallback
|
||||
|
||||
@@ -128,7 +128,7 @@ async def get_model_info(
|
||||
should_save = False
|
||||
# basic data
|
||||
basic_data = get_file_info(file, model_type)
|
||||
# Try to load a rgthree-info.json file next to the file.
|
||||
# Try to load the model info json file next to the file.
|
||||
info_data = get_model_info_file_data(file, model_type, default={})
|
||||
|
||||
for key in ['file', 'path', 'modified', 'imageLocal', 'hasInfoFile']:
|
||||
@@ -196,7 +196,7 @@ async def get_model_info(
|
||||
save_model_info(file, info_data, model_type)
|
||||
|
||||
# If we're saving, then the UI is likely waiting to see if the refreshed data is coming in.
|
||||
await PromptServer.instance.send(f"rgthree-refreshed-{model_type}-info", {"data": info_data})
|
||||
await PromptServer.instance.send(f"wanvid-refreshed-{model_type}-info", {"data": info_data})
|
||||
|
||||
return info_data
|
||||
|
||||
@@ -25,12 +25,12 @@ def is_param_truthy(request, param):
|
||||
def set_default_page_resources(path, routes):
|
||||
""" Sets up routes for handling static files under a path."""
|
||||
|
||||
@routes.get(f'/rgthree/{path}/{{file}}')
|
||||
@routes.get(f'/wanvid/{path}/{{file}}')
|
||||
async def get_resource(request):
|
||||
""" Returns a resource file. """
|
||||
return web.FileResponse(os.path.join(DIR_WEB, path, request.match_info['file']))
|
||||
|
||||
@routes.get(f'/rgthree/{path}/{{subdir}}/{{file}}')
|
||||
@routes.get(f'/wanvid/{path}/{{subdir}}/{{file}}')
|
||||
async def get_resource_subdir(request):
|
||||
""" Returns a resource file. """
|
||||
return web.FileResponse(
|
||||
@@ -38,14 +38,14 @@ def set_default_page_resources(path, routes):
|
||||
|
||||
|
||||
def set_default_page_routes(path, routes):
|
||||
""" Sets default path handling for a hosted rgthree page. """
|
||||
""" Sets default path handling for a hosted page. """
|
||||
|
||||
@routes.get(f'/rgthree/{path}')
|
||||
@routes.get(f'/wanvid/{path}')
|
||||
async def get_path_redir(request):
|
||||
""" Redirects to the path adding a trailing slash. """
|
||||
raise web.HTTPFound(f'{request.path}/')
|
||||
|
||||
@routes.get(f'/rgthree/{path}/')
|
||||
@routes.get(f'/wanvid/{path}/')
|
||||
async def get_path_index(request):
|
||||
""" Handles the page's index loading. """
|
||||
html = ''
|
||||
@@ -351,8 +351,7 @@ function getComfyDefaultLinkColor(canvas) {
|
||||
function linkHighlighted(canvas, targetNode, sourceNode) {
|
||||
return Boolean(
|
||||
targetNode?.selected || sourceNode?.selected ||
|
||||
canvas?.selectedItems?.has?.(targetNode) || canvas?.selectedItems?.has?.(sourceNode) ||
|
||||
canvas?.selected_nodes?.[targetNode?.id] || canvas?.selected_nodes?.[sourceNode?.id]
|
||||
canvas?.selectedItems?.has?.(targetNode) || canvas?.selectedItems?.has?.(sourceNode)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
* the top-left corner of its video's region in the stitched output.
|
||||
*/
|
||||
import { app } from '../../../scripts/app.js';
|
||||
import { RgthreeBaseWidget } from '../power_spline_editor/drawing_utils.js';
|
||||
import { QQBaseWidget } from '../power_spline_editor/drawing_utils.js';
|
||||
|
||||
export class PowerCompareRowWidget extends RgthreeBaseWidget {
|
||||
export class PowerCompareRowWidget extends QQBaseWidget {
|
||||
constructor(name = "PowerCompareRow") {
|
||||
super(name);
|
||||
this.type = "custom";
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*
|
||||
* When a second video is available (previous run's cache, or the images_b
|
||||
* input), the row above the playback area offers three compare modes:
|
||||
* - slide (default): rgthree-Image-Comparer-style slider on the playback
|
||||
* - slide (default): Image-Comparer-style slider on the playback
|
||||
* area - the divider follows the mouse, revealing B on the left and
|
||||
* keeping A on the right
|
||||
* - right: B placed on the right side of A
|
||||
@@ -290,7 +290,7 @@ app.registerExtension({
|
||||
};
|
||||
|
||||
// Left-to-right sliding compare: the divider follows the mouse while
|
||||
// it moves over the playback area (borrowed from rgthree comparer).
|
||||
// it moves over the playback area (borrowed from qq comparer).
|
||||
// Only active in slide mode with a second video present.
|
||||
const updateSplitFromMouse = (e) => {
|
||||
if ((this.compareMode || 'slide') !== 'slide') return;
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
* CropOverlayWidget — draggable/resizable crop box.
|
||||
* Draws the green overlay on the CropCanvas during mouse interaction.
|
||||
*/
|
||||
import { RgthreeBaseWidget } from '../power_spline_editor/drawing_utils.js';
|
||||
import { QQBaseWidget } from '../power_spline_editor/drawing_utils.js';
|
||||
|
||||
export class CropOverlayWidget extends RgthreeBaseWidget {
|
||||
export class CropOverlayWidget extends QQBaseWidget {
|
||||
constructor(name = "crop_overlay") {
|
||||
super(name);
|
||||
this.type = 'custom';
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
import { app } from '../../../scripts/app.js';
|
||||
import {
|
||||
drawWidgetButton,
|
||||
RgthreeBaseWidget,
|
||||
QQBaseWidget,
|
||||
} from '../power_spline_editor/drawing_utils.js';
|
||||
|
||||
export class PowerCropTopRowWidget extends RgthreeBaseWidget {
|
||||
export class PowerCropTopRowWidget extends QQBaseWidget {
|
||||
constructor(name = "PowerCropTopRow") {
|
||||
super(name);
|
||||
this.type = "custom";
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
*/
|
||||
|
||||
import { app } from '../../../scripts/app.js';
|
||||
import { RgthreeBaseWidget } from '../power_spline_editor/drawing_utils.js';
|
||||
import { QQBaseWidget } from '../power_spline_editor/drawing_utils.js';
|
||||
|
||||
export class BoxCropWidget extends RgthreeBaseWidget {
|
||||
export class BoxCropWidget extends QQBaseWidget {
|
||||
constructor(name = "box_crop") {
|
||||
super(name);
|
||||
this.type = 'custom';
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
*/
|
||||
import { api } from '../../../scripts/api.js';
|
||||
import { app } from '../../../scripts/app.js';
|
||||
import { RgthreeBaseWidget, fitString } from '../power_spline_editor/drawing_utils.js';
|
||||
import { QQBaseWidget, fitString } from '../power_spline_editor/drawing_utils.js';
|
||||
|
||||
export class PowerLoadVideoFileSelectorWidget extends RgthreeBaseWidget {
|
||||
export class PowerLoadVideoFileSelectorWidget extends QQBaseWidget {
|
||||
constructor(node) {
|
||||
super("PowerLoadVideoFileSelector");
|
||||
this.type = "custom";
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
* Timeline Widget for video playback control
|
||||
* Styled to match Power Spline Editor's timeline aesthetic
|
||||
*/
|
||||
import { RgthreeBaseWidget } from '../power_spline_editor/drawing_utils.js';
|
||||
import { QQBaseWidget } from '../power_spline_editor/drawing_utils.js';
|
||||
|
||||
export class PowerLoadVideoTimelineWidget extends RgthreeBaseWidget {
|
||||
export class PowerLoadVideoTimelineWidget extends QQBaseWidget {
|
||||
constructor(name = "PowerLoadVideoTimeline") {
|
||||
super(name);
|
||||
this.type = "custom";
|
||||
@@ -602,7 +602,7 @@ export class PowerLoadVideoTimelineWidget extends RgthreeBaseWidget {
|
||||
|
||||
/**
|
||||
* Drag to seek along timeline
|
||||
* Called by base class RgthreeBaseWidget when mouse is dragged over hit area
|
||||
* Called by base class QQBaseWidget when mouse is dragged over hit area
|
||||
* Uses direct position calculation for smooth real-time scrubbing
|
||||
*/
|
||||
dragSeek(event, pos, node) {
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
* Similar to Power Spline Editor's top row with Refresh button and text inputs
|
||||
*/
|
||||
import { app } from '../../../scripts/app.js';
|
||||
import { RgthreeBaseWidget } from '../power_spline_editor/drawing_utils.js';
|
||||
import { QQBaseWidget } from '../power_spline_editor/drawing_utils.js';
|
||||
|
||||
export class PowerLoadVideoTopRowWidget extends RgthreeBaseWidget {
|
||||
export class PowerLoadVideoTopRowWidget extends QQBaseWidget {
|
||||
constructor(name = "PowerLoadVideoTopRow") {
|
||||
super(name);
|
||||
this.type = "custom";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Import necessary modules
|
||||
import { generatePreviewFromFirstImage } from "./image_utils.js";
|
||||
import { rgthreeApi } from "./rgthree_api.js";
|
||||
import { vidApi } from "./wanvid_api.js";
|
||||
|
||||
/**
|
||||
* Batch Fetch Service - Handles bulk LoRA info fetching and preview generation
|
||||
@@ -160,7 +160,6 @@ export class BatchFetchService {
|
||||
if (isVideo) {
|
||||
hasVideos = true;
|
||||
videoCount++;
|
||||
console.log(`🎥 Skipping video preview: ${image.url}`);
|
||||
|
||||
// Only skip if SKIP_VIDEO_PREVIEWS is enabled
|
||||
if (!BatchFetchService.BATCH_CONFIG.SKIP_VIDEO_PREVIEWS) {
|
||||
@@ -172,7 +171,6 @@ export class BatchFetchService {
|
||||
}
|
||||
|
||||
if (hasVideos) {
|
||||
console.log(`ℹ️ Found ${videoCount} video preview(s), ${BatchFetchService.BATCH_CONFIG.SKIP_VIDEO_PREVIEWS ? 'skipping' : 'including'} them`);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -197,7 +195,6 @@ export class BatchFetchService {
|
||||
|
||||
return response.ok;
|
||||
} catch (error) {
|
||||
console.log(`Error checking placeholder preview for ${loraName}:`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -210,7 +207,6 @@ export class BatchFetchService {
|
||||
async createPlaceholderPreview(loraName) {
|
||||
try {
|
||||
const cleanLoraName = loraName.replace(/\.[^/.]+$/, ""); // Remove extension
|
||||
console.log(`📋 Creating placeholder preview for ${loraName} from no_preview.jpg`);
|
||||
|
||||
// Fetch the no_preview.jpg placeholder
|
||||
const placeholderResponse = await fetch('/wanvid/api/loras/preview?file=no_preview.jpg');
|
||||
@@ -237,7 +233,6 @@ export class BatchFetchService {
|
||||
}
|
||||
|
||||
const result = await saveResponse.json();
|
||||
console.log(`✓ Created placeholder preview for ${loraName}:`, result);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Failed to create placeholder preview for ${loraName}:`, error);
|
||||
@@ -262,7 +257,6 @@ export class BatchFetchService {
|
||||
updateProgress(overallProgress);
|
||||
updateText(`Processing ${loraName} (${index + 1}/${total})`);
|
||||
|
||||
console.log(`[${index + 1}/${total}] Processing ${loraName}`);
|
||||
|
||||
// Stage 1: Fetch LoRA info with retry logic
|
||||
const loraInfo = await this.fetchLoraInfoWithRetry(loraName, checkCancelled);
|
||||
@@ -279,7 +273,6 @@ export class BatchFetchService {
|
||||
const imageFilter = this.filterVideoImages(loraInfo);
|
||||
|
||||
if (!imageFilter.hasImages) {
|
||||
console.log(`ℹ️ ${loraName}: Only video previews found (${imageFilter.videoCount} videos), skipping to prevent timeouts`);
|
||||
return {
|
||||
success: true,
|
||||
loraName,
|
||||
@@ -299,7 +292,6 @@ export class BatchFetchService {
|
||||
};
|
||||
|
||||
updateText(`Generating preview for ${loraName} (${index + 1}/${total})`);
|
||||
console.log(`[${index + 1}/${total}] Generating preview for ${loraName} (${imageFilter.filteredImages.length} images, skipped ${imageFilter.videoCount} videos)`);
|
||||
|
||||
previewResult = await this.generatePreviewsWithRetry(filteredLoraInfo, checkCancelled);
|
||||
|
||||
@@ -308,7 +300,6 @@ export class BatchFetchService {
|
||||
throw new Error('Operation cancelled');
|
||||
}
|
||||
} else {
|
||||
console.log(`ℹ No images found for ${loraName}, will create placeholder preview to prevent refetching`);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -324,7 +315,6 @@ export class BatchFetchService {
|
||||
|
||||
} catch (error) {
|
||||
if (error.message === 'Operation cancelled') {
|
||||
console.log(`[${loraName}] Processing cancelled`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -449,7 +439,6 @@ export class BatchFetchService {
|
||||
existing.push(loraName);
|
||||
} else if (hasPlaceholder) {
|
||||
withPlaceholderPreviews.push(loraName);
|
||||
console.log(`ℹ️ ${loraName}: Has placeholder preview, already processed`);
|
||||
} else {
|
||||
missing.push(loraName);
|
||||
}
|
||||
@@ -457,7 +446,6 @@ export class BatchFetchService {
|
||||
|
||||
const processedTotal = existing.length + withPlaceholderPreviews.length;
|
||||
|
||||
console.log(`LoRA Preview Status: ${missing.length} missing, ${existing.length} have existing previews, ${withPlaceholderPreviews.length} have placeholder previews`);
|
||||
|
||||
switch (filterType) {
|
||||
case 'missing':
|
||||
@@ -514,7 +502,6 @@ export class BatchFetchService {
|
||||
|
||||
for (let attempt = 1; attempt <= maxRetries + 1; attempt++) {
|
||||
try {
|
||||
console.log(`[${loraName}] Fetching info from Civitai API (attempt ${attempt}/${maxRetries + 1})...`);
|
||||
|
||||
// Check for cancellation before starting API call
|
||||
if (this.cancelFlag || checkCancelled()) {
|
||||
@@ -525,7 +512,7 @@ export class BatchFetchService {
|
||||
const adaptiveTimeout = baseTimeout * Math.pow(backoffMultiplier, attempt - 1);
|
||||
|
||||
// Use cancellable promise wrapper with adaptive timeout
|
||||
const apiPromise = rgthreeApi.refreshSingleLoraInfo(loraName);
|
||||
const apiPromise = vidApi.refreshSingleLoraInfo(loraName);
|
||||
const result = await this.createCancellablePromise(
|
||||
apiPromise,
|
||||
adaptiveTimeout,
|
||||
@@ -537,13 +524,11 @@ export class BatchFetchService {
|
||||
throw new Error('Operation cancelled');
|
||||
}
|
||||
|
||||
console.log(`[${loraName}] Successfully fetched info from Civitai (attempt ${attempt})`);
|
||||
|
||||
// Check if we got image data
|
||||
if (result && result.length > 0) {
|
||||
const loraInfo = result[0];
|
||||
if (loraInfo.images && loraInfo.images.length > 0) {
|
||||
console.log(`[${loraName}] Found ${loraInfo.images.length} images for preview generation`);
|
||||
} else {
|
||||
console.warn(`[${loraName}] No images found in fetched data for preview generation`);
|
||||
}
|
||||
@@ -558,7 +543,6 @@ export class BatchFetchService {
|
||||
const isCancelled = error.message === 'Operation cancelled' || error.message === 'Operation timeout';
|
||||
|
||||
if (isCancelled) {
|
||||
console.log(`[${loraName}] Fetch cancelled/timeout on attempt ${attempt}`);
|
||||
throw new Error('Operation cancelled');
|
||||
}
|
||||
|
||||
@@ -605,7 +589,6 @@ export class BatchFetchService {
|
||||
|
||||
for (let attempt = 1; attempt <= maxRetries + 1; attempt++) {
|
||||
try {
|
||||
console.log(`[${loraName}] Generating previews (attempt ${attempt}/${maxRetries + 1}) with params:`, { loraName, loraPath });
|
||||
|
||||
// Check for cancellation before starting preview generation
|
||||
if (this.cancelFlag || checkCancelled()) {
|
||||
@@ -628,7 +611,6 @@ export class BatchFetchService {
|
||||
throw new Error('Operation cancelled');
|
||||
}
|
||||
|
||||
console.log(`[${loraName}] Preview generation successful on attempt ${attempt}:`, previewResult);
|
||||
return previewResult;
|
||||
|
||||
} catch (error) {
|
||||
@@ -636,7 +618,6 @@ export class BatchFetchService {
|
||||
const isCancelled = error.message === 'Operation cancelled' || error.message === 'Operation timeout';
|
||||
|
||||
if (isCancelled) {
|
||||
console.log(`[${loraName}] Preview generation cancelled/timeout on attempt ${attempt}`);
|
||||
throw new Error('Operation cancelled');
|
||||
}
|
||||
|
||||
@@ -686,25 +667,14 @@ export class BatchFetchService {
|
||||
const filteredData = await this.filterLorasByPreviewStatus(loras, hasPreviewCallback, filterType);
|
||||
const { loras: lorasToFetch, skipped, stats } = filteredData;
|
||||
|
||||
// Log current configuration for performance tuning
|
||||
const config = BatchFetchService.getConfig();
|
||||
console.log(`Batch fetch configuration:`, {
|
||||
batchSize: config.BATCH_SIZE,
|
||||
interLoraDelay: config.INTER_LORA_DELAY,
|
||||
interBatchDelay: config.INTER_BATCH_DELAY,
|
||||
adaptiveDelay: config.ADAPTIVE_DELAY_ENABLED
|
||||
});
|
||||
|
||||
console.log(`Starting ${stats.type} fetch: ${stats.fetching} LoRAs to fetch, ${stats.skipped} LoRAs skipped`);
|
||||
|
||||
if (lorasToFetch.length === 0) {
|
||||
console.log(`No LoRAs to fetch for ${filterType} mode`);
|
||||
return { success: true, message: 'No LoRAs to process', stats };
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`Starting PARALLEL batch processing of ${lorasToFetch.length} LoRAs`);
|
||||
console.log(`Configuration: batch_size=${BatchFetchService.BATCH_CONFIG.BATCH_SIZE}, concurrent=${BatchFetchService.BATCH_CONFIG.MAX_CONCURRENT_REQUESTS}`);
|
||||
|
||||
let completedCount = 0;
|
||||
let previewGeneratedCount = 0;
|
||||
@@ -719,13 +689,11 @@ export class BatchFetchService {
|
||||
for (let i = 0; i < lorasToFetch.length; i += batchSize) {
|
||||
// Check for cancellation before processing each batch
|
||||
if (this.cancelFlag || checkCancelled()) {
|
||||
console.log('Batch operation cancelled by user');
|
||||
updateText(`Operation cancelled. Processed ${completedCount}/${totalLoras} LoRAs`);
|
||||
break;
|
||||
}
|
||||
|
||||
const batch = lorasToFetch.slice(i, i + Math.min(batchSize, totalLoras - i));
|
||||
console.log(`Processing batch ${Math.floor(i/batchSize) + 1} with ${batch.length} LoRAs in PARALLEL:`, batch);
|
||||
|
||||
try {
|
||||
// Process the entire batch in parallel
|
||||
@@ -757,9 +725,7 @@ export class BatchFetchService {
|
||||
batchNoImages++;
|
||||
if (result.skippedVideos > 0) {
|
||||
batchVideosSkipped += result.skippedVideos;
|
||||
console.log(`ℹ️ ${result.loraName}: Skipped ${result.skippedVideos} video preview(s) to prevent timeouts`);
|
||||
} else {
|
||||
console.log(`ℹ️ ${result.loraName}: Info fetched successfully, but no preview images available`);
|
||||
}
|
||||
|
||||
// Queue for placeholder preview creation (even if cancelled)
|
||||
@@ -775,18 +741,15 @@ export class BatchFetchService {
|
||||
|
||||
// Create placeholder previews for LoRAs that need them (even if operation was cancelled)
|
||||
if (placeholderPreviewQueue.length > 0) {
|
||||
console.log(`Creating placeholder previews for ${placeholderPreviewQueue.length} LoRAs...`);
|
||||
for (const loraName of placeholderPreviewQueue) {
|
||||
try {
|
||||
await this.createPlaceholderPreview(loraName);
|
||||
console.log(`✓ Created placeholder preview for ${loraName}`);
|
||||
} catch (placeholderError) {
|
||||
console.warn(`⚠ Failed to create placeholder preview for ${loraName}:`, placeholderError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Batch ${Math.floor(i/batchSize) + 1} completed: ${batchInfoFetched} info fetched, ${batchPreviewsGenerated} previews generated, ${batchNoImages} no images, ${batchVideosSkipped} videos skipped, ${batchFailures} failures`);
|
||||
|
||||
// Accumulate counts
|
||||
videosSkippedCount += batchVideosSkipped;
|
||||
@@ -794,7 +757,6 @@ export class BatchFetchService {
|
||||
|
||||
} catch (error) {
|
||||
if (error.message === 'Operation cancelled') {
|
||||
console.log('Batch processing cancelled');
|
||||
break;
|
||||
}
|
||||
console.error(`Batch ${Math.floor(i/batchSize) + 1} failed:`, error);
|
||||
@@ -803,13 +765,11 @@ export class BatchFetchService {
|
||||
|
||||
// Check for cancellation between batches
|
||||
if (this.cancelFlag || checkCancelled()) {
|
||||
console.log('Batch operation cancelled between batches');
|
||||
break;
|
||||
}
|
||||
|
||||
// Short delay between batches for API safety (cancellable)
|
||||
if (i + batchSize < lorasToFetch.length) {
|
||||
console.log(`Batch ${Math.floor(i/batchSize) + 1} completed, short delay before next batch...`);
|
||||
try {
|
||||
await this.createCancellableDelay(
|
||||
BatchFetchService.BATCH_CONFIG.INTER_BATCH_DELAY,
|
||||
@@ -817,7 +777,6 @@ export class BatchFetchService {
|
||||
);
|
||||
} catch (delayError) {
|
||||
if (delayError.message === 'Operation cancelled') {
|
||||
console.log('Inter-batch delay cancelled, stopping batch processing');
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -859,7 +818,6 @@ export class BatchFetchService {
|
||||
}
|
||||
|
||||
updateText(completionMessage);
|
||||
console.log(`Batch processing ${this.cancelFlag ? 'cancelled' : 'completed'}: ${completionMessage}`);
|
||||
|
||||
return {
|
||||
success: !this.cancelFlag,
|
||||
@@ -879,7 +837,6 @@ export class BatchFetchService {
|
||||
|
||||
} catch (error) {
|
||||
if (error.message === 'Operation cancelled') {
|
||||
console.log('Batch fetch operation cancelled by user');
|
||||
updateText('Operation cancelled successfully');
|
||||
return {
|
||||
success: false,
|
||||
@@ -909,7 +866,6 @@ export class BatchFetchService {
|
||||
* Cancel the current batch operation aggressively
|
||||
*/
|
||||
cancel() {
|
||||
console.log('Cancelling batch operation aggressively...');
|
||||
this.cancelFlag = true;
|
||||
|
||||
// Abort any ongoing requests
|
||||
@@ -928,7 +884,6 @@ export class BatchFetchService {
|
||||
...BatchFetchService.BATCH_CONFIG,
|
||||
...newConfig
|
||||
};
|
||||
console.log('Batch fetch configuration updated:', BatchFetchService.BATCH_CONFIG);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -947,7 +902,6 @@ export class BatchFetchService {
|
||||
* @returns {Promise<Object>} Comprehensive statistics object
|
||||
*/
|
||||
async gatherComprehensiveStats(hasPreviewCallback, allLoras) {
|
||||
console.log('📊 Gathering comprehensive LoRA statistics...');
|
||||
|
||||
const stats = {
|
||||
totalLoRAs: 0,
|
||||
@@ -1000,7 +954,6 @@ export class BatchFetchService {
|
||||
stats.fileTypes[extension] = (stats.fileTypes[extension] || 0) + 1;
|
||||
}
|
||||
|
||||
console.log('✓ LoRA statistics gathered successfully');
|
||||
return stats;
|
||||
|
||||
} catch (error) {
|
||||
|
||||
@@ -3,13 +3,13 @@ import { createElement as $el, empty, appendChildren, getClosestOrSelf, query, q
|
||||
import { logoCivitai, link, pencilColored, diskColored, dotdotdot } from "./svgs.js";
|
||||
import { LORA_INFO_SERVICE } from "./model_info_service.js";
|
||||
import { generateId, injectCss } from "./shared_utils.js";
|
||||
import { rgthree } from "../rgthree/common/rgthree.js";
|
||||
import { qq } from "../qq_lib/common/qq.js";
|
||||
import { proxyImageUrl } from "./image_utils.js";
|
||||
|
||||
class WanInfoDialog extends WanDialog {
|
||||
constructor(file) {
|
||||
const dialogOptions = {
|
||||
class: "rgthree-info-dialog",
|
||||
class: "qq-info-dialog",
|
||||
title: `<h2>Loading...</h2>`,
|
||||
content: "<center>Loading..</center>",
|
||||
onBeforeClose: () => {
|
||||
@@ -80,28 +80,28 @@ class WanInfoDialog extends WanDialog {
|
||||
}
|
||||
|
||||
/* Info dialog specific styling - match power lora UI */
|
||||
.rgthree-info-dialog {
|
||||
.qq-info-dialog {
|
||||
width: 90vw;
|
||||
max-width: 960px;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-area {
|
||||
.qq-info-dialog .qq-info-area {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-area > li {
|
||||
.qq-info-dialog .qq-info-area > li {
|
||||
display: inline-flex;
|
||||
margin: 0;
|
||||
vertical-align: top;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-area > li + li {
|
||||
.qq-info-dialog .qq-info-area > li + li {
|
||||
margin-left: 4px;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-area > li:not(.-link) + li.-link {
|
||||
.qq-info-dialog .qq-info-area > li:not(.-link) + li.-link {
|
||||
margin-left: auto;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-area > li.rgthree-info-tag > * {
|
||||
.qq-info-dialog .qq-info-area > li.qq-info-tag > * {
|
||||
min-height: 20px;
|
||||
border-radius: 0px;
|
||||
line-height: 1;
|
||||
@@ -118,71 +118,71 @@ class WanInfoDialog extends WanDialog {
|
||||
align-items: center;
|
||||
border: 1px solid #333;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-area > li.-type > * {
|
||||
.qq-info-dialog .qq-info-area > li.-type > * {
|
||||
background: #2a2a2a;
|
||||
color: #89B;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-area > li.rgthree-info-tag > * > svg {
|
||||
.qq-info-dialog .qq-info-area > li.qq-info-tag > * > svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-area > li.rgthree-info-tag > *:empty {
|
||||
.qq-info-dialog .qq-info-area > li.qq-info-tag > *:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Table styling - match power lora UI */
|
||||
.rgthree-info-dialog .rgthree-info-table {
|
||||
.qq-info-dialog .qq-info-table {
|
||||
border-collapse: collapse;
|
||||
margin: 8px 0px;
|
||||
width: 100%;
|
||||
font-size: 11px;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-table td {
|
||||
.qq-info-dialog .qq-info-table td {
|
||||
position: relative;
|
||||
border: 1px solid #333;
|
||||
padding: 0;
|
||||
vertical-align: top;
|
||||
background: #1a1a1a;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-table td:first-child {
|
||||
.qq-info-dialog .qq-info-table td:first-child {
|
||||
background: #222;
|
||||
width: 10px;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-table td:first-child > *:first-child {
|
||||
.qq-info-dialog .qq-info-table td:first-child > *:first-child {
|
||||
white-space: nowrap;
|
||||
padding-right: 24px;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-table td:first-child small {
|
||||
.qq-info-dialog .qq-info-table td:first-child small {
|
||||
display: block;
|
||||
margin-top: 2px;
|
||||
opacity: 0.75;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-table td:first-child small > [data-action] {
|
||||
.qq-info-dialog .qq-info-table td:first-child small > [data-action] {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-table td:first-child small > [data-action]:hover {
|
||||
.qq-info-dialog .qq-info-table td:first-child small > [data-action]:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-table td a,
|
||||
.rgthree-info-dialog .rgthree-info-table td a:hover,
|
||||
.rgthree-info-dialog .rgthree-info-table td a:visited {
|
||||
.qq-info-dialog .qq-info-table td a,
|
||||
.qq-info-dialog .qq-info-table td a:hover,
|
||||
.qq-info-dialog .qq-info-table td a:visited {
|
||||
color: #89B;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-table td svg {
|
||||
.qq-info-dialog .qq-info-table td svg {
|
||||
width: 1.2em;
|
||||
height: 1.2em;
|
||||
vertical-align: -0.2em;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-table td svg.logo-civitai {
|
||||
.qq-info-dialog .qq-info-table td svg.logo-civitai {
|
||||
margin-right: 0.2em;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-table td > *:first-child {
|
||||
.qq-info-dialog .qq-info-table td > *:first-child {
|
||||
display: block;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-table td > input,
|
||||
.rgthree-info-dialog .rgthree-info-table td > textarea {
|
||||
.qq-info-dialog .qq-info-table td > input,
|
||||
.qq-info-dialog .qq-info-table td > textarea {
|
||||
padding: 4px 6px;
|
||||
border: 1px solid #333;
|
||||
box-shadow: none;
|
||||
@@ -194,16 +194,16 @@ class WanInfoDialog extends WanDialog {
|
||||
resize: vertical;
|
||||
width: 100%;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-table td > input:focus,
|
||||
.rgthree-info-dialog .rgthree-info-table td > textarea:focus {
|
||||
.qq-info-dialog .qq-info-table td > input:focus,
|
||||
.qq-info-dialog .qq-info-table td > textarea:focus {
|
||||
border-color: #555;
|
||||
background: #2a2a2a;
|
||||
outline: none;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-table tr[data-field-name=userNote] td > span:first-child {
|
||||
.qq-info-dialog .qq-info-table tr[data-field-name=userNote] td > span:first-child {
|
||||
white-space: pre;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-table td .-help {
|
||||
.qq-info-dialog .qq-info-table td .-help {
|
||||
border: 1px solid #555;
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
@@ -219,12 +219,12 @@ class WanInfoDialog extends WanDialog {
|
||||
cursor: help;
|
||||
background: #222;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-table td .-help::before {
|
||||
.qq-info-dialog .qq-info-table td .-help::before {
|
||||
content: "?";
|
||||
}
|
||||
|
||||
/* Button styling - match power lora UI */
|
||||
.rgthree-button {
|
||||
.qq-button {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
border: 1px solid #000;
|
||||
@@ -245,20 +245,20 @@ class WanInfoDialog extends WanDialog {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.rgthree-button:hover {
|
||||
.qq-button:hover {
|
||||
background: #2a2a2a;
|
||||
}
|
||||
.rgthree-button:active {
|
||||
.qq-button:active {
|
||||
background: #333;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-table .rgthree-button[data-action=fetch-civitai] {
|
||||
.qq-info-dialog .qq-info-table .qq-button[data-action=fetch-civitai] {
|
||||
font-size: inherit;
|
||||
padding: 4px 8px;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
/* Edit button styling */
|
||||
.rgthree-button-edit {
|
||||
.qq-button-edit {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
@@ -271,22 +271,22 @@ class WanInfoDialog extends WanDialog {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.rgthree-button-edit:hover {
|
||||
.qq-button-edit:hover {
|
||||
opacity: 1;
|
||||
color: #89B;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-table tr.editable button svg + svg {
|
||||
.qq-info-dialog .qq-info-table tr.editable button svg + svg {
|
||||
display: none;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-table tr.editable.-rgthree-editing button svg {
|
||||
.qq-info-dialog .qq-info-table tr.editable.-qq-editing button svg {
|
||||
display: none;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-table tr.editable.-rgthree-editing button svg + svg {
|
||||
.qq-info-dialog .qq-info-table tr.editable.-qq-editing button svg + svg {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Trained words styling - match power lora UI */
|
||||
.rgthree-info-dialog .rgthree-info-table td > ul.rgthree-info-trained-words-list {
|
||||
.qq-info-dialog .qq-info-table td > ul.qq-info-trained-words-list {
|
||||
list-style: none;
|
||||
padding: 2px 4px;
|
||||
margin: 0;
|
||||
@@ -296,7 +296,7 @@ class WanInfoDialog extends WanDialog {
|
||||
max-height: 15vh;
|
||||
overflow: auto;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-table td > ul.rgthree-info-trained-words-list > li {
|
||||
.qq-info-dialog .qq-info-table td > ul.qq-info-trained-words-list > li {
|
||||
display: inline-flex;
|
||||
margin: 1px;
|
||||
vertical-align: top;
|
||||
@@ -317,21 +317,21 @@ class WanInfoDialog extends WanDialog {
|
||||
white-space: nowrap;
|
||||
max-width: 183px;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-table td > ul.rgthree-info-trained-words-list > li:hover {
|
||||
.qq-info-dialog .qq-info-table td > ul.qq-info-trained-words-list > li:hover {
|
||||
background: #333;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-table td > ul.rgthree-info-trained-words-list > li > svg {
|
||||
.qq-info-dialog .qq-info-table td > ul.qq-info-trained-words-list > li > svg {
|
||||
width: auto;
|
||||
height: 1em;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-table td > ul.rgthree-info-trained-words-list > li > span {
|
||||
.qq-info-dialog .qq-info-table td > ul.qq-info-trained-words-list > li > span {
|
||||
padding-left: 0.3em;
|
||||
padding-right: 0.3em;
|
||||
padding-bottom: 0.1em;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-table td > ul.rgthree-info-trained-words-list > li > small {
|
||||
.qq-info-dialog .qq-info-table td > ul.qq-info-trained-words-list > li > small {
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -339,13 +339,13 @@ class WanInfoDialog extends WanDialog {
|
||||
padding: 0 0.3em;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-table td > ul.rgthree-info-trained-words-list > li.-rgthree-is-selected {
|
||||
.qq-info-dialog .qq-info-table td > ul.qq-info-trained-words-list > li.-qq-is-selected {
|
||||
background: #444;
|
||||
color: #89B;
|
||||
}
|
||||
|
||||
/* Images styling - match power lora UI */
|
||||
.rgthree-info-dialog .rgthree-info-images {
|
||||
.qq-info-dialog .qq-info-images {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
@@ -354,7 +354,7 @@ class WanInfoDialog extends WanDialog {
|
||||
flex-direction: row;
|
||||
overflow: auto;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-images > li {
|
||||
.qq-info-dialog .qq-info-images > li {
|
||||
scroll-snap-align: start;
|
||||
max-width: 90%;
|
||||
flex: 0 0 auto;
|
||||
@@ -369,15 +369,15 @@ class WanInfoDialog extends WanDialog {
|
||||
position: relative;
|
||||
border: 1px solid #333;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-images > li figure {
|
||||
.qq-info-dialog .qq-info-images > li figure {
|
||||
margin: 0;
|
||||
position: static;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-images > li figure video,
|
||||
.rgthree-info-dialog .rgthree-info-images > li figure img {
|
||||
.qq-info-dialog .qq-info-images > li figure video,
|
||||
.qq-info-dialog .qq-info-images > li figure img {
|
||||
max-height: 45vh;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-images > li figure figcaption {
|
||||
.qq-info-dialog .qq-info-images > li figure figcaption {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
@@ -389,7 +389,7 @@ class WanInfoDialog extends WanDialog {
|
||||
transform: translateY(50px);
|
||||
transition: all 0.25s ease-in-out;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-images > li figure figcaption > span {
|
||||
.qq-info-dialog .qq-info-images > li figure figcaption > span {
|
||||
display: inline-block;
|
||||
padding: 2px 4px;
|
||||
margin: 1px;
|
||||
@@ -397,7 +397,7 @@ class WanInfoDialog extends WanDialog {
|
||||
border: 1px solid #333;
|
||||
word-break: break-word;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-images > li figure figcaption > span label {
|
||||
.qq-info-dialog .qq-info-images > li figure figcaption > span label {
|
||||
display: inline;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
@@ -405,45 +405,45 @@ class WanInfoDialog extends WanDialog {
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-images > li figure figcaption > span a {
|
||||
.qq-info-dialog .qq-info-images > li figure figcaption > span a {
|
||||
color: #89B;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-images > li figure figcaption > span a:hover {
|
||||
.qq-info-dialog .qq-info-images > li figure figcaption > span a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-images > li figure figcaption > span a svg {
|
||||
.qq-info-dialog .qq-info-images > li figure figcaption > span a svg {
|
||||
height: 10px;
|
||||
margin-left: 4px;
|
||||
fill: currentColor;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-images > li figure figcaption:empty {
|
||||
.qq-info-dialog .qq-info-images > li figure figcaption:empty {
|
||||
text-align: center;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-images > li figure figcaption:empty::before {
|
||||
.qq-info-dialog .qq-info-images > li figure figcaption:empty::before {
|
||||
content: "No data.";
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-images > li:hover figure figcaption {
|
||||
.qq-info-dialog .qq-info-images > li:hover figure figcaption {
|
||||
opacity: 1;
|
||||
transform: translateY(0px);
|
||||
}
|
||||
|
||||
/* Constrain long prompt text (positive/negative) so the caption
|
||||
doesn't grow past the image and clip off the Copy+ button. */
|
||||
.rgthree-info-dialog .rgthree-info-images figure figcaption > span.-info-field-long {
|
||||
.qq-info-dialog .qq-info-images figure figcaption > span.-info-field-long {
|
||||
display: block;
|
||||
max-height: 8.8em; /* ~8 lines at 11px font */
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-images figure figcaption > span.-info-field-long > label {
|
||||
.qq-info-dialog .qq-info-images figure figcaption > span.-info-field-long > label {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-images figure figcaption > span.-info-field-long::-webkit-scrollbar {
|
||||
.qq-info-dialog .qq-info-images figure figcaption > span.-info-field-long::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
.rgthree-info-dialog .rgthree-info-images figure figcaption > span.-info-field-long::-webkit-scrollbar-thumb {
|
||||
.qq-info-dialog .qq-info-images figure figcaption > span.-info-field-long::-webkit-scrollbar-thumb {
|
||||
background: #444;
|
||||
}
|
||||
`;
|
||||
@@ -503,7 +503,7 @@ class WanInfoDialog extends WanDialog {
|
||||
|
||||
// Add to success message
|
||||
const itemTypeName = itemType === 'checkpoints' ? 'Model' : 'LoRA';
|
||||
rgthree.showMessage({
|
||||
qq.showMessage({
|
||||
id: "refresh-info-" + generateId(4),
|
||||
type: "success",
|
||||
message: `${itemTypeName} information refreshed successfully (preview image generated)`,
|
||||
@@ -511,7 +511,7 @@ class WanInfoDialog extends WanDialog {
|
||||
});
|
||||
} catch (previewError) {
|
||||
// Still show success message for the refresh itself
|
||||
rgthree.showMessage({
|
||||
qq.showMessage({
|
||||
id: "refresh-info-" + generateId(4),
|
||||
type: "success",
|
||||
message: "LoRA information refreshed successfully",
|
||||
@@ -520,7 +520,7 @@ class WanInfoDialog extends WanDialog {
|
||||
}
|
||||
} else {
|
||||
// Show success message
|
||||
rgthree.showMessage({
|
||||
qq.showMessage({
|
||||
id: "refresh-info-" + generateId(4),
|
||||
type: "success",
|
||||
message: "LoRA information refreshed successfully",
|
||||
@@ -529,7 +529,7 @@ class WanInfoDialog extends WanDialog {
|
||||
}
|
||||
} catch (error) {
|
||||
// Show error message
|
||||
rgthree.showMessage({
|
||||
qq.showMessage({
|
||||
id: "refresh-info-error-" + generateId(4),
|
||||
type: "error",
|
||||
message: "Failed to refresh LoRA information",
|
||||
@@ -564,14 +564,14 @@ class WanInfoDialog extends WanDialog {
|
||||
|
||||
if (positivePrompt) {
|
||||
await navigator.clipboard.writeText(positivePrompt);
|
||||
rgthree.showMessage({
|
||||
qq.showMessage({
|
||||
id: "copy-positive-" + generateId(4),
|
||||
type: "success",
|
||||
message: "Positive prompt copied to clipboard",
|
||||
timeout: 3000,
|
||||
});
|
||||
} else {
|
||||
rgthree.showMessage({
|
||||
qq.showMessage({
|
||||
id: "copy-positive-error-" + generateId(4),
|
||||
type: "error",
|
||||
message: "No positive prompt found to copy",
|
||||
@@ -580,10 +580,10 @@ class WanInfoDialog extends WanDialog {
|
||||
}
|
||||
}
|
||||
else if (action === "copy-trained-words") {
|
||||
const selected = queryAll(".-rgthree-is-selected", target.closest("tr"));
|
||||
const selected = queryAll(".-qq-is-selected", target.closest("tr"));
|
||||
const text = selected.map((el) => el.getAttribute("data-word")).join(", ");
|
||||
await navigator.clipboard.writeText(text);
|
||||
rgthree.showMessage({
|
||||
qq.showMessage({
|
||||
id: "copy-trained-words-" + generateId(4),
|
||||
type: "success",
|
||||
message: `Successfully copied ${selected.length} key word${selected.length === 1 ? "" : "s"}.`,
|
||||
@@ -591,7 +591,7 @@ class WanInfoDialog extends WanDialog {
|
||||
});
|
||||
}
|
||||
else if (action === "toggle-trained-word") {
|
||||
target === null || target === void 0 ? void 0 : target.classList.toggle("-rgthree-is-selected");
|
||||
target === null || target === void 0 ? void 0 : target.classList.toggle("-qq-is-selected");
|
||||
const tr = target.closest("tr");
|
||||
if (tr) {
|
||||
const span = query("td:first-child > *", tr);
|
||||
@@ -599,7 +599,7 @@ class WanInfoDialog extends WanDialog {
|
||||
if (!small) {
|
||||
small = $el("small", { parent: span });
|
||||
}
|
||||
const num = queryAll(".-rgthree-is-selected", tr).length;
|
||||
const num = queryAll(".-qq-is-selected", tr).length;
|
||||
small.innerHTML = num
|
||||
? `${num} selected | <span role="button" data-action="copy-trained-words">Copy</span>`
|
||||
: "";
|
||||
@@ -611,7 +611,7 @@ class WanInfoDialog extends WanDialog {
|
||||
const input = td.querySelector("input,textarea");
|
||||
if (!input) {
|
||||
const fieldName = tr.dataset["fieldName"];
|
||||
tr.classList.add("-rgthree-editing");
|
||||
tr.classList.add("-qq-editing");
|
||||
const isTextarea = fieldName === "userNote";
|
||||
const input = $el(`${isTextarea ? "textarea" : 'input[type="text"]'}`, {
|
||||
value: td.textContent,
|
||||
@@ -647,15 +647,15 @@ class WanInfoDialog extends WanDialog {
|
||||
const info = this.modelInfo || {};
|
||||
const civitaiLink = (_a = info.links) === null || _a === void 0 ? void 0 : _a.find((i) => i.includes("civitai.com/models"));
|
||||
const html = `
|
||||
<ul class="rgthree-info-area">
|
||||
<li title="Type" class="rgthree-info-tag -type -type-${(info.type || "").toLowerCase()}"><span>${info.type || ""}</span></li>
|
||||
<li title="Base Model" class="rgthree-info-tag -basemodel -basemodel-${(info.baseModel || "").toLowerCase()}"><span>${info.baseModel || ""}</span></li>
|
||||
<li class="rgthree-info-menu" stub="menu"></li>
|
||||
<ul class="qq-info-area">
|
||||
<li title="Type" class="qq-info-tag -type -type-${(info.type || "").toLowerCase()}"><span>${info.type || ""}</span></li>
|
||||
<li title="Base Model" class="qq-info-tag -basemodel -basemodel-${(info.baseModel || "").toLowerCase()}"><span>${info.baseModel || ""}</span></li>
|
||||
<li class="qq-info-menu" stub="menu"></li>
|
||||
${""}
|
||||
</ul>
|
||||
|
||||
<table class="rgthree-info-table">
|
||||
${infoTableRow("File", `${info.file || ""} <button class="rgthree-button" data-action="refresh-info" style="margin-left: 8px; padding: 2px 6px; font-size: 10px;">↻ Refresh</button>`)}
|
||||
<table class="qq-info-table">
|
||||
${infoTableRow("File", `${info.file || ""} <button class="qq-button" data-action="refresh-info" style="margin-left: 8px; padding: 2px 6px; font-size: 10px;">↻ Refresh</button>`)}
|
||||
${infoTableRow("Hash (sha256)", info.sha256 || "")}
|
||||
${civitaiLink
|
||||
? infoTableRow("Civitai", `<a href="${civitaiLink}" target="_blank">${logoCivitai}View on Civitai</a>`)
|
||||
@@ -664,7 +664,7 @@ class WanInfoDialog extends WanDialog {
|
||||
: ((_e = (_d = info.raw) === null || _d === void 0 ? void 0 : _d.civitai) === null || _e === void 0 ? void 0 : _e.error)
|
||||
? infoTableRow("Civitai", (_g = (_f = info.raw) === null || _f === void 0 ? void 0 : _f.civitai) === null || _g === void 0 ? void 0 : _g.error)
|
||||
: !((_h = info.raw) === null || _h === void 0 ? void 0 : _h.civitai)
|
||||
? infoTableRow("Civitai", `<button class="rgthree-button" data-action="fetch-civitai">Fetch info from civitai</button>`)
|
||||
? infoTableRow("Civitai", `<button class="qq-button" data-action="fetch-civitai">Fetch info from civitai</button>`)
|
||||
: ""}
|
||||
|
||||
${infoTableRow("Name", info.name || ((_k = (_j = info.raw) === null || _j === void 0 ? void 0 : _j.metadata) === null || _k === void 0 ? void 0 : _k.ss_output_name) || "", "The name for display.", "name")}
|
||||
@@ -688,7 +688,7 @@ class WanInfoDialog extends WanDialog {
|
||||
|
||||
</table>
|
||||
|
||||
<ul class="rgthree-info-images">${(_y = (_x = info.images) === null || _x === void 0 ? void 0 : _x.map((img) => {
|
||||
<ul class="qq-info-images">${(_y = (_x = info.images) === null || _x === void 0 ? void 0 : _x.map((img) => {
|
||||
// Use proxy for Civitai images to avoid CSP violations
|
||||
const proxiedUrl = proxyImageUrl(img.url);
|
||||
return `
|
||||
@@ -698,7 +698,7 @@ class WanInfoDialog extends WanDialog {
|
||||
: `<img src="${proxiedUrl}" />`}
|
||||
<figcaption><!--
|
||||
-->${imgInfoField("", img.civitaiUrl
|
||||
? `<a href="${img.civitaiUrl}" target="_blank">civitai${link}</a> ${img.positive ? `<button class="rgthree-button" data-action="copy-positive" data-positive="${encodeURIComponent(img.positive)}" style="margin-left: 4px; padding: 1px 4px; font-size: 9px;">Copy+</button>` : ""}`
|
||||
? `<a href="${img.civitaiUrl}" target="_blank">civitai${link}</a> ${img.positive ? `<button class="qq-button" data-action="copy-positive" data-positive="${encodeURIComponent(img.positive)}" style="margin-left: 4px; padding: 1px 4px; font-size: 9px;">Copy+</button>` : ""}`
|
||||
: undefined)}<!--
|
||||
-->${imgInfoField("seed", img.seed)}<!--
|
||||
-->${imgInfoField("steps", img.steps)}<!--
|
||||
@@ -713,7 +713,7 @@ class WanInfoDialog extends WanDialog {
|
||||
}).join("")) !== null && _y !== void 0 ? _y : ""}</ul>
|
||||
`;
|
||||
const div = $el("div", { html });
|
||||
if (rgthree.isDevMode()) {
|
||||
if (qq.isDevMode()) {
|
||||
setAttributes(query('[stub="menu"]', div), {
|
||||
children: [
|
||||
// Menu button functionality would go here
|
||||
@@ -742,15 +742,15 @@ function infoTableRow(name, value, help = "", editableFieldName = "") {
|
||||
<td><span>${name} ${help ? `<span class="-help" title="${help}">ℹ️</span>` : ""}<span></td>
|
||||
<td ${editableFieldName ? "" : 'colspan="2"'}>${String(value).startsWith("<") ? value : `<span>${value}<span>`}</td>
|
||||
${editableFieldName
|
||||
? `<td style="width: 24px;"><button class="rgthree-button-edit" data-action="edit-row">${pencilColored}${diskColored}</button></td>`
|
||||
? `<td style="width: 24px;"><button class="qq-button-edit" data-action="edit-row">${pencilColored}${diskColored}</button></td>`
|
||||
: ""}
|
||||
</tr>`;
|
||||
}
|
||||
|
||||
function getTrainedWordsMarkup(words) {
|
||||
let markup = `<ul class="rgthree-info-trained-words-list">`;
|
||||
let markup = `<ul class="qq-info-trained-words-list">`;
|
||||
for (const wordData of words || []) {
|
||||
markup += `<li title="${wordData.word}" data-word="${wordData.word}" class="rgthree-info-trained-words-list-item" data-action="toggle-trained-word">
|
||||
markup += `<li title="${wordData.word}" data-word="${wordData.word}" class="qq-info-trained-words-list-item" data-action="toggle-trained-word">
|
||||
<span>${wordData.word}</span>
|
||||
${wordData.civitai ? logoCivitai : ""}
|
||||
${wordData.count != null ? `<small>${wordData.count}</small>` : ""}
|
||||
@@ -778,7 +778,7 @@ function saveEditableRow(info, tr, saving = true) {
|
||||
LORA_INFO_SERVICE.savePartialInfo(info.file, { [fieldName]: newValue }, { generatePreview: true });
|
||||
modified = true;
|
||||
}
|
||||
tr.classList.remove("-rgthree-editing");
|
||||
tr.classList.remove("-qq-editing");
|
||||
const td = query("td:nth-child(2)", tr);
|
||||
appendChildren(empty(td), [$el("span", { text: newValue })]);
|
||||
return modified;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Handles image extraction, scaling, and preview generation for LoRA files
|
||||
*/
|
||||
|
||||
import { rgthreeApi } from "./rgthree_api.js";
|
||||
import { vidApi } from "./wanvid_api.js";
|
||||
|
||||
/**
|
||||
* Convert external Civitai image URLs to proxy URLs to avoid CSP violations
|
||||
|
||||
@@ -1,196 +1,168 @@
|
||||
import { app } from "../../../../scripts/app.js";
|
||||
import { moveArrayItem, removeArrayItem } from "./widgets.js";
|
||||
import { batchFetchService } from "./batch_fetch_service.js";
|
||||
import { renameLoRAFiles } from "./rename_utils.js";
|
||||
|
||||
// Function to display custom HTML context menu for LoRA widget actions
|
||||
export function showCustomLoraMenu(event, widget, node, position) {
|
||||
console.log("showCustomLoraMenu called.");
|
||||
// Remove any existing custom menu to prevent duplicates
|
||||
const existingMenu = document.getElementById('custom-lora-menu');
|
||||
if (existingMenu) {
|
||||
existingMenu.remove();
|
||||
// Slot context menus for the Power Lora Loader family.
|
||||
//
|
||||
// Newer ComfyUI frontends call `new LiteGraph.ContextMenu(options, ...)`
|
||||
// unconditionally with whatever `node.getSlotMenuOptions(slot)` returns, so
|
||||
// these handlers MUST return a proper options array (returning false/undefined
|
||||
// crashes with "t.some is not a function"). ComfyUI renders the menu itself.
|
||||
|
||||
const I_BLACK = '⚫';
|
||||
const I_GREEN = '🟢';
|
||||
const I_WHITE = '⚪';
|
||||
const I_BLUE = '🔵';
|
||||
const I_RED = '🔴';
|
||||
const I_PURPLE = '🟣';
|
||||
const I_INFO = 'ℹ️';
|
||||
const I_UP = '⬆️';
|
||||
const I_DOWN = '⬇️';
|
||||
const I_PENCIL = '✏️';
|
||||
const I_TRASH = '🗑️';
|
||||
// Path separators without backslash literals (transport-safe)
|
||||
const FWD_SLASH = String.fromCharCode(47); // forward slash
|
||||
const BACK_SLASH = String.fromCharCode(92); // back slash
|
||||
|
||||
function setDirty(node) {
|
||||
node.setDirtyCanvas(true, true);
|
||||
}
|
||||
|
||||
function moveLoraWidget(node, widget, offset) {
|
||||
const loraWidgets = node.widgets.filter(w => w.name?.startsWith("lora_"));
|
||||
const index = loraWidgets.indexOf(widget);
|
||||
const other = loraWidgets[index + offset];
|
||||
if (!other) return;
|
||||
const otherIndex = node.widgets.indexOf(other);
|
||||
const myIndex = node.widgets.indexOf(widget);
|
||||
node.widgets[otherIndex] = widget;
|
||||
node.widgets[myIndex] = other;
|
||||
node.setDirtyCanvas(true, true);
|
||||
}
|
||||
|
||||
// Builds the LiteGraph menu options for a lora slot (row right-click) or the
|
||||
// low-variant icon right-click. Returns null when the slot is not one of ours.
|
||||
function buildSlotMenuItems(slot, node) {
|
||||
const dirty = () => setDirty(node);
|
||||
|
||||
// --- Low variant icon right-click menu ---
|
||||
if (slot?.output?.type === "LOW_VARIANT_ICON") {
|
||||
const widget = slot.widget;
|
||||
const hasLowVariant = widget.value.is_low && widget.value.low_variant_name;
|
||||
const isBothMode = widget.value.use_for_both;
|
||||
const isLowOnlyMode = widget.value.low_only;
|
||||
const isHighOnlyMode = widget.value.high_only;
|
||||
|
||||
const setMode = (both, low, high) => () => {
|
||||
widget.value.use_for_both = both;
|
||||
widget.value.low_only = low;
|
||||
widget.value.high_only = high;
|
||||
dirty();
|
||||
};
|
||||
|
||||
if (hasLowVariant || isBothMode || isLowOnlyMode || isHighOnlyMode) {
|
||||
let displayText;
|
||||
if (isBothMode) {
|
||||
displayText = widget.value.lora + " (both)";
|
||||
} else if (isLowOnlyMode) {
|
||||
displayText = widget.value.lora + " (low only)";
|
||||
} else if (isHighOnlyMode) {
|
||||
displayText = widget.value.lora + " (high only)";
|
||||
} else {
|
||||
displayText = widget.value.low_variant_name;
|
||||
}
|
||||
const pathSplit = new RegExp('[' + FWD_SLASH + BACK_SLASH + ']');
|
||||
const fileName = displayText.includes(FWD_SLASH) || displayText.includes(BACK_SLASH)
|
||||
? displayText.split(pathSplit).pop()
|
||||
: displayText;
|
||||
|
||||
const items = [{ content: fileName, disabled: true }, null];
|
||||
|
||||
if (hasLowVariant) {
|
||||
items.push({
|
||||
content: (widget.value.low_active ? I_BLACK : I_GREEN) + " Toggle " + (widget.value.low_active ? 'Off' : 'On'),
|
||||
callback: () => {
|
||||
widget.value.low_active = !widget.value.low_active;
|
||||
dirty();
|
||||
},
|
||||
}, null);
|
||||
items.push({ content: I_INFO + ' Show Info', callback: () => widget.showLoraInfoDialog('low') });
|
||||
} else if (isBothMode) {
|
||||
items.push(
|
||||
{ content: I_WHITE + ' Disable', callback: setMode(false, false, false) },
|
||||
{ content: I_BLUE + ' Switch to Low only', callback: setMode(false, true, false) },
|
||||
{ content: I_RED + ' Switch to High only', callback: setMode(false, false, true) },
|
||||
);
|
||||
} else if (isLowOnlyMode) {
|
||||
items.push(
|
||||
{ content: I_WHITE + ' Disable', callback: setMode(false, false, false) },
|
||||
{ content: I_PURPLE + ' Switch to Both', callback: setMode(true, false, false) },
|
||||
{ content: I_RED + ' Switch to High only', callback: setMode(false, false, true) },
|
||||
);
|
||||
} else if (isHighOnlyMode) {
|
||||
items.push(
|
||||
{ content: I_WHITE + ' Disable', callback: setMode(false, false, false) },
|
||||
{ content: I_PURPLE + ' Switch to Both', callback: setMode(true, false, false) },
|
||||
{ content: I_BLUE + ' Switch to Low only', callback: setMode(false, true, false) },
|
||||
);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
// No low variant: show mode options
|
||||
return [
|
||||
{ content: I_PURPLE + ' Enable for both', callback: setMode(true, false, false) },
|
||||
{ content: I_BLUE + ' Low only', callback: setMode(false, true, false) },
|
||||
{ content: I_RED + ' High only', callback: setMode(false, false, true) },
|
||||
];
|
||||
}
|
||||
|
||||
const menu = document.createElement('div');
|
||||
menu.id = 'custom-lora-menu';
|
||||
menu.className = 'litegraph litecontextmenu litemenubar-panel';
|
||||
menu.style.cssText = `
|
||||
position: absolute !important;
|
||||
left: ${position.x}px !important;
|
||||
top: ${position.y}px !important;
|
||||
background-color: #1a1a1a !important;
|
||||
border: 1px solid #000 !important;
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,0.8) !important;
|
||||
z-index: 10000 !important;
|
||||
padding: 4px !important;
|
||||
border-radius: 0px !important;
|
||||
min-width: 140px !important;
|
||||
display: block !important;
|
||||
font-family: Arial, sans-serif !important;
|
||||
font-size: 11px !important;
|
||||
`;
|
||||
// --- LoRA row right-click menu ---
|
||||
if (slot?.output?.type === "LORA_WIDGET" || slot?.widget?.name?.startsWith("lora_")) {
|
||||
const widget = slot.widget;
|
||||
const loraWidgets = node.widgets.filter(w => w.name?.startsWith("lora_"));
|
||||
const index = loraWidgets.indexOf(widget);
|
||||
const canMoveUp = index > 0;
|
||||
const canMoveDown = index >= 0 && index < loraWidgets.length - 1;
|
||||
// The Simple loader (strengthSplit === false) has no toggle entry.
|
||||
const isV2 = widget.strengthSplit !== false;
|
||||
|
||||
const loraWidgets = node.widgets.filter(w => w.name?.startsWith("lora_"));
|
||||
const loraIndex = loraWidgets.indexOf(widget);
|
||||
const canMoveUp = loraIndex > 0;
|
||||
const canMoveDown = loraIndex < loraWidgets.length - 1;
|
||||
const items = [
|
||||
{ content: I_INFO + ' Show Info', callback: () => widget.showLoraInfoDialog('high') },
|
||||
null,
|
||||
];
|
||||
|
||||
// Helper to create menu item
|
||||
const createMenuItem = (icon, text, onClick, disabled = false) => {
|
||||
const item = document.createElement('div');
|
||||
item.style.cssText = `
|
||||
padding: 4px 8px !important;
|
||||
cursor: ${disabled ? 'default' : 'pointer'} !important;
|
||||
color: ${disabled ? '#555' : '#ddd'} !important;
|
||||
background-color: #1a1a1a !important;
|
||||
user-select: none !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
gap: 6px !important;
|
||||
`;
|
||||
|
||||
if (!disabled) {
|
||||
item.onmouseover = () => {
|
||||
item.style.backgroundColor = '#2a2a2a';
|
||||
};
|
||||
item.onmouseout = () => {
|
||||
item.style.backgroundColor = '#1a1a1a';
|
||||
};
|
||||
item.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
onClick();
|
||||
menu.remove();
|
||||
document.removeEventListener('click', hideMenu);
|
||||
document.removeEventListener('contextmenu', hideMenu);
|
||||
document.removeEventListener('mousedown', hideMenu);
|
||||
};
|
||||
if (isV2) {
|
||||
items.push({
|
||||
content: (widget.value.on ? I_BLACK : I_GREEN) + " Toggle " + (widget.value.on ? 'Off' : 'On'),
|
||||
callback: () => {
|
||||
widget.value.on = !widget.value.on;
|
||||
dirty();
|
||||
},
|
||||
}, null);
|
||||
}
|
||||
|
||||
const iconSpan = document.createElement('span');
|
||||
iconSpan.textContent = icon;
|
||||
iconSpan.style.cssText = `
|
||||
font-size: 12px !important;
|
||||
width: 16px !important;
|
||||
text-align: center !important;
|
||||
`;
|
||||
|
||||
const textSpan = document.createElement('span');
|
||||
textSpan.textContent = text;
|
||||
|
||||
item.appendChild(iconSpan);
|
||||
item.appendChild(textSpan);
|
||||
|
||||
return item;
|
||||
};
|
||||
|
||||
// Helper to create separator
|
||||
const createSeparator = () => {
|
||||
const separator = document.createElement('div');
|
||||
separator.style.cssText = `
|
||||
height: 1px !important;
|
||||
background-color: #333 !important;
|
||||
margin: 2px 0 !important;
|
||||
`;
|
||||
return separator;
|
||||
};
|
||||
|
||||
// Function to show LoRA in Windows Explorer
|
||||
const showInExplorer = (loraName) => {
|
||||
// Use the ComfyUI API to open Explorer for the LoRA file
|
||||
fetch('/wanvideo_wrapper/open_explorer', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
items.push(
|
||||
{ content: I_UP + ' Move Up', disabled: !canMoveUp, callback: () => moveLoraWidget(node, widget, -1) },
|
||||
{ content: I_DOWN + ' Move Down', disabled: !canMoveDown, callback: () => moveLoraWidget(node, widget, 1) },
|
||||
{
|
||||
content: I_PENCIL + ' Rename',
|
||||
callback: () => renameLoRAFiles(widget.value.lora, 'high', null, null, node, widget),
|
||||
},
|
||||
body: JSON.stringify({ lora_name: loraName })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
console.log('Explorer opened successfully for:', loraName);
|
||||
} else {
|
||||
console.error('Failed to open Explorer:', data.error || 'Unknown error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error opening Explorer:', error);
|
||||
});
|
||||
};
|
||||
{
|
||||
content: I_TRASH + ' Remove',
|
||||
callback: () => {
|
||||
removeArrayItem(node.widgets, widget);
|
||||
const computed = node.computeSize();
|
||||
node.size[1] = Math.max(node._tempHeight || 15, computed[1]);
|
||||
node.setDirtyCanvas(true, true);
|
||||
},
|
||||
},
|
||||
);
|
||||
return items;
|
||||
}
|
||||
|
||||
// Add menu items
|
||||
menu.appendChild(createMenuItem('ℹ️', 'Show Info', () => {
|
||||
// For the main LoRA widget context menu, always show the high variant info
|
||||
widget.showLoraInfoDialog('high');
|
||||
}));
|
||||
|
||||
menu.appendChild(createSeparator());
|
||||
|
||||
menu.appendChild(createMenuItem(
|
||||
widget.value.on ? '⚫' : '🟢',
|
||||
widget.value.on ? 'Toggle Off' : 'Toggle On',
|
||||
() => {
|
||||
widget.value.on = !widget.value.on;
|
||||
node.setDirtyCanvas(true, true);
|
||||
}
|
||||
));
|
||||
|
||||
menu.appendChild(createSeparator());
|
||||
|
||||
menu.appendChild(createMenuItem('⬆️', 'Move Up', () => {
|
||||
const index = node.widgets.indexOf(widget);
|
||||
const otherIndex = node.widgets.indexOf(loraWidgets[loraIndex - 1]);
|
||||
const temp = node.widgets[otherIndex];
|
||||
node.widgets[otherIndex] = widget;
|
||||
node.widgets[index] = temp;
|
||||
node.setDirtyCanvas(true, true);
|
||||
}, !canMoveUp));
|
||||
|
||||
menu.appendChild(createMenuItem('⬇️', 'Move Down', () => {
|
||||
const index = node.widgets.indexOf(widget);
|
||||
const otherIndex = node.widgets.indexOf(loraWidgets[loraIndex + 1]);
|
||||
const temp = node.widgets[otherIndex];
|
||||
node.widgets[otherIndex] = widget;
|
||||
node.widgets[index] = temp;
|
||||
node.setDirtyCanvas(true, true);
|
||||
}, !canMoveDown));
|
||||
|
||||
menu.appendChild(createMenuItem('✏️', 'Rename', () => {
|
||||
const loraName = widget.value.lora_name;
|
||||
renameLoRAFiles(loraName, 'high', null, widget.callback, node, widget);
|
||||
}));
|
||||
|
||||
menu.appendChild(createMenuItem('🗑️', 'Remove', () => {
|
||||
removeArrayItem(node.widgets, widget);
|
||||
const computed = node.computeSize();
|
||||
node.size[1] = Math.max(node._tempHeight || 15, computed[1]);
|
||||
node.setDirtyCanvas(true, true);
|
||||
}));
|
||||
|
||||
document.body.appendChild(menu);
|
||||
console.log("LoRA menu appended to body, position:", position);
|
||||
|
||||
// Prevent menu close when interacting with menu
|
||||
menu.onclick = (e) => e.stopPropagation();
|
||||
menu.onmousedown = (e) => e.stopPropagation();
|
||||
|
||||
// Hide menu when clicking outside
|
||||
const hideMenu = (e) => {
|
||||
if (menu && !menu.contains(e.target)) {
|
||||
console.log("Hiding LoRA menu");
|
||||
menu.remove();
|
||||
document.removeEventListener('click', hideMenu);
|
||||
document.removeEventListener('contextmenu', hideMenu);
|
||||
document.removeEventListener('mousedown', hideMenu);
|
||||
}
|
||||
};
|
||||
|
||||
// Add listeners after a short delay to prevent immediate closure
|
||||
setTimeout(() => {
|
||||
document.addEventListener('click', hideMenu);
|
||||
document.addEventListener('contextmenu', hideMenu);
|
||||
document.addEventListener('mousedown', hideMenu);
|
||||
}, 100);
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getLoraSlotInPosition(canvasX, canvasY) {
|
||||
@@ -207,10 +179,9 @@ export function getLoraSlotInPosition(canvasX, canvasY) {
|
||||
if (canvasY >= widgetTop && canvasY <= widgetBottom) {
|
||||
// Found the widget at this position
|
||||
if (widget.name?.startsWith("lora_")) {
|
||||
// Check if click is on the low variant icon (green dot)
|
||||
// Check if click is on the low variant icon (circle)
|
||||
if (widget.hitAreas && widget.hitAreas.low_variant_icon) {
|
||||
const iconBounds = widget.hitAreas.low_variant_icon.bounds;
|
||||
// iconBounds is [posX, width, height?]
|
||||
const iconAbsXStart = this.pos[0] + iconBounds[0];
|
||||
const iconAbsXEnd = iconAbsXStart + (iconBounds[1] || 20);
|
||||
const iconAbsYStart = widgetTop;
|
||||
@@ -238,354 +209,11 @@ export function getLoraSlotInPosition(canvasX, canvasY) {
|
||||
}
|
||||
|
||||
export function getLoraSlotMenuOptions(slot, event) {
|
||||
// Check if the slot is specifically for the LOW_VARIANT_ICON
|
||||
if (slot?.output?.type === "LOW_VARIANT_ICON") {
|
||||
const canvas = app.canvas;
|
||||
|
||||
// Try multiple ways to get the screen position
|
||||
let x = 100;
|
||||
let y = 100;
|
||||
|
||||
// Option 1: Try to get from last_mouse_position (canvas screen coords)
|
||||
if (canvas.last_mouse_position) {
|
||||
x = canvas.last_mouse_position[0];
|
||||
y = canvas.last_mouse_position[1];
|
||||
}
|
||||
// Option 2: Try canvas_mouse (might be available)
|
||||
else if (canvas.canvas_mouse) {
|
||||
x = canvas.canvas_mouse[0];
|
||||
y = canvas.canvas_mouse[1];
|
||||
}
|
||||
// Option 3: Convert graph coordinates to screen coordinates
|
||||
else if (canvas.graph_mouse && canvas.ds) {
|
||||
const graphX = canvas.graph_mouse[0];
|
||||
const graphY = canvas.graph_mouse[1];
|
||||
// Convert from graph space to canvas space
|
||||
x = graphX * canvas.ds.scale + canvas.ds.offset[0];
|
||||
y = graphY * canvas.ds.scale + canvas.ds.offset[1];
|
||||
// Convert from canvas space to screen space
|
||||
const canvasRect = canvas.canvas.getBoundingClientRect();
|
||||
x += canvasRect.left;
|
||||
y += canvasRect.top;
|
||||
}
|
||||
|
||||
console.log("Low variant menu position calculated:", {x, y});
|
||||
|
||||
// Use a slight delay to ensure the menu shows after the event completes
|
||||
setTimeout(() => {
|
||||
showLowVariantMenu(event, slot.widget, this, { x, y });
|
||||
}, 10);
|
||||
|
||||
// Return false to prevent LiteGraph from showing its default menu
|
||||
return false;
|
||||
const items = buildSlotMenuItems(slot, this);
|
||||
if (items) {
|
||||
// Must return a real options array: newer ComfyUI frontends feed the
|
||||
// return value straight into new LiteGraph.ContextMenu(options, ...).
|
||||
return items;
|
||||
}
|
||||
|
||||
// Check if the slot is for a LoRA widget (layer right-click)
|
||||
if (slot?.output?.type === "LORA_WIDGET" || slot?.widget?.name?.startsWith("lora_")) {
|
||||
const canvas = app.canvas;
|
||||
|
||||
// Try multiple ways to get the screen position
|
||||
let x = 100;
|
||||
let y = 100;
|
||||
|
||||
// Option 1: Try to get from last_mouse_position (canvas screen coords)
|
||||
if (canvas.last_mouse_position) {
|
||||
x = canvas.last_mouse_position[0];
|
||||
y = canvas.last_mouse_position[1];
|
||||
}
|
||||
// Option 2: Try canvas_mouse (might be available)
|
||||
else if (canvas.canvas_mouse) {
|
||||
x = canvas.canvas_mouse[0];
|
||||
y = canvas.canvas_mouse[1];
|
||||
}
|
||||
// Option 3: Convert graph coordinates to screen coordinates
|
||||
else if (canvas.graph_mouse && canvas.ds) {
|
||||
const graphX = canvas.graph_mouse[0];
|
||||
const graphY = canvas.graph_mouse[1];
|
||||
// Convert from graph space to canvas space
|
||||
x = graphX * canvas.ds.scale + canvas.ds.offset[0];
|
||||
y = graphY * canvas.ds.scale + canvas.ds.offset[1];
|
||||
// Convert from canvas space to screen space
|
||||
const canvasRect = canvas.canvas.getBoundingClientRect();
|
||||
x += canvasRect.left;
|
||||
y += canvasRect.top;
|
||||
}
|
||||
|
||||
console.log("LoRA menu position calculated:", {x, y});
|
||||
|
||||
// Use a slight delay to ensure the menu shows after the event completes
|
||||
setTimeout(() => {
|
||||
showCustomLoraMenu(event, slot.widget, this, { x, y });
|
||||
}, 10);
|
||||
|
||||
// Return false to prevent LiteGraph from showing its default menu
|
||||
return false;
|
||||
}
|
||||
|
||||
return LGraphNode.prototype.getSlotMenuOptions?.call(this, slot);
|
||||
}
|
||||
|
||||
// Function to display a simple context menu for low variant information
|
||||
function showLowVariantMenu(event, widget, node, position) {
|
||||
console.log("showLowVariantMenu called.");
|
||||
// Remove any existing menu to prevent duplicates
|
||||
const existingMenu = document.getElementById('low-variant-menu');
|
||||
if (existingMenu) {
|
||||
existingMenu.remove();
|
||||
}
|
||||
|
||||
// Determine which state this widget is in
|
||||
const hasLowVariant = widget.value.is_low && widget.value.low_variant_name;
|
||||
const isBothMode = widget.value.use_for_both;
|
||||
const isLowOnlyMode = widget.value.low_only;
|
||||
const isHighOnlyMode = widget.value.high_only;
|
||||
|
||||
const menu = document.createElement('div');
|
||||
menu.id = 'low-variant-menu';
|
||||
menu.className = 'litegraph litecontextmenu litemenubar-panel';
|
||||
menu.style.cssText = `
|
||||
position: absolute !important;
|
||||
left: ${position.x}px !important;
|
||||
top: ${position.y + 60}px !important;
|
||||
background-color: #1a1a1a !important;
|
||||
border: 1px solid #000 !important;
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,0.8) !important;
|
||||
z-index: 10000 !important;
|
||||
padding: 4px !important;
|
||||
border-radius: 0px !important;
|
||||
min-width: 200px !important;
|
||||
display: block !important;
|
||||
font-family: Arial, sans-serif !important;
|
||||
font-size: 11px !important;
|
||||
`;
|
||||
|
||||
// Helper to create menu item
|
||||
const createMenuItem = (icon, text, onClick, disabled = false) => {
|
||||
const item = document.createElement('div');
|
||||
item.style.cssText = `
|
||||
padding: 4px 8px !important;
|
||||
cursor: ${disabled ? 'default' : 'pointer'} !important;
|
||||
color: ${disabled ? '#555' : '#ddd'} !important;
|
||||
background-color: #1a1a1a !important;
|
||||
user-select: none !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
gap: 6px !important;
|
||||
`;
|
||||
|
||||
if (!disabled) {
|
||||
item.onmouseover = () => {
|
||||
item.style.backgroundColor = '#2a2a2a';
|
||||
};
|
||||
item.onmouseout = () => {
|
||||
item.style.backgroundColor = '#1a1a1a';
|
||||
};
|
||||
item.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
onClick();
|
||||
menu.remove();
|
||||
document.removeEventListener('click', hideMenu);
|
||||
document.removeEventListener('contextmenu', hideMenu);
|
||||
document.removeEventListener('mousedown', hideMenu);
|
||||
};
|
||||
}
|
||||
|
||||
const iconSpan = document.createElement('span');
|
||||
iconSpan.textContent = icon;
|
||||
iconSpan.style.cssText = `
|
||||
font-size: 12px !important;
|
||||
width: 16px !important;
|
||||
text-align: center !important;
|
||||
`;
|
||||
|
||||
const textSpan = document.createElement('span');
|
||||
textSpan.textContent = text;
|
||||
|
||||
item.appendChild(iconSpan);
|
||||
item.appendChild(textSpan);
|
||||
|
||||
return item;
|
||||
};
|
||||
|
||||
// Helper to create separator
|
||||
const createSeparator = () => {
|
||||
const separator = document.createElement('div');
|
||||
separator.style.cssText = `
|
||||
height: 1px !important;
|
||||
background-color: #333 !important;
|
||||
margin: 2px 0 !important;
|
||||
`;
|
||||
return separator;
|
||||
};
|
||||
|
||||
if (hasLowVariant || isBothMode || isLowOnlyMode || isHighOnlyMode) {
|
||||
// For low variant, both mode, low only mode, or high only mode: show the name
|
||||
let displayText;
|
||||
if (isBothMode) {
|
||||
displayText = `${widget.value.lora} (both)`;
|
||||
} else if (isLowOnlyMode) {
|
||||
displayText = `${widget.value.lora} (low only)`;
|
||||
} else if (isHighOnlyMode) {
|
||||
displayText = `${widget.value.lora} (high only)`;
|
||||
} else {
|
||||
displayText = widget.value.low_variant_name;
|
||||
}
|
||||
|
||||
const fileName = displayText.includes('/') || displayText.includes('\\')
|
||||
? displayText.split(/[\/\\]/).pop()
|
||||
: displayText;
|
||||
|
||||
const nameItem = document.createElement('div');
|
||||
nameItem.style.cssText = `
|
||||
padding: 4px 8px !important;
|
||||
cursor: default !important;
|
||||
color: #ddd !important;
|
||||
background-color: #1a1a1a !important;
|
||||
user-select: none !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
`;
|
||||
|
||||
const textSpan = document.createElement('span');
|
||||
textSpan.textContent = fileName;
|
||||
|
||||
nameItem.appendChild(textSpan);
|
||||
menu.appendChild(nameItem);
|
||||
|
||||
// Add separator
|
||||
menu.appendChild(createSeparator());
|
||||
|
||||
if (hasLowVariant) {
|
||||
// For low variant: add Toggle On/Off option
|
||||
menu.appendChild(createMenuItem(
|
||||
widget.value.low_active ? '⚫' : '🟢',
|
||||
widget.value.low_active ? 'Toggle Off' : 'Toggle On',
|
||||
() => {
|
||||
widget.value.low_active = !widget.value.low_active;
|
||||
node.setDirtyCanvas(true, true);
|
||||
}
|
||||
));
|
||||
|
||||
// Add separator
|
||||
menu.appendChild(createSeparator());
|
||||
|
||||
// Add Show Info option
|
||||
menu.appendChild(createMenuItem('ℹ️', 'Show Info', () => {
|
||||
widget.showLoraInfoDialog('low');
|
||||
}));
|
||||
} else if (isBothMode) {
|
||||
// For both mode: add Disable and switching options
|
||||
menu.appendChild(createMenuItem('⚪', 'Disable', () => {
|
||||
widget.value.use_for_both = false;
|
||||
widget.value.low_only = false;
|
||||
widget.value.high_only = false;
|
||||
node.setDirtyCanvas(true, true);
|
||||
}));
|
||||
|
||||
menu.appendChild(createMenuItem('🔵', 'Switch to Low only', () => {
|
||||
widget.value.use_for_both = false;
|
||||
widget.value.low_only = true;
|
||||
widget.value.high_only = false;
|
||||
node.setDirtyCanvas(true, true);
|
||||
}));
|
||||
|
||||
menu.appendChild(createMenuItem('🔴', 'Switch to High only', () => {
|
||||
widget.value.use_for_both = false;
|
||||
widget.value.low_only = false;
|
||||
widget.value.high_only = true;
|
||||
node.setDirtyCanvas(true, true);
|
||||
}));
|
||||
} else if (isLowOnlyMode) {
|
||||
// For low only mode: add Disable and switching options
|
||||
menu.appendChild(createMenuItem('⚪', 'Disable', () => {
|
||||
widget.value.use_for_both = false;
|
||||
widget.value.low_only = false;
|
||||
widget.value.high_only = false;
|
||||
node.setDirtyCanvas(true, true);
|
||||
}));
|
||||
|
||||
menu.appendChild(createMenuItem('🟣', 'Switch to Both', () => {
|
||||
widget.value.use_for_both = true;
|
||||
widget.value.low_only = false;
|
||||
widget.value.high_only = false;
|
||||
node.setDirtyCanvas(true, true);
|
||||
}));
|
||||
|
||||
menu.appendChild(createMenuItem('🔴', 'Switch to High only', () => {
|
||||
widget.value.use_for_both = false;
|
||||
widget.value.low_only = false;
|
||||
widget.value.high_only = true;
|
||||
node.setDirtyCanvas(true, true);
|
||||
}));
|
||||
} else if (isHighOnlyMode) {
|
||||
// For high only mode: add Disable and switching options
|
||||
menu.appendChild(createMenuItem('⚪', 'Disable', () => {
|
||||
widget.value.use_for_both = false;
|
||||
widget.value.low_only = false;
|
||||
widget.value.high_only = false;
|
||||
node.setDirtyCanvas(true, true);
|
||||
}));
|
||||
|
||||
menu.appendChild(createMenuItem('🟣', 'Switch to Both', () => {
|
||||
widget.value.use_for_both = true;
|
||||
widget.value.low_only = false;
|
||||
widget.value.high_only = false;
|
||||
node.setDirtyCanvas(true, true);
|
||||
}));
|
||||
|
||||
menu.appendChild(createMenuItem('🔵', 'Switch to Low only', () => {
|
||||
widget.value.use_for_both = false;
|
||||
widget.value.low_only = true;
|
||||
widget.value.high_only = false;
|
||||
node.setDirtyCanvas(true, true);
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
// No low variant: show all three options
|
||||
menu.appendChild(createMenuItem('🟣', 'Enable for both', () => {
|
||||
widget.value.use_for_both = true;
|
||||
widget.value.low_only = false;
|
||||
widget.value.high_only = false;
|
||||
node.setDirtyCanvas(true, true);
|
||||
}));
|
||||
|
||||
menu.appendChild(createMenuItem('🔵', 'Low only', () => {
|
||||
widget.value.use_for_both = false;
|
||||
widget.value.low_only = true;
|
||||
widget.value.high_only = false;
|
||||
node.setDirtyCanvas(true, true);
|
||||
}));
|
||||
|
||||
menu.appendChild(createMenuItem('🔴', 'High only', () => {
|
||||
widget.value.use_for_both = false;
|
||||
widget.value.low_only = false;
|
||||
widget.value.high_only = true;
|
||||
node.setDirtyCanvas(true, true);
|
||||
}));
|
||||
}
|
||||
|
||||
document.body.appendChild(menu);
|
||||
console.log("Low variant menu appended to body, position:", position);
|
||||
|
||||
// Prevent menu close when interacting with menu
|
||||
menu.onclick = (e) => e.stopPropagation();
|
||||
menu.onmousedown = (e) => e.stopPropagation();
|
||||
|
||||
// Hide menu when clicking outside
|
||||
const hideMenu = (e) => {
|
||||
if (menu && !menu.contains(e.target)) {
|
||||
console.log("Hiding low variant menu");
|
||||
menu.remove();
|
||||
document.removeEventListener('click', hideMenu);
|
||||
document.removeEventListener('contextmenu', hideMenu);
|
||||
document.removeEventListener('mousedown', hideMenu);
|
||||
}
|
||||
};
|
||||
|
||||
// Add listeners after a short delay to prevent immediate closure
|
||||
setTimeout(() => {
|
||||
document.addEventListener('click', hideMenu);
|
||||
document.addEventListener('contextmenu', hideMenu);
|
||||
document.addEventListener('mousedown', hideMenu);
|
||||
}, 100);
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import { CHECKPOINT_INFO_SERVICE } from "./model_info_service.js";
|
||||
|
||||
// Function to show context menu for LoRA list items
|
||||
function showLoraListContextMenu(event, loraName) {
|
||||
console.log("[LoraPickerDialog] Opening context menu for:", loraName); // Debug log
|
||||
// Remove any existing context menu
|
||||
const existingMenu = document.getElementById('lora-list-context-menu');
|
||||
if (existingMenu) {
|
||||
@@ -91,7 +90,6 @@ function showLoraListContextMenu(event, loraName) {
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
console.log('Explorer opened successfully for:', loraName);
|
||||
} else {
|
||||
console.error('Failed to open Explorer:', data.error || 'Unknown error');
|
||||
}
|
||||
@@ -128,7 +126,6 @@ function showLoraListContextMenu(event, loraName) {
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
console.log('Explorer opened successfully for model:', loraName);
|
||||
} else {
|
||||
console.error('Failed to open Explorer for model:', data.error || 'Unknown error');
|
||||
}
|
||||
@@ -187,11 +184,9 @@ function showLoraInfoDialog(loraName) {
|
||||
const infoDialog = new WanLoraInfoDialog(loraName).show();
|
||||
// Store the item type for use in preview generation
|
||||
infoDialog.itemType = 'loras';
|
||||
console.log(`[LoraPickerDialog Debug] Created LoRA dialog with itemType: ${infoDialog.itemType}`);
|
||||
infoDialog.addEventListener("close", ((e) => {
|
||||
if (e.detail.dirty) {
|
||||
// Dialog was modified, could trigger refresh if needed
|
||||
console.log("LoRA info was modified");
|
||||
}
|
||||
}));
|
||||
}).catch(error => {
|
||||
@@ -211,17 +206,14 @@ function showModelInfoDialog(modelName) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[LoraPickerDialog] Opening info dialog for model: ${modelName}`);
|
||||
|
||||
// Import the dialog dynamically and create a model-specific info dialog
|
||||
import("./dialog_info.js").then(({ WanLoraInfoDialog }) => {
|
||||
// Create a model-specific info dialog by extending WanLoraInfoDialog
|
||||
class WanModelInfoDialog extends WanLoraInfoDialog {
|
||||
async getModelInfo(file) {
|
||||
console.log(`[LoraPickerDialog] Fetching info for model: ${file}`);
|
||||
try {
|
||||
const info = await CHECKPOINT_INFO_SERVICE.getInfo(file, false, false);
|
||||
console.log(`[LoraPickerDialog] Model info received:`, info);
|
||||
return info;
|
||||
} catch (error) {
|
||||
console.error(`[LoraPickerDialog] Error fetching model info:`, error);
|
||||
@@ -237,11 +229,8 @@ function showModelInfoDialog(modelName) {
|
||||
|
||||
// Override refreshModelInfo to use CHECKPOINT_INFO_SERVICE instead of LORA_INFO_SERVICE
|
||||
async refreshModelInfo(file) {
|
||||
console.log(`[LoraPickerDialog] Refreshing model info for: ${file}`);
|
||||
console.log(`[LoraPickerDialog] Using CHECKPOINT_INFO_SERVICE for Civitai fetch`);
|
||||
try {
|
||||
const info = await CHECKPOINT_INFO_SERVICE.refreshInfo(file);
|
||||
console.log(`[LoraPickerDialog] Model info refreshed from Civitai:`, info);
|
||||
return info;
|
||||
} catch (error) {
|
||||
console.error(`[LoraPickerDialog] Error refreshing model info from Civitai:`, error);
|
||||
@@ -257,10 +246,8 @@ function showModelInfoDialog(modelName) {
|
||||
|
||||
// Override the save method to use CHECKPOINT_INFO_SERVICE
|
||||
async savePartialInfo(info, fieldName, newValue) {
|
||||
console.log(`[LoraPickerDialog] Saving model info: ${fieldName} = ${newValue}`);
|
||||
try {
|
||||
await CHECKPOINT_INFO_SERVICE.savePartialInfo(info.file, { [fieldName]: newValue });
|
||||
console.log(`[LoraPickerDialog] Model info saved successfully`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(`[LoraPickerDialog] Error saving model info:`, error);
|
||||
@@ -273,7 +260,6 @@ function showModelInfoDialog(modelName) {
|
||||
const dialog = new WanModelInfoDialog(modelName);
|
||||
// Store the item type for use in preview generation
|
||||
dialog.itemType = 'checkpoints';
|
||||
console.log(`[LoraPickerDialog Debug] Created model dialog with itemType: ${dialog.itemType}`);
|
||||
dialog.show();
|
||||
}).catch(error => {
|
||||
// Fallback to simple alert if dialog fails to load
|
||||
@@ -377,6 +363,65 @@ style.textContent = `
|
||||
flex: 1 !important;
|
||||
}
|
||||
|
||||
.lora-picker-folder-arrow {
|
||||
font-size: 9px !important;
|
||||
color: #999 !important;
|
||||
margin-left: auto !important;
|
||||
margin-right: 2px !important;
|
||||
padding: 1px 4px !important;
|
||||
cursor: pointer !important;
|
||||
user-select: none !important;
|
||||
flex-shrink: 0 !important;
|
||||
border-radius: 2px !important;
|
||||
transition: color 0.2s !important;
|
||||
}
|
||||
|
||||
.lora-picker-folder-arrow:hover {
|
||||
color: #fff !important;
|
||||
background-color: #3a3a3a !important;
|
||||
}
|
||||
|
||||
.lora-picker-subfolder-item {
|
||||
display: flex !important;
|
||||
flex-direction: row !important;
|
||||
align-items: center !important;
|
||||
padding: 2px 4px 2px 18px !important;
|
||||
cursor: pointer !important;
|
||||
border-radius: 0px !important;
|
||||
transition: background-color 0.2s !important;
|
||||
margin: 0 !important;
|
||||
border-left: 2px solid transparent !important;
|
||||
border-right: 2px solid transparent !important;
|
||||
}
|
||||
|
||||
.lora-picker-subfolder-item:hover {
|
||||
background-color: #333 !important;
|
||||
}
|
||||
|
||||
.lora-picker-subfolder-item.selected {
|
||||
background-color: #2a2a2a !important;
|
||||
border-left: 2px solid #89B !important;
|
||||
border-right: 2px solid #89B !important;
|
||||
}
|
||||
|
||||
.lora-picker-subfolder-icon {
|
||||
font-size: 10px !important;
|
||||
margin-right: 4px !important;
|
||||
color: #89B !important;
|
||||
flex-shrink: 0 !important;
|
||||
}
|
||||
|
||||
.lora-picker-subfolder-name {
|
||||
font-size: 10px !important;
|
||||
text-align: left !important;
|
||||
color: #bbb !important;
|
||||
white-space: nowrap !important;
|
||||
overflow: hidden !important;
|
||||
text-overflow: ellipsis !important;
|
||||
user-select: none !important;
|
||||
flex: 1 !important;
|
||||
}
|
||||
|
||||
.lora-picker-main-content {
|
||||
flex: 1 !important;
|
||||
display: flex !important;
|
||||
@@ -664,6 +709,16 @@ function loadSelectedFolder() {
|
||||
return selectedFolder !== null ? JSON.parse(selectedFolder) : null;
|
||||
}
|
||||
|
||||
// Persistence functions for selected subfolder
|
||||
function saveSelectedSubfolder(selectedSubfolder) {
|
||||
localStorage.setItem("wanVideoPowerLoraLoader.selectedSubfolder", JSON.stringify(selectedSubfolder));
|
||||
}
|
||||
|
||||
function loadSelectedSubfolder() {
|
||||
const selectedSubfolder = localStorage.getItem("wanVideoPowerLoraLoader.selectedSubfolder");
|
||||
return selectedSubfolder !== null ? JSON.parse(selectedSubfolder) : null;
|
||||
}
|
||||
|
||||
// Persistence functions for eye refresh button state
|
||||
function saveEyeRefreshState(eyeRefreshState) {
|
||||
localStorage.setItem("wanVideoPowerLoraLoader.eyeRefreshState", JSON.stringify(eyeRefreshState));
|
||||
@@ -679,12 +734,14 @@ function saveAllUIState(dialog) {
|
||||
foldersVisible: dialog.options.foldersVisible,
|
||||
eyeRefreshState: dialog.eyeRefreshState,
|
||||
selectedFolder: dialog.selectedFolder,
|
||||
selectedSubfolder: dialog.selectedSubfolder || null,
|
||||
circleIconState: dialog.circleIconState
|
||||
};
|
||||
|
||||
saveFoldersVisible(dialog.options.foldersVisible);
|
||||
saveEyeRefreshState(dialog.eyeRefreshState);
|
||||
saveSelectedFolder(dialog.selectedFolder);
|
||||
saveSelectedSubfolder(dialog.selectedSubfolder || null);
|
||||
|
||||
if (dialog.parentNode && dialog.parentNode.widgets) {
|
||||
// Try both widget names to support both node types
|
||||
@@ -707,6 +764,7 @@ function loadAllUIState(dialog) {
|
||||
foldersVisible: undefined,
|
||||
eyeRefreshState: undefined,
|
||||
selectedFolder: undefined,
|
||||
selectedSubfolder: undefined,
|
||||
circleIconState: undefined
|
||||
};
|
||||
|
||||
@@ -734,6 +792,9 @@ function loadAllUIState(dialog) {
|
||||
if (uiState.selectedFolder === undefined) {
|
||||
uiState.selectedFolder = loadSelectedFolder();
|
||||
}
|
||||
if (uiState.selectedSubfolder === undefined) {
|
||||
uiState.selectedSubfolder = loadSelectedSubfolder();
|
||||
}
|
||||
if (uiState.circleIconState === undefined) {
|
||||
uiState.circleIconState = 0; // Default to 0 (empty circle)
|
||||
}
|
||||
@@ -741,6 +802,7 @@ function loadAllUIState(dialog) {
|
||||
dialog.options.foldersVisible = uiState.foldersVisible !== false;
|
||||
dialog.eyeRefreshState = uiState.eyeRefreshState || false;
|
||||
dialog.selectedFolder = uiState.selectedFolder || null;
|
||||
dialog.selectedSubfolder = uiState.selectedSubfolder || null;
|
||||
dialog.circleIconState = uiState.circleIconState || 0;
|
||||
|
||||
if (dialog.parentNode && dialog.parentNode.widgets) {
|
||||
@@ -771,6 +833,10 @@ export class LoraPickerDialog {
|
||||
|
||||
this.sidebarWidth = 150;
|
||||
|
||||
// Subfolder support state (must exist before loadAllUIState restores it)
|
||||
this.expandedFolders = new Set();
|
||||
this.selectedSubfolder = null;
|
||||
|
||||
loadAllUIState(this);
|
||||
|
||||
// Initialize circle icon state if not already set
|
||||
@@ -1343,38 +1409,132 @@ export class LoraPickerDialog {
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the distinct subfolder names (second path segment) under a given
|
||||
// top-level folder - only subfolders that directly contain loras.
|
||||
getSubfolders(parentFolder) {
|
||||
const subs = new Set();
|
||||
for (const lora of this.loras) {
|
||||
const name = typeof lora === 'string' ? lora : lora.name;
|
||||
if (!name) continue;
|
||||
const parts = name.split(/[\/\\]/);
|
||||
if (parts.length >= 3 && parts[0] === parentFolder && parts[1]) {
|
||||
subs.add(parts[1]);
|
||||
}
|
||||
}
|
||||
return [...subs].sort((a, b) => a.localeCompare(b));
|
||||
}
|
||||
|
||||
renderFoldersInSidebar(container) {
|
||||
container.innerHTML = "";
|
||||
|
||||
for (const folder of this.folders) {
|
||||
// Wrapper holds the folder row and (when expanded) its subfolders
|
||||
const wrapper = document.createElement("div");
|
||||
wrapper.className = "lora-picker-folder-wrapper";
|
||||
|
||||
const folderItem = document.createElement("div");
|
||||
folderItem.className = "lora-picker-folder-item";
|
||||
if (this.selectedFolder === folder) {
|
||||
folderItem.classList.add("selected");
|
||||
}
|
||||
|
||||
|
||||
// Folder icon
|
||||
const folderIcon = document.createElement("div");
|
||||
folderIcon.className = "lora-picker-folder-icon";
|
||||
folderIcon.textContent = "📁";
|
||||
|
||||
|
||||
// Folder name
|
||||
const folderName = document.createElement("div");
|
||||
folderName.className = "lora-picker-folder-name";
|
||||
folderName.textContent = folder;
|
||||
|
||||
|
||||
folderItem.appendChild(folderIcon);
|
||||
folderItem.appendChild(folderName);
|
||||
|
||||
// Add click handler
|
||||
|
||||
// Add click handler for the folder row
|
||||
folderItem.addEventListener("click", () => {
|
||||
this.selectFolder(folder);
|
||||
});
|
||||
|
||||
container.appendChild(folderItem);
|
||||
|
||||
// Folder row goes first...
|
||||
wrapper.appendChild(folderItem);
|
||||
|
||||
// Down arrow for folders that have subdirectories (right-aligned).
|
||||
// Only shows subfolders 1 level deep.
|
||||
const subfolders = folder === "root" ? [] : this.getSubfolders(folder);
|
||||
if (subfolders.length > 0) {
|
||||
const isExpanded = this.expandedFolders.has(folder);
|
||||
const arrow = document.createElement("div");
|
||||
arrow.className = "lora-picker-folder-arrow";
|
||||
arrow.textContent = isExpanded ? "▼" : "▶";
|
||||
arrow.title = isExpanded ? "Collapse subfolders" : "Show subfolders";
|
||||
arrow.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
if (this.expandedFolders.has(folder)) {
|
||||
this.expandedFolders.delete(folder);
|
||||
} else {
|
||||
this.expandedFolders.add(folder);
|
||||
}
|
||||
this.renderFoldersInSidebar(container);
|
||||
});
|
||||
folderItem.appendChild(arrow);
|
||||
|
||||
// ...then the subfolder rows underneath it
|
||||
if (isExpanded) {
|
||||
for (const sub of subfolders) {
|
||||
const subItem = document.createElement("div");
|
||||
subItem.className = "lora-picker-subfolder-item";
|
||||
if (this.selectedFolder === folder && this.selectedSubfolder === sub) {
|
||||
subItem.classList.add("selected");
|
||||
}
|
||||
|
||||
const subIcon = document.createElement("div");
|
||||
subIcon.className = "lora-picker-subfolder-icon";
|
||||
subIcon.textContent = "📂";
|
||||
|
||||
const subName = document.createElement("div");
|
||||
subName.className = "lora-picker-subfolder-name";
|
||||
subName.textContent = sub;
|
||||
|
||||
subItem.appendChild(subIcon);
|
||||
subItem.appendChild(subName);
|
||||
|
||||
subItem.addEventListener("click", () => {
|
||||
this.selectSubfolder(folder, sub);
|
||||
});
|
||||
|
||||
wrapper.appendChild(subItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
container.appendChild(wrapper);
|
||||
}
|
||||
}
|
||||
|
||||
getSidebarFolderContainer() {
|
||||
return this.sidebar
|
||||
? (this.sidebar.querySelector(".lora-picker-sidebar-content") || this.sidebar)
|
||||
: null;
|
||||
}
|
||||
|
||||
selectSubfolder(parentFolder, subfolder) {
|
||||
// Clicking the already selected subfolder deselects it (parent stays)
|
||||
if (this.selectedFolder === parentFolder && this.selectedSubfolder === subfolder) {
|
||||
this.selectedSubfolder = null;
|
||||
saveAllUIState(this);
|
||||
this.renderFoldersInSidebar(this.getSidebarFolderContainer());
|
||||
this.renderList();
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectedFolder = parentFolder;
|
||||
this.selectedSubfolder = subfolder;
|
||||
saveAllUIState(this);
|
||||
this.renderFoldersInSidebar(this.getSidebarFolderContainer());
|
||||
this.renderList();
|
||||
}
|
||||
|
||||
extractFolders() {
|
||||
const folderSet = new Set();
|
||||
// Always add "root" folder
|
||||
@@ -1410,20 +1570,25 @@ export class LoraPickerDialog {
|
||||
prevSelected.classList.remove("selected");
|
||||
}
|
||||
this.selectedFolder = null;
|
||||
this.selectedSubfolder = null;
|
||||
// Save all UI state to both localStorage and workflow
|
||||
saveAllUIState(this);
|
||||
// Update indicator line visibility
|
||||
this.updateFolderIndicator();
|
||||
this.renderFoldersInSidebar(this.getSidebarFolderContainer());
|
||||
this.renderList(); // Refresh the list
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Remove selection from previous folder
|
||||
const prevSelected = this.sidebar.querySelector(".lora-picker-folder-item.selected");
|
||||
if (prevSelected) {
|
||||
prevSelected.classList.remove("selected");
|
||||
}
|
||||
|
||||
|
||||
// Switching to a different folder clears any subfolder filter
|
||||
this.selectedSubfolder = null;
|
||||
|
||||
// Add selection to new folder
|
||||
const folderItems = this.sidebar.querySelectorAll(".lora-picker-folder-item");
|
||||
for (const item of folderItems) {
|
||||
@@ -1433,12 +1598,13 @@ export class LoraPickerDialog {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.selectedFolder = folderName;
|
||||
// Save all UI state to both localStorage and workflow
|
||||
saveAllUIState(this);
|
||||
// Update indicator line visibility
|
||||
this.updateFolderIndicator();
|
||||
this.renderFoldersInSidebar(this.getSidebarFolderContainer());
|
||||
this.renderList(); // Refresh the list
|
||||
}
|
||||
|
||||
@@ -1455,12 +1621,23 @@ export class LoraPickerDialog {
|
||||
// Remove folder prefix for display purposes only (first level only)
|
||||
let displayName = name;
|
||||
if (this.selectedFolder && this.selectedFolder !== "root") {
|
||||
const prefix = this.selectedFolder + "/";
|
||||
const altPrefix = this.selectedFolder + "\\";
|
||||
// Strip the subfolder prefix too when one is selected
|
||||
const prefix = this.selectedSubfolder
|
||||
? this.selectedFolder + "/" + this.selectedSubfolder + "/"
|
||||
: this.selectedFolder + "/";
|
||||
const altPrefix = prefix.replace("/", "\\");
|
||||
if (name.startsWith(prefix)) {
|
||||
displayName = name.substring(prefix.length);
|
||||
} else if (name.startsWith(altPrefix)) {
|
||||
displayName = name.substring(altPrefix.length);
|
||||
} else if (!this.selectedSubfolder) {
|
||||
const plainPrefix = this.selectedFolder + "/";
|
||||
const plainAltPrefix = this.selectedFolder + "\\";
|
||||
if (name.startsWith(plainPrefix)) {
|
||||
displayName = name.substring(plainPrefix.length);
|
||||
} else if (name.startsWith(plainAltPrefix)) {
|
||||
displayName = name.substring(plainAltPrefix.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1582,8 +1759,15 @@ export class LoraPickerDialog {
|
||||
|
||||
let filteredLoras = loras;
|
||||
|
||||
// Filter by selected folder
|
||||
if (this.selectedFolder && this.selectedFolder !== "root") {
|
||||
// Filter by selected folder + subfolder (subfolder takes precedence)
|
||||
if (this.selectedFolder && this.selectedFolder !== "root" && this.selectedSubfolder) {
|
||||
const prefixFwd = this.selectedFolder + "/" + this.selectedSubfolder + "/";
|
||||
const prefixBck = this.selectedFolder + "\\" + this.selectedSubfolder + "\\";
|
||||
filteredLoras = filteredLoras.filter(l => {
|
||||
const name = typeof l === 'string' ? l : l.name;
|
||||
return !!name && (name.startsWith(prefixFwd) || name.startsWith(prefixBck));
|
||||
});
|
||||
} else if (this.selectedFolder && this.selectedFolder !== "root") {
|
||||
filteredLoras = filteredLoras.filter(l => {
|
||||
const name = typeof l === 'string' ? l : l.name;
|
||||
// Check if the lora is in the selected folder
|
||||
@@ -2104,7 +2288,6 @@ export class LoraPickerDialog {
|
||||
menu.appendChild(createMenuItem('📊 Show Stats', async () => {
|
||||
// Show comprehensive LoRA statistics
|
||||
try {
|
||||
console.log('📊 Gathering LoRA statistics...');
|
||||
|
||||
// Get all available LoRA names from the system
|
||||
const allLoras = await fetch('/wanvid/api/loras')
|
||||
@@ -2262,7 +2445,6 @@ export class LoraPickerDialog {
|
||||
// Update preview availability cache and refresh
|
||||
try {
|
||||
await this.loadPreviewAvailability();
|
||||
console.log('Preview availability cache updated successfully');
|
||||
} catch (error) {
|
||||
console.warn('Failed to update preview availability cache:', error);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { rgthreeApi } from "./rgthree_api.js";
|
||||
import { vidApi } from "./wanvid_api.js";
|
||||
import { api } from "../../../scripts/api.js";
|
||||
import { generatePreviewFromFirstImage } from "./image_utils.js";
|
||||
|
||||
@@ -22,15 +22,15 @@ class BaseModelInfoService extends EventTarget {
|
||||
}
|
||||
async clearFetchedInfo(file) {
|
||||
if (this.modelInfoType === 'loras') {
|
||||
await rgthreeApi.clearSingleLoraInfo(file);
|
||||
await vidApi.clearSingleLoraInfo(file);
|
||||
} else if (this.modelInfoType === 'checkpoints') {
|
||||
await rgthreeApi.clearSingleCheckpointInfo(file);
|
||||
await vidApi.clearSingleCheckpointInfo(file);
|
||||
}
|
||||
this.fileToInfo.delete(file);
|
||||
return null;
|
||||
}
|
||||
async savePartialInfo(file, data, options = {}) {
|
||||
let info = await rgthreeApi.saveModelInfo(this.modelInfoType, file, data);
|
||||
let info = await vidApi.saveModelInfo(this.modelInfoType, file, data);
|
||||
this.fileToInfo.set(file, info);
|
||||
|
||||
// Generate preview image for LoRAs ONLY if explicitly requested and we have image data
|
||||
@@ -44,7 +44,6 @@ class BaseModelInfoService extends EventTarget {
|
||||
|
||||
// Generate and save preview image
|
||||
const previewResult = await generatePreviewFromFirstImage(info, loraName, loraPath);
|
||||
console.log(`[LoraInfoService] Preview image generated for ${loraName}:`, previewResult);
|
||||
} catch (error) {
|
||||
console.warn(`[LoraInfoService] Failed to generate preview image for ${file}:`, error);
|
||||
// Don't let preview generation failure stop the save operation
|
||||
@@ -64,10 +63,10 @@ class BaseModelInfoService extends EventTarget {
|
||||
var _a;
|
||||
let info = null;
|
||||
if (!refresh) {
|
||||
info = await rgthreeApi.getModelsInfo({ type: this.modelInfoType, files: [file], light });
|
||||
info = await vidApi.getModelsInfo({ type: this.modelInfoType, files: [file], light });
|
||||
}
|
||||
else {
|
||||
info = await rgthreeApi.refreshModelsInfo({ type: this.modelInfoType, files: [file] });
|
||||
info = await vidApi.refreshModelsInfo({ type: this.modelInfoType, files: [file] });
|
||||
}
|
||||
info = (_a = info === null || info === void 0 ? void 0 : info[0]) !== null && _a !== void 0 ? _a : null;
|
||||
if (!light) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
import { app } from "../../../scripts/app.js";
|
||||
import { rgthree } from "../rgthree/common/rgthree.js";
|
||||
import { RgthreeBetterButtonWidget, RgthreeDividerWidget, CombinedOptionsWidget, PowerLoraLoaderHeaderWidget, PowerLoraLoaderWidget } from "./widgets.js";
|
||||
import { qq } from "../qq_lib/common/qq.js";
|
||||
import { QQBetterButtonWidget, QQDividerWidget, CombinedOptionsWidget, PowerLoraLoaderHeaderWidget, PowerLoraLoaderWidget } from "./widgets.js";
|
||||
import { LoraPickerDialog } from "./lora_picker_dialog.js";
|
||||
import { getLoraSlotInPosition, getLoraSlotMenuOptions } from "./lora_context_menu.js";
|
||||
import { modelDataManager, UIManager } from "./pull_info.js";
|
||||
@@ -51,19 +51,15 @@ function showLoraPicker(event, callback, node = null) {
|
||||
refreshCallback: async () => {
|
||||
// Perform actual refresh of the LoRA list using unified system
|
||||
try {
|
||||
console.log('[WanVideoPowerLoraLoader] Refreshing LoRA list...');
|
||||
const freshLoras = await getLoras(true);
|
||||
dialog.updateLoras(freshLoras);
|
||||
|
||||
// Also refresh preview availability for blue dots using unified system
|
||||
console.log('[WanVideoPowerLoraLoader] Refreshing preview availability...');
|
||||
await modelDataManager.getPreviewAvailability('loras');
|
||||
|
||||
// Re-render the list to show updated blue dots
|
||||
console.log('[WanVideoPowerLoraLoader] Re-rendering list to show updated blue dots...');
|
||||
dialog.renderList();
|
||||
|
||||
console.log('[WanVideoPowerLoraLoader] LoRA list refreshed successfully');
|
||||
} catch (error) {
|
||||
console.error('[WanVideoPowerLoraLoader] Error refreshing LoRA list:', error);
|
||||
}
|
||||
@@ -159,10 +155,10 @@ app.registerExtension({
|
||||
|
||||
nodeType.prototype.addNonLoraWidgets = function () {
|
||||
this.addCustomWidget(new CombinedOptionsWidget());
|
||||
this.addCustomWidget(new RgthreeDividerWidget({ marginTop: 4, marginBottom: 0, thickness: 0 }));
|
||||
this.addCustomWidget(new QQDividerWidget({ marginTop: 4, marginBottom: 0, thickness: 0 }));
|
||||
this.addCustomWidget(new PowerLoraLoaderHeaderWidget());
|
||||
this.widgetButtonSpacer = this.addCustomWidget(new RgthreeDividerWidget({ marginTop: 4, marginBottom: 0, thickness: 0 }));
|
||||
this.addCustomWidget(new RgthreeBetterButtonWidget("➕ Add Lora", (event, pos, node) => {
|
||||
this.widgetButtonSpacer = this.addCustomWidget(new QQDividerWidget({ marginTop: 4, marginBottom: 0, thickness: 0 }));
|
||||
this.addCustomWidget(new QQBetterButtonWidget("➕ Add Lora", (event, pos, node) => {
|
||||
showLoraPicker(event, (lora) => {
|
||||
if (lora) {
|
||||
this.addNewLoraWidget().setLora(lora);
|
||||
@@ -328,12 +324,6 @@ app.registerExtension({
|
||||
// Re-run the low variant check with updated cache
|
||||
widget.checkLowLoraVariant(widget.value.lora);
|
||||
|
||||
// Log changes in low variant detection
|
||||
if (oldIsLow !== widget.value.is_low || oldVariantName !== widget.value.low_variant_name) {
|
||||
console.log(`[WanVideoPowerLoraLoader] Low variant status changed for '${widget.value.lora}': ` +
|
||||
`${oldIsLow} -> ${widget.value.is_low}, ` +
|
||||
`variant: '${oldVariantName}' -> '${widget.value.low_variant_name}'`);
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger a value change to update the UI
|
||||
@@ -348,7 +338,6 @@ app.registerExtension({
|
||||
}
|
||||
});
|
||||
|
||||
console.log('[WanVideoPowerLoraLoader] LoRA definitions refreshed - individual LoRA info available in dialogs');
|
||||
}).catch(error => {
|
||||
console.error('[WanVideoPowerLoraLoader] Error refreshing LoRA list:', error);
|
||||
});
|
||||
|
||||
@@ -19,9 +19,10 @@
|
||||
|
||||
import { app } from "../../../scripts/app.js";
|
||||
import {
|
||||
RgthreeBaseWidget, RgthreeBetterButtonWidget, RgthreeDividerWidget,
|
||||
QQBaseWidget, QQBetterButtonWidget, QQDividerWidget,
|
||||
StrengthCopyWidget, PowerLoraLoaderHeaderWidget, PowerLoraLoaderWidget,
|
||||
fitString, drawTogglePart, drawRoundedRectangle,
|
||||
drawNumberWidgetPart, drawInfoIcon,
|
||||
} from "./widgets.js";
|
||||
import { LoraPickerDialog } from "./lora_picker_dialog.js";
|
||||
import { getLoraSlotInPosition, getLoraSlotMenuOptions } from "./lora_context_menu.js";
|
||||
@@ -183,12 +184,166 @@ export class PowerLoraLoaderWidgetEx extends PowerLoraLoaderWidget {
|
||||
if (!strengthSplit) {
|
||||
// Single-strength mode: no Low strength hit area / interactions
|
||||
delete this.hitAreas.low_strength;
|
||||
// Simple mode: no colored variant circle at the end of the row,
|
||||
// so also remove its hit area (no variant menu on right-click there)
|
||||
delete this.hitAreas.low_variant_icon;
|
||||
// Simple mode extras (borrowed from qq's Power Lora Loader):
|
||||
// "i" info badge next to the strength, and "<" / ">" steppers.
|
||||
this.hitAreas.info = { bounds: [0, 0], onDown: this.onInfoDown };
|
||||
this.hitAreas.strengthDec = { bounds: [0, 0], onDown: this.onStrengthDecDown };
|
||||
this.hitAreas.strengthInc = { bounds: [0, 0], onDown: this.onStrengthIncDown };
|
||||
// Drag-to-change area spanning the whole "< value >" part; the
|
||||
// click-to-prompt popup only fires on the number itself (strength).
|
||||
this.hitAreas.strengthAny = { bounds: [0, 0], onMove: this.onStrengthAnyMove };
|
||||
}
|
||||
}
|
||||
|
||||
// --- Simple-mode only handlers (qq-style) ---
|
||||
|
||||
onInfoDown(event, pos, node) {
|
||||
if (!this.value.lora || this.value.lora === "None") return;
|
||||
this.cancelMouseDown();
|
||||
this.showLoraInfoDialog('high');
|
||||
}
|
||||
|
||||
onStrengthDecDown(event, pos, node) {
|
||||
this.stepStrength(-1);
|
||||
node.setDirtyCanvas(true, false);
|
||||
}
|
||||
|
||||
onStrengthIncDown(event, pos, node) {
|
||||
this.stepStrength(1);
|
||||
node.setDirtyCanvas(true, false);
|
||||
}
|
||||
|
||||
stepStrength(direction) {
|
||||
const step = 0.05;
|
||||
const strength = (this.value.strength ?? 1) + step * direction;
|
||||
this.value.strength = Math.round(strength * 100) / 100;
|
||||
}
|
||||
|
||||
// --- Drag-to-reorder (Shift + Left Click on the LoRA name) ---
|
||||
// Mechanism ported from Power Spline Editor's layer widgets.
|
||||
|
||||
mouse(event, pos, node) {
|
||||
// While a reorder drag is active, swallow move/up events so no hit
|
||||
// areas (toggle, strength drag, lora chooser...) fire accidentally.
|
||||
if (this._reorderDragActive) {
|
||||
if (event.type === 'pointerup' || event.type === 'pointermove') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Shift + Left Click on the lora name => begin drag-to-reorder
|
||||
if (event.type === 'pointerdown' && event.shiftKey && event.button === 0) {
|
||||
if (this.clickWasWithinBounds(pos, this.hitAreas.lora.bounds)) {
|
||||
this._beginLoraReorderDrag(event, node);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return super.mouse(event, pos, node);
|
||||
}
|
||||
|
||||
_beginLoraReorderDrag(pointerDownEvent, node) {
|
||||
const getLoras = () => node.widgets.filter(w => w.name?.startsWith("lora_"));
|
||||
const loras = getLoras();
|
||||
const startIndex = loras.indexOf(this);
|
||||
if (startIndex < 0) return;
|
||||
|
||||
this._reorderDragActive = true;
|
||||
this._reorderStartIndex = startIndex;
|
||||
this._reorderTargetIndex = startIndex;
|
||||
this._reorderStartClientY = pointerDownEvent.clientY;
|
||||
|
||||
// Cursor feedback
|
||||
this._prevBodyCursor = document.body.style.cursor;
|
||||
document.body.style.cursor = 'grabbing';
|
||||
try { app.canvas.canvas.style.cursor = 'grabbing'; } catch {}
|
||||
|
||||
const onMove = (e) => {
|
||||
if (!this._reorderDragActive) return;
|
||||
const dy = (e.clientY - this._reorderStartClientY) || 0;
|
||||
const step = Math.round(dy / (LiteGraph.NODE_WIDGET_HEIGHT || 24));
|
||||
let idx = this._reorderStartIndex + step;
|
||||
// Allow dropping after the last lora (idx === loras.length)
|
||||
idx = Math.max(0, Math.min(loras.length, idx));
|
||||
if (idx !== this._reorderTargetIndex) {
|
||||
this._reorderTargetIndex = idx;
|
||||
}
|
||||
try { node.setDirtyCanvas(true, true); } catch {}
|
||||
};
|
||||
const onUp = () => {
|
||||
document.removeEventListener('pointermove', onMove, true);
|
||||
document.removeEventListener('pointerup', onUp, true);
|
||||
// Restore cursor
|
||||
try { app.canvas.canvas.style.cursor = ''; } catch {}
|
||||
document.body.style.cursor = this._prevBodyCursor || '';
|
||||
|
||||
if (!this._reorderDragActive) return;
|
||||
this._reorderDragActive = false;
|
||||
|
||||
const lorasNow = getLoras();
|
||||
const from = lorasNow.indexOf(this);
|
||||
const to = this._reorderTargetIndex ?? from;
|
||||
if (from >= 0 && to >= 0 && from !== to) {
|
||||
const widgets = node.widgets;
|
||||
// Remove from current global position
|
||||
widgets.splice(widgets.indexOf(this), 1);
|
||||
if (to < lorasNow.length) {
|
||||
// Insert before the target lora widget
|
||||
const before = lorasNow[to];
|
||||
const targetGlobal = widgets.indexOf(before);
|
||||
widgets.splice(targetGlobal >= 0 ? targetGlobal : widgets.length, 0, this);
|
||||
} else {
|
||||
// Insert after the last lora widget
|
||||
const afterRemoval = getLoras();
|
||||
const last = afterRemoval[afterRemoval.length - 1];
|
||||
const lastIdx = last ? widgets.indexOf(last) : -1;
|
||||
widgets.splice(lastIdx >= 0 ? lastIdx + 1 : widgets.length, 0, this);
|
||||
}
|
||||
try { node.setDirtyCanvas(true, true); } catch {}
|
||||
}
|
||||
this._reorderStartIndex = undefined;
|
||||
this._reorderTargetIndex = undefined;
|
||||
this._reorderStartClientY = undefined;
|
||||
};
|
||||
|
||||
document.addEventListener('pointermove', onMove, true);
|
||||
document.addEventListener('pointerup', onUp, true);
|
||||
}
|
||||
|
||||
// Cyan insertion indicator line, drawn at the widget sitting at the
|
||||
// current drop target index while a reorder drag is active.
|
||||
_drawReorderIndicatorIfTarget(ctx, node, y, h) {
|
||||
try {
|
||||
const loras = node.widgets.filter(w => w.name?.startsWith("lora_"));
|
||||
const dragging = loras.find(w => w && w._reorderDragActive);
|
||||
if (!dragging) return;
|
||||
const target = dragging._reorderTargetIndex ?? -1;
|
||||
const myIndex = loras.indexOf(this);
|
||||
const isLast = myIndex === loras.length - 1;
|
||||
const showTopLine = myIndex === target && target < loras.length;
|
||||
const showBottomLine = isLast && target === loras.length; // after-last sentinel
|
||||
if (showTopLine || showBottomLine) {
|
||||
ctx.save();
|
||||
ctx.strokeStyle = '#2cc6ff';
|
||||
ctx.globalAlpha = app.canvas.editor_alpha;
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
const left = 10;
|
||||
const right = node.size[0] - 10;
|
||||
const lineY = showTopLine ? (y - 1) : (y + h - 1);
|
||||
ctx.moveTo(left, lineY);
|
||||
ctx.lineTo(right, lineY);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
draw(ctx, node, w, y, h) {
|
||||
if (this.strengthSplit) {
|
||||
super.draw(ctx, node, w, y, h);
|
||||
this._drawReorderIndicatorIfTarget(ctx, node, y, h);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -202,38 +357,37 @@ export class PowerLoraLoaderWidgetEx extends PowerLoraLoaderWidget {
|
||||
this.hitAreas.toggle.bounds = drawTogglePart(ctx, [posX, y], h, this.value.on);
|
||||
posX += this.hitAreas.toggle.bounds[1] + MARGIN;
|
||||
|
||||
let rposX = w - MARGIN;
|
||||
if (this.value.use_for_both) {
|
||||
ctx.fillStyle = "#9370DB"; // Purple for "both" state
|
||||
} else if (this.value.low_only) {
|
||||
ctx.fillStyle = "#4169E1"; // Blue for "low only" state
|
||||
} else if (this.value.high_only) {
|
||||
ctx.fillStyle = "#DC143C"; // Red for "high only" state
|
||||
} else if (this.value.is_low) {
|
||||
ctx.fillStyle = this.value.low_active ? "lime" : "orange";
|
||||
} else {
|
||||
ctx.fillStyle = "#555";
|
||||
}
|
||||
ctx.beginPath();
|
||||
ctx.arc(rposX - ICON_WIDTH / 2, y + h / 2, h * 0.2, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
// Simple mode: no colored circle at the end of the row. Instead, from
|
||||
// the right edge: "< strength >" stepper, then the "i" info badge
|
||||
// (v2 keeps the circle + plain Low/High text values).
|
||||
const innerMargin = MARGIN * 0.33;
|
||||
const iconGap = 5; // extra breathing room
|
||||
// right offset => spacing after the ">" arrow
|
||||
let rposX = w - MARGIN - innerMargin - iconGap;
|
||||
|
||||
// Store the bounds for the low variant icon
|
||||
this.hitAreas.low_variant_icon.bounds = [
|
||||
rposX - ICON_WIDTH,
|
||||
y,
|
||||
ICON_WIDTH,
|
||||
h
|
||||
];
|
||||
|
||||
rposX -= ICON_WIDTH + MARGIN;
|
||||
|
||||
this.hitAreas.strength.bounds = [rposX - STRENGTH_WIDTH, STRENGTH_WIDTH];
|
||||
ctx.textAlign = "right";
|
||||
// "< strength >" arrows. Clicking the number opens the prompt popup;
|
||||
// the arrows only step the value (no popup). Dragging anywhere over
|
||||
// the part changes the value.
|
||||
ctx.fillStyle = "white";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText(this.value.strength.toFixed(2), rposX, y + h / 2);
|
||||
rposX -= STRENGTH_WIDTH + MARGIN;
|
||||
const [leftArrow, textBounds, rightArrow] = drawNumberWidgetPart(ctx, {
|
||||
posX: rposX,
|
||||
posY: y,
|
||||
height: h,
|
||||
value: this.value.strength ?? 1,
|
||||
direction: -1,
|
||||
});
|
||||
this.hitAreas.strengthDec.bounds = leftArrow;
|
||||
this.hitAreas.strengthInc.bounds = rightArrow;
|
||||
this.hitAreas.strength.bounds = textBounds; // click => prompt popup
|
||||
this.hitAreas.strengthAny.bounds = [leftArrow[0], rightArrow[0] + rightArrow[1] - leftArrow[0]]; // drag
|
||||
// spacing after the "i" icon (gap between icon and stepper)
|
||||
rposX = leftArrow[0] - innerMargin - iconGap;
|
||||
|
||||
// "i" info badge -> Show Info dialog (gray, not blue-outlined)
|
||||
const infoIconSize = h * 0.66;
|
||||
drawInfoIcon(ctx, rposX - infoIconSize, y + (h - infoIconSize) / 2, infoIconSize, "GRAYED");
|
||||
this.hitAreas.info.bounds = [rposX - infoIconSize - innerMargin, infoIconSize + innerMargin * 2];
|
||||
rposX -= infoIconSize + innerMargin * 2 + innerMargin;
|
||||
|
||||
const loraWidth = rposX - posX;
|
||||
this.hitAreas.lora.bounds = [posX, loraWidth];
|
||||
@@ -241,6 +395,7 @@ export class PowerLoraLoaderWidgetEx extends PowerLoraLoaderWidget {
|
||||
ctx.fillText(fitString(ctx, this.value.lora, loraWidth), posX, y + h / 2);
|
||||
|
||||
ctx.globalAlpha = 1;
|
||||
this._drawReorderIndicatorIfTarget(ctx, node, y, h);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,10 +548,10 @@ export function createPowerLoraLoaderExtension(config) {
|
||||
if (outputBoth || copyButtons) {
|
||||
this.addCustomWidget(new StrengthCopyWidget(outputBoth, copyButtons));
|
||||
}
|
||||
this.addCustomWidget(new RgthreeDividerWidget({ marginTop: 4, marginBottom: 0, thickness: 0 }));
|
||||
this.addCustomWidget(new QQDividerWidget({ marginTop: 4, marginBottom: 0, thickness: 0 }));
|
||||
this.addCustomWidget(new PowerLoraLoaderHeaderWidgetEx(strengthSplit));
|
||||
this.widgetButtonSpacer = this.addCustomWidget(new RgthreeDividerWidget({ marginTop: 4, marginBottom: 0, thickness: 0 }));
|
||||
this.addCustomWidget(new RgthreeBetterButtonWidget("➕ Add Lora", (event, pos, node) => {
|
||||
this.widgetButtonSpacer = this.addCustomWidget(new QQDividerWidget({ marginTop: 4, marginBottom: 0, thickness: 0 }));
|
||||
this.addCustomWidget(new QQBetterButtonWidget("➕ Add Lora", (event, pos, node) => {
|
||||
showPicker(event, (lora) => {
|
||||
if (lora) {
|
||||
this.addNewLoraWidget().setLora(lora);
|
||||
@@ -592,12 +747,6 @@ export function createPowerLoraLoaderExtension(config) {
|
||||
// Re-run the low variant check with updated cache
|
||||
widget.checkLowLoraVariant(widget.value.lora);
|
||||
|
||||
// Log changes in low variant detection
|
||||
if (oldIsLow !== widget.value.is_low || oldVariantName !== widget.value.low_variant_name) {
|
||||
console.log(`[${logTag}] Low variant status changed for '${widget.value.lora}': ` +
|
||||
`${oldIsLow} -> ${widget.value.is_low}, ` +
|
||||
`variant: '${oldVariantName}' -> '${widget.value.low_variant_name}'`);
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger a value change to update the UI
|
||||
@@ -612,7 +761,6 @@ export function createPowerLoraLoaderExtension(config) {
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`[${logTag}] LoRA definitions refreshed - individual LoRA info available in dialogs`);
|
||||
}).catch(error => {
|
||||
console.error(`[${logTag}] Error refreshing LoRA list:`, error);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { app } from "../../../scripts/app.js";
|
||||
import { rgthree } from "../rgthree/common/rgthree.js";
|
||||
import { RgthreeBetterButtonWidget, RgthreeDividerWidget, RgthreeBaseWidget } from "./widgets.js";
|
||||
import { qq } from "../qq_lib/common/qq.js";
|
||||
import { QQBetterButtonWidget, QQDividerWidget, QQBaseWidget } from "./widgets.js";
|
||||
import { drawRoundedRectangle, drawTogglePart, drawWidgetButton, fitString, isLowQuality } from "./widgets.js";
|
||||
import { LoraPickerDialog } from "./lora_picker_dialog.js";
|
||||
import { WanLoraInfoDialog } from "./dialog_info.js";
|
||||
@@ -52,7 +52,7 @@ function loadEyeRefreshState() {
|
||||
|
||||
|
||||
// Custom widget for model selection
|
||||
class PowerModelLoaderWidget extends RgthreeBaseWidget {
|
||||
class PowerModelLoaderWidget extends QQBaseWidget {
|
||||
constructor(name, showModelChooser) {
|
||||
super(name);
|
||||
this.showModelChooser = showModelChooser;
|
||||
@@ -420,7 +420,7 @@ app.registerExtension({
|
||||
};
|
||||
|
||||
nodeType.prototype.addNonModelWidgets = function () {
|
||||
this.addCustomWidget(new RgthreeDividerWidget({ marginTop: 4, marginBottom: 0, thickness: 0 }));
|
||||
this.addCustomWidget(new QQDividerWidget({ marginTop: 4, marginBottom: 0, thickness: 0 }));
|
||||
|
||||
// Add hidden widget for UI state persistence
|
||||
const uiStateWidget = this.addWidget("string", "ui_state", "{}", () => {},
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Eliminates code duplication across power_lora_loader.js, power_lora_loader_v2.js, and power_model_loader_v2.js
|
||||
*/
|
||||
|
||||
import { rgthreeApi as wanvidApi } from "./rgthree_api.js";
|
||||
import { vidApi } from "./wanvid_api.js";
|
||||
|
||||
// ============================================================================
|
||||
// DIRECTORY STRUCTURE MANAGER
|
||||
@@ -183,14 +183,14 @@ class ModelDataManager {
|
||||
try {
|
||||
let itemsData;
|
||||
|
||||
// Use appropriate rgthreeApi method
|
||||
// Use appropriate vidApi method
|
||||
if (type === 'checkpoints') {
|
||||
itemsData = await wanvidApi.getCheckpoints(forceRefresh);
|
||||
itemsData = await vidApi.getCheckpoints(forceRefresh);
|
||||
} else {
|
||||
itemsData = await wanvidApi.getLoras(forceRefresh);
|
||||
itemsData = await vidApi.getLoras(forceRefresh);
|
||||
}
|
||||
|
||||
// Convert rgthreeApi format to our expected format
|
||||
// Convert vidApi format to our expected format
|
||||
if (!itemsData || !Array.isArray(itemsData)) {
|
||||
this.caches[cacheKey] = [];
|
||||
} else {
|
||||
@@ -228,7 +228,6 @@ class ModelDataManager {
|
||||
// Prevent multiple simultaneous calls only
|
||||
const cacheKey = type;
|
||||
if (this.previewLoadPromises[cacheKey]) {
|
||||
console.log(`[Preview] Preview load already in progress for ${type}, waiting...`);
|
||||
return this.previewLoadPromises[cacheKey];
|
||||
}
|
||||
|
||||
@@ -417,7 +416,6 @@ class ModelDataManager {
|
||||
return jsonData;
|
||||
}
|
||||
} catch (attemptError) {
|
||||
console.log(`[PullInfo Debug] JSON attempt failed: ${attemptError}`);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,6 @@ export async function renameLoRAFiles(loraName, variant = 'high', refreshCallbac
|
||||
let responseText;
|
||||
try {
|
||||
responseText = await response.text();
|
||||
console.log('Raw response:', responseText); // Debug log
|
||||
data = JSON.parse(responseText);
|
||||
} catch (parseError) {
|
||||
console.error('JSON parse error:', parseError);
|
||||
@@ -131,6 +130,8 @@ export async function renameLoRAFiles(loraName, variant = 'high', refreshCallbac
|
||||
widget.value.lora_name = data.new_low_path;
|
||||
}
|
||||
} else {
|
||||
// The displayed/serialized value is `lora` (not lora_name)
|
||||
widget.value.lora = data.new_high_path;
|
||||
widget.value.lora_name = data.new_high_path;
|
||||
// Also update low variant if it exists and is based on the high one
|
||||
if (widget.value.is_low && widget.value.low_variant_name &&
|
||||
@@ -139,6 +140,11 @@ export async function renameLoRAFiles(loraName, variant = 'high', refreshCallbac
|
||||
}
|
||||
}
|
||||
|
||||
// Re-check low variant detection for the new name
|
||||
if (typeof widget.checkLowLoraVariant === 'function') {
|
||||
widget.checkLowLoraVariant(widget.value.lora);
|
||||
}
|
||||
|
||||
// Update the displayed name
|
||||
if (widgetCallback) {
|
||||
widgetCallback(widget.value, null, null, null);
|
||||
@@ -151,34 +157,29 @@ export async function renameLoRAFiles(loraName, variant = 'high', refreshCallbac
|
||||
// Handle refresh callback (for picker dialog)
|
||||
if (refreshCallback) {
|
||||
// Show success message in console and call refresh
|
||||
console.log('LoRA renamed successfully:', data.message);
|
||||
|
||||
// Show a temporary success message instead of blocking alert
|
||||
showTemporaryMessage(`✅ LoRA renamed to: ${finalNewName}.safetensors`, 'success');
|
||||
|
||||
try {
|
||||
await refreshCallback();
|
||||
console.log('Refresh callback executed successfully');
|
||||
} catch (refreshError) {
|
||||
console.warn('Refresh callback failed:', refreshError);
|
||||
showTemporaryMessage('⚠️ List may need manual refresh to see updated name', 'warning');
|
||||
}
|
||||
} else if (!widget) {
|
||||
// Show success message for non-widget, non-picker contexts
|
||||
console.log('LoRA renamed successfully:', data.message);
|
||||
showTemporaryMessage(`✅ LoRA renamed to: ${finalNewName}.safetensors`, 'success');
|
||||
|
||||
// If we have access to the dialog, refresh preview availability for blue dots
|
||||
const dialog = document.querySelector('.lora-picker-dialog');
|
||||
if (dialog && dialog.dialogInstance && dialog.dialogInstance.loadPreviewAvailability) {
|
||||
try {
|
||||
console.log('Refreshing preview availability after rename...');
|
||||
await dialog.dialogInstance.loadPreviewAvailability();
|
||||
// Re-render list to update blue dots
|
||||
if (dialog.dialogInstance.renderList) {
|
||||
dialog.dialogInstance.renderList();
|
||||
}
|
||||
console.log('Preview availability refreshed after rename');
|
||||
} catch (previewError) {
|
||||
console.warn('Failed to refresh preview availability after rename:', previewError);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class RgthreeApi {
|
||||
class QQApi {
|
||||
constructor(baseUrl) {
|
||||
this.getCheckpointsPromise = null;
|
||||
this.getSamplersPromise = null;
|
||||
@@ -38,7 +38,7 @@ class RgthreeApi {
|
||||
async postJson(route, json) {
|
||||
const body = new FormData();
|
||||
body.append("json", JSON.stringify(json));
|
||||
return await rgthreeApi.fetchJson(route, { method: "POST", body });
|
||||
return await vidApi.fetchJson(route, { method: "POST", body });
|
||||
}
|
||||
getLoras(force = false) {
|
||||
if (!this.getLorasPromise || force) {
|
||||
@@ -168,4 +168,4 @@ class RgthreeApi {
|
||||
return fetch(url, options);
|
||||
}
|
||||
}
|
||||
export const rgthreeApi = new RgthreeApi();
|
||||
export const vidApi = new QQApi();
|
||||
@@ -2,8 +2,6 @@
|
||||
import { app } from "../../../scripts/app.js";
|
||||
import { loraPatternMatcher } from "./lora_pattern_matcher.js";
|
||||
|
||||
// From rgthree/src_web/comfyui/utils_canvas.ts
|
||||
|
||||
export function fitString(ctx, str, maxWidth) {
|
||||
let width = ctx.measureText(str).width;
|
||||
const ellipsis = "…";
|
||||
@@ -83,9 +81,81 @@ export function drawWidgetButton(ctx, pos, size, text, isMouseDownedAndOver) {
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
// From rgthree/src_web/comfyui/utils_widgets.ts
|
||||
// arrows. direction = -1 lays it out right-to-left (grows leftwards from posX).
|
||||
// Returns [leftArrow, number, rightArrow] hit-area bounds.
|
||||
export function drawNumberWidgetPart(ctx, options) {
|
||||
const arrowWidth = 9;
|
||||
const arrowHeight = 10;
|
||||
const innerMargin = 3;
|
||||
const numberWidth = 32;
|
||||
const xBoundsArrowLess = [0, 0];
|
||||
const xBoundsNumber = [0, 0];
|
||||
const xBoundsArrowMore = [0, 0];
|
||||
ctx.save();
|
||||
let posX = options.posX;
|
||||
const { posY, height, value, textColor } = options;
|
||||
const midY = posY + height / 2;
|
||||
if (options.direction === -1) {
|
||||
posX = posX - arrowWidth - innerMargin - numberWidth - innerMargin - arrowWidth;
|
||||
}
|
||||
ctx.fill(new Path2D(`M ${posX} ${midY} l ${arrowWidth} ${arrowHeight / 2} l 0 -${arrowHeight} L ${posX} ${midY} z`));
|
||||
xBoundsArrowLess[0] = posX;
|
||||
xBoundsArrowLess[1] = arrowWidth;
|
||||
posX += arrowWidth + innerMargin;
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
const oldTextColor = ctx.fillStyle;
|
||||
if (textColor) {
|
||||
ctx.fillStyle = textColor;
|
||||
}
|
||||
ctx.fillText(fitString(ctx, value.toFixed(2), numberWidth), posX + numberWidth / 2, midY);
|
||||
ctx.fillStyle = oldTextColor;
|
||||
xBoundsNumber[0] = posX;
|
||||
xBoundsNumber[1] = numberWidth;
|
||||
posX += numberWidth + innerMargin;
|
||||
ctx.fill(new Path2D(`M ${posX} ${midY - arrowHeight / 2} l ${arrowWidth} ${arrowHeight / 2} l -${arrowWidth} ${arrowHeight / 2} v -${arrowHeight} z`));
|
||||
xBoundsArrowMore[0] = posX;
|
||||
xBoundsArrowMore[1] = arrowWidth;
|
||||
ctx.restore();
|
||||
return [xBoundsArrowLess, xBoundsNumber, xBoundsArrowMore];
|
||||
}
|
||||
drawNumberWidgetPart.WIDTH_TOTAL = 9 + 3 + 32 + 3 + 9;
|
||||
|
||||
export class RgthreeBaseWidget {
|
||||
|
||||
export function drawInfoIcon(ctx, x, y, size = 12, treatment = 'GRAYED') {
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(x, y, size, size, [size * 0.1]);
|
||||
if (treatment === 'GRAYED') {
|
||||
ctx.fillStyle = "#aaa";
|
||||
ctx.strokeStyle = "#aaa";
|
||||
} else {
|
||||
ctx.fillStyle = "#2f82ec";
|
||||
ctx.strokeStyle = "#2f82ec";
|
||||
}
|
||||
if (treatment === 'FILLED') {
|
||||
ctx.fill();
|
||||
} else {
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.strokeStyle = "#FFF";
|
||||
ctx.lineWidth = 2;
|
||||
const midX = x + size / 2;
|
||||
const serifSize = size * 0.175;
|
||||
ctx.stroke(new Path2D(`
|
||||
M ${midX} ${y + size * 0.15}
|
||||
v 2
|
||||
M ${midX - serifSize} ${y + size * 0.45}
|
||||
h ${serifSize}
|
||||
v ${size * 0.325}
|
||||
h ${serifSize}
|
||||
h -${serifSize * 2}
|
||||
`));
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
|
||||
export class QQBaseWidget {
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
this.type = "custom";
|
||||
@@ -195,7 +265,7 @@ export class RgthreeBaseWidget {
|
||||
onMouseMove(event, pos, node) { return true; }
|
||||
}
|
||||
|
||||
export class RgthreeBetterButtonWidget extends RgthreeBaseWidget {
|
||||
export class QQBetterButtonWidget extends QQBaseWidget {
|
||||
constructor(name, callback, label) {
|
||||
super(name);
|
||||
this.callback = callback;
|
||||
@@ -212,7 +282,7 @@ export class RgthreeBetterButtonWidget extends RgthreeBaseWidget {
|
||||
}
|
||||
}
|
||||
|
||||
export class RgthreeDividerWidget extends RgthreeBaseWidget {
|
||||
export class QQDividerWidget extends QQBaseWidget {
|
||||
constructor(options) {
|
||||
super("divider");
|
||||
this.value = {};
|
||||
@@ -247,7 +317,7 @@ const STRENGTH_WIDTH = 60;
|
||||
const ICON_WIDTH = 20;
|
||||
const MARGIN = 10;
|
||||
|
||||
export class StrengthCopyWidget extends RgthreeBaseWidget {
|
||||
export class StrengthCopyWidget extends QQBaseWidget {
|
||||
constructor(showOutputBoth = true, showCopyButtons = true) {
|
||||
super("StrengthCopyWidget");
|
||||
this.showOutputBoth = showOutputBoth;
|
||||
@@ -397,7 +467,7 @@ export class StrengthCopyWidget extends RgthreeBaseWidget {
|
||||
}
|
||||
}
|
||||
|
||||
export class CombinedOptionsWidget extends RgthreeBaseWidget {
|
||||
export class CombinedOptionsWidget extends QQBaseWidget {
|
||||
constructor() {
|
||||
super("CombinedOptionsWidget");
|
||||
this.value = {};
|
||||
@@ -609,7 +679,7 @@ export class CombinedOptionsWidget extends RgthreeBaseWidget {
|
||||
}
|
||||
}
|
||||
|
||||
export class PowerLoraLoaderHeaderWidget extends RgthreeBaseWidget {
|
||||
export class PowerLoraLoaderHeaderWidget extends QQBaseWidget {
|
||||
constructor() {
|
||||
super("PowerLoraLoaderHeaderWidget");
|
||||
this.value = { type: "PowerLoraLoaderHeaderWidget" };
|
||||
@@ -645,7 +715,7 @@ export class PowerLoraLoaderHeaderWidget extends RgthreeBaseWidget {
|
||||
}
|
||||
}
|
||||
|
||||
export class PowerLoraLoaderWidget extends RgthreeBaseWidget {
|
||||
export class PowerLoraLoaderWidget extends QQBaseWidget {
|
||||
constructor(name, showLoraChooser) {
|
||||
super(name);
|
||||
this.showLoraChooser = showLoraChooser;
|
||||
@@ -657,6 +727,7 @@ export class PowerLoraLoaderWidget extends RgthreeBaseWidget {
|
||||
strength: { bounds: [0, 0], onClick: this.onStrengthClick, onMove: this.onStrengthAnyMove },
|
||||
low_strength: { bounds: [0, 0], onClick: this.onLowStrengthClick, onMove: this.onLowStrengthAnyMove },
|
||||
low_variant_icon: { bounds: [0, 0] },
|
||||
info: { bounds: [0, 0], onDown: this.onInfoDown },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -743,6 +814,12 @@ export class PowerLoraLoaderWidget extends RgthreeBaseWidget {
|
||||
ctx.fillText(this.value.strength.toFixed(2), rposX, y + h / 2);
|
||||
rposX -= STRENGTH_WIDTH + MARGIN;
|
||||
|
||||
// "i" info badge before (left of) the High strength value.
|
||||
const infoIconSize = h * 0.66;
|
||||
drawInfoIcon(ctx, rposX - infoIconSize, y + (h - infoIconSize) / 2, infoIconSize, "GRAYED");
|
||||
this.hitAreas.info.bounds = [rposX - infoIconSize - MARGIN * 0.33, infoIconSize + MARGIN * 0.66];
|
||||
rposX -= infoIconSize + MARGIN * 0.66 + MARGIN * 0.33;
|
||||
|
||||
const loraWidth = rposX - posX;
|
||||
this.hitAreas.lora.bounds = [posX, loraWidth];
|
||||
ctx.textAlign = "left";
|
||||
@@ -758,6 +835,12 @@ export class PowerLoraLoaderWidget extends RgthreeBaseWidget {
|
||||
return true;
|
||||
}
|
||||
|
||||
onInfoDown(event, pos, node) {
|
||||
if (!this.value.lora || this.value.lora === "None") return;
|
||||
this.cancelMouseDown();
|
||||
this.showLoraInfoDialog('high');
|
||||
}
|
||||
|
||||
setLora(lora) {
|
||||
// Store the original lora path/name as received from the picker
|
||||
this.value.lora = lora;
|
||||
@@ -848,11 +931,9 @@ export class PowerLoraLoaderWidget extends RgthreeBaseWidget {
|
||||
const infoDialog = new WanLoraInfoDialog(loraToShow).show();
|
||||
// Store the item type for use in preview generation (this is for LoRA widgets)
|
||||
infoDialog.itemType = 'loras';
|
||||
console.log(`[Widgets Debug] Created LoRA widget dialog with itemType: ${infoDialog.itemType}`);
|
||||
infoDialog.addEventListener("close", ((e) => {
|
||||
if (e.detail.dirty) {
|
||||
// Dialog was modified, could trigger refresh if needed
|
||||
console.log("LoRA info was modified");
|
||||
}
|
||||
}));
|
||||
}).catch(error => {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import {
|
||||
drawWidgetButton,
|
||||
drawNumberWidgetPart,
|
||||
RgthreeBaseWidget
|
||||
QQBaseWidget
|
||||
} from './drawing_utils.js';
|
||||
import { app } from '../../../scripts/app.js';
|
||||
|
||||
@@ -541,7 +541,7 @@ export async function handleFramesRefresh(node) {
|
||||
// == TOP ROW WIDGET
|
||||
|
||||
|
||||
export class TopRowWidget extends RgthreeBaseWidget {
|
||||
export class TopRowWidget extends QQBaseWidget {
|
||||
constructor(name = "TopRowWidget", controlsVisibility = {}, handlers = {}) {
|
||||
super(name);
|
||||
this.type = "custom";
|
||||
|
||||
@@ -183,7 +183,7 @@ export function drawNumberWidgetPart(ctx, options) {
|
||||
drawNumberWidgetPart.WIDTH_TOTAL = 9 + 3 + 32 + 3 + 9;
|
||||
|
||||
// === BASE WIDGET CLASS ===
|
||||
export class RgthreeBaseWidget {
|
||||
export class QQBaseWidget {
|
||||
constructor(name) {
|
||||
this.type = "custom";
|
||||
this.options = {};
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// Button bar widget for adding layers (Add Spline, Add Keyframes, Draw, Duplicate)
|
||||
import { RgthreeBaseWidget, drawWidgetButton } from './drawing_utils.js';
|
||||
import { QQBaseWidget, drawWidgetButton } from './drawing_utils.js';
|
||||
|
||||
/**
|
||||
* Creates a button bar widget with Add Spline, Add Keyframes, Draw, and Duplicate buttons
|
||||
* @returns {RgthreeBaseWidget} The configured button bar widget
|
||||
* @returns {QQBaseWidget} The configured button bar widget
|
||||
*/
|
||||
export function createButtonBarWidget() {
|
||||
const buttonBarWidget = new RgthreeBaseWidget("button_bar");
|
||||
const buttonBarWidget = new QQBaseWidget("button_bar");
|
||||
buttonBarWidget.type = "custom";
|
||||
buttonBarWidget.serialize = false;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Header widget that displays column labels for spline layers
|
||||
import { app } from '../../../scripts/app.js';
|
||||
import { isLowQuality, drawTogglePart, drawNumberWidgetPart, RgthreeBaseWidget } from './drawing_utils.js';
|
||||
import { isLowQuality, drawTogglePart, drawNumberWidgetPart, QQBaseWidget } from './drawing_utils.js';
|
||||
|
||||
export class PowerSplineHeaderWidget extends RgthreeBaseWidget {
|
||||
export class PowerSplineHeaderWidget extends QQBaseWidget {
|
||||
constructor(name = "PowerSplineHeaderWidget") {
|
||||
super(name);
|
||||
this.value = { type: "PowerSplineHeaderWidget" };
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { RgthreeBaseWidget, drawRoundedRectangle, drawTogglePart, drawNumberWidgetPart, fitString } from './drawing_utils.js';
|
||||
import { QQBaseWidget, drawRoundedRectangle, drawTogglePart, drawNumberWidgetPart, fitString } from './drawing_utils.js';
|
||||
import { initializeDrivenConfig, initializeEasingConfig, initializeScaleConfig, toggleDrivenState, prepareDrivenMenu } from './persistence.js';
|
||||
import { showCustomEasingMenu, showCustomDrivenToggleMenu, showInterpolationMenu, showRefSelectionMenu } from './context_menu.js';
|
||||
import { app } from '../../../scripts/app.js';
|
||||
|
||||
const BOX_TIMELINE_MAX_POINTS = 250;
|
||||
|
||||
export class BoxLayerWidget extends RgthreeBaseWidget {
|
||||
export class BoxLayerWidget extends QQBaseWidget {
|
||||
constructor(name) {
|
||||
super(name);
|
||||
this.type = 'custom';
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// Hand-draw layer widget and helpers
|
||||
import { RgthreeBaseWidget, drawRoundedRectangle, drawTogglePart, drawNumberWidgetPart, fitString } from './drawing_utils.js';
|
||||
import { QQBaseWidget, drawRoundedRectangle, drawTogglePart, drawNumberWidgetPart, fitString } from './drawing_utils.js';
|
||||
import { initializeDrivenConfig, initializeEasingConfig, initializeScaleConfig, toggleDrivenState, prepareDrivenMenu } from './persistence.js';
|
||||
import { showCustomEasingMenu, showCustomDrivenToggleMenu } from './context_menu.js';
|
||||
import { app } from '../../../scripts/app.js';
|
||||
|
||||
export class HandDrawLayerWidget extends RgthreeBaseWidget {
|
||||
export class HandDrawLayerWidget extends QQBaseWidget {
|
||||
constructor(name) {
|
||||
super(name);
|
||||
this.type = 'custom';
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Simple reference layer widget
|
||||
import { RgthreeBaseWidget, drawRoundedRectangle } from './drawing_utils.js';
|
||||
import { QQBaseWidget, drawRoundedRectangle } from './drawing_utils.js';
|
||||
import { app } from '../../../scripts/app.js';
|
||||
|
||||
export class RefLayerWidget extends RgthreeBaseWidget {
|
||||
export class RefLayerWidget extends QQBaseWidget {
|
||||
constructor(name) {
|
||||
super(name);
|
||||
this.type = "custom";
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// Normal spline layer widget
|
||||
import { RgthreeBaseWidget, drawRoundedRectangle, drawTogglePart, drawNumberWidgetPart, fitString } from './drawing_utils.js';
|
||||
import { QQBaseWidget, drawRoundedRectangle, drawTogglePart, drawNumberWidgetPart, fitString } from './drawing_utils.js';
|
||||
import { initializeDrivenConfig, initializeEasingConfig, initializeScaleConfig, toggleDrivenState, prepareDrivenMenu } from './persistence.js';
|
||||
import { showCustomEasingMenu, showCustomDrivenToggleMenu, showInterpolationMenu } from './context_menu.js';
|
||||
import { app } from '../../../scripts/app.js';
|
||||
|
||||
export class PowerSplineWidget extends RgthreeBaseWidget {
|
||||
export class PowerSplineWidget extends QQBaseWidget {
|
||||
constructor(name) {
|
||||
super(name);
|
||||
this.type = "custom";
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
makeUUID,
|
||||
loadScript,
|
||||
create_documentation_stylesheet,
|
||||
RgthreeBaseWidget,
|
||||
QQBaseWidget,
|
||||
DimensionsWidget,
|
||||
PowerSplineHeaderWidget,
|
||||
NodeSizeManager,
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
drawWidgetButton,
|
||||
drawTogglePart,
|
||||
drawNumberWidgetPart,
|
||||
RgthreeBaseWidget
|
||||
QQBaseWidget
|
||||
} from './drawing_utils.js';
|
||||
|
||||
// Re-export for convenience
|
||||
@@ -35,7 +35,7 @@ export {
|
||||
drawWidgetButton,
|
||||
drawTogglePart,
|
||||
drawNumberWidgetPart,
|
||||
RgthreeBaseWidget
|
||||
QQBaseWidget
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -200,7 +200,7 @@ export const create_documentation_stylesheet = () => {
|
||||
}
|
||||
|
||||
// === DIMENSIONS WIDGET (Simplified version for backward compatibility) ===
|
||||
export class DimensionsWidget extends RgthreeBaseWidget {
|
||||
export class DimensionsWidget extends QQBaseWidget {
|
||||
constructor(name = "DimensionsWidget") {
|
||||
super(name);
|
||||
this.type = "custom";
|
||||
@@ -321,7 +321,7 @@ export class DimensionsWidget extends RgthreeBaseWidget {
|
||||
}
|
||||
|
||||
// === INTERPOLATION WIDGET ===
|
||||
export class InterpolationWidget extends RgthreeBaseWidget {
|
||||
export class InterpolationWidget extends QQBaseWidget {
|
||||
constructor(name = "InterpolationWidget") {
|
||||
super(name);
|
||||
this.type = "custom";
|
||||
@@ -437,7 +437,7 @@ export class InterpolationWidget extends RgthreeBaseWidget {
|
||||
}
|
||||
|
||||
// === DRIVER ROTATION & D_SCALE WIDGET (Combined driver_rotation and driver_d_scale) ===
|
||||
export class DriverRotationDScaleWidget extends RgthreeBaseWidget {
|
||||
export class DriverRotationDScaleWidget extends QQBaseWidget {
|
||||
constructor(name = "DriverRotationDScaleWidget") {
|
||||
super(name);
|
||||
this.type = "custom";
|
||||
@@ -624,7 +624,7 @@ export class DriverRotationDScaleWidget extends RgthreeBaseWidget {
|
||||
}
|
||||
|
||||
// === PAUSE WIDGET (Combined start_pause and end_pause) ===
|
||||
export class PauseWidget extends RgthreeBaseWidget {
|
||||
export class PauseWidget extends QQBaseWidget {
|
||||
constructor(name = "PauseWidget") {
|
||||
super(name);
|
||||
this.type = "custom";
|
||||
@@ -812,7 +812,7 @@ export class PauseWidget extends RgthreeBaseWidget {
|
||||
}
|
||||
|
||||
// === OFFSET REPEAT WIDGET (Combined offset and repeat) ===
|
||||
export class OffsetRepeatWidget extends RgthreeBaseWidget {
|
||||
export class OffsetRepeatWidget extends QQBaseWidget {
|
||||
constructor(name = "OffsetRepeatWidget") {
|
||||
super(name);
|
||||
this.type = "custom";
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
// Enhanced rgthree implementation for WanVideoWrapper QQ
|
||||
// Provides proper toast messages and development utilities
|
||||
|
||||
export const rgthree = {
|
||||
export const qq = {
|
||||
showMessage: function(options) {
|
||||
if (typeof options === 'string') {
|
||||
// If it's just a string, convert to options object
|
||||
Reference in New Issue
Block a user