Merge current dev and refresh PR #6741 from the 7011 integration

This commit is contained in:
pewdiepie-archdaemon
2026-10-26 16:21:28 +00:00
295 changed files with 24233 additions and 1587 deletions
@@ -0,0 +1,157 @@
#!/usr/bin/env node
// Quick Edit coverage audit runner (standalone; not a Playwright spec).
//
// Drives the live editor agent for every prompt in CASES and grades each by
// whether an expected control op appears in the executed-step trace.
//
// Usage:
// BASE_URL=http://127.0.0.1:7011 SESSION_FILE=/path/sessions.json \
// NODE_PATH=/path/node_modules node quickedit-coverage.js
//
// PROMPTS=1,2,3 limit to a subset
// OUT=/tmp/qe output dir for the JSON report
// HEADFUL=1 show the browser
const fs = require('node:fs');
const path = require('node:path');
const { chromium } = require('@playwright/test');
const BASE = process.env.BASE_URL || 'http://127.0.0.1:7011';
const OUT = process.env.OUT || '/tmp/opencode/qe-coverage';
const SESSION_FILE = process.env.SESSION_FILE || '/home/pewds/odysseus-cookbook-fresh/data/sessions.json';
const CASES = [
{ id: 1, group: 'Fill', prompt: 'fill the white area with red', expect: ['fill.flood', 'selection.fill'] },
{ id: 2, group: 'Fill', prompt: 'flood fill the sky with blue', expect: ['fill.flood', 'selection.fill', 'selection.sky'] },
{ id: 3, group: 'Text', prompt: 'make the text bigger', expect: ['text.edit'], setup: 'text' },
{ id: 4, group: 'Text', prompt: 'make the title red', expect: ['text.edit'], setup: 'text' },
{ id: 5, group: 'Text', prompt: 'add the title "Summer"', expect: ['text.add'] },
{ id: 6, group: 'Layer style', prompt: 'add a drop shadow to the title', expect: ['layers.addStyle'], setup: 'text' },
{ id: 7, group: 'Layer style', prompt: 'add a stroke to the layer', expect: ['layers.addStyle'] },
{ id: 8, group: 'Layer style', prompt: 'add a colour overlay', expect: ['layers.addStyle'] },
{ id: 9, group: 'Align', prompt: 'align the logo to the centre', expect: ['layers.align'] },
{ id: 10, group: 'Align', prompt: 'align the layer to the left', expect: ['layers.align'] },
{ id: 11, group: 'Layer transform', prompt: 'rotate only this layer 90 degrees', expect: ['layer.rotate'] },
{ id: 12, group: 'Layer transform', prompt: 'flip this layer horizontally', expect: ['layer.flip'] },
{ id: 13, group: 'Document transform', prompt: 'rotate the whole image 90 degrees', expect: ['document.rotate'] },
{ id: 14, group: 'Document transform', prompt: 'flip the canvas vertically', expect: ['document.flip'] },
{ id: 15, group: 'Crop', prompt: 'crop to a square', expect: ['document.crop'] },
{ id: 16, group: 'Crop', prompt: 'make this 16:9', expect: ['document.crop'] },
{ id: 17, group: 'Crop', prompt: 'crop to 9:16 for a story', expect: ['document.crop'] },
{ id: 18, group: 'Extend', prompt: 'extend the canvas to 16:9', expect: ['ai.outpaint'] },
{ id: 19, group: 'Clipboard', prompt: 'copy the selection', expect: ['selection.copy'], setup: 'select' },
{ id: 20, group: 'Clipboard', prompt: 'paste', expect: ['selection.paste'], setup: 'select' },
{ id: 21, group: 'Selection', prompt: 'select the subject', expect: ['selection.subject'] },
{ id: 22, group: 'Selection', prompt: 'select the sky', expect: ['selection.sky'] },
{ id: 23, group: 'Selection', prompt: 'select all', expect: ['selection.selectAll'] },
{ id: 24, group: 'Selection', prompt: 'invert the selection', expect: ['selection.invert'] },
{ id: 25, group: 'Background', prompt: 'remove the background', expect: ['ai.removeBackground', 'selection.background'] },
{ id: 26, group: 'Background', prompt: 'blur the background', expect: ['filters.applyMasked', 'filters.apply'] },
{ id: 27, group: 'Adjustment', prompt: 'make it brighter', expect: ['photo.autoTone', 'adjustments.apply', 'shadows-highlights'] },
{ id: 28, group: 'Adjustment', prompt: 'make it black and white', expect: ['adjustments.apply', 'black-white'], assert: 'black' },
{ id: 29, group: 'Adjustment', prompt: 'warm it up', expect: ['adjustments.apply', 'photo.autoTone', 'white-balance'] },
{ id: 30, group: 'Adjustment', prompt: 'make it punchy', expect: ['photo.autoTone', 'adjustments.apply', 'vibrance'] },
{ id: 31, group: 'Adjustment', prompt: 'make it moody and cinematic', expect: ['adjustments.apply', 'white-balance'] },
{ id: 32, group: 'Restore', prompt: 'restore this old photo', expect: ['photo.autoTone', 'adjustments.apply'] },
{ id: 33, group: 'Retouch', prompt: 'smooth the skin', expect: ['retouch.skin', 'adjustments.apply'] },
{ id: 34, group: 'Retouch', prompt: 'whiten the teeth', expect: ['retouch.teeth', 'adjustments.apply'] },
{ id: 35, group: 'Retouch', prompt: 'remove the blemishes', expect: ['retouch.blemish', 'retouch.skin', 'adjustments.apply'] },
{ id: 36, group: 'Filter', prompt: 'sharpen it', expect: ['filters.apply', 'adjustments.apply', 'photo.autoTone'] },
{ id: 37, group: 'Filter', prompt: 'add a vignette', expect: ['filters.apply', 'adjustments.apply'] },
{ id: 38, group: 'Layers', prompt: 'add a new layer', expect: ['layers.addRaster', 'layers.addAdjustment'] },
{ id: 39, group: 'Layers', prompt: 'duplicate this layer', expect: ['layers.duplicate'] },
{ id: 40, group: 'Layers', prompt: 'flatten the image', expect: ['layers.flatten'] },
{ id: 41, group: 'Layers', prompt: 'merge the layer down', expect: ['layers.mergeDown'] },
{ id: 42, group: 'Document', prompt: 'save as PSD', expect: ['document.savePsd'] },
{ id: 43, group: 'Document', prompt: 'resize to 1920 wide', expect: ['document.resizeCanvas'] },
{ id: 44, group: 'AI edit', prompt: 'remove the person on the left', expect: ['ai.generativeFill'] },
{ id: 45, group: 'AI generate', prompt: 'add a full moon in the sky', expect: ['ai.generativeFill'] },
];
function authToken() {
const data = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf8'));
const now = Date.now() / 1000;
for (const [token, info] of Object.entries(data)) {
if (info.expiry == null || info.expiry > now) return token;
}
throw new Error(`no unexpired session in ${SESSION_FILE}`);
}
function selected() {
const raw = process.env.PROMPTS;
if (!raw) return CASES;
const want = new Set(raw.split(',').map(s => Number(s.trim())));
return CASES.filter(c => want.has(c.id));
}
(async () => {
fs.mkdirSync(OUT, { recursive: true });
const browser = await chromium.launch({ headless: !process.env.HEADFUL });
const context = await browser.newContext({ viewport: { width: 1280, height: 860 }, serviceWorkers: 'block' });
await context.addCookies([{ name: 'odysseus_session', value: authToken(), domain: new URL(BASE).hostname, path: '/' }]);
const page = await context.newPage();
page.on('pageerror', e => console.log(' [pageerror]', String(e).slice(0, 240)));
const results = [];
for (const c of selected()) {
let record = { id: c.id, group: c.group, prompt: c.prompt, expect: c.expect, ops: [], pass: false, error: null };
try {
await page.goto(BASE + '/?nodraft=1', { waitUntil: 'domcontentloaded' });
await page.locator('#tool-gallery-btn').waitFor({ state: 'attached', timeout: 30000 });
await page.evaluate(async () => {
const gallery = await import('/static/js/gallery.js?v=20260830editor4');
gallery.openGallery();
});
await page.locator('#gallery-editor-tab').first().waitFor({ state: 'attached', timeout: 30000 });
await page.evaluate(() => document.getElementById('gallery-editor-tab')?.click());
await page.evaluate(async () => {
const editor = await import('/static/js/galleryEditor.js?qe=' + Date.now());
window.__geNoDraft = true;
if (editor.isEditorOpen()) editor.closeEditor({ force: true });
editor.openEditor(null, null, { w: 640, h: 480 }, 'QE ' + Date.now());
});
await page.locator('.ge-main-canvas').waitFor({ state: 'visible', timeout: 30000 });
await page.waitForFunction(() => !!window.geEditorControl, null, { timeout: 20000 });
if (c.setup) {
await page.evaluate(async (setup) => {
const control = window.geEditorControl;
if (setup === 'text') await control.invoke('text.add', { text: 'Summer', x: 80, y: 80, size: 64 });
else if (setup === 'select') await control.invoke('selection.rectangle', { x: 100, y: 100, width: 200, height: 160 });
}, c.setup);
}
const trace = await page.evaluate(async (prompt) => {
const ops = [];
const control = window.geEditorControl;
const orig = control.invoke.bind(control);
control.invoke = async (op, params) => { ops.push({ op, params }); return orig(op, params); };
let result = null, error = null;
try {
result = window.geQuickEditRun
? await window.geQuickEditRun(prompt, {})
: await window.geEditorAgent.run(prompt, {});
}
catch (err) { error = String((err && err.message) || err); }
finally { control.invoke = orig; }
return { ops, reply: result && (result.reply || result.message), error };
}, c.prompt);
record.ops = trace.ops.map(o => o.op);
record.reply = trace.reply;
record.error = trace.error;
const opHit = c.expect.some(e => record.ops.some(o => o === e || o.startsWith(e + '.')));
const assertHit = !c.assert || trace.ops.some(o => JSON.stringify(o.params || {}).includes(c.assert));
record.pass = opHit && assertHit;
} catch (err) {
record.error = String((err && err.message) || err);
}
results.push(record);
console.log(`QE ${record.pass ? 'PASS' : 'FAIL'} #${c.id} [${c.group}] "${c.prompt}" -> ${record.ops.join(', ') || record.error}`);
}
const pass = results.filter(r => r.pass).length;
fs.writeFileSync(path.join(OUT, 'quickedit-coverage.json'), JSON.stringify({ base: BASE, pass, total: results.length, results }, null, 2));
console.log(`QUICKEDIT_COVERAGE ${pass}/${results.length} passed -> ${path.join(OUT, 'quickedit-coverage.json')}`);
await browser.close();
process.exit(0);
})().catch(err => { console.error('FAIL', err); process.exit(1); });