Files
DemonGatanjieu-Anomalous_Mo…/tests/ai_results.html
T
DemonGatanjieuandClaude Opus 5.5 e57858c93b Canvas receipts: a prompt change shows the tags taken out and put in, with their meanings
- 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>
2026-10-22 15:47:44 +08:00

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>