Files
odysseus/tests/test_editor_look_recipes.py
T
pewdiepie-archdaemonandClaude Opus 5.5 5fac693c1c feat(photo-editor): Ajax Image Editor client (Photoshop-parity tools)
Photoshop-parity editor client: layers, selection/masking, brushes,
smart filters, LUT import, Quick Edit, mobile gestures, workspaces,
batch operations, and the service-worker versioning (v978->v1006).

Source: vendor/photocraft (ad1cff44) photo-editor client files, merged
onto upstream/dev (a8c147b2). Conflict resolution documented in
pr_work/A_conflicts.md; editor files taken from vendor/photocraft
(upstream's editor is an earlier snapshot of the same feature).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 11:57:35 +00:00

124 lines
4.5 KiB
Python

import json
import subprocess
import textwrap
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
MODULE = (ROOT / "static/js/editor/look-recipes.js").as_uri()
EDITOR = (ROOT / "static/js/galleryEditor.js").read_text(encoding="utf-8")
def _recipe_name(prompt: str):
source = textwrap.dedent(
f"""
import {{ lookRecipeSteps }} from {json.dumps(MODULE)};
const r = lookRecipeSteps({json.dumps(prompt)});
process.stdout.write(JSON.stringify(r ? {{ name: r.name, ops: r.steps.map(s => s.op) }} : null));
"""
)
out = subprocess.run(
["node", "--input-type=module", "-e", source],
capture_output=True, text=True, check=True,
)
return json.loads(out.stdout)
def _recipe_steps(prompt: str, strength=None):
opts = "" if strength is None else f", {{ strength: {strength} }}"
source = textwrap.dedent(
f"""
import {{ lookRecipeSteps }} from {json.dumps(MODULE)};
const r = lookRecipeSteps({json.dumps(prompt)}{opts});
process.stdout.write(JSON.stringify(r ? r.steps : null));
"""
)
out = subprocess.run(
["node", "--input-type=module", "-e", source],
capture_output=True, text=True, check=True,
)
return json.loads(out.stdout)
def test_named_looks_map_to_deterministic_recipes():
for prompt in [
"make it look like a Wes Anderson film",
"make it look like a National Geographic photo",
"make it look like a Kodachrome photo",
"make it look like an Ansel Adams black and white landscape",
"make it look like a 1970s film photograph",
"make it look like a vintage Polaroid",
"make it look like a film noir",
"give it a cinematic teal and orange grade",
"make it look like a golden hour portrait",
"make it a moody Scandinavian scene",
]:
recipe = _recipe_name(prompt)
assert recipe is not None, prompt
# Every step must be one of the supported deterministic control ops.
assert all(op in {"photo.autoTone", "adjustments.apply", "filters.apply"} for op in recipe["ops"]), prompt
assert any(op == "adjustments.apply" for op in recipe["ops"]), prompt
def test_content_looks_route_to_the_model_not_a_recipe():
for prompt in [
"give it a Blade Runner neon night look",
"make it a neon-lit city street",
"give it a Studio Ghibli animated look",
"make it look like an anime",
"turn it into an oil painting",
"make it look like a watercolour",
]:
assert _recipe_name(prompt) is None, prompt
def test_strength_scales_the_recipe():
base = _recipe_steps("make it look like a Kodachrome photo")
assert base is not None
strong = _recipe_steps("make it look like a Kodachrome photo", strength=2)
assert strong is not None
# The vibrance-style adjustment must scale up with strength.
def _max_numeric(steps):
best = 0.0
for s in steps:
if s.get("op") != "adjustments.apply":
continue
for v in (s.get("params", {}).get("params", {}) or {}).values():
if isinstance(v, (int, float)):
best = max(best, abs(float(v)))
return best
assert _max_numeric(strong) > _max_numeric(base)
assert _max_numeric(strong) >= 2 * _max_numeric(base) - 1
def test_non_look_prompts_are_not_intercepted():
for prompt in [
"rotate 90",
"brighten the photo",
"make it warmer",
"remove the background",
"add a man to the scene",
"make it look snowy",
"make it a square",
]:
assert _recipe_name(prompt) is None, prompt
def test_general_look_deriver_handles_unknown_named_looks():
recipe = _recipe_name("give it a warm dreamy aesthetic")
assert recipe is not None
ops = recipe["ops"]
assert "photo.autoTone" in ops
assert "adjustments.apply" in ops
def test_editor_wires_the_look_recipe_branch():
assert "import { lookRecipeSteps } from './editor/look-recipes.js';" in EDITOR
assert "const _lookRec = lookRecipeSteps(prompt, { strength: _lookStrength() });" in EDITOR
assert "for (const step of _lookRec.steps) {" in EDITOR
# Content-change looks route to the model (creative repaint), not a grade.
assert r"neon|blade\\s*runner|bloom|holograph" in EDITOR
ai_settings = (ROOT / "static/js/editor/ai-settings.js").read_text(encoding="utf-8")
assert 'id="ge-look-strength"' in ai_settings
assert "localStorage.setItem('ge-look-strength'" in ai_settings