| |
| |
| |
| |
|
|
| import { app } from "../../../scripts/app.js"; |
| import { escapeHtml } from './safe_dom.js'; |
|
|
|
|
|
|
| export async function loadGalleryImages(page = 1, reset = false) { |
| if (this.galleryLoading) return; |
| this.galleryLoading = true; |
| this.gallerySentinel.innerHTML = window.anomalous_browser_lang === 'zh' ? '加载中...' : 'Loading...'; |
|
|
| try { |
| const res = await fetch(`/anomalous/gallery_images?page=${page}&limit=50`); |
| const data = await res.json(); |
|
|
| if (reset) { |
| |
| const cards = this.galleryGrid.querySelectorAll('.anomalous-gallery-card'); |
| cards.forEach(c => c.remove()); |
| this.galleryLoaded = true; |
| } |
|
|
| if (data.images && data.images.length > 0) { |
| data.images.forEach(imgData => { |
| const card = document.createElement('div'); |
| card.className = 'anomalous-gallery-card'; |
|
|
| const q_sub = encodeURIComponent(imgData.subfolder); |
| const q_file = encodeURIComponent(imgData.filename); |
| const imgUrl = `/view?filename=${q_file}&subfolder=${q_sub}&type=output`; |
|
|
| const img = document.createElement('img'); |
| img.src = imgUrl; |
| img.loading = 'lazy'; |
| img.draggable = true; |
|
|
| |
| img.addEventListener('dragstart', (e) => { |
| const fullUrl = new URL(imgUrl, window.location.href).href; |
| e.dataTransfer.setData('text/uri-list', fullUrl); |
| e.dataTransfer.setData('text/plain', fullUrl); |
|
|
| |
| if (window.anomalousDragGhostImg) { |
| e.dataTransfer.setDragImage(window.anomalousDragGhostImg, 40, 40); |
| } |
| }); |
|
|
| |
| img.onclick = () => { |
| if (this.gallerySelectModel) { |
| const model = this.gallerySelectModel; |
| fetch('/anomalous/set_custom_cover', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ |
| type: this.currentType, |
| path_idx: this.currentPathIdx, |
| subfolder: this.currentSubfolder, |
| filename: model.filename, |
| source_image: imgData.subfolder ? imgData.subfolder + '/' + imgData.filename : imgData.filename |
| }) |
| }).then(res => res.json()).then(async data => { |
| if (data.status === 'success') { |
| const tempModel = this.gallerySelectModel; |
| this.gallerySelectModel = null; |
| const banner = document.getElementById('anomalous-gallery-select-banner'); |
| if (banner) banner.style.display = 'none'; |
| this.galleryPanel.style.display = 'none'; |
|
|
| await this.loadModels(); |
| const updatedModel = this.models.find(m => m.filename === model.filename); |
|
|
| if (this.currentDetailModel && this.currentDetailModel.filename === model.filename) { |
| this.detailPanel.style.display = 'flex'; |
| if (updatedModel) this.showDetail(updatedModel); |
| } else { |
| this.grid.style.display = 'grid'; |
| |
| } |
|
|
|
|
| } else { |
| alert((window.anomalous_browser_lang === 'zh' ? '错误: ' : 'Error: ') + data.message); |
| } |
| }); |
| return; |
| } |
| this.showGalleryViewer(imgUrl); |
| }; |
|
|
| const delBtn = document.createElement('button'); |
| delBtn.className = 'anomalous-gallery-delete'; |
| delBtn.innerHTML = '🗑️'; |
| delBtn.title = window.anomalous_browser_lang === 'zh' ? '删除' : 'Delete'; |
|
|
| delBtn.onclick = (e) => { |
| e.stopPropagation(); |
|
|
| const overlay = document.createElement('div'); |
| overlay.style.position = 'absolute'; |
| overlay.style.top = '0'; |
| overlay.style.left = '0'; |
| overlay.style.width = '100%'; |
| overlay.style.height = '100%'; |
| overlay.style.background = 'rgba(0,0,0,0.85)'; |
| overlay.style.display = 'flex'; |
| overlay.style.flexDirection = 'column'; |
| overlay.style.alignItems = 'center'; |
| overlay.style.justifyContent = 'center'; |
| overlay.style.gap = '15px'; |
| overlay.style.zIndex = '10'; |
|
|
| const msg = document.createElement('div'); |
| msg.innerHTML = '彻底删除这张图片?<br><span style="font-size:0.8em;color:#aaa">Delete permanently?</span>'; |
| msg.style.color = '#fff'; |
| msg.style.fontWeight = 'bold'; |
| msg.style.textAlign = 'center'; |
|
|
| const btnRow = document.createElement('div'); |
| btnRow.style.display = 'flex'; |
| btnRow.style.gap = '12px'; |
|
|
| const confirmBtn = document.createElement('button'); |
| confirmBtn.innerHTML = window.anomalous_browser_lang === 'zh' ? '🗑️ 删除' : '🗑️ Delete'; |
| confirmBtn.style.background = '#dc3545'; |
| confirmBtn.style.color = '#fff'; |
| confirmBtn.style.border = 'none'; |
| confirmBtn.style.padding = '10px 16px'; |
| confirmBtn.style.borderRadius = '6px'; |
| confirmBtn.style.cursor = 'pointer'; |
| confirmBtn.style.fontWeight = 'bold'; |
| confirmBtn.style.transition = 'background 0.2s'; |
| confirmBtn.onmouseover = () => confirmBtn.style.background = '#ff0000'; |
| confirmBtn.onmouseout = () => confirmBtn.style.background = '#dc3545'; |
|
|
| const cancelBtn = document.createElement('button'); |
| cancelBtn.innerHTML = window.anomalous_browser_lang === 'zh' ? '取消' : 'Cancel'; |
| cancelBtn.style.background = '#444'; |
| cancelBtn.style.color = '#fff'; |
| cancelBtn.style.border = 'none'; |
| cancelBtn.style.padding = '10px 16px'; |
| cancelBtn.style.borderRadius = '6px'; |
| cancelBtn.style.cursor = 'pointer'; |
| cancelBtn.style.transition = 'background 0.2s'; |
| cancelBtn.onmouseover = () => cancelBtn.style.background = '#666'; |
| cancelBtn.onmouseout = () => cancelBtn.style.background = '#444'; |
|
|
| cancelBtn.onclick = (ce) => { |
| ce.stopPropagation(); |
| overlay.remove(); |
| }; |
|
|
| confirmBtn.onclick = async (ce) => { |
| ce.stopPropagation(); |
| confirmBtn.innerHTML = window.anomalous_browser_lang === 'zh' ? '删除中...' : 'Deleting...'; |
| confirmBtn.disabled = true; |
| try { |
| const dr = await fetch('/anomalous/delete_gallery_image', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ filename: imgData.filename, subfolder: imgData.subfolder }) |
| }); |
| const dd = await dr.json(); |
| if (dd.status === 'success') { |
| card.remove(); |
| } else { |
| alert((window.anomalous_browser_lang === 'zh' ? '删除失败: ' : 'Delete failed: ') + dd.message); |
| overlay.remove(); |
| } |
| } catch (err) { |
| alert((window.anomalous_browser_lang === 'zh' ? '错误: ' : 'Error: ') + err); |
| overlay.remove(); |
| } |
| }; |
|
|
| btnRow.appendChild(cancelBtn); |
| btnRow.appendChild(confirmBtn); |
| overlay.appendChild(msg); |
| overlay.appendChild(btnRow); |
|
|
| card.appendChild(overlay); |
| }; |
|
|
| card.appendChild(img); |
| card.appendChild(delBtn); |
| this.galleryGrid.insertBefore(card, this.gallerySentinel); |
| }); |
|
|
| this.galleryCurrentPage = page; |
| this.galleryHasMore = page < data.pages; |
|
|
| if (!this.galleryHasMore) { |
| this.gallerySentinel.innerHTML = window.anomalous_browser_lang === 'zh' ? '没有更多图片了' : 'No more images'; |
| } else { |
| this.gallerySentinel.innerHTML = window.anomalous_browser_lang === 'zh' ? '向下滚动加载更多' : 'Scroll for more'; |
| } |
| } else { |
| this.galleryHasMore = false; |
| this.gallerySentinel.innerHTML = reset ? '图库为空 / Gallery is empty' : '没有更多图片了 / No more images'; |
| } |
| } catch (e) { |
| console.error('Failed to load gallery images', e); |
| this.gallerySentinel.innerHTML = window.anomalous_browser_lang === 'zh' ? '加载失败' : 'Load failed'; |
| } |
|
|
| this.galleryLoading = false; |
| } |
|
|
|
|
|
|
|
|
| export async function showGeneratedGallery(model) { |
| let overlay = document.getElementById('anomalous-generated-gallery-overlay'); |
| if (!overlay) { |
| overlay = document.createElement('div'); |
| overlay.id = 'anomalous-generated-gallery-overlay'; |
| overlay.style.position = 'absolute'; |
| overlay.style.top = '0'; |
| overlay.style.left = '0'; |
| overlay.style.width = '100%'; |
| overlay.style.height = '100%'; |
| overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.85)'; |
| overlay.style.zIndex = '999999'; |
| overlay.style.display = 'flex'; |
| overlay.style.alignItems = 'center'; |
| overlay.style.justifyContent = 'center'; |
|
|
| const modalBox = document.createElement('div'); |
| modalBox.id = 'anomalous-generated-gallery-modal'; |
| modalBox.style.width = '95%'; |
| modalBox.style.maxHeight = '95%'; |
| modalBox.style.backgroundColor = 'var(--comfy-menu-bg, #222)'; |
| modalBox.style.borderRadius = '12px'; |
| modalBox.style.display = 'flex'; |
| modalBox.style.flexDirection = 'column'; |
| modalBox.style.overflow = 'hidden'; |
| modalBox.style.boxShadow = '0 10px 40px rgba(0, 0, 0, 0.8)'; |
|
|
| const header = document.createElement('div'); |
| header.style.padding = '15px 25px'; |
| header.style.background = '#333'; |
| header.style.display = 'flex'; |
| header.style.justifyContent = 'space-between'; |
| header.style.alignItems = 'center'; |
| header.style.borderBottom = '1px solid #444'; |
|
|
| const title = document.createElement('h2'); |
| title.id = 'anomalous-generated-gallery-title'; |
| title.style.margin = '0'; |
| title.style.color = '#fff'; |
|
|
| const closeBtn = document.createElement('button'); |
| closeBtn.innerHTML = '✖ ' + (window.anomalous_browser_lang === 'zh' ? '关闭图库' : 'Close Gallery'); |
| closeBtn.style.padding = '8px 15px'; |
| closeBtn.style.background = '#dc3545'; |
| closeBtn.style.color = '#fff'; |
| closeBtn.style.border = 'none'; |
| closeBtn.style.borderRadius = '5px'; |
| closeBtn.style.cursor = 'pointer'; |
| closeBtn.style.fontWeight = 'bold'; |
| closeBtn.onmouseover = () => closeBtn.style.background = '#c82333'; |
| closeBtn.onmouseout = () => closeBtn.style.background = '#dc3545'; |
| closeBtn.onclick = () => { |
| overlay.style.display = 'none'; |
| }; |
|
|
| header.appendChild(title); |
| header.appendChild(closeBtn); |
| modalBox.appendChild(header); |
|
|
| const contentCont = document.createElement('div'); |
| contentCont.id = 'anomalous-generated-gallery-content'; |
| contentCont.style.flex = '1'; |
| contentCont.style.overflowY = 'auto'; |
| contentCont.style.padding = '20px'; |
| contentCont.style.display = 'grid'; |
| contentCont.style.gridTemplateColumns = 'repeat(auto-fill, minmax(220px, 1fr))'; |
| contentCont.style.gap = '25px'; |
| contentCont.style.rowGap = '40px'; |
| contentCont.style.alignContent = 'start'; |
| modalBox.appendChild(contentCont); |
|
|
| overlay.appendChild(modalBox); |
| document.getElementById('anomalous-container').appendChild(overlay); |
| } |
|
|
| const title = document.getElementById('anomalous-generated-gallery-title'); |
| title.innerText = (window.anomalous_browser_lang === 'zh' ? '历史生成图库: ' : 'Generated History: ') + (model.name || model.filename); |
|
|
| const contentCont = document.getElementById('anomalous-generated-gallery-content'); |
| contentCont.innerHTML = ''; |
|
|
| const loading = document.createElement('div'); |
| loading.innerText = window.anomalous_browser_lang === 'zh' ? '加载中,正在扫描图片元数据...' : 'Loading, scanning metadata...'; |
| loading.style.textAlign = 'center'; |
| loading.style.gridColumn = '1 / -1'; |
| loading.style.padding = '50px'; |
| loading.style.color = '#aaa'; |
| contentCont.appendChild(loading); |
|
|
| overlay.style.display = 'flex'; |
|
|
| try { |
| const res = await fetch('/anomalous/model_images?model_name=' + encodeURIComponent(model.filename) + '&t=' + Date.now()); |
| const data = await res.json(); |
| contentCont.innerHTML = ''; |
|
|
| if (!data.images || data.images.length === 0) { |
| const emptyMsg = document.createElement('div'); |
| emptyMsg.innerText = window.anomalous_browser_lang === 'zh' ? '没有找到使用此模型生成的历史图片。' : 'No images found generated by this model.'; |
| emptyMsg.style.textAlign = 'center'; |
| emptyMsg.style.gridColumn = '1 / -1'; |
| emptyMsg.style.padding = '50px'; |
| emptyMsg.style.color = '#888'; |
| contentCont.appendChild(emptyMsg); |
| return; |
| } |
|
|
| data.images.forEach(img => { |
| const imgCont = document.createElement('div'); |
| imgCont.className = 'anomalous-card'; |
| imgCont.style.cursor = 'pointer'; |
|
|
| const el = document.createElement('img'); |
| el.src = img.url || img; |
| el.loading = 'lazy'; |
| el.draggable = true; |
|
|
| el.addEventListener('dragstart', (e) => { |
| const fullUrl = new URL(el.src, window.location.href).href; |
| e.dataTransfer.setData('text/uri-list', fullUrl); |
| e.dataTransfer.setData('text/plain', fullUrl); |
| if (window.anomalousDragGhostImg) { |
| e.dataTransfer.setDragImage(window.anomalousDragGhostImg, 40, 40); |
| } |
| }); |
|
|
| let source_image = ""; |
| let filenameText = ""; |
| if (img.url) { |
| try { |
| const urlParams = new URLSearchParams(img.url.split('?')[1]); |
| filenameText = urlParams.get('filename') || ''; |
| const sub = urlParams.get('subfolder') || ''; |
| source_image = sub ? sub + '/' + filenameText : filenameText; |
| } catch (e) { } |
| } else { |
| filenameText = img.split('/').pop().split('?')[0]; |
| source_image = filenameText; |
| } |
|
|
| const titleDiv = document.createElement('div'); |
| titleDiv.className = 'anomalous-card-title'; |
| titleDiv.innerText = filenameText; |
|
|
| const setCoverBtn = document.createElement('button'); |
| setCoverBtn.innerText = window.anomalous_browser_lang === 'zh' ? '设为封面' : 'Set Cover'; |
| setCoverBtn.style.position = 'absolute'; |
| setCoverBtn.style.bottom = '40px'; |
| setCoverBtn.style.right = '5px'; |
| setCoverBtn.style.background = 'rgba(40, 167, 69, 0.85)'; |
| setCoverBtn.style.color = '#fff'; |
| setCoverBtn.style.border = '1px solid rgba(255,255,255,0.3)'; |
| setCoverBtn.style.borderRadius = '4px'; |
| setCoverBtn.style.padding = '4px 8px'; |
| setCoverBtn.style.cursor = 'pointer'; |
| setCoverBtn.style.zIndex = '10'; |
| setCoverBtn.style.fontSize = '12px'; |
|
|
| setCoverBtn.onclick = (e) => { |
| e.stopPropagation(); |
| if (!confirm(window.anomalous_browser_lang === 'zh' ? '确定将此图片设为封面吗?' : 'Set this image as cover?')) return; |
|
|
| fetch('/anomalous/set_custom_cover', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ |
| type: this.currentType, |
| path_idx: this.currentPathIdx, |
| subfolder: this.currentSubfolder, |
| filename: model.filename, |
| source_image: source_image |
| }) |
| }).then(res => res.json()).then(async result => { |
| if (result.status === 'success') { |
| alert(window.anomalous_browser_lang === 'zh' ? '设置成功!' : 'Cover set successfully!'); |
| await this.loadModels(); |
| const updatedModel = this.models.find(m => m.filename === model.filename); |
| if (updatedModel && this.currentDetailModel && this.currentDetailModel.filename === model.filename) { |
| this.showDetail(updatedModel); |
| } |
| } else { |
| alert((window.anomalous_browser_lang === 'zh' ? '错误: ' : 'Error: ') + result.message); |
| } |
| }).catch(err => { |
| alert('Error: ' + err.message); |
| }); |
| }; |
|
|
| imgCont.onclick = () => { |
| this.showGalleryViewer(img.url || img); |
| }; |
|
|
| imgCont.appendChild(el); |
| imgCont.appendChild(setCoverBtn); |
| imgCont.appendChild(titleDiv); |
| contentCont.appendChild(imgCont); |
| }); |
| } catch (e) { |
| contentCont.innerHTML = '<div style="color:red; text-align:center; grid-column: 1/-1; padding: 50px;">Error loading images</div>'; |
| } |
| } |
|
|
|
|
|
|
| export function showGallerySelectMode(model) { |
| this.gallerySelectModel = model; |
| this.grid.style.display = 'none'; |
| this.detailPanel.style.display = 'none'; |
| this.galleryPanel.style.display = 'flex'; |
| let banner = document.getElementById('anomalous-gallery-select-banner'); |
| if (!banner) { |
| banner = document.createElement('div'); |
| banner.id = 'anomalous-gallery-select-banner'; |
| banner.style.background = '#28a745'; |
| banner.style.color = '#fff'; |
| banner.style.padding = '10px'; |
| banner.style.textAlign = 'center'; |
| banner.style.fontWeight = 'bold'; |
| banner.style.position = 'sticky'; |
| banner.style.top = '0'; |
| banner.style.zIndex = '1000'; |
| this.galleryPanel.insertBefore(banner, this.galleryPanel.firstChild); |
| } |
| banner.style.display = 'block'; |
| banner.innerHTML = window.anomalous_browser_lang === 'zh' |
| ? `正在为模型 <span style="color:#ff0;">${escapeHtml(model.filename)}</span> 选择封面。请点击下方的图片。<button id="anomalous-cancel-select" style="margin-left:15px;color:#000;background:#fff;border:none;padding:2px 8px;border-radius:4px;cursor:pointer;">取消</button>` |
| : `Selecting cover for <span style="color:#ff0;">${escapeHtml(model.filename)}</span>. Click an image below.<button id="anomalous-cancel-select" style="margin-left:15px;color:#000;background:#fff;border:none;padding:2px 8px;border-radius:4px;cursor:pointer;">Cancel</button>`; |
|
|
| document.getElementById('anomalous-cancel-select').onclick = () => { |
| const tempModel = this.gallerySelectModel; |
| this.gallerySelectModel = null; |
| banner.style.display = 'none'; |
| this.galleryPanel.style.display = 'none'; |
| if (this.currentDetailModel) { |
| this.detailPanel.style.display = 'flex'; |
| } else { |
| this.grid.style.display = 'grid'; |
| } |
| if (tempModel) { |
| this.showEditModal(tempModel); |
| } |
| }; |
|
|
| if (this.galleryImages.length === 0) { |
| this.loadGalleryImages(1, true); |
| } |
| } |
|
|
|
|
|
|
| export function showGalleryViewer(src) { |
| let viewer = document.getElementById('anomalous-gallery-viewer'); |
| if (!viewer) { |
| viewer = document.createElement('div'); |
| viewer.id = 'anomalous-gallery-viewer'; |
| viewer.className = 'anomalous-gallery-viewer'; |
|
|
| const closeBtn = document.createElement('div'); |
| closeBtn.className = 'anomalous-gallery-viewer-close'; |
| closeBtn.innerHTML = '×'; |
|
|
| const img = document.createElement('img'); |
| img.id = 'anomalous-gallery-viewer-img'; |
|
|
| viewer.appendChild(img); |
| viewer.appendChild(closeBtn); |
|
|
| let scale = 1; |
| let translateX = 0; |
| let translateY = 0; |
| let isDragging = false; |
| let startX = 0, startY = 0; |
|
|
| const resetImgTransform = () => { |
| scale = 1; translateX = 0; translateY = 0; |
| img.style.transform = `translate(0px, 0px) scale(1)`; |
| img.style.cursor = 'grab'; |
| }; |
|
|
| closeBtn.onclick = () => { |
| viewer.style.display = 'none'; |
| resetImgTransform(); |
| }; |
|
|
| viewer.onclick = (e) => { |
| if (e.target === viewer) { |
| viewer.style.display = 'none'; |
| resetImgTransform(); |
| } |
| }; |
|
|
| viewer.addEventListener('wheel', (e) => { |
| e.preventDefault(); |
| const zoomFactor = 0.1; |
| if (e.deltaY < 0) scale += zoomFactor; |
| else scale = Math.max(0.1, scale - zoomFactor); |
| img.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`; |
| }); |
|
|
| img.addEventListener('mousedown', (e) => { |
| e.preventDefault(); |
| isDragging = true; |
| startX = e.clientX - translateX; |
| startY = e.clientY - translateY; |
| img.style.cursor = 'grabbing'; |
| }); |
|
|
| window.addEventListener('mousemove', (e) => { |
| if (!isDragging) return; |
| translateX = e.clientX - startX; |
| translateY = e.clientY - startY; |
| img.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`; |
| }); |
|
|
| window.addEventListener('mouseup', () => { |
| isDragging = false; |
| img.style.cursor = 'grab'; |
| }); |
|
|
| document.body.appendChild(viewer); |
| } |
|
|
| const img = document.getElementById('anomalous-gallery-viewer-img'); |
| img.src = src; |
|
|
| |
| img.style.transform = `translate(0px, 0px) scale(1)`; |
| img.style.cursor = 'grab'; |
|
|
| viewer.style.display = 'flex'; |
| } |
|
|