mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d8408e626 | ||
|
|
0906271aa9 | ||
|
|
4c33c9d256 | ||
|
|
fa9c78209f | ||
|
|
6678ec8a60 | ||
|
|
854e467c12 | ||
|
|
e6b94c7b21 | ||
|
|
2c6f9d8602 | ||
|
|
c74033b9c0 | ||
|
|
d2b21d27bb | ||
|
|
215272469f | ||
|
|
f7d05ab0f1 | ||
|
|
6f2ad2be77 |
@@ -34,6 +34,14 @@ Enhance your Civitai browsing experience with our companion browser extension! S
|
||||
|
||||
## Release Notes
|
||||
|
||||
### v0.8.26
|
||||
* **Creator Search Option**
|
||||
- Added ability to search models by creator name, making it easier to find models from specific authors.
|
||||
* **Enhanced Node Usability**
|
||||
- Improved user experience for Lora Loader, Lora Stacker, and WanVideo Lora Select nodes by fixing the maximum height of the text input area. Users can now freely and conveniently adjust the LoRA region within these nodes.
|
||||
* **Compatibility Fixes**
|
||||
- Resolved compatibility issues with ComfyUI and certain custom nodes, including ComfyUI-Custom-Scripts, ensuring smoother integration and operation.
|
||||
|
||||
### v0.8.25
|
||||
* **LoRA List Reordering**
|
||||
- Drag & Drop: Easily rearrange LoRA entries using the drag handle.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import json
|
||||
import os
|
||||
import asyncio
|
||||
import re
|
||||
import numpy as np
|
||||
import folder_paths # type: ignore
|
||||
|
||||
@@ -38,7 +38,7 @@ class BaseModelRoutes(ABC):
|
||||
prefix: URL prefix (e.g., 'loras', 'checkpoints')
|
||||
"""
|
||||
# Common model management routes
|
||||
app.router.add_get(f'/api/{prefix}', self.get_models)
|
||||
app.router.add_get(f'/api/{prefix}/list', self.get_models)
|
||||
app.router.add_post(f'/api/{prefix}/delete', self.delete_model)
|
||||
app.router.add_post(f'/api/{prefix}/exclude', self.exclude_model)
|
||||
app.router.add_post(f'/api/{prefix}/fetch-civitai', self.fetch_civitai)
|
||||
@@ -177,6 +177,7 @@ class BaseModelRoutes(ABC):
|
||||
'filename': request.query.get('search_filename', 'true').lower() == 'true',
|
||||
'modelname': request.query.get('search_modelname', 'true').lower() == 'true',
|
||||
'tags': request.query.get('search_tags', 'false').lower() == 'true',
|
||||
'creator': request.query.get('search_creator', 'false').lower() == 'true',
|
||||
'recursive': request.query.get('recursive', 'false').lower() == 'true',
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import os
|
||||
import subprocess
|
||||
import aiohttp
|
||||
import logging
|
||||
import toml
|
||||
@@ -7,7 +6,6 @@ import git
|
||||
import zipfile
|
||||
import shutil
|
||||
import tempfile
|
||||
from datetime import datetime
|
||||
from aiohttp import web
|
||||
from typing import Dict, List
|
||||
|
||||
|
||||
@@ -199,6 +199,22 @@ class BaseModelService(ABC):
|
||||
for tag in item['tags']):
|
||||
search_results.append(item)
|
||||
continue
|
||||
|
||||
# Search by creator
|
||||
civitai = item.get('civitai')
|
||||
creator_username = ''
|
||||
if civitai and isinstance(civitai, dict):
|
||||
creator = civitai.get('creator')
|
||||
if creator and isinstance(creator, dict):
|
||||
creator_username = creator.get('username', '')
|
||||
if search_options.get('creator', False) and creator_username:
|
||||
if fuzzy_search:
|
||||
if fuzzy_match(creator_username, search):
|
||||
search_results.append(item)
|
||||
continue
|
||||
elif search.lower() in creator_username.lower():
|
||||
search_results.append(item)
|
||||
continue
|
||||
|
||||
return search_results
|
||||
|
||||
|
||||
@@ -199,8 +199,6 @@ class ModelHashIndex:
|
||||
|
||||
def get_hash_by_filename(self, filename: str) -> Optional[str]:
|
||||
"""Get hash for a filename without extension"""
|
||||
# Strip extension if present to make the function more flexible
|
||||
filename = os.path.splitext(filename)[0]
|
||||
return self._filename_to_hash.get(filename)
|
||||
|
||||
def clear(self) -> None:
|
||||
|
||||
@@ -50,7 +50,8 @@ VALID_LORA_TYPES = ['lora', 'locon', 'dora']
|
||||
|
||||
# Civitai model tags in priority order for subfolder organization
|
||||
CIVITAI_MODEL_TAGS = [
|
||||
'character', 'style', 'concept', 'clothing', 'base model',
|
||||
'character', 'style', 'concept', 'clothing',
|
||||
# 'base model', # exclude 'base model'
|
||||
'poses', 'background', 'tool', 'vehicle', 'buildings',
|
||||
'objects', 'assets', 'animal', 'action'
|
||||
]
|
||||
@@ -91,7 +91,7 @@ class DownloadManager:
|
||||
with open(progress_file, 'r', encoding='utf-8') as f:
|
||||
saved_progress = json.load(f)
|
||||
download_progress['processed_models'] = set(saved_progress.get('processed_models', []))
|
||||
logger.info(f"Loaded previous progress, {len(download_progress['processed_models'])} models already processed")
|
||||
logger.debug(f"Loaded previous progress, {len(download_progress['processed_models'])} models already processed")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load progress file: {e}")
|
||||
download_progress['processed_models'] = set()
|
||||
@@ -230,7 +230,7 @@ class DownloadManager:
|
||||
|
||||
# Update total count
|
||||
download_progress['total'] = len(all_models)
|
||||
logger.info(f"Found {download_progress['total']} models to process")
|
||||
logger.debug(f"Found {download_progress['total']} models to process")
|
||||
|
||||
# Process each model
|
||||
for i, (scanner_type, model, scanner) in enumerate(all_models):
|
||||
@@ -250,7 +250,7 @@ class DownloadManager:
|
||||
# Mark as completed
|
||||
download_progress['status'] = 'completed'
|
||||
download_progress['end_time'] = time.time()
|
||||
logger.info(f"Example images download completed: {download_progress['completed']}/{download_progress['total']} models processed")
|
||||
logger.debug(f"Example images download completed: {download_progress['completed']}/{download_progress['total']} models processed")
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error during example images download: {str(e)}"
|
||||
@@ -307,7 +307,7 @@ class DownloadManager:
|
||||
logger.debug(f"Skipping already processed model: {model_name}")
|
||||
return False
|
||||
else:
|
||||
logger.info(f"Model {model_name} marked as processed but folder empty or missing, reprocessing")
|
||||
logger.debug(f"Model {model_name} marked as processed but folder empty or missing, reprocessing")
|
||||
|
||||
# Create model directory
|
||||
model_dir = os.path.join(output_dir, model_hash)
|
||||
|
||||
@@ -47,7 +47,7 @@ def get_lora_info(lora_name):
|
||||
# No event loop is running, we can use asyncio.run()
|
||||
return asyncio.run(_get_lora_info_async())
|
||||
|
||||
def fuzzy_match(text: str, pattern: str, threshold: float = 0.7) -> bool:
|
||||
def fuzzy_match(text: str, pattern: str, threshold: float = 0.85) -> bool:
|
||||
"""
|
||||
Check if text matches pattern using fuzzy matching.
|
||||
Returns True if similarity ratio is above threshold.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfyui-lora-manager"
|
||||
description = "Revolutionize your workflow with the ultimate LoRA companion for ComfyUI!"
|
||||
version = "0.8.25"
|
||||
version = "0.8.26"
|
||||
license = {file = "LICENSE"}
|
||||
dependencies = [
|
||||
"aiohttp",
|
||||
|
||||
@@ -7,5 +7,4 @@ olefile
|
||||
toml
|
||||
numpy
|
||||
natsort
|
||||
pyyaml
|
||||
GitPython
|
||||
|
||||
@@ -55,7 +55,7 @@ export function getApiEndpoints(modelType) {
|
||||
|
||||
return {
|
||||
// Base CRUD operations
|
||||
list: `/api/${modelType}`,
|
||||
list: `/api/${modelType}/list`,
|
||||
delete: `/api/${modelType}/delete`,
|
||||
exclude: `/api/${modelType}/exclude`,
|
||||
rename: `/api/${modelType}/rename`,
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
DOWNLOAD_ENDPOINTS,
|
||||
WS_ENDPOINTS
|
||||
} from './apiConfig.js';
|
||||
import { createModelApiClient } from './modelApiFactory.js';
|
||||
import { resetAndReload } from './modelApiFactory.js';
|
||||
|
||||
/**
|
||||
* Abstract base class for all model API clients
|
||||
@@ -91,10 +91,7 @@ export class BaseModelApiClient {
|
||||
pageState.currentPage = 1; // Reset to first page
|
||||
}
|
||||
|
||||
const startTime = performance.now();
|
||||
const result = await this.fetchModelsPage(pageState.currentPage, pageState.pageSize);
|
||||
const endTime = performance.now();
|
||||
console.log(`fetchModelsPage耗时: ${(endTime - startTime).toFixed(2)} ms`);
|
||||
|
||||
state.virtualScroller.refreshWithData(
|
||||
result.items,
|
||||
@@ -105,8 +102,16 @@ export class BaseModelApiClient {
|
||||
pageState.hasMore = result.hasMore;
|
||||
pageState.currentPage = pageState.currentPage + 1;
|
||||
|
||||
if (updateFolders && result.folders) {
|
||||
updateFolderTags(result.folders);
|
||||
if (updateFolders) {
|
||||
const response = await fetch(this.apiConfig.endpoints.folders);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
updateFolderTags(data.folders);
|
||||
} else {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
const errorMsg = errorData && errorData.error ? errorData.error : response.statusText;
|
||||
console.error(`Error getting folders: ${errorMsg}`);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -321,6 +326,8 @@ export class BaseModelApiClient {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to refresh ${this.apiConfig.config.displayName}s: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
resetAndReload(true);
|
||||
|
||||
showToast(`${fullRebuild ? 'Full rebuild' : 'Refresh'} complete`, 'success');
|
||||
} catch (error) {
|
||||
@@ -629,6 +636,9 @@ export class BaseModelApiClient {
|
||||
if (pageState.searchOptions.tags !== undefined) {
|
||||
params.append('search_tags', pageState.searchOptions.tags.toString());
|
||||
}
|
||||
if (pageState.searchOptions.creator !== undefined) {
|
||||
params.append('search_creator', pageState.searchOptions.creator.toString());
|
||||
}
|
||||
params.append('recursive', (pageState.searchOptions?.recursive ?? false).toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,9 @@ export class HeaderManager {
|
||||
this.filterManager = null;
|
||||
|
||||
// Initialize appropriate managers based on current page
|
||||
this.initializeManagers();
|
||||
if (this.currentPage !== 'statistics') {
|
||||
this.initializeManagers();
|
||||
}
|
||||
|
||||
// Set up common header functionality
|
||||
this.initializeCommonElements();
|
||||
@@ -37,11 +39,8 @@ export class HeaderManager {
|
||||
this.searchManager = new SearchManager({ page: this.currentPage });
|
||||
window.searchManager = this.searchManager;
|
||||
|
||||
// Initialize FilterManager for all page types that have filters
|
||||
if (document.getElementById('filterButton')) {
|
||||
this.filterManager = new FilterManager({ page: this.currentPage });
|
||||
window.filterManager = this.filterManager;
|
||||
}
|
||||
this.filterManager = new FilterManager({ page: this.currentPage });
|
||||
window.filterManager = this.filterManager;
|
||||
}
|
||||
|
||||
initializeCommonElements() {
|
||||
|
||||
@@ -241,7 +241,7 @@ function showModelModalFromCard(card, modelType) {
|
||||
tags: JSON.parse(card.dataset.tags || '[]'),
|
||||
modelDescription: card.dataset.modelDescription || '',
|
||||
// LoRA specific fields
|
||||
...(modelType === 'lora' && {
|
||||
...(modelType === MODEL_TYPES.LORA && {
|
||||
usage_tips: card.dataset.usage_tips,
|
||||
})
|
||||
};
|
||||
|
||||
@@ -297,7 +297,10 @@ export class DownloadManager {
|
||||
// Set default root if available
|
||||
const defaultRootKey = `default_${this.apiClient.modelType}_root`;
|
||||
const defaultRoot = getStorageItem('settings', {})[defaultRootKey];
|
||||
console.log(`Default root for ${this.apiClient.modelType}:`, defaultRoot);
|
||||
console.log('Available roots:', rootsData.roots);
|
||||
if (defaultRoot && rootsData.roots.includes(defaultRoot)) {
|
||||
console.log(`Setting default root: ${defaultRoot}`);
|
||||
modelRoot.value = defaultRoot;
|
||||
}
|
||||
|
||||
|
||||
@@ -763,22 +763,7 @@ class ExampleImagesManager {
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
// Only show progress if there are actually items to download
|
||||
if (data.status && data.status.total > 0) {
|
||||
this.isDownloading = true;
|
||||
this.isPaused = false;
|
||||
this.hasShownCompletionToast = false;
|
||||
this.startTime = new Date();
|
||||
this.updateUI(data.status);
|
||||
this.showProgressPanel();
|
||||
this.startProgressUpdates();
|
||||
this.updateDownloadButtonText();
|
||||
console.log(`Auto download started: ${data.status.total} items to process`);
|
||||
} else {
|
||||
console.log('Auto download check completed - no new items to download');
|
||||
}
|
||||
} else {
|
||||
if (!data.success) {
|
||||
console.warn('Auto download check failed:', data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -318,6 +318,7 @@ export class SearchManager {
|
||||
filename: options.filename || false,
|
||||
modelname: options.modelname || false,
|
||||
tags: options.tags || false,
|
||||
creator: options.creator || false,
|
||||
recursive: recursive
|
||||
};
|
||||
} else if (this.currentPage === 'checkpoints') {
|
||||
@@ -325,6 +326,7 @@ export class SearchManager {
|
||||
filename: options.filename || false,
|
||||
modelname: options.modelname || false,
|
||||
tags: options.tags || false,
|
||||
creator: options.creator || false,
|
||||
recursive: recursive
|
||||
};
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ export const state = {
|
||||
filename: true,
|
||||
modelname: true,
|
||||
tags: false,
|
||||
creator: false,
|
||||
recursive: false
|
||||
},
|
||||
filters: {
|
||||
@@ -83,6 +84,7 @@ export const state = {
|
||||
searchOptions: {
|
||||
filename: true,
|
||||
modelname: true,
|
||||
creator: false,
|
||||
recursive: false
|
||||
},
|
||||
filters: {
|
||||
@@ -110,6 +112,7 @@ export const state = {
|
||||
filename: true,
|
||||
modelname: true,
|
||||
tags: false,
|
||||
creator: false,
|
||||
recursive: false
|
||||
},
|
||||
filters: {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
{% block init_title %}Initializing Checkpoints Manager{% endblock %}
|
||||
{% block init_message %}Scanning and building checkpoints cache. This may take a few moments...{% endblock %}
|
||||
{% block init_check_url %}/api/checkpoints?page=1&page_size=1{% endblock %}
|
||||
{% block init_check_url %}/api/checkpoints/list?page=1&page_size=1{% endblock %}
|
||||
|
||||
{% block additional_components %}
|
||||
|
||||
|
||||
@@ -86,15 +86,18 @@
|
||||
<div class="search-option-tag active" data-option="filename">Filename</div>
|
||||
<div class="search-option-tag active" data-option="modelname">Checkpoint Name</div>
|
||||
<div class="search-option-tag active" data-option="tags">Tags</div>
|
||||
<div class="search-option-tag" data-option="creator">Creator</div>
|
||||
{% elif request.path == '/embeddings' %}
|
||||
<div class="search-option-tag active" data-option="filename">Filename</div>
|
||||
<div class="search-option-tag active" data-option="modelname">Embedding Name</div>
|
||||
<div class="search-option-tag active" data-option="tags">Tags</div>
|
||||
<div class="search-option-tag" data-option="creator">Creator</div>
|
||||
{% else %}
|
||||
<!-- Default options for LoRAs page -->
|
||||
<div class="search-option-tag active" data-option="filename">Filename</div>
|
||||
<div class="search-option-tag active" data-option="modelname">Model Name</div>
|
||||
<div class="search-option-tag active" data-option="tags">Tags</div>
|
||||
<div class="search-option-tag" data-option="creator">Creator</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
{% block init_title %}Initializing Embeddings Manager{% endblock %}
|
||||
{% block init_message %}Scanning and building embeddings cache. This may take a few moments...{% endblock %}
|
||||
{% block init_check_url %}/api/embeddings?page=1&page_size=1{% endblock %}
|
||||
{% block init_check_url %}/api/embeddings/list?page=1&page_size=1{% endblock %}
|
||||
|
||||
{% block additional_components %}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
{% block init_title %}Initializing LoRA Manager{% endblock %}
|
||||
{% block init_message %}Scanning and building LoRA cache. This may take a few minutes...{% endblock %}
|
||||
{% block init_check_url %}/api/loras?page=1&page_size=1{% endblock %}
|
||||
{% block init_check_url %}/api/loras/list?page=1&page_size=1{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include 'components/controls.html' %}
|
||||
|
||||
@@ -5,9 +5,6 @@ export function addJsonDisplayWidget(node, name, opts) {
|
||||
|
||||
// Set initial height
|
||||
const defaultHeight = 200;
|
||||
container.style.setProperty('--comfy-widget-min-height', `${defaultHeight}px`);
|
||||
container.style.setProperty('--comfy-widget-max-height', `${defaultHeight * 2}px`);
|
||||
container.style.setProperty('--comfy-widget-height', `${defaultHeight}px`);
|
||||
|
||||
Object.assign(container.style, {
|
||||
display: "block",
|
||||
@@ -113,16 +110,6 @@ export function addJsonDisplayWidget(node, name, opts) {
|
||||
widgetValue = v;
|
||||
displayJson(widgetValue, widget);
|
||||
},
|
||||
getMinHeight: function() {
|
||||
return parseInt(container.style.getPropertyValue('--comfy-widget-min-height')) || defaultHeight;
|
||||
},
|
||||
getMaxHeight: function() {
|
||||
return parseInt(container.style.getPropertyValue('--comfy-widget-max-height')) || defaultHeight * 2;
|
||||
},
|
||||
getHeight: function() {
|
||||
// Return actual container height to reduce the gap
|
||||
return parseInt(container.style.getPropertyValue('--comfy-widget-height')) || defaultHeight;
|
||||
},
|
||||
hideOnZoom: true
|
||||
});
|
||||
|
||||
|
||||
@@ -156,6 +156,7 @@ app.registerExtension({
|
||||
|
||||
// Update input widget callback
|
||||
const inputWidget = this.widgets[0];
|
||||
inputWidget.options.getMaxHeight = () => 100;
|
||||
this.inputWidget = inputWidget;
|
||||
inputWidget.callback = (value) => {
|
||||
if (isUpdating) return;
|
||||
|
||||
@@ -77,6 +77,7 @@ app.registerExtension({
|
||||
|
||||
// Update input widget callback
|
||||
const inputWidget = this.widgets[0];
|
||||
inputWidget.options.getMaxHeight = () => 100;
|
||||
this.inputWidget = inputWidget;
|
||||
inputWidget.callback = (value) => {
|
||||
if (isUpdating) return;
|
||||
|
||||
@@ -19,9 +19,6 @@ export function addLorasWidget(node, name, opts, callback) {
|
||||
|
||||
// Set initial height using CSS variables approach
|
||||
const defaultHeight = 200;
|
||||
container.style.setProperty('--comfy-widget-min-height', `${defaultHeight}px`);
|
||||
container.style.setProperty('--comfy-widget-max-height', `${defaultHeight * 2}px`);
|
||||
container.style.setProperty('--comfy-widget-height', `${defaultHeight}px`);
|
||||
|
||||
Object.assign(container.style, {
|
||||
display: "flex",
|
||||
@@ -712,23 +709,8 @@ export function addLorasWidget(node, name, opts, callback) {
|
||||
widgetValue = updatedValue;
|
||||
renderLoras(widgetValue, widget);
|
||||
},
|
||||
getMinHeight: function() {
|
||||
return parseInt(container.style.getPropertyValue('--comfy-widget-min-height')) || defaultHeight;
|
||||
},
|
||||
getMaxHeight: function() {
|
||||
return parseInt(container.style.getPropertyValue('--comfy-widget-max-height')) || defaultHeight * 2;
|
||||
},
|
||||
getHeight: function() {
|
||||
return parseInt(container.style.getPropertyValue('--comfy-widget-height')) || defaultHeight;
|
||||
},
|
||||
hideOnZoom: true,
|
||||
selectOn: ['click', 'focus'],
|
||||
afterResize: function(node) {
|
||||
// Re-render after node resize
|
||||
if (this.value && this.value.length > 0) {
|
||||
renderLoras(this.value, this);
|
||||
}
|
||||
}
|
||||
selectOn: ['click', 'focus']
|
||||
});
|
||||
|
||||
widget.value = defaultValue;
|
||||
|
||||
@@ -5,9 +5,6 @@ export function addTagsWidget(node, name, opts, callback) {
|
||||
|
||||
// Set initial height
|
||||
const defaultHeight = 150;
|
||||
container.style.setProperty('--comfy-widget-min-height', `${defaultHeight}px`);
|
||||
container.style.setProperty('--comfy-widget-max-height', `${defaultHeight * 2}px`);
|
||||
container.style.setProperty('--comfy-widget-height', `${defaultHeight}px`);
|
||||
|
||||
Object.assign(container.style, {
|
||||
display: "flex",
|
||||
@@ -199,23 +196,8 @@ export function addTagsWidget(node, name, opts, callback) {
|
||||
widgetValue = v;
|
||||
renderTags(widgetValue, widget);
|
||||
},
|
||||
getMinHeight: function() {
|
||||
return parseInt(container.style.getPropertyValue('--comfy-widget-min-height')) || defaultHeight;
|
||||
},
|
||||
getMaxHeight: function() {
|
||||
return parseInt(container.style.getPropertyValue('--comfy-widget-max-height')) || defaultHeight * 2;
|
||||
},
|
||||
getHeight: function() {
|
||||
return parseInt(container.style.getPropertyValue('--comfy-widget-height')) || defaultHeight;
|
||||
},
|
||||
hideOnZoom: true,
|
||||
selectOn: ['click', 'focus'],
|
||||
afterResize: function(node) {
|
||||
// Re-render tags after node resize
|
||||
if (this.value && this.value.length > 0) {
|
||||
renderTags(this.value, this);
|
||||
}
|
||||
}
|
||||
selectOn: ['click', 'focus']
|
||||
});
|
||||
|
||||
// Set initial value
|
||||
|
||||
@@ -78,6 +78,7 @@ app.registerExtension({
|
||||
|
||||
// Update input widget callback
|
||||
const inputWidget = this.widgets[1];
|
||||
inputWidget.options.getMaxHeight = () => 100;
|
||||
this.inputWidget = inputWidget;
|
||||
inputWidget.callback = (value) => {
|
||||
if (isUpdating) return;
|
||||
|
||||
Reference in New Issue
Block a user