import { showToast } from '../../utils/uiHelpers.js'; import { safeTranslate } from '../../utils/i18nHelpers.js'; /** * ModelDescription.js * Handles model description related functionality - General version */ /** * Set up tab switching functionality */ export function setupTabSwitching() { const tabButtons = document.querySelectorAll('.showcase-tabs .tab-btn'); tabButtons.forEach(button => { button.addEventListener('click', async () => { // 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, load content lazily if (button.dataset.tab === 'description') { await loadModelDescription(); } }); }); } /** * Load model description lazily */ async function loadModelDescription() { const descriptionContent = document.querySelector('.model-description-content'); const descriptionLoading = document.querySelector('.model-description-loading'); const showcaseSection = document.querySelector('.showcase-section'); if (!descriptionContent || !showcaseSection) return; // Check if already loaded if (descriptionContent.dataset.loaded === 'true') { return; } const filePath = showcaseSection.dataset.filepath; if (!filePath) return; try { // Show loading state descriptionLoading?.classList.remove('hidden'); descriptionContent.classList.add('hidden'); // Fetch description from API const { getModelApiClient } = await import('../../api/modelApiFactory.js'); const description = await getModelApiClient().fetchModelDescription(filePath); // Update content const noDescriptionText = safeTranslate('modals.model.description.noDescription', {}, 'No model description available'); descriptionContent.innerHTML = description || `
${noDescriptionText}
`; descriptionContent.dataset.loaded = 'true'; // Set up editing functionality await setupModelDescriptionEditing(filePath); } catch (error) { console.error('Error loading model description:', error); const failedText = safeTranslate('modals.model.description.failedToLoad', {}, 'Failed to load model description'); descriptionContent.innerHTML = `
${failedText}
`; } finally { // Hide loading state descriptionLoading?.classList.add('hidden'); descriptionContent.classList.remove('hidden'); } } /** * Set up model description editing functionality * @param {string} filePath - File path */ export async function setupModelDescriptionEditing(filePath) { const descContent = document.querySelector('.model-description-content'); const descContainer = document.querySelector('.model-description-container'); if (!descContent || !descContainer) return; // Add edit button if not present let editBtn = descContainer.querySelector('.edit-model-description-btn'); if (!editBtn) { editBtn = document.createElement('button'); editBtn.className = 'edit-model-description-btn'; // Set title using i18n const editTitle = safeTranslate('modals.model.description.editTitle', {}, 'Edit model description'); editBtn.title = editTitle; editBtn.innerHTML = ''; descContainer.insertBefore(editBtn, descContent); } // Show edit button on hover descContainer.addEventListener('mouseenter', () => { editBtn.classList.add('visible'); }); descContainer.addEventListener('mouseleave', () => { if (!descContainer.classList.contains('editing')) { editBtn.classList.remove('visible'); } }); // Handle edit button click editBtn.addEventListener('click', () => { descContainer.classList.add('editing'); descContent.setAttribute('contenteditable', 'true'); descContent.dataset.originalValue = descContent.innerHTML.trim(); descContent.focus(); // Place cursor at the end const range = document.createRange(); const sel = window.getSelection(); range.selectNodeContents(descContent); range.collapse(false); sel.removeAllRanges(); sel.addRange(range); editBtn.classList.add('visible'); }); // Keyboard events descContent.addEventListener('keydown', function(e) { if (!this.getAttribute('contenteditable')) return; if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); this.blur(); } else if (e.key === 'Escape') { e.preventDefault(); this.innerHTML = this.dataset.originalValue; exitEditMode(); } }); // Save on blur descContent.addEventListener('blur', async function() { if (!this.getAttribute('contenteditable')) return; const newValue = this.innerHTML.trim(); const originalValue = this.dataset.originalValue; if (newValue === originalValue) { exitEditMode(); return; } if (!newValue) { this.innerHTML = originalValue; const emptyErrorText = safeTranslate('modals.model.description.validation.cannotBeEmpty', {}, 'Description cannot be empty'); showToast(emptyErrorText, 'error'); exitEditMode(); return; } try { // Save to backend const { getModelApiClient } = await import('../../api/modelApiFactory.js'); await getModelApiClient().saveModelMetadata(filePath, { modelDescription: newValue }); const successText = safeTranslate('modals.model.description.messages.updated', {}, 'Model description updated'); showToast(successText, 'success'); } catch (err) { this.innerHTML = originalValue; const errorText = safeTranslate('modals.model.description.messages.updateFailed', {}, 'Failed to update model description'); showToast(errorText, 'error'); } finally { exitEditMode(); } }); function exitEditMode() { descContent.removeAttribute('contenteditable'); descContainer.classList.remove('editing'); editBtn.classList.remove('visible'); } }