Refresh metadata prompt previews when workflow text changes
This commit is contained in:
@@ -3002,6 +3002,36 @@ test('Missing Models filter toggles persist state and rerender once per change',
|
||||
}
|
||||
});
|
||||
|
||||
test('metadata refresh updates prompts even when the model workflow signature is unchanged', async () => {
|
||||
const refresh = eval(`(${extractMethod(workflowStateMethodsSource, 'refreshForActiveWorkflowChange')})`);
|
||||
const workflow = { nodes: [{ id: 1, type: 'CLIPTextEncode', widgets_values: ['edited prompt'] }] };
|
||||
let promptRefreshes = 0;
|
||||
const dialog = {
|
||||
_workflowRefreshGeneration: 1,
|
||||
activeWorkflowRouteKey: 'workflow-a',
|
||||
activeWorkflowSignature: 'same-model-signature',
|
||||
activeTab: 'metadata',
|
||||
isVisible: () => true,
|
||||
getActiveWorkflowRouteKey: () => 'workflow-a',
|
||||
getCurrentWorkflow: () => workflow,
|
||||
getWorkflowSignature: () => 'same-model-signature',
|
||||
getMissingWorkflowSignature: () => 'same-model-signature',
|
||||
refreshMetadataPromptPreview(value) {
|
||||
assert.equal(value, workflow);
|
||||
promptRefreshes += 1;
|
||||
return true;
|
||||
},
|
||||
openMetadataTransfer() { assert.fail('Prompt-only edits must not rescan models'); },
|
||||
syncWorkflowScopedQueue() { assert.fail('Prompt-only edits must preserve model state'); },
|
||||
};
|
||||
await refresh.call(dialog, { reason: 'node-widget-change', generation: 1 });
|
||||
assert.equal(promptRefreshes, 1);
|
||||
assert.equal(dialog._workflowRefreshRetryTimer, undefined);
|
||||
dialog._applyWorkflowRefreshSuppressionDepth = 1;
|
||||
await refresh.call(dialog, { reason: 'node-widget-change', generation: 1 });
|
||||
assert.equal(promptRefreshes, 1);
|
||||
});
|
||||
|
||||
test('node widget changes request a content-preserving Missing Models refresh', async () => {
|
||||
const log = { debug() {} };
|
||||
const refreshForActiveWorkflowChange = eval(
|
||||
|
||||
@@ -73,6 +73,28 @@ function createContext(sourceWorkflow, currentWorkflow) {
|
||||
};
|
||||
}
|
||||
|
||||
test('live prompt preview detects positive and negative text changes without changing imported metadata', () => {
|
||||
const source = createRoleWorkflow('imported positive', 'imported negative');
|
||||
const current = createRoleWorkflow('current positive', 'current negative');
|
||||
const dialog = createContext(source, current);
|
||||
let refreshes = 0;
|
||||
dialog.renderMetadataPromptTransferPanelInPlace = () => {
|
||||
refreshes += 1;
|
||||
dialog.renderMetadataPromptTransferPanel(dialog.imageInspectorState);
|
||||
return true;
|
||||
};
|
||||
dialog.renderMetadataPromptTransferPanel(dialog.imageInspectorState);
|
||||
assert.equal(dialog.refreshMetadataPromptPreview(current), false);
|
||||
current.nodes[0].widgets_values[0] = 'edited positive';
|
||||
assert.equal(dialog.refreshMetadataPromptPreview(current), true);
|
||||
assert.match(dialog.renderMetadataPromptTransferPanel(dialog.imageInspectorState), /edited positive/);
|
||||
current.nodes[1].widgets_values[0] = 'edited negative';
|
||||
assert.equal(dialog.refreshMetadataPromptPreview(current), true);
|
||||
assert.equal(dialog.refreshMetadataPromptPreview(current), false);
|
||||
assert.equal(refreshes, 2);
|
||||
assert.equal(source.nodes[0].widgets_values[0], 'imported positive');
|
||||
});
|
||||
|
||||
test('workflow prompt extraction distinguishes positive and negative CLIP text nodes', () => {
|
||||
const prompts = extractWorkflowPrompts(createRoleWorkflow());
|
||||
|
||||
|
||||
@@ -955,6 +955,15 @@ export class ModelResolver {
|
||||
const focusHandler = () => {
|
||||
window.__ModelResolverWorkflowChangeOwner?.handleActiveWorkflowRouteChange('window-focus');
|
||||
};
|
||||
const promptInputHandler = (event) => {
|
||||
const dialog = window.__ModelResolverWorkflowChangeOwner?.dialog;
|
||||
const target = event.target instanceof Element ? event.target : null;
|
||||
if (!dialog?.isVisible() || dialog.activeTab !== 'metadata') return;
|
||||
if (target?.closest('#model-resolver-modal, .model-resolver-backdrop')) return;
|
||||
if (!target?.matches('textarea, input[type="text"], [contenteditable="true"]')) return;
|
||||
dialog.scheduleActiveWorkflowRefresh('node-widget-change');
|
||||
};
|
||||
document.addEventListener('input', promptInputHandler);
|
||||
const visibilityHandler = () => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
window.__ModelResolverWorkflowChangeOwner?.handleActiveWorkflowRouteChange('visibility-change');
|
||||
@@ -970,6 +979,7 @@ export class ModelResolver {
|
||||
document.addEventListener('visibilitychange', visibilityHandler);
|
||||
|
||||
window.__ModelResolverWorkflowChangeHandlers = [
|
||||
{ target: document, event: 'input', handler: promptInputHandler },
|
||||
{ target: window, event: 'hashchange', handler: routeHandler },
|
||||
{ target: window, event: 'popstate', handler: routeHandler },
|
||||
{ target: window, event: 'model-resolver-locationchange', handler: routeHandler },
|
||||
|
||||
@@ -872,6 +872,10 @@ export const workflowStateMethods = {
|
||||
const missingSignatureChanged = missingSignature
|
||||
&& missingSignature !== this.activeMissingWorkflowSignature;
|
||||
const graphStillLooksOld = routeChanged && previousSignature && signature === previousSignature;
|
||||
if (this.activeTab === 'metadata' && !routeChanged) {
|
||||
const promptsChanged = this.refreshMetadataPromptPreview?.(workflow);
|
||||
if (promptsChanged && !signatureChanged) return;
|
||||
}
|
||||
const workflowChangeStillPending = (
|
||||
String(reason || '').startsWith('node-')
|
||||
&& !routeChanged
|
||||
|
||||
@@ -851,6 +851,7 @@ export const imageMetadataMethods = {
|
||||
const imageState = state || getImageInspectorState(this);
|
||||
const promptTransfer = this.getMetadataPromptTransferState(imageState);
|
||||
const data = this.getMetadataPromptTransferData(imageState);
|
||||
this._metadataPromptPreviewSignature = JSON.stringify(data.targetPrompts);
|
||||
const sourceSummary = data.sourcePrompts.length
|
||||
? `${data.sourcePrompts.length} editable prompt${data.sourcePrompts.length === 1 ? '' : 's'} found in the imported workflow.`
|
||||
: imageState.loading
|
||||
@@ -977,6 +978,12 @@ export const imageMetadataMethods = {
|
||||
`;
|
||||
},
|
||||
|
||||
refreshMetadataPromptPreview(workflow) {
|
||||
const signature = JSON.stringify(extractWorkflowPrompts(workflow || {}).filter(prompt => !prompt.linked));
|
||||
if (signature === this._metadataPromptPreviewSignature) return false;
|
||||
return this.renderMetadataPromptTransferPanelInPlace({ fallback: false });
|
||||
},
|
||||
|
||||
renderMetadataPromptTransferPanelInPlace({ fallback = true } = {}) {
|
||||
const result = this.contentElement?.querySelector('[data-image-inspector-result]');
|
||||
const currentPanel = result?.querySelector('.mr-image-prompt-transfer');
|
||||
|
||||
Reference in New Issue
Block a user