From 67b7e4a4547b33f806a32c1eeada5256fc578340 Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Mon, 5 Oct 2026 16:11:05 +0200 Subject: [PATCH] Add copy and resize controls to prompt previews --- tests/test_prompt_transfer.mjs | 46 ++++++++++++++++++ web/css/resolver-main.css | 19 +++++++- .../views/tabs_image_metadata_methods.js | 47 +++++++++++++++++-- 3 files changed, 105 insertions(+), 7 deletions(-) diff --git a/tests/test_prompt_transfer.mjs b/tests/test_prompt_transfer.mjs index 1edc877..cafedd9 100644 --- a/tests/test_prompt_transfer.mjs +++ b/tests/test_prompt_transfer.mjs @@ -373,6 +373,10 @@ test('metadata prompt panel renders separate positive and negative transfer rows assert.match(html, /data-image-prompt-target="negative"/); assert.match(html, /data-image-prompt-role-selection="positive"[^>]*checked/); assert.match(html, /data-image-prompt-role-selection="negative"[^>]*checked/); + assert.equal((html.match(/data-image-prompt-copy/g) || []).length, 4); + assert.match(html, /class="mr-btn mr-btn-secondary mr-btn-sm mr-image-prompt-copy"[^>]*data-image-prompt-copy/); + assert.match(html, /data-tooltip="["]*Positive\/negative role detected from workflow connections\./); + assert.doesNotMatch(html, /class="mr-image-prompt-meta"/); assert.match(html, /class="mr-tooltip-badge"[^>]*data-tooltip="[\s\S]*Select which prompt roles to replace/); assert.doesNotMatch(html, /class="mr-image-prompt-note"/); const positiveTargetIndex = html.indexOf('data-image-prompt-target="positive"'); @@ -386,6 +390,48 @@ test('metadata prompt panel renders separate positive and negative transfer rows assert.match(html, /Replace prompts/); }); +test('metadata prompt copy uses the visible prompt preview text', async () => { + const context = createContext(null, createRoleWorkflow()); + const copied = []; + const previousNavigator = Object.getOwnPropertyDescriptor(globalThis, 'navigator'); + Object.defineProperty(globalThis, 'navigator', { + configurable: true, + value: { + clipboard: { + async writeText(value) { + copied.push(value); + }, + }, + }, + writable: true, + }); + const button = { + innerHTML: ' Copy', + isConnected: false, + classList: { + add() {}, + remove() {}, + }, + closest(selector) { + assert.equal(selector, '.mr-image-prompt-side'); + return { + querySelector(query) { + assert.equal(query, '.mr-image-prompt-text'); + return { textContent: 'visible prompt preview' }; + }, + }; + }, + }; + + try { + assert.equal(await context.copyMetadataPromptText(button), true); + assert.deepEqual(copied, ['visible prompt preview']); + } finally { + if (previousNavigator) Object.defineProperty(globalThis, 'navigator', previousNavigator); + else delete globalThis.navigator; + } +}); + test('metadata prompt selectors put the most likely prompt node first', () => { const currentWorkflow = { nodes: [ diff --git a/web/css/resolver-main.css b/web/css/resolver-main.css index aaf6293..ab07bcc 100644 --- a/web/css/resolver-main.css +++ b/web/css/resolver-main.css @@ -5080,9 +5080,12 @@ button.mr-info-link:disabled { } .mr-image-prompt-select { + box-sizing: border-box; width: 100%; min-width: 0; min-height: 26px; + margin: 0 !important; + align-self: stretch; padding: var(--mr-spacing-4) var(--mr-spacing-7); border: 1px solid var(--mr-border-strong, rgba(255, 255, 255, 0.24)); border-radius: var(--mr-radius-md, var(--mr-radius-lg)); @@ -5105,10 +5108,13 @@ button.mr-info-link:disabled { .mr-image-prompt-text { box-sizing: border-box; width: 100%; - max-height: 150px; + height: auto; + min-height: 28px; + max-height: 70vh; + resize: vertical; margin: 0; overflow: auto; - padding: var(--mr-spacing-8); + padding: var(--mr-spacing-4) var(--mr-spacing-8); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: var(--mr-radius-md, var(--mr-radius-lg)); background: rgba(0, 0, 0, 0.16); @@ -5124,6 +5130,15 @@ button.mr-info-link:disabled { background: rgba(255, 255, 255, 0.045); } +.mr-image-prompt-copy { + align-self: flex-start; +} + +.mr-image-prompt-copy.is-copied { + background: rgba(114, 211, 138, 0.16); + border-color: rgba(114, 211, 138, 0.28); +} + .mr-image-prompt-arrow { color: var(--mr-accent); font-size: var(--mr-font-lg); diff --git a/web/resolver/views/tabs_image_metadata_methods.js b/web/resolver/views/tabs_image_metadata_methods.js index 3cdefff..3b5717a 100644 --- a/web/resolver/views/tabs_image_metadata_methods.js +++ b/web/resolver/views/tabs_image_metadata_methods.js @@ -1,4 +1,4 @@ -import { safeStorage } from "../utils/html_utils.js"; +import { copyTextWithFeedback, safeStorage } from "../utils/html_utils.js"; import { isExistingResolvedModel, toResolverContextModel, @@ -17,6 +17,7 @@ const METADATA_TRANSFER_MODE_STORAGE_KEY = 'model_resolver_metadata_transfer_mod const METADATA_TRANSFER_ACTIVITY_SCOPE_STORAGE_KEY = 'model_resolver_metadata_transfer_activity_scope'; const METADATA_PROMPT_ROLES = ['positive', 'negative']; const MAX_PROMPT_PREVIEW_LENGTH = 12_000; +const COPY_ICON_HTML = ''; const MERGE_LORA_NODE_TYPES = new Set([ 'LoraLoaderV2', @@ -264,6 +265,12 @@ export const imageMetadataMethods = { return; } + const promptCopyButton = event.target.closest?.('[data-image-prompt-copy]'); + if (promptCopyButton) { + void this.copyMetadataPromptText(promptCopyButton); + return; + } + const action = event.target.closest?.('[data-image-inspector-action]')?.dataset?.imageInspectorAction; if (!action) return; if (action === 'choose-file') fileInput?.click(); @@ -726,6 +733,30 @@ export const imageMetadataMethods = { return `${text.slice(0, MAX_PROMPT_PREVIEW_LENGTH)}\n…`; }, + getMetadataPromptCopyButton(prompt = {}) { + if (!String(prompt.text || '').trim()) return ''; + return ` + + `; + }, + + async copyMetadataPromptText(button) { + const textElement = button?.closest?.('.mr-image-prompt-side') + ?.querySelector?.('.mr-image-prompt-text'); + const text = textElement?.textContent || ''; + if (!text) return false; + + await copyTextWithFeedback(String(text), button, { + successClass: 'is-copied', + successHtml: `${COPY_ICON_HTML} Copied`, + errorText: 'Failed', + duration: 1200, + }); + return true; + }, + getMetadataTooltipBadge(text, ariaLabel = 'More information') { const tooltip = String(text ?? '').trim(); if (!tooltip) return ''; @@ -862,7 +893,7 @@ export const imageMetadataMethods = { ${sourceOptions}
${this.escapeHtml(this.getMetadataPromptPreviewText(row.source) || '(empty)')}
- ${this.escapeHtml(sourceRoleHint)} + ${this.getMetadataPromptCopyButton(row.source)} ` : '

No imported prompt available.

'; const targetHtml = row.target @@ -871,7 +902,7 @@ export const imageMetadataMethods = { ${targetOptions}
${this.escapeHtml(this.getMetadataPromptPreviewText(row.target) || '(empty)')}
- ${this.escapeHtml(targetRoleHint)} + ${this.getMetadataPromptCopyButton(row.target)} ` : '

No current text field available.

'; return ` @@ -885,12 +916,18 @@ export const imageMetadataMethods = {
- Current text field + + Current text field + ${this.getMetadataTooltipBadge(targetRoleHint, `Current ${row.role} prompt role information`)} + ${targetHtml}
- Imported + + Imported + ${this.getMetadataTooltipBadge(sourceRoleHint, `Imported ${row.role} prompt role information`)} + ${sourceHtml}