- ui_canvas_changes.js: a prompt written as tags lists the tags taken out (red, struck) and put in (green) first; "Show meanings" puts each one's translation under it (prompt_gloss.js); "Whole text" marks those tags in both texts and dims the rest. Other text marks the part that differs. A tag the log cut in half at either end is never called changed. - activity_log.py: before/after values keep up to 2000 characters (was 400), room for a whole prompt; longer ones are still cut around the first difference. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
125 lines
9.2 KiB
HTML
125 lines
9.2 KiB
HTML
<!doctype html>
|
|
<meta charset="utf-8">
|
|
<title>AI result integration regression</title>
|
|
<link rel="stylesheet" href="../web/styles/07-prompt-studio.css">
|
|
<link rel="stylesheet" href="../web/styles/27-param-compare.css">
|
|
<link rel="stylesheet" href="../web/styles/37-ai-workbench.css">
|
|
<link rel="stylesheet" href="../web/styles/41-ai-results.css">
|
|
<style>
|
|
:root { --amb-bg-panel:#202027; --amb-text-main:#eee; --amb-text-soft:#bbb; --amb-text-dim:#999; --amb-border:#454550; --amb-link:#b39eef; --amb-ok:#52b98d; --amb-ink-rgb:240,240,250; --amb-scrim:#0008; }
|
|
body { background:#17171b; color:#eee; font:14px sans-serif; margin:20px; }
|
|
#fixture { max-width:760px; } #results { white-space:pre-wrap; }
|
|
</style>
|
|
<!-- Host graph and network are the only substituted boundaries. All AMB rendering/actions are real imports. -->
|
|
<script type="importmap">{"imports":{"/scripts/app.js":"data:text/javascript,export const app = globalThis.ambTestApp;","/scripts/api.js":"data:text/javascript,export const api = new EventTarget();"}}</script>
|
|
<script>
|
|
const sampler = cfg => ({id:3,type:'TestSampler',title:'Sampler',mode:0,inputs:[],widgets:[{name:'cfg',value:cfg},{name:'seed',value:123}]});
|
|
const graph = {_nodes:[sampler(7)],links:{},getNodeById(id){return this._nodes.find(node=>String(node.id)===String(id));}};
|
|
globalThis.ambTestApp = {graph,rootGraph:graph,canvas:{graph}};
|
|
globalThis.LiteGraph = {LGraph:class {configure(workflow){this._nodes=workflow.nodes;this.links={};}}};
|
|
const requests = [];
|
|
globalThis.fetch = async (url,options={}) => {
|
|
requests.push(String(url));
|
|
if(String(url).includes('inspect_image_material')) {
|
|
const source=JSON.parse(options.body).source_image;
|
|
if(source.filename==='missing.png') return new Response('{}',{status:400});
|
|
return new Response(JSON.stringify({workflow:{nodes:[sampler(source.filename==='b.png'?8:7)]}}),{headers:{'Content-Type':'application/json'}});
|
|
}
|
|
if(String(url).includes('kept_images')) return new Response(JSON.stringify({images:[]}));
|
|
throw Error(`Unexpected request: ${url}`);
|
|
};
|
|
</script>
|
|
<pre id="results">Running…</pre>
|
|
<div id="fixture"></div>
|
|
<script type="module">
|
|
import { compareBar, imageResults } from '../web/modules/ui_ai_images.js';
|
|
import { canvasChanges } from '../web/modules/ui_canvas_changes.js';
|
|
import { closeKeepMenu } from '../web/modules/ui_keep_menu.js';
|
|
import { slider } from '../web/modules/ui_ai_stage.js';
|
|
const fixture=document.querySelector('#fixture'), results=document.querySelector('#results');
|
|
const a={filename:'a.png'}, b={filename:'b.png'}, c={filename:'c.png'}, video={filename:'clip.mp4'};
|
|
const check=(value,message)=>{if(!value)throw Error(message);results.textContent+=`PASS ${message}
|
|
`;};
|
|
const settle=()=>new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve)));
|
|
const waitFor=async predicate=>{for(let i=0;i<120;i++){if(predicate())return;await settle();}throw Error('Timed out');};
|
|
const closeCompare=()=>document.querySelector('.anomalous-compare-done').click();
|
|
const tickOf=(box,name)=>[...box.querySelectorAll('figure')].find(f=>f.querySelector('img')?.alt===name)?.querySelector('.anomalous-ai-tick');
|
|
try {
|
|
results.textContent='';
|
|
const bar=compareBar();
|
|
const cards=imageResults([a,b,a,video,{filename:'a.png',folder:'temp'}],{});
|
|
fixture.append(cards,bar);
|
|
check(cards.querySelectorAll('.anomalous-ai-tick').length===2,'only output pictures can be ticked (no videos or temp files, no duplicates)');
|
|
check(requests.length===0,'rendering result tools does not preload metadata');
|
|
check(bar.hidden,'no compare bar until a picture is ticked');
|
|
tickOf(cards,'a.png').click();
|
|
check(!bar.hidden && bar.querySelector('.anomalous-ai-compare-go').hidden,'one tick asks for a second');
|
|
tickOf(cards,'b.png').click();
|
|
check(!bar.querySelector('.anomalous-ai-compare-go').hidden,'two ticks can be compared');
|
|
const later=imageResults([c],{});fixture.append(later);
|
|
tickOf(later,'c.png').click();
|
|
check(!tickOf(cards,'a.png').classList.contains('is-on') && tickOf(cards,'b.png').classList.contains('is-on'),'a third tick replaces the oldest, also on pictures drawn before');
|
|
bar.querySelector('.anomalous-ai-compare-go').click();
|
|
await waitFor(()=>document.querySelector('.anomalous-compare-row:not(.is-head)'));
|
|
check([...document.querySelectorAll('.anomalous-compare-value')].map(n=>n.textContent).join(',')==='8,7','actual parameter comparer reads both ticked workflows');
|
|
closeCompare();
|
|
const tools=[...cards.querySelectorAll('figure')].find(f=>f.querySelector('img')?.alt==='b.png').querySelector('.anomalous-ai-pic-tools');
|
|
tools.children[1].click();
|
|
await waitFor(()=>document.querySelector('.anomalous-compare-apply'));
|
|
document.querySelector('.anomalous-compare-apply').click();
|
|
check(ambTestApp.graph._nodes[0].widgets[0].value===8,'real comparison apply writes the canvas parameter');
|
|
await waitFor(()=>document.querySelector('.anomalous-mixer-toast.is-show'));
|
|
const undo=document.querySelector('.anomalous-mixer-toast-action');
|
|
const r=undo.getBoundingClientRect();
|
|
check(undo.contains(document.elementFromPoint(r.x+r.width/2,r.y+r.height/2)),'Undo remains clickable above the comparison overlay');
|
|
undo.click();
|
|
check(ambTestApp.graph._nodes[0].widgets[0].value===7,'real comparison undo restores the prior value');
|
|
check(ambTestApp.graph._nodes[0].widgets[1].value===123,'seed is never changed by comparison apply');
|
|
closeCompare();
|
|
tools.children[2].click();
|
|
await waitFor(()=>document.querySelector('.anomalous-keep-row'));
|
|
check(document.querySelectorAll('.anomalous-keep-row').length===3,'result Keep uses the existing workflow/combo/prompt menu');
|
|
closeKeepMenu();
|
|
check(![...cards.querySelectorAll('figure')].find(f=>f.querySelector('video'))?.querySelector('.anomalous-ai-pic-tools'),'videos offer no unsupported parameter or save-workflow tools');
|
|
bar.querySelector('.anomalous-ai-compare-clear').click();
|
|
check(bar.hidden && !fixture.querySelector('.anomalous-ai-tick.is-on'),'clearing unticks every picture');
|
|
const many=imageResults(Array.from({length:15},(_,i)=>({filename:`${i}.png`})),{});
|
|
fixture.append(many);
|
|
check(many.querySelectorAll('figure').length===12,'initial image DOM is bounded');
|
|
many.querySelector(':scope > button').click();
|
|
check(many.querySelectorAll('figure').length===15,'more results are reachable on demand');
|
|
const moved=[];
|
|
const receipt=canvasChanges({detail:{total:5,changes:[
|
|
{kind:'changed',node:'Sampler #3',widget:'cfg',before:'7',after:'8'},
|
|
{kind:'changed',node:'Prompt #6',widget:'text',before:'<img src=x onerror=alert(1)>',after:'soft light'},
|
|
{kind:'removed',node:'Old #9'},
|
|
{kind:'added',node:'New #10'},
|
|
]}},{locate:change=>moved.push(change.node)});
|
|
fixture.replaceChildren(receipt);
|
|
check(receipt.querySelectorAll('.anomalous-canvas-change').length===3,'receipt initially shows three changes');
|
|
check(!receipt.querySelector('img'),'recorded text is not interpreted as markup');
|
|
check(!receipt.querySelector('.is-removed button'),'removed nodes do not offer locate');
|
|
receipt.querySelector('button').click();check(moved[0]==='Sampler #3','locate passes the recorded node to its owner');
|
|
receipt.querySelector('details').open=true;
|
|
await settle();
|
|
check(receipt.querySelectorAll('.anomalous-canvas-change').length===4,'remaining changes expand without losing prior rows');
|
|
check(canvasChanges(null).textContent.includes('不可用'),'old records have an explicit unavailable state');
|
|
const tagReceipt=canvasChanges({detail:{total:1,changes:[{kind:'changed',node:'Prompt #2',widget:'text',before:'masterpiece, best quality, slime girl, purple hair, smile',after:'masterpiece, best quality, vampire girl, silver hair, smile'}]}});
|
|
check(tagReceipt.querySelectorAll('.anomalous-canvas-tag.is-out').length===2&&tagReceipt.querySelectorAll('.anomalous-canvas-tag.is-in').length===2,'a prompt receipt lists the tags taken out and put in');
|
|
tagReceipt.querySelector('.anomalous-canvas-change-full').open=true;await settle();
|
|
check(tagReceipt.querySelectorAll('.anomalous-canvas-mark.is-in').length===2,'the whole texts mark the tags put in');
|
|
const cut=canvasChanges({detail:{total:1,changes:[{kind:'changed',node:'P',widget:'text',before:'…iece, best quality, a, b, slime girl, c, d, smi…',after:'…iece, best quality, a, b, vampire girl, c, d, sm…'}]}});
|
|
check([...cut.querySelectorAll('.anomalous-canvas-tag')].map(n=>n.textContent).join('|')==='slime girl|vampire girl','half tags at cut ends are never called changed');
|
|
const comparisonSlider=slider({before:a,after:b});
|
|
check(Boolean(comparisonSlider.querySelector('.anomalous-ai-slider-compare')),'AI slider connects to real parameter comparison');
|
|
const broken=imageResults([a,{filename:'missing.png'}],{});fixture.append(broken);
|
|
tickOf(broken,'a.png').click();tickOf(broken,'missing.png').click();
|
|
bar.querySelector('.anomalous-ai-compare-go').click();
|
|
await waitFor(()=>document.querySelector('.anomalous-compare-summary.is-error'));
|
|
check(!document.querySelector('.anomalous-compare-apply'),'missing workflow fails without an apply action');
|
|
closeCompare();
|
|
results.textContent+='ALL PASSED\n';
|
|
document.title='PASS AI result integration';
|
|
} catch(error) { results.textContent+=`FAIL ${error.stack}\n`;document.title='FAIL AI result integration'; }
|
|
</script>
|