From 2fc06ae64e37803829f5ad3d5a1fdb9856f43195 Mon Sep 17 00:00:00 2001
From: Will Miao <13051207myq@gmail.com>
Date: Sat, 12 Apr 2025 16:35:35 +0800
Subject: [PATCH] Refactor file name update in Lora card
- Updated the setupFileNameEditing function to pass the new file name in the updates object when calling updateLoraCard.
- Removed the page reload after file name change to improve user experience.
- Enhanced the updateLoraCard function to handle the 'file_name' update, ensuring the dataset reflects the new file name correctly.
---
static/js/components/LoraModal.js | 1733 -----------------
.../js/components/loraModal/ModelMetadata.js | 8 +-
static/js/utils/cardUpdater.js | 5 +
3 files changed, 7 insertions(+), 1739 deletions(-)
delete mode 100644 static/js/components/LoraModal.js
diff --git a/static/js/components/LoraModal.js b/static/js/components/LoraModal.js
deleted file mode 100644
index 3f771e64..00000000
--- a/static/js/components/LoraModal.js
+++ /dev/null
@@ -1,1733 +0,0 @@
-import { showToast } from '../utils/uiHelpers.js';
-import { state } from '../state/index.js';
-import { modalManager } from '../managers/ModalManager.js';
-import { NSFW_LEVELS, BASE_MODELS } from '../utils/constants.js';
-
-export function showLoraModal(lora) {
- const escapedWords = lora.civitai?.trainedWords?.length ?
- lora.civitai.trainedWords.map(word => word.replace(/'/g, '\\\'')) : [];
-
- const content = `
-
-
×
-
-
-
-
-
-
- Version
- ${lora.civitai.name || 'N/A'}
-
-
-
File Name
-
- ${lora.file_name || 'N/A'}
-
-
-
-
-
-
-
- Location
- ${lora.file_path.replace(/[^/]+$/, '') || 'N/A'}
-
-
-
-
-
Base Model
-
- ${lora.base_model || 'N/A'}
-
-
-
-
-
-
- Size
- ${formatFileSize(lora.file_size)}
-
-
-
-
Usage Tips
-
-
-
- Add preset parameter...
- Strength Min
- Strength Max
- Strength
- Clip Skip
-
-
- Add
-
-
- ${renderPresetTags(parsePresets(lora.usage_tips))}
-
-
-
- ${renderTriggerWords(escapedWords, lora.file_path)}
-
-
Additional Notes
-
-
${lora.notes || 'Add your notes here...'}
-
-
-
-
-
-
-
About this version
-
${lora.description || 'N/A'}
-
-
-
-
-
-
- Examples
- Model Description
-
-
-
-
- ${renderShowcaseContent(lora.civitai?.images)}
-
-
-
-
-
- Loading model description...
-
-
- ${lora.modelDescription || ''}
-
-
-
-
-
-
-
-
-
-
-
- `;
-
- modalManager.showModal('loraModal', content);
- setupEditableFields();
- setupShowcaseScroll();
- setupTabSwitching();
- setupTagTooltip();
- setupTriggerWordsEditMode();
- setupModelNameEditing();
- setupBaseModelEditing();
- setupFileNameEditing();
-
- // If we have a model ID but no description, fetch it
- if (lora.civitai?.modelId && !lora.modelDescription) {
- loadModelDescription(lora.civitai.modelId, lora.file_path);
- }
-}
-
-// Function to render showcase content
-function renderShowcaseContent(images) {
- if (!images?.length) return 'No example images available
';
-
- // Filter images based on SFW setting
- const showOnlySFW = state.settings.show_only_sfw;
- let filteredImages = images;
- let hiddenCount = 0;
-
- if (showOnlySFW) {
- filteredImages = images.filter(img => {
- const nsfwLevel = img.nsfwLevel !== undefined ? img.nsfwLevel : 0;
- const isSfw = nsfwLevel < NSFW_LEVELS.R;
- if (!isSfw) hiddenCount++;
- return isSfw;
- });
- }
-
- // Show message if no images are available after filtering
- if (filteredImages.length === 0) {
- return `
-
-
All example images are filtered due to NSFW content settings
-
Your settings are currently set to show only safe-for-work content
-
You can change this in Settings
-
- `;
- }
-
- // Show hidden content notification if applicable
- const hiddenNotification = hiddenCount > 0 ?
- `
- ${hiddenCount} ${hiddenCount === 1 ? 'image' : 'images'} hidden due to SFW-only setting
-
` : '';
-
- return `
-
-
- Scroll or click to show ${filteredImages.length} examples
-
-
- ${hiddenNotification}
-
- ${filteredImages.map(img => {
- // 计算适当的展示高度:
- // 1. 保持原始宽高比
- // 2. 限制最大高度为视窗高度的60%
- // 3. 确保最小高度为容器宽度的40%
- const aspectRatio = (img.height / img.width) * 100;
- const containerWidth = 800; // modal content的最大宽度
- const minHeightPercent = 40; // 最小高度为容器宽度的40%
- const maxHeightPercent = (window.innerHeight * 0.6 / containerWidth) * 100;
- const heightPercent = Math.max(
- minHeightPercent,
- Math.min(maxHeightPercent, aspectRatio)
- );
-
- // Check if image should be blurred
- const nsfwLevel = img.nsfwLevel !== undefined ? img.nsfwLevel : 0;
- const shouldBlur = state.settings.blurMatureContent && nsfwLevel > NSFW_LEVELS.PG13;
-
- // Determine NSFW warning text based on level
- let nsfwText = "Mature Content";
- if (nsfwLevel >= NSFW_LEVELS.XXX) {
- nsfwText = "XXX-rated Content";
- } else if (nsfwLevel >= NSFW_LEVELS.X) {
- nsfwText = "X-rated Content";
- } else if (nsfwLevel >= NSFW_LEVELS.R) {
- nsfwText = "R-rated Content";
- }
-
- // Extract metadata from the image
- const meta = img.meta || {};
- const prompt = meta.prompt || '';
- const negativePrompt = meta.negative_prompt || meta.negativePrompt || '';
- const size = meta.Size || `${img.width}x${img.height}`;
- const seed = meta.seed || '';
- const model = meta.Model || '';
- const steps = meta.steps || '';
- const sampler = meta.sampler || '';
- const cfgScale = meta.cfgScale || '';
- const clipSkip = meta.clipSkip || '';
-
- // Check if we have any meaningful generation parameters
- const hasParams = seed || model || steps || sampler || cfgScale || clipSkip;
- const hasPrompts = prompt || negativePrompt;
-
- // If no metadata available, show a message
- if (!hasParams && !hasPrompts) {
- const metadataPanel = `
-
- `;
-
- if (img.type === 'video') {
- return generateVideoWrapper(img, heightPercent, shouldBlur, nsfwText, metadataPanel);
- }
- return generateImageWrapper(img, heightPercent, shouldBlur, nsfwText, metadataPanel);
- }
-
- // Create a data attribute with the prompt for copying instead of trying to handle it in the onclick
- // This avoids issues with quotes and special characters
- const promptIndex = Math.random().toString(36).substring(2, 15);
- const negPromptIndex = Math.random().toString(36).substring(2, 15);
-
- // Create parameter tags HTML
- const paramTags = `
-
- `;
-
- // Metadata panel HTML
- const metadataPanel = `
-
- `;
-
- if (img.type === 'video') {
- return generateVideoWrapper(img, heightPercent, shouldBlur, nsfwText, metadataPanel);
- }
- return generateImageWrapper(img, heightPercent, shouldBlur, nsfwText, metadataPanel);
- }).join('')}
-
-
- `;
-}
-
-// Helper function to generate video wrapper HTML
-function generateVideoWrapper(img, heightPercent, shouldBlur, nsfwText, metadataPanel) {
- return `
-
- `;
-}
-
-// Helper function to generate image wrapper HTML
-function generateImageWrapper(img, heightPercent, shouldBlur, nsfwText, metadataPanel) {
- return `
-
- `;
-}
-
-// New function to handle tab switching
-function setupTabSwitching() {
- const tabButtons = document.querySelectorAll('.showcase-tabs .tab-btn');
-
- tabButtons.forEach(button => {
- button.addEventListener('click', () => {
- // Remove active class from all tabs
- document.querySelectorAll('.showcase-tabs .tab-btn').forEach(btn =>
- btn.classList.remove('active')
- );
- document.querySelectorAll('.tab-content .tab-pane').forEach(tab =>
- tab.classList.remove('active')
- );
-
- // Add active class to clicked tab
- button.classList.add('active');
- const tabId = `${button.dataset.tab}-tab`;
- document.getElementById(tabId).classList.add('active');
-
- // If switching to description tab, make sure content is properly sized
- if (button.dataset.tab === 'description') {
- const descriptionContent = document.querySelector('.model-description-content');
- if (descriptionContent) {
- const hasContent = descriptionContent.innerHTML.trim() !== '';
- document.querySelector('.model-description-loading')?.classList.add('hidden');
-
- // If no content, show a message
- if (!hasContent) {
- descriptionContent.innerHTML = 'No model description available
';
- descriptionContent.classList.remove('hidden');
- }
- }
- }
- });
- });
-}
-
-// New function to load model description
-async function loadModelDescription(modelId, filePath) {
- try {
- const descriptionContainer = document.querySelector('.model-description-content');
- const loadingElement = document.querySelector('.model-description-loading');
-
- if (!descriptionContainer || !loadingElement) return;
-
- // Show loading indicator
- loadingElement.classList.remove('hidden');
- descriptionContainer.classList.add('hidden');
-
- // Try to get model description from API
- const response = await fetch(`/api/lora-model-description?model_id=${modelId}&file_path=${encodeURIComponent(filePath)}`);
-
- if (!response.ok) {
- throw new Error(`Failed to fetch model description: ${response.statusText}`);
- }
-
- const data = await response.json();
-
- if (data.success && data.description) {
- // Update the description content
- descriptionContainer.innerHTML = data.description;
-
- // Process any links in the description to open in new tab
- const links = descriptionContainer.querySelectorAll('a');
- links.forEach(link => {
- link.setAttribute('target', '_blank');
- link.setAttribute('rel', 'noopener noreferrer');
- });
-
- // Show the description and hide loading indicator
- descriptionContainer.classList.remove('hidden');
- loadingElement.classList.add('hidden');
- } else {
- throw new Error(data.error || 'No description available');
- }
- } catch (error) {
- console.error('Error loading model description:', error);
- const loadingElement = document.querySelector('.model-description-loading');
- if (loadingElement) {
- loadingElement.innerHTML = `Failed to load model description. ${error.message}
`;
- }
-
- // Show empty state message in the description container
- const descriptionContainer = document.querySelector('.model-description-content');
- if (descriptionContainer) {
- descriptionContainer.innerHTML = 'No model description available
';
- descriptionContainer.classList.remove('hidden');
- }
- }
-}
-
-// 添加复制文件名的函数
-window.copyFileName = async function(fileName) {
- try {
- await navigator.clipboard.writeText(fileName);
- showToast('File name copied', 'success');
- } catch (err) {
- console.error('Copy failed:', err);
- showToast('Copy failed', 'error');
- }
-};
-
-// Add function to save model name
-window.saveModelName = async function(filePath) {
- const modelNameElement = document.querySelector('.model-name-content');
- const newModelName = modelNameElement.textContent.trim();
-
- // Validate model name
- if (!newModelName) {
- showToast('Model name cannot be empty', 'error');
- return;
- }
-
- // Check if model name is too long (limit to 100 characters)
- if (newModelName.length > 100) {
- showToast('Model name is too long (maximum 100 characters)', 'error');
- // Truncate the displayed text
- modelNameElement.textContent = newModelName.substring(0, 100);
- return;
- }
-
- try {
- await saveModelMetadata(filePath, { model_name: newModelName });
-
- // Update the corresponding lora card's dataset and display
- const loraCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`);
- if (loraCard) {
- loraCard.dataset.model_name = newModelName;
- const titleElement = loraCard.querySelector('.card-title');
- if (titleElement) {
- titleElement.textContent = newModelName;
- }
- }
-
- showToast('Model name updated successfully', 'success');
-
- // Reload the page to reflect the sorted order
- setTimeout(() => {
- window.location.reload();
- }, 1500);
- } catch (error) {
- showToast('Failed to update model name', 'error');
- }
-};
-
-function setupEditableFields() {
- const editableFields = document.querySelectorAll('.editable-field [contenteditable]');
-
- editableFields.forEach(field => {
- field.addEventListener('focus', function() {
- if (this.textContent === 'Add your notes here...') {
- this.textContent = '';
- }
- });
-
- field.addEventListener('blur', function() {
- if (this.textContent.trim() === '') {
- if (this.classList.contains('notes-content')) {
- this.textContent = 'Add your notes here...';
- }
- }
- });
- });
-
- const presetSelector = document.getElementById('preset-selector');
- const presetValue = document.getElementById('preset-value');
- const addPresetBtn = document.querySelector('.add-preset-btn');
- const presetTags = document.querySelector('.preset-tags');
-
- presetSelector.addEventListener('change', function() {
- const selected = this.value;
- if (selected) {
- presetValue.style.display = 'inline-block';
- presetValue.min = selected.includes('strength') ? -10 : 0;
- presetValue.max = selected.includes('strength') ? 10 : 10;
- presetValue.step = 0.5;
- if (selected === 'clip_skip') {
- presetValue.type = 'number';
- presetValue.step = 1;
- }
- // Add auto-focus
- setTimeout(() => presetValue.focus(), 0);
- } else {
- presetValue.style.display = 'none';
- }
- });
-
- addPresetBtn.addEventListener('click', async function() {
- const key = presetSelector.value;
- const value = presetValue.value;
-
- if (!key || !value) return;
-
- const filePath = document.querySelector('#loraModal .modal-content')
- .querySelector('.file-path').textContent +
- document.querySelector('#loraModal .modal-content')
- .querySelector('#file-name').textContent + '.safetensors';
-
- const loraCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`);
- const currentPresets = parsePresets(loraCard.dataset.usage_tips);
-
- currentPresets[key] = parseFloat(value);
- const newPresetsJson = JSON.stringify(currentPresets);
-
- await saveModelMetadata(filePath, {
- usage_tips: newPresetsJson
- });
-
- loraCard.dataset.usage_tips = newPresetsJson;
- presetTags.innerHTML = renderPresetTags(currentPresets);
-
- presetSelector.value = '';
- presetValue.value = '';
- presetValue.style.display = 'none';
- });
-
- // Add keydown event listeners for notes
- const notesContent = document.querySelector('.notes-content');
- if (notesContent) {
- notesContent.addEventListener('keydown', async function(e) {
- if (e.key === 'Enter') {
- if (e.shiftKey) {
- // Allow shift+enter for new line
- return;
- }
- e.preventDefault();
- const filePath = document.querySelector('#loraModal .modal-content')
- .querySelector('.file-path').textContent +
- document.querySelector('#loraModal .modal-content')
- .querySelector('#file-name').textContent + '.safetensors';
- await saveNotes(filePath);
- }
- });
- }
-
- // Add keydown event for preset value
- presetValue.addEventListener('keydown', function(e) {
- if (e.key === 'Enter') {
- e.preventDefault();
- addPresetBtn.click();
- }
- });
-}
-
-window.saveNotes = async function(filePath) {
- const content = document.querySelector('.notes-content').textContent;
- try {
- await saveModelMetadata(filePath, { notes: content });
-
- // Update the corresponding lora card's dataset
- const loraCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`);
- if (loraCard) {
- loraCard.dataset.notes = content;
- }
-
- showToast('Notes saved successfully', 'success');
- } catch (error) {
- showToast('Failed to save notes', 'error');
- }
-};
-
-async function saveModelMetadata(filePath, data) {
- const response = await fetch('/api/loras/save-metadata', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- file_path: filePath,
- ...data
- })
- });
-
- if (!response.ok) {
- throw new Error('Failed to save metadata');
- }
-}
-
-function renderTriggerWords(words, filePath) {
- if (!words.length) return `
-
-
-
-
No trigger word needed
-
-
-
-
- Add
-
-
- Save
-
-
-
-
- Add
- Cancel
-
-
- `;
-
- return `
-
-
-
-
-
- Add
-
-
- Save
-
-
-
-
- Add
- Cancel
-
-
- `;
-}
-
-export function toggleShowcase(element) {
- const carousel = element.nextElementSibling;
- const isCollapsed = carousel.classList.contains('collapsed');
- const indicator = element.querySelector('span');
- const icon = element.querySelector('i');
-
- carousel.classList.toggle('collapsed');
-
- if (isCollapsed) {
- const count = carousel.querySelectorAll('.media-wrapper').length;
- indicator.textContent = `Scroll or click to hide examples`;
- icon.classList.replace('fa-chevron-down', 'fa-chevron-up');
- initLazyLoading(carousel);
-
- // Initialize NSFW content blur toggle handlers
- initNsfwBlurHandlers(carousel);
-
- // Initialize metadata panel interaction handlers
- initMetadataPanelHandlers(carousel);
- } else {
- const count = carousel.querySelectorAll('.media-wrapper').length;
- indicator.textContent = `Scroll or click to show ${count} examples`;
- icon.classList.replace('fa-chevron-up', 'fa-chevron-down');
-
- // Make sure any open metadata panels get closed
- const carouselContainer = carousel.querySelector('.carousel-container');
- if (carouselContainer) {
- carouselContainer.style.height = '0';
- setTimeout(() => {
- carouselContainer.style.height = '';
- }, 300);
- }
- }
-}
-
-// Function to initialize metadata panel interactions
-function initMetadataPanelHandlers(container) {
- // Find all media wrappers
- const mediaWrappers = container.querySelectorAll('.media-wrapper');
-
- mediaWrappers.forEach(wrapper => {
- // Get the metadata panel
- const metadataPanel = wrapper.querySelector('.image-metadata-panel');
- if (!metadataPanel) return;
-
- // Prevent events from the metadata panel from bubbling
- metadataPanel.addEventListener('click', (e) => {
- e.stopPropagation();
- });
-
- // Handle copy prompt button clicks
- const copyBtns = metadataPanel.querySelectorAll('.copy-prompt-btn');
- copyBtns.forEach(copyBtn => {
- const promptIndex = copyBtn.dataset.promptIndex;
- const promptElement = wrapper.querySelector(`#prompt-${promptIndex}`);
-
- copyBtn.addEventListener('click', async (e) => {
- e.stopPropagation(); // Prevent bubbling
-
- if (!promptElement) return;
-
- try {
- await navigator.clipboard.writeText(promptElement.textContent);
- showToast('Prompt copied to clipboard', 'success');
- } catch (err) {
- console.error('Copy failed:', err);
- showToast('Copy failed', 'error');
- }
- });
- });
-
- // Prevent scrolling in the metadata panel from scrolling the whole modal
- metadataPanel.addEventListener('wheel', (e) => {
- const isAtTop = metadataPanel.scrollTop === 0;
- const isAtBottom = metadataPanel.scrollHeight - metadataPanel.scrollTop === metadataPanel.clientHeight;
-
- // Only prevent default if scrolling would cause the panel to scroll
- if ((e.deltaY < 0 && !isAtTop) || (e.deltaY > 0 && !isAtBottom)) {
- e.stopPropagation();
- }
- }, { passive: true });
- });
-}
-
-// New function to initialize blur toggle handlers for showcase images/videos
-function initNsfwBlurHandlers(container) {
- // Handle toggle blur buttons
- const toggleButtons = container.querySelectorAll('.toggle-blur-btn');
- toggleButtons.forEach(btn => {
- btn.addEventListener('click', (e) => {
- e.stopPropagation();
- const wrapper = btn.closest('.media-wrapper');
- const media = wrapper.querySelector('img, video');
- const isBlurred = media.classList.toggle('blurred');
- const icon = btn.querySelector('i');
-
- // Update the icon based on blur state
- if (isBlurred) {
- icon.className = 'fas fa-eye';
- } else {
- icon.className = 'fas fa-eye-slash';
- }
-
- // Toggle the overlay visibility
- const overlay = wrapper.querySelector('.nsfw-overlay');
- if (overlay) {
- overlay.style.display = isBlurred ? 'flex' : 'none';
- }
- });
- });
-
- // Handle "Show" buttons in overlays
- const showButtons = container.querySelectorAll('.show-content-btn');
- showButtons.forEach(btn => {
- btn.addEventListener('click', (e) => {
- e.stopPropagation();
- const wrapper = btn.closest('.media-wrapper');
- const media = wrapper.querySelector('img, video');
- media.classList.remove('blurred');
-
- // Update the toggle button icon
- const toggleBtn = wrapper.querySelector('.toggle-blur-btn');
- if (toggleBtn) {
- toggleBtn.querySelector('i').className = 'fas fa-eye-slash';
- }
-
- // Hide the overlay
- const overlay = wrapper.querySelector('.nsfw-overlay');
- if (overlay) {
- overlay.style.display = 'none';
- }
- });
- });
-}
-
-// Add lazy loading initialization
-function initLazyLoading(container) {
- const lazyElements = container.querySelectorAll('.lazy');
-
- const lazyLoad = (element) => {
- if (element.tagName.toLowerCase() === 'video') {
- element.src = element.dataset.src;
- element.querySelector('source').src = element.dataset.src;
- element.load();
- } else {
- element.src = element.dataset.src;
- }
- element.classList.remove('lazy');
- };
-
- const observer = new IntersectionObserver((entries) => {
- entries.forEach(entry => {
- if (entry.isIntersecting) {
- lazyLoad(entry.target);
- observer.unobserve(entry.target);
- }
- });
- });
-
- lazyElements.forEach(element => observer.observe(element));
-}
-
-export function setupShowcaseScroll() {
- // Add event listener to document for wheel events
- document.addEventListener('wheel', (event) => {
- // Find the active modal content
- const modalContent = document.querySelector('#loraModal .modal-content');
- if (!modalContent) return;
-
- const showcase = modalContent.querySelector('.showcase-section');
- if (!showcase) return;
-
- const carousel = showcase.querySelector('.carousel');
- const scrollIndicator = showcase.querySelector('.scroll-indicator');
-
- if (carousel?.classList.contains('collapsed') && event.deltaY > 0) {
- const isNearBottom = modalContent.scrollHeight - modalContent.scrollTop - modalContent.clientHeight < 100;
-
- if (isNearBottom) {
- toggleShowcase(scrollIndicator);
- event.preventDefault();
- }
- }
- }, { passive: false });
-
- // Use MutationObserver instead of deprecated DOMNodeInserted
- const observer = new MutationObserver((mutations) => {
- for (const mutation of mutations) {
- if (mutation.type === 'childList' && mutation.addedNodes.length) {
- // Check if loraModal content was added
- const loraModal = document.getElementById('loraModal');
- if (loraModal && loraModal.querySelector('.modal-content')) {
- setupBackToTopButton(loraModal.querySelector('.modal-content'));
- }
- }
- }
- });
-
- // Start observing the document body for changes
- observer.observe(document.body, { childList: true, subtree: true });
-
- // Also try to set up the button immediately in case the modal is already open
- const modalContent = document.querySelector('#loraModal .modal-content');
- if (modalContent) {
- setupBackToTopButton(modalContent);
- }
-}
-
-// New helper function to set up the back to top button
-function setupBackToTopButton(modalContent) {
- // Remove any existing scroll listeners to avoid duplicates
- modalContent.onscroll = null;
-
- // Add new scroll listener
- modalContent.addEventListener('scroll', () => {
- const backToTopBtn = modalContent.querySelector('.back-to-top');
- if (backToTopBtn) {
- if (modalContent.scrollTop > 300) {
- backToTopBtn.classList.add('visible');
- } else {
- backToTopBtn.classList.remove('visible');
- }
- }
- });
-
- // Trigger a scroll event to check initial position
- modalContent.dispatchEvent(new Event('scroll'));
-}
-
-export function scrollToTop(button) {
- const modalContent = button.closest('.modal-content');
- if (modalContent) {
- modalContent.scrollTo({
- top: 0,
- behavior: 'smooth'
- });
- }
-}
-
-function parsePresets(usageTips) {
- if (!usageTips) return {};
- try {
- return JSON.parse(usageTips);
- } catch {
- return {};
- }
-}
-
-function renderPresetTags(presets) {
- return Object.entries(presets).map(([key, value]) => `
-
- ${formatPresetKey(key)}: ${value}
-
-
- `).join('');
-}
-
-function formatPresetKey(key) {
- return key.split('_').map(word =>
- word.charAt(0).toUpperCase() + word.slice(1)
- ).join(' ');
-}
-
-window.removePreset = async function(key) {
- const filePath = document.querySelector('#loraModal .modal-content')
- .querySelector('.file-path').textContent +
- document.querySelector('#loraModal .modal-content')
- .querySelector('#file-name').textContent + '.safetensors';
- const loraCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`);
- const currentPresets = parsePresets(loraCard.dataset.usage_tips);
-
- delete currentPresets[key];
- const newPresetsJson = JSON.stringify(currentPresets);
-
- await saveModelMetadata(filePath, {
- usage_tips: newPresetsJson
- });
-
- loraCard.dataset.usage_tips = newPresetsJson;
- document.querySelector('.preset-tags').innerHTML = renderPresetTags(currentPresets);
-};
-
-// 添加文件大小格式化函数
-function formatFileSize(bytes) {
- if (!bytes) return 'N/A';
- const units = ['B', 'KB', 'MB', 'GB'];
- let size = bytes;
- let unitIndex = 0;
-
- while (size >= 1024 && unitIndex < units.length - 1) {
- size /= 1024;
- unitIndex++;
- }
-
- return `${size.toFixed(1)} ${units[unitIndex]}`;
-}
-
-// New function to render compact tags with tooltip
-function renderCompactTags(tags) {
- if (!tags || tags.length === 0) return '';
-
- // Display up to 5 tags, with a tooltip indicator if there are more
- const visibleTags = tags.slice(0, 5);
- const remainingCount = Math.max(0, tags.length - 5);
-
- return `
-
- `;
-}
-
-// Setup tooltip functionality
-function setupTagTooltip() {
- const tagsContainer = document.querySelector('.model-tags-container');
- const tooltip = document.querySelector('.model-tags-tooltip');
-
- if (tagsContainer && tooltip) {
- tagsContainer.addEventListener('mouseenter', () => {
- tooltip.classList.add('visible');
- });
-
- tagsContainer.addEventListener('mouseleave', () => {
- tooltip.classList.remove('visible');
- });
- }
-}
-
-// Set up trigger words edit mode
-function setupTriggerWordsEditMode() {
- const editBtn = document.querySelector('.edit-trigger-words-btn');
- if (!editBtn) return;
-
- editBtn.addEventListener('click', function() {
- const triggerWordsSection = this.closest('.trigger-words');
- const isEditMode = triggerWordsSection.classList.toggle('edit-mode');
-
- // Toggle edit mode UI elements
- const triggerWordTags = triggerWordsSection.querySelectorAll('.trigger-word-tag');
- const editControls = triggerWordsSection.querySelector('.trigger-words-edit-controls');
- const noTriggerWords = triggerWordsSection.querySelector('.no-trigger-words');
- const tagsContainer = triggerWordsSection.querySelector('.trigger-words-tags');
-
- if (isEditMode) {
- this.innerHTML = ' '; // Change to cancel icon
- this.title = "Cancel editing";
- editControls.style.display = 'flex';
-
- // If we have no trigger words yet, hide the "No trigger word needed" text
- // and show the empty tags container
- if (noTriggerWords) {
- noTriggerWords.style.display = 'none';
- if (tagsContainer) tagsContainer.style.display = 'flex';
- }
-
- // Disable click-to-copy and show delete buttons
- triggerWordTags.forEach(tag => {
- tag.onclick = null;
- tag.querySelector('.trigger-word-copy').style.display = 'none';
- tag.querySelector('.delete-trigger-word-btn').style.display = 'block';
- });
- } else {
- this.innerHTML = ' '; // Change back to edit icon
- this.title = "Edit trigger words";
- editControls.style.display = 'none';
-
- // If we have no trigger words, show the "No trigger word needed" text
- // and hide the empty tags container
- const currentTags = triggerWordsSection.querySelectorAll('.trigger-word-tag');
- if (noTriggerWords && currentTags.length === 0) {
- noTriggerWords.style.display = '';
- if (tagsContainer) tagsContainer.style.display = 'none';
- }
-
- // Restore original state
- triggerWordTags.forEach(tag => {
- const word = tag.dataset.word;
- tag.onclick = () => copyTriggerWord(word);
- tag.querySelector('.trigger-word-copy').style.display = 'flex';
- tag.querySelector('.delete-trigger-word-btn').style.display = 'none';
- });
-
- // Hide add form if open
- triggerWordsSection.querySelector('.add-trigger-word-form').style.display = 'none';
- }
- });
-
- // Set up add trigger word button
- const addBtn = document.querySelector('.add-trigger-word-btn');
- if (addBtn) {
- addBtn.addEventListener('click', function() {
- const triggerWordsSection = this.closest('.trigger-words');
- const addForm = triggerWordsSection.querySelector('.add-trigger-word-form');
- addForm.style.display = 'flex';
- addForm.querySelector('input').focus();
- });
- }
-
- // Set up confirm and cancel add buttons
- const confirmAddBtn = document.querySelector('.confirm-add-trigger-word-btn');
- const cancelAddBtn = document.querySelector('.cancel-add-trigger-word-btn');
- const triggerWordInput = document.querySelector('.new-trigger-word-input');
-
- if (confirmAddBtn && triggerWordInput) {
- confirmAddBtn.addEventListener('click', function() {
- addNewTriggerWord(triggerWordInput.value);
- });
-
- // Add keydown event to input
- triggerWordInput.addEventListener('keydown', function(e) {
- if (e.key === 'Enter') {
- e.preventDefault();
- addNewTriggerWord(this.value);
- }
- });
- }
-
- if (cancelAddBtn) {
- cancelAddBtn.addEventListener('click', function() {
- const addForm = this.closest('.add-trigger-word-form');
- addForm.style.display = 'none';
- addForm.querySelector('input').value = '';
- });
- }
-
- // Set up save button
- const saveBtn = document.querySelector('.save-trigger-words-btn');
- if (saveBtn) {
- saveBtn.addEventListener('click', saveTriggerWords);
- }
-
- // Set up delete buttons
- document.querySelectorAll('.delete-trigger-word-btn').forEach(btn => {
- btn.addEventListener('click', function(e) {
- e.stopPropagation();
- const tag = this.closest('.trigger-word-tag');
- tag.remove();
- });
- });
-}
-
-// Function to add a new trigger word
-function addNewTriggerWord(word) {
- word = word.trim();
- if (!word) return;
-
- const triggerWordsSection = document.querySelector('.trigger-words');
- let tagsContainer = document.querySelector('.trigger-words-tags');
-
- // Ensure tags container exists and is visible
- if (tagsContainer) {
- tagsContainer.style.display = 'flex';
- } else {
- // Create tags container if it doesn't exist
- const contentDiv = triggerWordsSection.querySelector('.trigger-words-content');
- if (contentDiv) {
- tagsContainer = document.createElement('div');
- tagsContainer.className = 'trigger-words-tags';
- contentDiv.appendChild(tagsContainer);
- }
- }
-
- if (!tagsContainer) return;
-
- // Hide "no trigger words" message if it exists
- const noTriggerWordsMsg = triggerWordsSection.querySelector('.no-trigger-words');
- if (noTriggerWordsMsg) {
- noTriggerWordsMsg.style.display = 'none';
- }
-
- // Validation: Check length
- if (word.split(/\s+/).length > 30) {
- showToast('Trigger word should not exceed 30 words', 'error');
- return;
- }
-
- // Validation: Check total number
- const currentTags = tagsContainer.querySelectorAll('.trigger-word-tag');
- if (currentTags.length >= 10) {
- showToast('Maximum 10 trigger words allowed', 'error');
- return;
- }
-
- // Validation: Check for duplicates
- const existingWords = Array.from(currentTags).map(tag => tag.dataset.word);
- if (existingWords.includes(word)) {
- showToast('This trigger word already exists', 'error');
- return;
- }
-
- // Create new tag
- const newTag = document.createElement('div');
- newTag.className = 'trigger-word-tag';
- newTag.dataset.word = word;
- newTag.innerHTML = `
- ${word}
-
-
-
-
-
-
- `;
-
- // Add event listener to delete button
- const deleteBtn = newTag.querySelector('.delete-trigger-word-btn');
- deleteBtn.addEventListener('click', function() {
- newTag.remove();
- });
-
- tagsContainer.appendChild(newTag);
-
- // Clear and hide the input form
- const triggerWordInput = document.querySelector('.new-trigger-word-input');
- triggerWordInput.value = '';
- document.querySelector('.add-trigger-word-form').style.display = 'none';
-}
-
-// Function to save updated trigger words
-async function saveTriggerWords() {
- const filePath = document.querySelector('.edit-trigger-words-btn').dataset.filePath;
- const triggerWordTags = document.querySelectorAll('.trigger-word-tag');
- const words = Array.from(triggerWordTags).map(tag => tag.dataset.word);
-
- try {
- // Special format for updating nested civitai.trainedWords
- await saveModelMetadata(filePath, {
- civitai: { trainedWords: words }
- });
-
- // Update UI
- const editBtn = document.querySelector('.edit-trigger-words-btn');
- editBtn.click(); // Exit edit mode
-
- // Update the LoRA card's dataset
- const loraCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`);
- if (loraCard) {
- try {
- // Create a proper structure for civitai data
- let civitaiData = {};
-
- // Parse existing data if available
- if (loraCard.dataset.meta) {
- civitaiData = JSON.parse(loraCard.dataset.meta);
- }
-
- // Update trainedWords property
- civitaiData.trainedWords = words;
-
- // Update the meta dataset attribute with the full civitai data
- loraCard.dataset.meta = JSON.stringify(civitaiData);
-
- // For debugging, log the updated data to verify it's correct
- console.log("Updated civitai data:", civitaiData);
- } catch (e) {
- console.error('Error updating civitai data:', e);
- }
- }
-
- // If we saved an empty array and there's a no-trigger-words element, show it
- const noTriggerWords = document.querySelector('.no-trigger-words');
- const tagsContainer = document.querySelector('.trigger-words-tags');
- if (words.length === 0 && noTriggerWords) {
- noTriggerWords.style.display = '';
- if (tagsContainer) tagsContainer.style.display = 'none';
- }
-
- showToast('Trigger words updated successfully', 'success');
- } catch (error) {
- console.error('Error saving trigger words:', error);
- showToast('Failed to update trigger words', 'error');
- }
-}
-
-// Add copy trigger word function
-window.copyTriggerWord = async function(word) {
- try {
- await navigator.clipboard.writeText(word);
- showToast('Trigger word copied', 'success');
- } catch (err) {
- console.error('Copy failed:', err);
- showToast('Copy failed', 'error');
- }
-};
-
-// New function to handle model name editing
-function setupModelNameEditing() {
- const modelNameContent = document.querySelector('.model-name-content');
- const editBtn = document.querySelector('.edit-model-name-btn');
-
- if (!modelNameContent || !editBtn) return;
-
- // Show edit button on hover
- const modelNameHeader = document.querySelector('.model-name-header');
- modelNameHeader.addEventListener('mouseenter', () => {
- editBtn.classList.add('visible');
- });
-
- modelNameHeader.addEventListener('mouseleave', () => {
- if (!modelNameContent.getAttribute('data-editing')) {
- editBtn.classList.remove('visible');
- }
- });
-
- // Handle edit button click
- editBtn.addEventListener('click', () => {
- modelNameContent.setAttribute('data-editing', 'true');
- modelNameContent.focus();
-
- // Place cursor at the end
- const range = document.createRange();
- const sel = window.getSelection();
- if (modelNameContent.childNodes.length > 0) {
- range.setStart(modelNameContent.childNodes[0], modelNameContent.textContent.length);
- range.collapse(true);
- sel.removeAllRanges();
- sel.addRange(range);
- }
-
- editBtn.classList.add('visible');
- });
-
- // Handle focus out
- modelNameContent.addEventListener('blur', function() {
- this.removeAttribute('data-editing');
- editBtn.classList.remove('visible');
-
- if (this.textContent.trim() === '') {
- // Restore original model name if empty
- const filePath = document.querySelector('#loraModal .modal-content')
- .querySelector('.file-path').textContent +
- document.querySelector('#loraModal .modal-content')
- .querySelector('#file-name').textContent + '.safetensors';
- const loraCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`);
- if (loraCard) {
- this.textContent = loraCard.dataset.model_name;
- }
- }
- });
-
- // Handle enter key
- modelNameContent.addEventListener('keydown', function(e) {
- if (e.key === 'Enter') {
- e.preventDefault();
- const filePath = document.querySelector('#loraModal .modal-content')
- .querySelector('.file-path').textContent +
- document.querySelector('#loraModal .modal-content')
- .querySelector('#file-name').textContent + '.safetensors';
- saveModelName(filePath);
- this.blur();
- }
- });
-
- // Limit model name length
- modelNameContent.addEventListener('input', function() {
- // Limit model name length
- if (this.textContent.length > 100) {
- this.textContent = this.textContent.substring(0, 100);
- // Place cursor at the end
- const range = document.createRange();
- const sel = window.getSelection();
- range.setStart(this.childNodes[0], 100);
- range.collapse(true);
- sel.removeAllRanges();
- sel.addRange(range);
-
- showToast('Model name is limited to 100 characters', 'warning');
- }
- });
-}
-
-// Add save model base model function
-window.saveBaseModel = async function(filePath, originalValue) {
- const baseModelElement = document.querySelector('.base-model-content');
- const newBaseModel = baseModelElement.textContent.trim();
-
- // Only save if the value has actually changed
- if (newBaseModel === originalValue) {
- return; // No change, no need to save
- }
-
- try {
- await saveModelMetadata(filePath, { base_model: newBaseModel });
-
- // Update the corresponding lora card's dataset
- const loraCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`);
- if (loraCard) {
- loraCard.dataset.base_model = newBaseModel;
- }
-
- showToast('Base model updated successfully', 'success');
- } catch (error) {
- showToast('Failed to update base model', 'error');
- }
-};
-
-// New function to handle base model editing
-function setupBaseModelEditing() {
- const baseModelContent = document.querySelector('.base-model-content');
- const editBtn = document.querySelector('.edit-base-model-btn');
-
- if (!baseModelContent || !editBtn) return;
-
- // Show edit button on hover
- const baseModelDisplay = document.querySelector('.base-model-display');
- baseModelDisplay.addEventListener('mouseenter', () => {
- editBtn.classList.add('visible');
- });
-
- baseModelDisplay.addEventListener('mouseleave', () => {
- if (!baseModelDisplay.classList.contains('editing')) {
- editBtn.classList.remove('visible');
- }
- });
-
- // Handle edit button click
- editBtn.addEventListener('click', () => {
- baseModelDisplay.classList.add('editing');
-
- // Store the original value to check for changes later
- const originalValue = baseModelContent.textContent.trim();
-
- // Create dropdown selector to replace the base model content
- const currentValue = originalValue;
- const dropdown = document.createElement('select');
- dropdown.className = 'base-model-selector';
-
- // Flag to track if a change was made
- let valueChanged = false;
-
- // Add options from BASE_MODELS constants
- const baseModelCategories = {
- 'Stable Diffusion 1.x': [BASE_MODELS.SD_1_4, BASE_MODELS.SD_1_5, BASE_MODELS.SD_1_5_LCM, BASE_MODELS.SD_1_5_HYPER],
- 'Stable Diffusion 2.x': [BASE_MODELS.SD_2_0, BASE_MODELS.SD_2_1],
- 'Stable Diffusion 3.x': [BASE_MODELS.SD_3, BASE_MODELS.SD_3_5, BASE_MODELS.SD_3_5_MEDIUM, BASE_MODELS.SD_3_5_LARGE, BASE_MODELS.SD_3_5_LARGE_TURBO],
- 'SDXL': [BASE_MODELS.SDXL, BASE_MODELS.SDXL_LIGHTNING, BASE_MODELS.SDXL_HYPER],
- 'Video Models': [BASE_MODELS.SVD, BASE_MODELS.WAN_VIDEO, BASE_MODELS.HUNYUAN_VIDEO],
- 'Other Models': [
- BASE_MODELS.FLUX_1_D, BASE_MODELS.FLUX_1_S, BASE_MODELS.AURAFLOW,
- BASE_MODELS.PIXART_A, BASE_MODELS.PIXART_E, BASE_MODELS.HUNYUAN_1,
- BASE_MODELS.LUMINA, BASE_MODELS.KOLORS, BASE_MODELS.NOOBAI,
- BASE_MODELS.ILLUSTRIOUS, BASE_MODELS.PONY, BASE_MODELS.UNKNOWN
- ]
- };
-
- // Create option groups for better organization
- Object.entries(baseModelCategories).forEach(([category, models]) => {
- const group = document.createElement('optgroup');
- group.label = category;
-
- models.forEach(model => {
- const option = document.createElement('option');
- option.value = model;
- option.textContent = model;
- option.selected = model === currentValue;
- group.appendChild(option);
- });
-
- dropdown.appendChild(group);
- });
-
- // Replace content with dropdown
- baseModelContent.style.display = 'none';
- baseModelDisplay.insertBefore(dropdown, editBtn);
-
- // Hide edit button during editing
- editBtn.style.display = 'none';
-
- // Focus the dropdown
- dropdown.focus();
-
- // Handle dropdown change
- dropdown.addEventListener('change', function() {
- const selectedModel = this.value;
- baseModelContent.textContent = selectedModel;
-
- // Mark that a change was made if the value differs from original
- if (selectedModel !== originalValue) {
- valueChanged = true;
- } else {
- valueChanged = false;
- }
- });
-
- // Function to save changes and exit edit mode
- const saveAndExit = function() {
- // Check if dropdown still exists and remove it
- if (dropdown && dropdown.parentNode === baseModelDisplay) {
- baseModelDisplay.removeChild(dropdown);
- }
-
- // Show the content and edit button
- baseModelContent.style.display = '';
- editBtn.style.display = '';
-
- // Remove editing class
- baseModelDisplay.classList.remove('editing');
-
- // Only save if the value has actually changed
- if (valueChanged || baseModelContent.textContent.trim() !== originalValue) {
- // Get file path for saving
- const filePath = document.querySelector('#loraModal .modal-content')
- .querySelector('.file-path').textContent +
- document.querySelector('#loraModal .modal-content')
- .querySelector('#file-name').textContent + '.safetensors';
-
- // Save the changes, passing the original value for comparison
- saveBaseModel(filePath, originalValue);
- }
-
- // Remove this event listener
- document.removeEventListener('click', outsideClickHandler);
- };
-
- // Handle outside clicks to save and exit
- const outsideClickHandler = function(e) {
- // If click is outside the dropdown and base model display
- if (!baseModelDisplay.contains(e.target)) {
- saveAndExit();
- }
- };
-
- // Add delayed event listener for outside clicks
- setTimeout(() => {
- document.addEventListener('click', outsideClickHandler);
- }, 0);
-
- // Also handle dropdown blur event
- dropdown.addEventListener('blur', function(e) {
- // Only save if the related target is not the edit button or inside the baseModelDisplay
- if (!baseModelDisplay.contains(e.relatedTarget)) {
- saveAndExit();
- }
- });
- });
-}
-
-// New function to handle file name editing
-function setupFileNameEditing() {
- const fileNameContent = document.querySelector('.file-name-content');
- const editBtn = document.querySelector('.edit-file-name-btn');
-
- if (!fileNameContent || !editBtn) return;
-
- // Show edit button on hover
- const fileNameWrapper = document.querySelector('.file-name-wrapper');
- fileNameWrapper.addEventListener('mouseenter', () => {
- editBtn.classList.add('visible');
- });
-
- fileNameWrapper.addEventListener('mouseleave', () => {
- if (!fileNameWrapper.classList.contains('editing')) {
- editBtn.classList.remove('visible');
- }
- });
-
- // Handle edit button click
- editBtn.addEventListener('click', () => {
- fileNameWrapper.classList.add('editing');
- fileNameContent.setAttribute('contenteditable', 'true');
- fileNameContent.focus();
-
- // Store original value for comparison later
- fileNameContent.dataset.originalValue = fileNameContent.textContent.trim();
-
- // Place cursor at the end
- const range = document.createRange();
- const sel = window.getSelection();
- range.selectNodeContents(fileNameContent);
- range.collapse(false);
- sel.removeAllRanges();
- sel.addRange(range);
-
- editBtn.classList.add('visible');
- });
-
- // Handle keyboard events in edit mode
- fileNameContent.addEventListener('keydown', function(e) {
- if (!this.getAttribute('contenteditable')) return;
-
- if (e.key === 'Enter') {
- e.preventDefault();
- this.blur(); // Trigger save on Enter
- } else if (e.key === 'Escape') {
- e.preventDefault();
- // Restore original value
- this.textContent = this.dataset.originalValue;
- exitEditMode();
- }
- });
-
- // Handle input validation
- fileNameContent.addEventListener('input', function() {
- if (!this.getAttribute('contenteditable')) return;
-
- // Replace invalid characters for filenames
- const invalidChars = /[\\/:*?"<>|]/g;
- if (invalidChars.test(this.textContent)) {
- const cursorPos = window.getSelection().getRangeAt(0).startOffset;
- this.textContent = this.textContent.replace(invalidChars, '');
-
- // Restore cursor position
- const range = document.createRange();
- const sel = window.getSelection();
- const newPos = Math.min(cursorPos, this.textContent.length);
-
- if (this.firstChild) {
- range.setStart(this.firstChild, newPos);
- range.collapse(true);
- sel.removeAllRanges();
- sel.addRange(range);
- }
-
- showToast('Invalid characters removed from filename', 'warning');
- }
- });
-
- // Handle focus out - save changes
- fileNameContent.addEventListener('blur', async function() {
- if (!this.getAttribute('contenteditable')) return;
-
- const newFileName = this.textContent.trim();
- const originalValue = this.dataset.originalValue;
-
- // Basic validation
- if (!newFileName) {
- // Restore original value if empty
- this.textContent = originalValue;
- showToast('File name cannot be empty', 'error');
- exitEditMode();
- return;
- }
-
- if (newFileName === originalValue) {
- // No changes, just exit edit mode
- exitEditMode();
- return;
- }
-
- try {
- // Get the full file path
- const filePath = document.querySelector('#loraModal .modal-content')
- .querySelector('.file-path').textContent + originalValue + '.safetensors';
-
- // Call API to rename the file
- const response = await fetch('/api/rename_lora', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- file_path: filePath,
- new_file_name: newFileName
- })
- });
-
- const result = await response.json();
-
- if (result.success) {
- showToast('File name updated successfully', 'success');
-
- // Update card in the gallery
- const loraCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`);
- if (loraCard) {
- // Update the card's filepath attribute to the new path
- loraCard.dataset.filepath = result.new_file_path;
- loraCard.dataset.file_name = newFileName;
-
- // Update the filename display in the card
- const cardFileName = loraCard.querySelector('.card-filename');
- if (cardFileName) {
- cardFileName.textContent = newFileName;
- }
- }
-
- // Handle the case where we need to reload the page
- if (result.reload_required) {
- showToast('Reloading page to apply changes...', 'info');
- setTimeout(() => {
- window.location.reload();
- }, 1500);
- }
- } else {
- // Show error and restore original filename
- showToast(result.error || 'Failed to update file name', 'error');
- this.textContent = originalValue;
- }
- } catch (error) {
- console.error('Error saving filename:', error);
- showToast('Failed to update file name', 'error');
- this.textContent = originalValue;
- } finally {
- exitEditMode();
- }
- });
-
- function exitEditMode() {
- fileNameContent.removeAttribute('contenteditable');
- fileNameWrapper.classList.remove('editing');
- editBtn.classList.remove('visible');
- }
-}
diff --git a/static/js/components/loraModal/ModelMetadata.js b/static/js/components/loraModal/ModelMetadata.js
index b5eb72ac..eb0a3d18 100644
--- a/static/js/components/loraModal/ModelMetadata.js
+++ b/static/js/components/loraModal/ModelMetadata.js
@@ -452,12 +452,8 @@ export function setupFileNameEditing(filePath) {
// Get the new file path and update the card
const newFilePath = filePath.replace(originalValue, newFileName);
- updateLoraCard(filePath, {}, newFilePath);
-
- // Reload the page after a short delay to reflect changes
- setTimeout(() => {
- window.location.reload();
- }, 1500);
+ // Pass the new file_name in the updates object for proper card update
+ updateLoraCard(filePath, { file_name: newFileName }, newFilePath);
} else {
throw new Error(result.error || 'Unknown error');
}
diff --git a/static/js/utils/cardUpdater.js b/static/js/utils/cardUpdater.js
index 7f5b69f6..ce099f98 100644
--- a/static/js/utils/cardUpdater.js
+++ b/static/js/utils/cardUpdater.js
@@ -86,6 +86,11 @@ export function updateLoraCard(filePath, updates, newFilePath) {
if (modelNameElement) modelNameElement.textContent = value;
break;
+ case 'file_name':
+ // Update the file_name in the dataset
+ loraCard.dataset.file_name = value;
+ break;
+
case 'base_model':
// Update the base model label in the card header if it exists
const baseModelLabel = loraCard.querySelector('.base-model-label');