mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
MCP: the trace-and-style prompt's steps run as written
When an agent followed the trace-and-style prompt step by step, it failed at the second step: open_file leaves the image unselected, so imageTrace.make returned "nothing selected". The text did not say that the Default preset traces in black and white. No step expanded the trace, so the source image stayed in the document. The set_paint step gave every traced shape the same paint. Step 2 also said that screenshots show the Image Trace view, while render_preview and screenshot without window draw the tracing result. Step 1 said that the image opens at its own pixel size, while an image that declares a resolution opens at its size at that resolution. The steps now select the image with select.all (or name its id), say which presets keep colors, expand the trace with imageTrace.expand or imageTrace.makeAndExpand, recolor with recolor.colors and recolor.apply, and check the result with render_preview or screenshot. The prompt's name, arguments and defaults are unchanged.
This commit is contained in:
@@ -270,7 +270,7 @@ One undo step covers the whole change; gradients and linked swatches stay linked
|
||||
PromptDef {
|
||||
name: "trace-and-style",
|
||||
title: "Trace an image and style it",
|
||||
description: "Trace a placed raster with Image Trace, then apply a live effect and paint over the result.",
|
||||
description: "Trace an image with Image Trace and expand the trace, then apply a live effect and recolor the result.",
|
||||
arguments: &[
|
||||
PromptArg { name: "path", description: "Path to the image file", required: true, default: "", values: Values::Fixed(NONE) },
|
||||
PromptArg {
|
||||
@@ -293,8 +293,13 @@ Trace the image at {path} in VectorCraft with the \"{preset}\" preset, then styl
|
||||
with the {effect} effect.
|
||||
|
||||
Method:
|
||||
1. open_file the image: it becomes a document of its own pixel size.
|
||||
2. imageTrace.make with the preset name, adjusting `params` — mode blackAndWhite, grayscale,
|
||||
1. open_file the image: it becomes a document of the image's size at the resolution it declares
|
||||
(1 px = 1 pt at 72 ppi or when it declares none), with nothing selected.
|
||||
Then run_command select.all to select the image, or pass the image's `id` (inspect_document
|
||||
lists it) to imageTrace.make.
|
||||
2. imageTrace.make with the preset name. The Default preset traces in black and white; a color
|
||||
preset such as 6 Colors or 16 Colors, or `params` with mode color, keeps the image's colors.
|
||||
Adjust `params`: mode blackAndWhite, grayscale,
|
||||
color (palette limited, fullTone for photos, automatic, or documentLibrary to trace with
|
||||
the document's swatches or a swatch `library`) or logo (flat-colour logos only; the Flat Logo
|
||||
preset; colours via logoColors), then threshold, colors (colorDetail for
|
||||
@@ -304,11 +309,16 @@ Method:
|
||||
imageTrace.presets to see every preset with its numbers.
|
||||
Lower paths and higher corners give the clean, logo-like result; high fidelity needs paths
|
||||
near 90. imageTrace.setView {view: \"outlinesWithSourceImage\"} draws the traced outlines
|
||||
over the image on screen to compare them (screenshots show it; exports keep the result).
|
||||
3. apply_effect {effect} on the traced shapes. Call apply_effect with no `effect` for the
|
||||
over the image in the app's window to compare them (screenshot {window: true} captures it);
|
||||
render_preview and screenshot without `window` draw the tracing result, as exports do.
|
||||
3. imageTrace.expand keeps only the traced paths and removes the source image.
|
||||
imageTrace.makeAndExpand traces and expands in one command, in place of steps 2 and 3.
|
||||
4. apply_effect {effect} on the traced shapes. Call apply_effect with no `effect` for the
|
||||
catalogue and each effect's parameters.
|
||||
4. set_paint over the traced shapes to give the artwork its own colours.
|
||||
5. screenshot, then remove the placed image if it is still underneath.
|
||||
5. To change the colors, run recolor.colors on the traced shapes, then recolor.apply with a
|
||||
`map` from each traced color to its new color. set_paint gives every selected shape the
|
||||
same fill or stroke.
|
||||
6. render_preview or screenshot, and compare the result with the source image.
|
||||
|
||||
The trace is real geometry: every traced shape stays selectable and editable.",
|
||||
},
|
||||
@@ -546,6 +556,26 @@ mod tests {
|
||||
assert!(e.contains("brief"), "{e}");
|
||||
}
|
||||
|
||||
/// trace-and-style selects the opened image before tracing it, expands the trace, recolors it
|
||||
/// instead of painting it one color, and says that screenshots draw the tracing result.
|
||||
#[test]
|
||||
fn trace_and_style_selects_expands_and_recolors() {
|
||||
let v = get("trace-and-style", &json!({"path": "art.png"})).unwrap();
|
||||
let text = v["messages"][0]["content"]["text"].as_str().unwrap_or_default();
|
||||
for step in ["open_file", "select.all", "imageTrace.make", "imageTrace.expand", "apply_effect", "recolor.apply", "render_preview"] {
|
||||
assert!(text.contains(step), "{step}: {text}");
|
||||
}
|
||||
let at = |s: &str| text.find(s).unwrap_or(usize::MAX);
|
||||
assert!(at("select.all") < at("imageTrace.make") && at("imageTrace.make") < at("imageTrace.expand"), "{text}");
|
||||
assert!(text.contains("black and white"), "{text}");
|
||||
assert!(!text.contains("screenshots show it"), "{text}");
|
||||
assert!(!text.contains("own pixel size") && text.contains("resolution it declares"), "{text}");
|
||||
// No step of its own paints the trace with set_paint.
|
||||
let steps: Vec<&str> = text.lines().filter(|l| is_item(l)).filter_map(|l| l.split_once(". ").map(|(_, rest)| rest)).collect();
|
||||
assert!(!steps.is_empty(), "{text}");
|
||||
assert!(steps.iter().all(|s| !s.starts_with("set_paint")), "{steps:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completions_rank_prefix_first_and_cap_at_100() {
|
||||
let mut b = b();
|
||||
|
||||
@@ -296,6 +296,60 @@ fn headless_end_to_end() {
|
||||
assert_eq!(v["error"]["code"], -32002);
|
||||
}
|
||||
|
||||
/// The trace-and-style prompt's steps, run in its order on an image drawn here: each one
|
||||
/// succeeds, the expanded trace holds no image, and its paths keep the image's colors.
|
||||
#[test]
|
||||
fn trace_and_style_prompt_steps_run_in_order() {
|
||||
let mut s = server();
|
||||
// 60 × 40 pixels: a red and a blue block on white.
|
||||
let png = tmp("trace-and-style.png");
|
||||
image::RgbImage::from_fn(60, 40, |x, y| match (x, y) {
|
||||
(5..25, 5..35) => image::Rgb([220, 30, 30]),
|
||||
(35..55, 5..35) => image::Rgb([30, 60, 200]),
|
||||
_ => image::Rgb([255, 255, 255]),
|
||||
})
|
||||
.save(&png)
|
||||
.unwrap();
|
||||
let path = png.to_str().unwrap();
|
||||
let v = rpc(&mut s, 1, "prompts/get", json!({"name": "trace-and-style", "arguments": {"path": path}}));
|
||||
let prompt = v["result"]["messages"][0]["content"]["text"].as_str().unwrap().to_string();
|
||||
|
||||
let ok = |s: &mut Server, id: u64, name: &str, args: Value| {
|
||||
let r = call(s, id, name, args);
|
||||
assert_eq!(r["isError"], false, "{name}: {r}");
|
||||
serde_json::from_str::<Value>(&text_of(&r)).unwrap_or(Value::Null)
|
||||
};
|
||||
let command = |s: &mut Server, id: u64, command: &str, params: Value| {
|
||||
assert!(prompt.contains(command), "the prompt names {command}: {prompt}");
|
||||
ok(s, id, "run_command", json!({"command": command, "params": params}))
|
||||
};
|
||||
ok(&mut s, 2, "open_file", json!({"path": path}));
|
||||
command(&mut s, 3, "select.all", json!({}));
|
||||
let traced = command(&mut s, 4, "imageTrace.make", json!({"preset": "6 Colors"}));
|
||||
assert!(traced["colors"].as_u64().unwrap() > 1, "{traced}");
|
||||
command(&mut s, 5, "imageTrace.expand", json!({}));
|
||||
ok(&mut s, 6, "apply_effect", json!({"effect": "stylize.scribble"}));
|
||||
let colors = command(&mut s, 7, "recolor.colors", json!({}));
|
||||
let from = colors["colors"].as_array().unwrap().iter().filter_map(|c| c["hex"].as_str()).find(|h| *h != "#ffffff").unwrap().to_string();
|
||||
command(&mut s, 8, "recolor.apply", json!({"map": {from.as_str(): "#00aa00"}}));
|
||||
let r = call(&mut s, 9, "render_preview", json!({}));
|
||||
assert_eq!(r["isError"], false, "{r}");
|
||||
|
||||
// No image is left, and the paths carry more than one fill color, the new one among them.
|
||||
let doc = ok(&mut s, 10, "inspect_document", json!({}));
|
||||
let (mut kinds, mut fills) = (vec![], std::collections::BTreeSet::new());
|
||||
let mut stack: Vec<&Value> = doc["layers"].as_array().unwrap().iter().collect();
|
||||
while let Some(n) = stack.pop() {
|
||||
kinds.push(n["kind"].as_str().unwrap_or_default().to_string());
|
||||
if let Some(fill) = n["fill"].as_str().filter(|f| f.starts_with('#')) {
|
||||
fills.insert(fill.to_string());
|
||||
}
|
||||
stack.extend(n["children"].as_array().into_iter().flatten());
|
||||
}
|
||||
assert!(!kinds.iter().any(|k| k == "Image"), "{kinds:?}");
|
||||
assert!(fills.len() > 1 && fills.contains("#00aa00") && !fills.contains(&from), "{fills:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn headless_path_gesture_undo() {
|
||||
let mut s = server();
|
||||
|
||||
Reference in New Issue
Block a user