mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-27 16:22:20 +01:00
Merge current dev and refresh PR #6741 from the 7011 integration
This commit is contained in:
@@ -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); });
|
||||
Reference in New Issue
Block a user