/**
* Model metadata editor and cover-selection entry.
*/
import { translate } from './locales.js';
import { isPhysicalRenameProtectedType } from './model_policies.js';
const t = (key, params) => translate(key, params);
export function showEditModal(model) {
const modal = document.createElement('div');
modal.style.position = 'fixed';
modal.style.top = '0';
modal.style.left = '0';
modal.style.width = '100vw';
modal.style.height = '100vh';
modal.style.background = 'rgba(0,0,0,0.6)';
modal.style.backdropFilter = 'blur(4px)';
modal.style.zIndex = '10000';
modal.style.display = 'flex';
modal.style.alignItems = 'center';
modal.style.justifyContent = 'center';
const content = document.createElement('div');
content.style.background = 'var(--amb-bg-card)';
content.style.padding = '24px';
content.style.borderRadius = '12px';
content.style.width = '720px';
content.style.maxWidth = '90%';
content.style.border = '1px solid var(--amb-border-strong)';
content.style.boxShadow = '0 8px 32px rgba(0,0,0,0.5)';
content.style.display = 'flex';
content.style.flexDirection = 'row';
content.style.gap = '24px';
content.style.fontFamily = 'Inter, Roboto, sans-serif';
// --- LEFT COLUMN ---
const leftCol = document.createElement('div');
leftCol.style.width = '240px';
leftCol.style.flexShrink = '0';
leftCol.style.display = 'flex';
leftCol.style.flexDirection = 'column';
leftCol.style.gap = '12px';
const previewContainer = document.createElement('div');
previewContainer.style.width = '100%';
previewContainer.style.height = '320px';
previewContainer.style.background = 'var(--amb-bg-card-hover)';
previewContainer.style.borderRadius = '8px';
previewContainer.style.display = 'flex';
previewContainer.style.alignItems = 'center';
previewContainer.style.justifyContent = 'center';
previewContainer.style.overflow = 'hidden';
previewContainer.style.border = '1px solid var(--amb-border-strong)';
previewContainer.style.position = 'relative';
if (model.preview_url) {
const isVideo = model.preview_url.match(/\.mp4(?:&|$)/i) || model.preview_url.match(/\.webm(?:&|$)/i);
if (isVideo) {
const video = document.createElement('video');
video.src = model.preview_url;
video.controls = false;
video.autoplay = true;
video.loop = true;
video.muted = true;
video.style.width = '100%';
video.style.height = '100%';
video.style.objectFit = 'cover';
const muteBtn = document.createElement('div');
muteBtn.innerHTML = '🔇';
muteBtn.style.position = 'absolute';
muteBtn.style.bottom = '8px';
muteBtn.style.right = '8px';
muteBtn.style.background = 'rgba(0,0,0,0.6)';
muteBtn.style.color = '#fff';
muteBtn.style.padding = '6px';
muteBtn.style.borderRadius = '50%';
muteBtn.style.cursor = 'pointer';
muteBtn.style.fontSize = '14px';
muteBtn.style.zIndex = '10';
muteBtn.title = t('detailToggleSound');
muteBtn.onclick = (e) => {
e.stopPropagation();
video.muted = !video.muted;
muteBtn.innerHTML = video.muted ? '🔇' : '🔊';
};
previewContainer.appendChild(video);
previewContainer.appendChild(muteBtn);
} else {
const img = document.createElement('img');
img.src = model.preview_url;
img.style.width = '100%';
img.style.height = '100%';
img.style.objectFit = 'cover';
previewContainer.appendChild(img);
}
} else {
const noCover = document.createElement('div');
noCover.style.color = '#9aa0a6';
noCover.style.fontSize = '0.9em';
noCover.style.textAlign = 'center';
noCover.textContent = t('detailNoCover');
previewContainer.appendChild(noCover);
}
const coverRow = document.createElement('div');
coverRow.style.display = 'flex';
coverRow.style.flexDirection = 'column';
coverRow.style.gap = '8px';
const galleryBtn = document.createElement('button');
galleryBtn.innerHTML = `${t('detailPickGallery')}`;
galleryBtn.style.padding = '8px';
galleryBtn.style.background = 'var(--amb-bg-card-hover)';
galleryBtn.style.color = '#e5e7eb';
galleryBtn.style.border = '1px solid #5f6368';
galleryBtn.style.borderRadius = '6px';
galleryBtn.style.cursor = 'pointer';
galleryBtn.style.fontWeight = '500';
galleryBtn.style.fontSize = '0.9em';
galleryBtn.onmouseover = () => galleryBtn.style.background = 'var(--amb-bg-card-hover)';
galleryBtn.onmouseout = () => galleryBtn.style.background = 'var(--amb-bg-card-hover)';
galleryBtn.onclick = () => {
document.body.removeChild(modal);
this.showGallerySelectMode(model);
};
const localBtn = document.createElement('button');
localBtn.innerHTML = `${t('detailUploadLocal')}`;
localBtn.style.padding = '8px';
localBtn.style.background = 'var(--amb-bg-card-hover)';
localBtn.style.color = '#e5e7eb';
localBtn.style.border = '1px solid #5f6368';
localBtn.style.borderRadius = '6px';
localBtn.style.cursor = 'pointer';
localBtn.style.fontWeight = '500';
localBtn.style.fontSize = '0.9em';
localBtn.onmouseover = () => localBtn.style.background = 'var(--amb-bg-card-hover)';
localBtn.onmouseout = () => localBtn.style.background = 'var(--amb-bg-card-hover)';
localBtn.onclick = () => {
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'image/*';
fileInput.onchange = async (e) => {
if (e.target.files && e.target.files.length > 0) {
const file = e.target.files[0];
const formData = new FormData();
formData.append('type', this.currentType);
formData.append('path_idx', this.currentPathIdx);
formData.append('subfolder', this.currentSubfolder);
formData.append('filename', model.filename);
formData.append('image', file);
try {
const res = await fetch('/anomalous/upload_custom_cover', { method: 'POST', body: formData });
const data = await res.json();
if (data.status === 'success') {
await this.loadModels();
const updatedModel = this.models.find(m => m.filename === model.filename);
if (this.currentDetailModel && this.currentDetailModel.filename === model.filename) {
if (updatedModel) this.showDetail(updatedModel);
}
document.body.removeChild(modal);
} else {
alert(t('detailUploadError') + data.message);
}
} catch (err) {
alert(t('detailUploadFailed') + err);
}
}
};
fileInput.click();
};
coverRow.appendChild(galleryBtn);
coverRow.appendChild(localBtn);
leftCol.appendChild(previewContainer);
leftCol.appendChild(coverRow);
// --- RIGHT COLUMN ---
const rightCol = document.createElement('div');
rightCol.style.flex = '1';
rightCol.style.display = 'flex';
rightCol.style.flexDirection = 'column';
rightCol.style.gap = '15px';
const title = document.createElement('h2');
title.textContent = t('detailModelInfo');
title.style.margin = '0';
title.style.color = '#e8eaed';
title.style.fontSize = '1.25em';
title.style.fontWeight = '500';
const filenameLabel = document.createElement('div');
const filenamePrefix = document.createElement('span');
filenamePrefix.style.color = '#9aa0a6';
filenamePrefix.textContent = t('detailFile');
filenameLabel.append(filenamePrefix, document.createTextNode(` ${model.filename}`));
filenameLabel.style.color = '#e8eaed';
filenameLabel.style.fontSize = '0.9em';
filenameLabel.style.wordBreak = 'break-all';
const inputStyle = `
width: 100%;
padding: 12px 14px;
background: var(--amb-bg-card-hover);
color: #e8eaed;
border: 1px solid #5f6368;
border-radius: 6px;
box-sizing: border-box;
outline: none;
font-size: 14px;
transition: border 0.2s;
`;
const nameInput = document.createElement('input');
nameInput.placeholder = t('detailCustomNamePlaceholder');
nameInput.value = (model.metadata && model.metadata.custom_name) ? model.metadata.custom_name : '';
nameInput.style.cssText = inputStyle;
nameInput.onfocus = () => nameInput.style.borderColor = 'rgba(255, 255, 255, 0.4)';
nameInput.onblur = () => nameInput.style.borderColor = '#5f6368';
const notesInput = document.createElement('textarea');
notesInput.placeholder = t('detailNotesPlaceholder');
notesInput.value = (model.metadata && model.metadata.custom_notes) ? model.metadata.custom_notes : '';
notesInput.style.cssText = inputStyle;
notesInput.style.flex = '1'; // fill remaining space
notesInput.style.minHeight = '150px';
notesInput.style.resize = 'vertical';
// Notebook styling override
notesInput.style.background = 'linear-gradient(135deg, var(--amb-bg-card-hover) 0%, var(--amb-bg-card) 100%)';
notesInput.style.backgroundImage = 'repeating-linear-gradient(transparent, transparent 23px, rgba(163, 141, 83, 0.04) 23px, rgba(163, 141, 83, 0.04) 24px)';
notesInput.style.backgroundAttachment = 'local';
notesInput.style.border = '1px solid var(--amb-border-strong)';
notesInput.style.borderLeft = '4px solid #a38d53';
notesInput.style.borderRadius = '4px 8px 8px 4px';
notesInput.style.color = '#d1c9b4';
notesInput.style.fontFamily = '"Consolas", "Courier New", monospace';
notesInput.style.lineHeight = '24px';
// Removed text shadow for cleaner look
notesInput.onfocus = () => {
notesInput.style.boxShadow = '0 0 0 2px rgba(163, 141, 83, 0.2)';
notesInput.style.borderColor = '#a38d53';
};
notesInput.onblur = () => {
notesInput.style.boxShadow = 'none';
notesInput.style.borderColor = 'var(--amb-border-strong)';
};
const physicalRow = document.createElement('div');
physicalRow.style.display = 'flex';
physicalRow.style.flexDirection = 'column';
physicalRow.style.gap = '4px';
const physicalCheckboxWrapper = document.createElement('div');
physicalCheckboxWrapper.style.display = 'flex';
physicalCheckboxWrapper.style.alignItems = 'center';
physicalCheckboxWrapper.style.gap = '8px';
const physicalCheckbox = document.createElement('input');
physicalCheckbox.type = 'checkbox';
physicalCheckbox.id = 'anomalous-physical-rename-checkbox';
physicalCheckbox.style.cursor = 'pointer';
const physicalRenameProtected = isPhysicalRenameProtectedType(this.currentType);
physicalCheckbox.disabled = physicalRenameProtected;
physicalCheckbox.style.cursor = physicalRenameProtected ? 'not-allowed' : 'pointer';
const physicalLabel = document.createElement('label');
physicalLabel.htmlFor = 'anomalous-physical-rename-checkbox';
physicalLabel.textContent = t('detailPhysicalRename');
physicalLabel.style.color = '#e8eaed';
physicalLabel.style.fontSize = '0.9em';
physicalLabel.style.cursor = physicalRenameProtected ? 'not-allowed' : 'pointer';
physicalLabel.style.opacity = physicalRenameProtected ? '0.6' : '1';
physicalCheckboxWrapper.appendChild(physicalCheckbox);
physicalCheckboxWrapper.appendChild(physicalLabel);
const physicalDesc = document.createElement('div');
physicalDesc.style.fontSize = '0.8em';
physicalDesc.style.color = '#9aa0a6';
physicalDesc.style.marginLeft = '22px';
physicalDesc.textContent = t(physicalRenameProtected
? 'detailPhysicalRenameProtectedDesc'
: 'detailPhysicalRenameDesc');
physicalRow.appendChild(physicalCheckboxWrapper);
physicalRow.appendChild(physicalDesc);
const actionRow = document.createElement('div');
actionRow.style.display = 'flex';
actionRow.style.justifyContent = 'space-between';
actionRow.style.marginTop = 'auto';
const leftActions = document.createElement('div');
leftActions.style.display = 'flex';
leftActions.style.gap = '10px';
const resetBtn = document.createElement('button');
resetBtn.textContent = t('detailResetAll');
resetBtn.style.padding = '8px 16px';
resetBtn.style.background = 'transparent';
resetBtn.style.color = '#f28b82';
resetBtn.style.border = '1px solid #f28b82';
resetBtn.style.borderRadius = '4px';
resetBtn.style.cursor = 'pointer';
resetBtn.style.fontWeight = '500';
resetBtn.onclick = async () => {
if (!confirm(t('detailResetConfirm'))) return;
document.body.removeChild(modal);
try {
const res = await fetch('/anomalous/update_metadata', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: this.currentType,
path_idx: this.currentPathIdx,
subfolder: this.currentSubfolder,
filename: model.filename,
custom_name: '',
custom_notes: '',
reset_cover: true,
physical_rename: false
})
});
const result = await res.json();
if (res.ok && result.status === 'success') {
await this.loadModels();
const updatedModel = this.models.find(m => m.filename === model.filename);
if (this.currentDetailModel && this.currentDetailModel.filename === model.filename) {
if (updatedModel) {
this.showDetail(updatedModel);
} else {
this.grid.style.display = 'grid';
this.detailPanel.style.display = 'none';
}
}
if (result.cover_reset === false) {
const noSource = result.cover_reset_source === 'preserved_current';
alert(t(noSource ? 'detailResetCoverPreserved' : 'detailResetCoverFailed'));
}
} else {
alert(result.message || t('detailResetFailed'));
}
} catch (e) { console.error(e); }
};
const cancelBtn = document.createElement('button');
cancelBtn.textContent = t('detailCancel');
cancelBtn.style.padding = '8px 16px';
cancelBtn.style.background = 'transparent';
cancelBtn.style.color = '#9ca3af';
cancelBtn.style.border = 'none';
cancelBtn.style.borderRadius = '4px';
cancelBtn.style.cursor = 'pointer';
cancelBtn.style.fontWeight = '500';
cancelBtn.onclick = () => document.body.removeChild(modal);
leftActions.appendChild(resetBtn);
leftActions.appendChild(cancelBtn);
const saveBtn = document.createElement('button');
saveBtn.textContent = t('detailSaveChanges');
saveBtn.style.padding = '8px 24px';
saveBtn.style.background = '#e5e7eb';
saveBtn.style.color = '#111827';
saveBtn.style.border = 'none';
saveBtn.style.borderRadius = '4px';
saveBtn.style.cursor = 'pointer';
saveBtn.style.fontWeight = '600';
saveBtn.onclick = () => {
const newName = nameInput.value.trim();
const newNotes = notesInput.value.trim();
saveBtn.textContent = t('detailSaving');
saveBtn.disabled = true;
fetch('/anomalous/update_metadata', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: this.currentType,
path_idx: this.currentPathIdx,
subfolder: this.currentSubfolder,
filename: model.filename,
custom_name: newName,
custom_notes: newNotes,
physical_rename: physicalCheckbox.checked
})
}).then(res => res.json()).then(data => {
document.body.removeChild(modal);
if (data.status === 'success') {
if (!model.metadata) model.metadata = {};
model.metadata.custom_name = newName;
model.metadata.custom_notes = newNotes;
if (data.new_filename && physicalCheckbox.checked) {
model.filename = data.new_filename;
}
this.loadModels();
if (this.currentDetailModel && (this.currentDetailModel.filename === model.filename || (data.new_filename && this.currentDetailModel.filename === data.new_filename))) {
this.showDetail(model);
}
} else {
alert(t('detailUploadError') + data.message);
}
}).catch(e => {
document.body.removeChild(modal);
alert(t('detailUploadError') + e);
});
};
actionRow.appendChild(leftActions);
actionRow.appendChild(saveBtn);
rightCol.appendChild(title);
rightCol.appendChild(filenameLabel);
rightCol.appendChild(nameInput);
rightCol.appendChild(notesInput);
rightCol.appendChild(physicalRow);
rightCol.appendChild(actionRow);
content.appendChild(leftCol);
content.appendChild(rightCol);
modal.appendChild(content);
document.body.appendChild(modal);
}