diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 10d1e97..b46ef6f 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -98,8 +98,13 @@ DOM or live LiteGraph state. vertical workspace; replaces clumsy text copy buttons with sleek SVG micro-copy icons with instant green checkmark feedback; rebuilds the Parameters Tab (`renderRecipeParameters`) into a modern Bento Matrix (`.anomalous-recipe-bento-grid`, steps/CFG/sampler/resolution/denoise/seed tiles, - independent base model card, and structured LoRA pill stacks); snapshot rail replaces legacy `
` - with smooth hover-reveal micro-actions; enforces 48px bottom safety padding to eliminate clipping and overlap. + independent base model card, and structured LoRA pill stacks); prompts section (`renderPromptSection`) + is upgraded into full-width streamlined cards eliminating the cramped two-column split, clumsy + "调整用途" buttons, and isolated square copy boxes, moving actions into a top-right action tray + (save to material + micro copy) and making the role badge interactively clickable; snapshot rail + replaces legacy `
` and vertical text badges with smooth hover-reveal micro-actions and a glowing + green active pulse dot (`.anomalous-preset-active-dot`); enforces 48px bottom safety padding to eliminate + clipping and overlap. - `ui_materials.js` owns the Workspace Material Library and material CRUD presentation; features an integrated single-row Studio topbar with category micro-pills (`all`, `workflow`, `params`, `prompts`), unified search with tag filtering, grid/list view mode toggle (persisted to localStorage), diff --git a/web/modules/ui_recipe_detail.js b/web/modules/ui_recipe_detail.js index c222823..a99a005 100644 --- a/web/modules/ui_recipe_detail.js +++ b/web/modules/ui_recipe_detail.js @@ -2186,8 +2186,25 @@ function renderPromptSection(parent, owner, recipe, source, rerender, onSaveNode const heading = document.createElement('div'); heading.className = 'anomalous-recipe-detail-section-heading'; - appendText(heading, 'h5', t('recipeDetailPrompts')); - appendText(heading, 'small', t('recipePromptSupportNotice'), 'anomalous-recipe-detail-muted'); + heading.style.display = 'flex'; + heading.style.alignItems = 'center'; + heading.style.justifyContent = 'space-between'; + heading.style.marginBottom = '12px'; + + const headingLeft = document.createElement('div'); + headingLeft.style.display = 'flex'; + headingLeft.style.alignItems = 'center'; + headingLeft.style.gap = '8px'; + appendText(headingLeft, 'h5', t('recipeDetailPrompts') || '提示词'); + + const noticeTooltip = t('recipePromptSupportNotice') || '当前仅自动识别 ComfyUI 原生 CLIPTextEncode 与已知官方连接;第三方文本节点请手动标注。'; + const infoIcon = document.createElement('span'); + infoIcon.className = 'anomalous-recipe-info-bubble'; + infoIcon.title = noticeTooltip; + infoIcon.innerHTML = `ⓘ ${window.anomalous_browser_lang === 'zh' ? '支持说明' : 'Notice'}`; + infoIcon.style.cursor = 'help'; + headingLeft.appendChild(infoIcon); + heading.appendChild(headingLeft); parent.appendChild(heading); const promptList = document.createElement('div'); @@ -2196,12 +2213,30 @@ function renderPromptSection(parent, owner, recipe, source, rerender, onSaveNode const card = document.createElement('article'); card.className = `anomalous-recipe-detail-prompt anomalous-recipe-prompt-role-${entry.role}`; - const meta = document.createElement('div'); - meta.className = 'anomalous-recipe-prompt-meta'; - appendText(meta, 'strong', entry.title, 'anomalous-recipe-detail-prompt-label'); - appendText(meta, 'small', entry.type, 'anomalous-recipe-detail-muted'); - const badge = appendText(meta, 'span', promptRoleLabel(entry.role), `anomalous-recipe-prompt-role-badge is-${entry.role}`); - badge.title = entry.manual ? t('recipePromptRoleManual') : t('recipePromptRoleAutomatic'); + // Top full-width header bar + const headerRow = document.createElement('div'); + headerRow.className = 'anomalous-recipe-prompt-card-header'; + headerRow.style.display = 'flex'; + headerRow.style.justifyContent = 'space-between'; + headerRow.style.alignItems = 'center'; + headerRow.style.gap = '10px'; + headerRow.style.marginBottom = '8px'; + + // Left info group: Interactive role badge + Node title & type + const leftGroup = document.createElement('div'); + leftGroup.className = 'anomalous-recipe-prompt-card-header-left'; + leftGroup.style.display = 'flex'; + leftGroup.style.alignItems = 'center'; + leftGroup.style.gap = '8px'; + leftGroup.style.flexWrap = 'wrap'; + + const isZh = window.anomalous_browser_lang === 'zh'; + const roleBadge = document.createElement('button'); + roleBadge.type = 'button'; + roleBadge.className = `anomalous-recipe-prompt-role-badge is-${entry.role} is-interactive`; + const roleDot = entry.role === 'positive' ? '🟢' : entry.role === 'negative' ? '🔴' : '🟣'; + roleBadge.innerHTML = `${roleDot} ${promptRoleLabel(entry.role)} `; + roleBadge.title = isZh ? '点击切换提示词用途 (正向/负向/忽略)' : 'Click to adjust prompt role'; const roleSelect = document.createElement('select'); roleSelect.className = 'anomalous-recipe-prompt-role-select'; @@ -2221,6 +2256,7 @@ function renderPromptSection(parent, owner, recipe, source, rerender, onSaveNode roleSelect.appendChild(option); } roleSelect.value = entry.manual ? entry.role : 'auto'; + roleSelect.style.display = 'none'; roleSelect.onchange = async () => { const previous = entry.manual ? entry.role : 'auto'; roleSelect.disabled = true; @@ -2237,42 +2273,83 @@ function renderPromptSection(parent, owner, recipe, source, rerender, onSaveNode await anomalousAlert(t('recipePromptRoleSaveError')); } }; - roleSelect.hidden = true; - const adjustRole = button(meta, t('promptAdjustRole'), 'anomalous-btn-ghost'); - adjustRole.setAttribute('aria-expanded', 'false'); - adjustRole.onclick = () => { - roleSelect.hidden = !roleSelect.hidden; - adjustRole.setAttribute('aria-expanded', String(!roleSelect.hidden)); - if (!roleSelect.hidden) roleSelect.focus(); + roleBadge.onclick = () => { + if (roleSelect.style.display === 'none') { + roleSelect.style.display = 'inline-block'; + roleSelect.focus(); + } else { + roleSelect.style.display = 'none'; + } }; - meta.appendChild(roleSelect); - if (typeof onSaveNodes === 'function') { - const savePrompt = button(meta, t('materialSavePromptAction'), 'anomalous-material-node-save'); - savePrompt.onclick = () => onSaveNodes([entry.id], entry.title || t('materialPromptNode'), savePrompt); + + leftGroup.appendChild(roleBadge); + leftGroup.appendChild(roleSelect); + + const nodeTitle = document.createElement('span'); + nodeTitle.className = 'anomalous-recipe-prompt-card-node-title'; + nodeTitle.textContent = entry.title || 'CLIPTextEncode'; + nodeTitle.title = entry.type || ''; + leftGroup.appendChild(nodeTitle); + + if (entry.type && entry.type !== entry.title) { + const nodeTypeEl = document.createElement('span'); + nodeTypeEl.className = 'anomalous-recipe-prompt-card-node-type'; + nodeTypeEl.textContent = `(${entry.type})`; + leftGroup.appendChild(nodeTypeEl); } - const copyBtn = document.createElement('button'); - copyBtn.className = 'anomalous-recipe-prompt-micro-copy'; - const isZh = window.anomalous_browser_lang === 'zh'; - copyBtn.title = isZh ? '复制提示词' : 'Copy prompt'; - copyBtn.innerHTML = ``; - copyBtn.onclick = (e) => { + headerRow.appendChild(leftGroup); + + // Right actions tray (Save to Material + Micro Copy) + const actionTray = document.createElement('div'); + actionTray.className = 'anomalous-recipe-prompt-card-action-tray'; + actionTray.style.display = 'flex'; + actionTray.style.alignItems = 'center'; + actionTray.style.gap = '6px'; + + if (typeof onSaveNodes === 'function') { + const savePromptBtn = document.createElement('button'); + savePromptBtn.type = 'button'; + savePromptBtn.className = 'anomalous-recipe-prompt-micro-copy'; + savePromptBtn.title = t('materialSavePromptAction') || '存入素材库'; + savePromptBtn.innerHTML = ``; + savePromptBtn.onclick = (e) => { + e.stopPropagation(); + onSaveNodes([entry.id], entry.title || t('materialPromptNode'), savePromptBtn); + }; + actionTray.appendChild(savePromptBtn); + } + + const copyPromptBtn = document.createElement('button'); + copyPromptBtn.type = 'button'; + copyPromptBtn.className = 'anomalous-recipe-prompt-micro-copy'; + const copyTitle = isZh ? '复制提示词' : 'Copy prompt'; + const copiedTitle = isZh ? '已复制' : 'Copied'; + copyPromptBtn.title = copyTitle; + copyPromptBtn.innerHTML = ``; + copyPromptBtn.onclick = (e) => { e.stopPropagation(); navigator.clipboard.writeText(entry.text || '').then(() => { - copyBtn.classList.add('is-copied'); - copyBtn.innerHTML = ``; + copyPromptBtn.classList.add('is-copied'); + copyPromptBtn.innerHTML = ``; + copyPromptBtn.title = copiedTitle; setTimeout(() => { - copyBtn.classList.remove('is-copied'); - copyBtn.innerHTML = ``; + copyPromptBtn.classList.remove('is-copied'); + copyPromptBtn.innerHTML = ``; + copyPromptBtn.title = copyTitle; }, 1200); }); }; - meta.appendChild(copyBtn); + actionTray.appendChild(copyPromptBtn); + headerRow.appendChild(actionTray); + card.appendChild(headerRow); + + // Full-width prompt body + const body = document.createElement('div'); + body.className = 'anomalous-recipe-prompt-card-body'; + appendValueViewer(body, entry.text, '', { copy: false }); + card.appendChild(body); - const value = document.createElement('div'); - value.className = 'anomalous-recipe-prompt-value'; - appendValueViewer(value, entry.text, '', { copy: false }); - card.append(meta, value); promptList.appendChild(card); } parent.appendChild(promptList); @@ -2412,15 +2489,27 @@ function renderRecipeParameters(content, owner, recipe, gallery, refreshGallery, selectParameterTab?.(); }; - const titleEl = appendText(main, 'div', notebookName, 'anomalous-preset-item-title'); + const titleRow = document.createElement('div'); + titleRow.className = 'anomalous-preset-item-title-row'; + titleRow.style.display = 'flex'; + titleRow.style.alignItems = 'center'; + titleRow.style.gap = '6px'; + titleRow.style.minWidth = '0'; + + if (isSelected) { + const activeDot = document.createElement('span'); + activeDot.className = 'anomalous-preset-active-dot'; + activeDot.title = t('recipeParameterActive') || '当前生效'; + titleRow.appendChild(activeDot); + } + + const titleEl = appendText(titleRow, 'div', notebookName, 'anomalous-preset-item-title'); titleEl.title = notebookName; + main.appendChild(titleRow); const meta = document.createElement('div'); meta.className = 'anomalous-preset-item-meta'; appendText(meta, 'span', dateText(notebook.timestamp), 'anomalous-preset-item-date'); - if (isSelected) { - appendText(meta, 'span', t('recipeParameterActive'), 'anomalous-preset-item-badge'); - } main.appendChild(meta); row.appendChild(main); diff --git a/web/styles.css b/web/styles.css index 52ad6c3..f5d1f14 100644 --- a/web/styles.css +++ b/web/styles.css @@ -3007,32 +3007,115 @@ dialog.anomalous-gallery-viewer[open] { } .anomalous-recipe-detail-prompt { - display: grid; - grid-template-columns: minmax(170px, 0.28fr) minmax(0, 1fr); - gap: 12px; - align-items: start; - padding: 11px; + display: flex; + flex-direction: column; + gap: 10px; + padding: 14px 16px; border: 1px solid rgba(255, 255, 255, 0.08); - border-left: 3px solid #7d8796; - border-radius: 9px; - background: rgba(0, 0, 0, 0.14); + border-left: 4px solid #7d8796; + border-radius: 10px; + background: rgba(255, 255, 255, 0.025); transition: opacity 0.18s ease, border-color 0.18s ease, background 0.18s ease; + margin-bottom: 8px; +} + +.anomalous-recipe-detail-prompt:hover { + background: rgba(255, 255, 255, 0.035); + border-color: rgba(255, 255, 255, 0.14); } .anomalous-recipe-detail-prompt.is-saving { opacity: 0.55; } -.anomalous-recipe-prompt-role-positive { border-left-color: #6ee7b7; } -.anomalous-recipe-prompt-role-negative { border-left-color: #df8d9a; } -.anomalous-recipe-prompt-role-both { border-left-color: #c4a7ff; } +.anomalous-recipe-prompt-role-positive { border-left-color: #10b981 !important; } +.anomalous-recipe-prompt-role-negative { border-left-color: #f43f5e !important; } +.anomalous-recipe-prompt-role-both { border-left-color: #a855f7 !important; } .anomalous-recipe-prompt-role-ignored, -.anomalous-recipe-prompt-role-unknown { border-left-color: #7d8796; } -.anomalous-recipe-detail-prompt-label { padding-top: 5px; color: #d7e6ff; font-size: 0.85rem; } +.anomalous-recipe-prompt-role-unknown { border-left-color: #64748b !important; } + +.anomalous-recipe-prompt-card-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; +} + +.anomalous-recipe-prompt-card-header-left { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; +} + +.anomalous-recipe-prompt-role-badge.is-interactive { + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 5px; + border: 1px solid rgba(255, 255, 255, 0.15); + border-radius: 6px; + padding: 3px 8px; + font-size: 0.74rem; + font-weight: 600; + background: rgba(255, 255, 255, 0.05); + color: #e2e8f0; + transition: all 0.15s ease; +} + +.anomalous-recipe-prompt-role-badge.is-interactive:hover { + background: rgba(255, 255, 255, 0.12); + border-color: rgba(255, 255, 255, 0.25); + transform: translateY(-1px); +} + +.anomalous-recipe-prompt-role-badge.is-positive { + background: rgba(16, 185, 129, 0.12); + border-color: rgba(16, 185, 129, 0.3); + color: #34d399; +} + +.anomalous-recipe-prompt-role-badge.is-negative { + background: rgba(244, 63, 94, 0.12); + border-color: rgba(244, 63, 94, 0.3); + color: #fb7185; +} + +.anomalous-recipe-prompt-card-node-title { + font-size: 0.84rem; + font-weight: 600; + color: #e2e8f0; +} + +.anomalous-recipe-prompt-card-node-type { + font-size: 0.74rem; + color: #64748b; +} + +.anomalous-recipe-prompt-card-body { + width: 100%; +} + +.anomalous-recipe-prompt-card-body .anomalous-recipe-detail-value-viewer { + display: block; + width: 100%; +} + +.anomalous-recipe-prompt-card-body .anomalous-recipe-detail-full-value { + width: 100%; + box-sizing: border-box; + padding: 12px 14px; + border-radius: 8px; + background: rgba(0, 0, 0, 0.22); + border: 1px solid rgba(255, 255, 255, 0.06); + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + font-size: 0.84rem; + line-height: 1.6; + color: #cbd5e1; +} .anomalous-recipe-prompt-meta { display: flex; min-width: 0; - flex-direction: column; - align-items: flex-start; - gap: 6px; + align-items: center; + gap: 8px; } .anomalous-recipe-prompt-meta strong, @@ -5917,8 +6000,42 @@ dialog.anomalous-gallery-viewer::backdrop { .anomalous-preset-item-card.is-active { background: rgba(255, 255, 255, 0.06); - border-color: rgba(255, 255, 255, 0.22); - box-shadow: inset 3px 0 0 rgba(255, 255, 255, 0.7); + border-color: rgba(16, 185, 129, 0.4); + box-shadow: inset 3px 0 0 #10b981; +} + +.anomalous-preset-active-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: #10b981; + box-shadow: 0 0 8px #10b981; + flex-shrink: 0; + display: inline-block; +} + +.anomalous-preset-item-title-row { + width: 100%; + overflow: hidden; +} + +.anomalous-recipe-info-bubble { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 7px; + border-radius: 6px; + background: rgba(255, 255, 255, 0.04); + border: 1px solid rgba(255, 255, 255, 0.08); + color: #94a3b8; + font-size: 0.74rem; + transition: all 0.15s ease; +} + +.anomalous-recipe-info-bubble:hover { + background: rgba(255, 255, 255, 0.08); + color: #e2e8f0; + border-color: rgba(255, 255, 255, 0.16); } .anomalous-preset-item-main {