import { app } from "../../../scripts/app.js"; import { i18n } from './locales.js'; import { escapeHtml } from './safe_dom.js'; const t = (key) => { let lang = window.anomalous_browser_lang || 'zh'; if (lang.startsWith('en')) lang = 'en'; return (i18n[lang] && i18n[lang][key]) ? i18n[lang][key] : (i18n['zh'][key] || key); }; export function showParameters() { if (this.paramInitialized) { if (this.nbPanel) this.nbPanel.style.display = 'none'; this.paramPanel.style.display = 'flex'; this.refreshParameters(true); return; } this.paramInitialized = true; this.paramPanel = document.createElement('div'); this.paramPanel.className = 'anomalous-nb-modal'; this.paramPanel.style.display = 'flex'; this.paramPanel.onclick = (e) => { if (e.target === this.paramPanel) { this.paramPanel.style.display = 'none'; this.closeWorkspace(); } }; this.modal.querySelector('.anomalous-container').appendChild(this.paramPanel); const container = document.createElement('div'); container.className = 'anomalous-nb-container'; const header = document.createElement('div'); header.className = 'anomalous-nb-header'; const headerMain = document.createElement('div'); headerMain.className = 'anomalous-nb-header-main'; const heading = document.createElement('h2'); heading.textContent = t('parameterNotebookTitle') || 'Parameter Notebooks'; headerMain.appendChild(heading); header.appendChild(headerMain); const closeBtn = document.createElement('span'); closeBtn.className = 'anomalous-nb-close'; closeBtn.innerHTML = '×'; closeBtn.onclick = () => { this.paramPanel.style.display = 'none'; this.closeWorkspace(); }; header.appendChild(closeBtn); const body = document.createElement('div'); body.className = 'anomalous-nb-body'; const sidebar = document.createElement('div'); sidebar.className = 'anomalous-nb-sidebar'; this.paramListEl = document.createElement('div'); this.paramListEl.className = 'anomalous-nb-list'; sidebar.appendChild(this.paramListEl); this.paramEditor = document.createElement('div'); this.paramEditor.className = 'anomalous-nb-editor'; body.appendChild(sidebar); body.appendChild(this.paramEditor); container.appendChild(header); container.appendChild(body); this.paramPanel.appendChild(container); this.refreshParameters(true); } export async function refreshParameters(autoOpenFirst = false) { try { const res = await fetch('/anomalous/parameters'); const data = await res.json(); this.paramListEl.innerHTML = ''; if (data.notebooks && data.notebooks.length > 0) { const current = this.currentParameter && data.notebooks.find((item) => item.filename === this.currentParameter.filename); this.currentParameter = current || (autoOpenFirst ? data.notebooks[0] : null); if (this.currentParameter) { this.renderParameterEditor(); } else { this.paramEditor.replaceChildren(); } data.notebooks.forEach(nb => { const item = document.createElement('div'); item.className = 'anomalous-nb-item'; if (this.currentParameter && this.currentParameter.filename === nb.filename) { item.classList.add('active'); } item.innerHTML = `📄 ${escapeHtml(nb.name)}`; item.onclick = () => { this.currentParameter = nb; this.renderParameterEditor(); this.refreshParameters(); }; this.paramListEl.appendChild(item); }); } else { this.currentParameter = null; this.paramEditor.replaceChildren(); } } catch (e) { console.error('Failed to refresh parameters:', e); } } export async function deleteCurrentParameter() { if (!this.currentParameter) return; if (!confirm(t('deleteNotebook') + ' ?')) return; try { const response = await fetch('/anomalous/delete_parameter', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ filename: this.currentParameter.filename }) }); if (!response.ok) throw new Error('parameter deletion failed'); this.currentParameter = null; this.paramEditor.innerHTML = ''; this.refreshParameters(); } catch (e) { } } function displayValue(value) { if (value === null || value === undefined) return '—'; if (typeof value === 'object') { try { return JSON.stringify(value); } catch (e) { return String(value); } } return String(value); } export function renderParameterEditor() { this.paramEditor.innerHTML = ''; if (!this.currentParameter) return; const data = this.currentParameter.data || {}; const tb = document.createElement('div'); tb.className = 'anomalous-nb-toolbar'; const titleArea = document.createElement('h3'); titleArea.textContent = this.currentParameter.name; titleArea.style.margin = '0'; const rightBtns = document.createElement('div'); const delBtn = document.createElement('button'); delBtn.innerHTML = t('deleteNotebook'); delBtn.className = 'anomalous-btn-danger'; delBtn.onclick = () => this.deleteCurrentParameter(); rightBtns.appendChild(delBtn); tb.appendChild(titleArea); tb.appendChild(rightBtns); this.paramEditor.appendChild(tb); const content = document.createElement('div'); content.className = 'anomalous-recipe-detail-content'; content.style.padding = '20px'; content.style.overflowY = 'auto'; content.style.flex = '1'; const gallerySection = document.createElement('div'); gallerySection.className = 'anomalous-recipe-detail-gallery'; gallerySection.style.marginBottom = '20px'; gallerySection.style.display = 'grid'; gallerySection.style.gridTemplateColumns = 'repeat(auto-fill, minmax(120px, 1fr))'; gallerySection.style.gap = '10px'; content.appendChild(gallerySection); const loadGallery = async () => { try { const res = await fetch(`/anomalous/parameter_gallery?filename=${encodeURIComponent(this.currentParameter.filename)}`); const payload = await res.json(); if (payload.status === 'success' && payload.images && payload.images.length > 0) { for (const img of payload.images) { const imgEl = document.createElement('img'); const params = new URLSearchParams({ filename: img.filename, type: 'output' }); if (img.subfolder) params.set('subfolder', img.subfolder); imgEl.src = `/view?${params.toString()}`; imgEl.style.width = '100%'; imgEl.style.aspectRatio = '1 / 1'; imgEl.style.objectFit = 'cover'; imgEl.style.borderRadius = '4px'; gallerySection.appendChild(imgEl); } } else { gallerySection.style.display = 'none'; } } catch (e) { console.error('Failed to load parameter gallery', e); gallerySection.style.display = 'none'; } }; loadGallery(); const renderParams = () => { const nodes = data.params?.nodes || []; if (!nodes.length) { const empty = document.createElement('p'); empty.textContent = t('recipeDetailNoParameters') || 'No parameters saved.'; empty.className = 'anomalous-recipe-detail-muted'; content.appendChild(empty); return; } for (const node of nodes) { const widgets = node.widgets || []; if (!widgets.length) continue; const block = document.createElement('article'); block.className = 'anomalous-recipe-detail-parameter-node'; block.style.marginBottom = '15px'; block.style.padding = '10px'; block.style.background = 'rgba(0,0,0,0.2)'; block.style.borderRadius = '8px'; const title = document.createElement('strong'); title.textContent = node.title || node.type || 'Unknown Node'; title.style.display = 'block'; title.style.marginBottom = '8px'; block.appendChild(title); for (const widget of widgets) { const row = document.createElement('div'); row.className = 'anomalous-recipe-detail-parameter-widget'; row.style.display = 'flex'; row.style.justifyContent = 'space-between'; row.style.marginBottom = '4px'; const nameSpan = document.createElement('span'); nameSpan.className = 'anomalous-recipe-detail-parameter-name'; nameSpan.textContent = widget.name; nameSpan.style.color = 'rgba(255,255,255,0.7)'; const valSpan = document.createElement('span'); valSpan.textContent = displayValue(widget.value); row.appendChild(nameSpan); row.appendChild(valSpan); block.appendChild(row); } content.appendChild(block); } }; renderParams(); this.paramEditor.appendChild(content); }