From be078cfce92a40f43afa1b489ecaa6ac9a8071f1 Mon Sep 17 00:00:00 2001 From: Valeriy Selitskiy <239034+iamwavecut@users.noreply.github.com> Date: Wed, 21 Oct 2026 01:07:17 +0200 Subject: [PATCH 1/2] MOSS layout v2: official pixel draft, request kind, layout before prompts The opt-in MOSS-Image layout (MOSS model config "prompt_layout": true) now follows the official prompt enhancer (skills/moss-image-prompt-enhancer): - moss_layout holds canvas_width and canvas_height, the 1 MP canvas draw-api renders for the chosen aspect ratio (listed in the prompt), summary, and items with [x_min, y_min, x_max, y_max] pixel boxes; no word budgets. Items are two schema shapes, so only a text item carries text: given text on any item, the model folded captions into panels. - moss_kind names the request (poster, comic, meme, infographic, arrangement, card, cover, menu, other_design, scene) instead of a boolean, and code keeps a layout only for the first five. With the boolean the model also marked menus, cards and covers as designed. - moss_kind and moss_layout sort before outputs, so the layout is drafted before the prompts. After them, the three-slot VIP call ran the MOSS prompt to the token limit in 5 of 25 production calls; before them, in 0 of 60. slot_moss_negative stays after the prompts. - Quotes that wrap a whole layout text are removed; MOSS-Image drew them. The prose route and the prompt without a layout are unchanged. --- crates/openplotva-media/src/lib.rs | 352 +++++++++++++++++++++++------ prompts/image/optimizer.prompt | 21 +- 2 files changed, 300 insertions(+), 73 deletions(-) diff --git a/crates/openplotva-media/src/lib.rs b/crates/openplotva-media/src/lib.rs index e236f138..4ee42ad8 100644 --- a/crates/openplotva-media/src/lib.rs +++ b/crates/openplotva-media/src/lib.rs @@ -24,6 +24,35 @@ pub const IMAGE_ASPECT_RATIOS: [&str; 9] = [ "1:1", "2:3", "3:2", "3:4", "4:3", "9:16", "16:9", "1:2", "2:1", ]; +/// Side of the square whose area every MOSS-Image canvas keeps, as draw-api sizes it. +const MOSS_CANVAS_SIDE: f64 = 1024.0; +/// MOSS-Image canvas sides are multiples of its latent grid. +const MOSS_CANVAS_GRID: f64 = 16.0; + +/// MOSS-Image canvas in pixels for an optimizer aspect ratio such as "3:4": the 1 MP +/// training area on the 16-pixel grid, the same arithmetic as draw-api's `moss_job`. +#[must_use] +pub fn moss_canvas(aspect_ratio: &str) -> Option<(u32, u32)> { + let (width, height) = aspect_ratio.split_once(':')?; + let (width, height) = ( + width.trim().parse::().ok()?, + height.trim().parse::().ok()?, + ); + if width <= 0.0 || height <= 0.0 { + return None; + } + let ratio = (width / height).clamp(0.4, 2.5); + let width = (MOSS_CANVAS_SIDE * MOSS_CANVAS_SIDE * ratio).sqrt(); + let height = width / ratio; + let snap = |side: f64| { + // Sides stay below 2048 px, so the cast cannot truncate. + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + let snapped = ((side / MOSS_CANVAS_GRID).round() * MOSS_CANVAS_GRID) as u32; + snapped + }; + Some((snap(width), snap(height))) +} + pub const OPTIMIZE_PROMPT_TERMINATOR_TOOL_NAME: &str = "optimize_prompt_terminator"; pub const OPTIMIZE_EDIT_PROMPT_TERMINATOR_TOOL_NAME: &str = "optimize_edit_prompt_terminator"; pub const IMAGE_OPTIMIZER_PROMPT_NAME: &str = "image/optimizer"; @@ -44,20 +73,54 @@ pub struct ImageOptimize { /// empty when the request rules nothing out or no slot targets MOSS-Image. #[serde(default, rename = "slot_moss_negative")] pub moss_negative: String, - /// Whether the request is a designed image (poster, card, menu, meme, ...) that - /// MOSS-Image renders from `moss_layout` instead of its prose prompt. - #[serde(default, rename = "slot_moss_design")] + /// Whether MOSS-Image renders the request from `moss_layout` instead of its prose + /// prompt: the optimizer's `moss_kind` is one of [`MOSS_LAYOUT_KINDS`]. + #[serde( + default, + rename = "moss_kind", + deserialize_with = "deserialize_moss_kind" + )] pub moss_design: bool, - /// MOSS-Image layout of a designed image: `summary` plus `items` with `yxyx` boxes - /// on a 0-1000 grid. draw-api compiles it to the model's pixel format. + /// MOSS-Image layout of a designed image: `canvas_width` and `canvas_height`, `summary`, + /// and `items` with `[x_min, y_min, x_max, y_max]` pixel boxes, the draft format of the + /// official prompt enhancer. draw-api compiles it to the model's centred format. #[serde( default, - rename = "slot_moss_layout", + rename = "moss_layout", deserialize_with = "deserialize_moss_layout" )] pub moss_layout: Option, } +/// Request kinds MOSS-Image draws from a layout, as the official prompt enhancer +/// advises: placement, a poster, several panels, or a controlled arrangement. +pub const MOSS_LAYOUT_KINDS: [&str; 5] = ["poster", "comic", "meme", "infographic", "arrangement"]; + +/// Kinds of `moss_kind`: the model names the request, and code decides the route, +/// which a model follows better than a boolean rule with exceptions. +const MOSS_KINDS: [&str; 10] = [ + "poster", + "comic", + "meme", + "infographic", + "arrangement", + "card", + "cover", + "menu", + "other_design", + "scene", +]; + +fn deserialize_moss_kind<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let value = Value::deserialize(deserializer)?; + Ok(value + .as_str() + .is_some_and(|kind| MOSS_LAYOUT_KINDS.contains(&kind.trim()))) +} + /// Keep a layout only when it is an object with a non-empty `items` array, so a /// malformed one degrades to the prose prompt instead of failing the whole payload. fn deserialize_moss_layout<'de, D>(deserializer: D) -> Result, D::Error> @@ -530,6 +593,13 @@ fn optimizer_prompt_data(options: OptimizePromptOptions) -> Value { "krea2": models.contains(&ImageModel::Krea2), "moss": models.contains(&ImageModel::Moss), "moss_layout": options.targets.moss_layout(), + "moss_canvases": IMAGE_ASPECT_RATIOS + .iter() + .filter_map(|ratio| { + moss_canvas(ratio) + .map(|(width, height)| json!({ "ratio": ratio, "width": width, "height": height })) + }) + .collect::>(), }) } @@ -681,12 +751,37 @@ pub fn normalize_image_optimize( if !optimized.moss_design { optimized.moss_layout = None; } + if let Some(items) = optimized + .moss_layout + .as_mut() + .and_then(|layout| layout.get_mut("items")) + .and_then(Value::as_array_mut) + { + // A model used to quoting text in prose also quotes a layout `text`, and + // MOSS-Image then draws the quotation marks. + for text in items.iter_mut().filter_map(|item| item.get_mut("text")) { + if let Some(inner) = text.as_str().and_then(unquoted_lettering) { + *text = Value::from(inner); + } + } + } if optimized.input.is_empty() { optimized.input = fallback.trim().to_owned(); } optimized } +/// The lettering inside a pair of double quotes that wraps all of it; guillemets stay, +/// since Russian lettering uses them. +fn unquoted_lettering(text: &str) -> Option<&str> { + let text = text.trim(); + [('"', '"'), ('“', '”'), ('„', '“')] + .iter() + .find_map(|&(open, close)| text.strip_prefix(open)?.strip_suffix(close)) + .map(str::trim) + .filter(|inner| !inner.is_empty()) +} + #[must_use] pub fn normalize_image_edit_optimize( mut optimized: ImageEditOptimize, @@ -840,24 +935,27 @@ fn optimizer_terminator_definition( } // Required, so the model decides each one: a grammar-constrained model skips // optional fields. Schema keys serialize in alphabetical order, which a - // grammar-constrained model writes in: the `slot_` prefix keeps these after the - // prompts, and the design decision comes before the layout it gates. + // grammar-constrained model writes in. `moss_kind` and `moss_layout` sort before + // `outputs`, so the layout is drafted before the prompts, as the official enhancer + // drafts it: written after them, the MOSS-Image prompt of a three-slot call ran on + // to the token limit in 5 of 25 calls (2026-10-21). `slot_moss_negative` stays after. if moss == MossFields::NegativeAndLayout { properties.insert( - "slot_moss_design".to_owned(), + "moss_kind".to_owned(), json!({ - "type": "boolean", - "description": "True only for a designed image where placement matters, such as a poster, card, cover, menu, infographic, or meme.", + "type": "string", + "enum": MOSS_KINDS, + "description": "What the request is; scene for a photograph, painting, or illustration, also with a sign in it.", }), ); properties.insert( - "slot_moss_layout".to_owned(), + "moss_layout".to_owned(), json!({ "anyOf": [moss_layout_schema(), { "type": "null" }], - "description": "MOSS-Image layout when slot_moss_design is true, otherwise null.", + "description": "MOSS-Image layout for a poster, comic, meme, infographic, or arrangement, otherwise null.", }), ); - required.extend(["slot_moss_design", "slot_moss_layout"]); + required.extend(["moss_kind", "moss_layout"]); } if moss != MossFields::None { properties.insert( @@ -881,47 +979,69 @@ fn optimizer_terminator_definition( } } -/// MOSS-Image layout in the official `summary` / `items` vocabulary, with boxes as -/// `[y_min, x_min, y_max, x_max]` on a 0-1000 grid instead of pixels: the optimizer -/// picks the aspect ratio, and draw-api the canvas. +/// Most items one MOSS-Image layout carries; draw-api compiles at most this many. +const MOSS_LAYOUT_MAX_ITEMS: usize = 16; + +/// MOSS-Image layout as the official prompt enhancer drafts it: the canvas in pixels, +/// `summary`, and flat `items` with `[x_min, y_min, x_max, y_max]` pixel boxes from the +/// top-left corner. The prompt lists the canvas of each aspect ratio. fn moss_layout_schema() -> Value { let strings = json!({ "type": "array", "items": { "type": "string" } }); + let side = json!({ "type": "integer", "minimum": 16, "maximum": 2048 }); + let bbox = json!({ + "type": "array", + "minItems": 4, + "maxItems": 4, + "items": { "type": "integer", "minimum": 0 }, + }); + // Two item shapes, so only a text item can carry wording: a model that may put + // `text` on any item folds a caption into its panel, and the compiler drops it there. + let text_item = json!({ + "type": "object", + "additionalProperties": false, + "properties": { + "label": { "type": "string", "enum": ["text"] }, + "caption": { "type": "string" }, + "text": { "type": "string" }, + "bbox": bbox.clone(), + }, + "required": ["label", "caption", "text", "bbox"], + }); + let shape_item = json!({ + "type": "object", + "additionalProperties": false, + "properties": { + "label": { "type": "string", "enum": ["object", "character"] }, + "caption": { "type": "string" }, + "bbox": bbox, + }, + "required": ["label", "caption", "bbox"], + }); + // `canvas_` names sort before `items`, so the model fixes the canvas before + // the boxes; a plain `width` would come after them. json!({ "type": "object", "properties": { + "canvas_width": side.clone(), + "canvas_height": side, "summary": { "type": "object", "additionalProperties": false, "properties": { "caption": { "type": "string" }, - "meta": strings.clone(), + "meta": strings, "canvas": { "type": "string", "enum": ["single", "multi_panel", "collage"] }, - "palette": strings, }, - "required": ["caption"], + "required": ["caption", "meta", "canvas"], }, "items": { "type": "array", "minItems": 1, - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "label": { "type": "string", "enum": ["text", "object", "character"] }, - "caption": { "type": "string" }, - "text": { "type": "string" }, - "bbox": { - "type": "array", - "minItems": 4, - "maxItems": 4, - "items": { "type": "integer" }, - }, - }, - "required": ["label", "caption", "bbox"], - }, + "maxItems": MOSS_LAYOUT_MAX_ITEMS, + "items": { "anyOf": [text_item, shape_item] }, }, }, - "required": ["summary", "items"], + "required": ["canvas_width", "canvas_height", "summary", "items"], "additionalProperties": false, }) } @@ -1185,8 +1305,8 @@ mod tests { assert!(vip.contains("plus the MOSS-Image field `slot_moss_negative`,")); assert!(vip.contains("written as `\\n` in the JSON string")); assert!(vip.contains("**MOSS-Image negative prompt**")); - assert!(!vip.contains("slot_moss_layout")); - assert!(!vip.contains("slot_moss_design")); + assert!(!vip.contains("moss_layout")); + assert!(!vip.contains("moss_kind")); assert!(!vip.contains("{{")); let layout = render_image_optimizer_prompt(OptimizePromptOptions { @@ -1194,12 +1314,12 @@ mod tests { targets: ImageTargets::MOSS.with_moss_layout(true), }) .expect("render moss layout"); - assert!( - layout.contains("`slot_moss_design`, `slot_moss_layout`, and `slot_moss_negative`") - ); + assert!(layout.contains( + "`moss_kind`, `moss_layout`, `nsfw_result`, `outputs`, and `slot_moss_negative`, in that order" + )); assert!(layout.contains("**MOSS-Image layout**")); - assert!(layout.contains("Use the same breaks in `slot_moss_layout`.")); - assert!(layout.contains("\"slot_moss_design\": true, \"slot_moss_layout\": {")); + assert!(layout.contains("Use the same breaks in `moss_layout`.")); + assert!(layout.contains("\"moss_kind\": \"poster\", \"moss_layout\": {")); assert!(!layout.contains("{{")); let flux3 = @@ -1208,6 +1328,59 @@ mod tests { assert!(!flux3.contains("one array of")); } + #[test] + fn moss_layouts_use_the_pixel_canvas_draw_api_draws() { + // draw-api's area_dimensions(ratio, 1024, 16) for every optimizer aspect ratio. + for (ratio, width, height) in [ + ("1:1", 1024, 1024), + ("2:3", 832, 1248), + ("3:2", 1248, 832), + ("3:4", 880, 1184), + ("4:3", 1184, 880), + ("9:16", 768, 1360), + ("16:9", 1360, 768), + ("1:2", 720, 1456), + ("2:1", 1456, 720), + ] { + assert_eq!(moss_canvas(ratio), Some((width, height)), "{ratio}"); + } + assert_eq!(moss_canvas("square"), None); + assert_eq!(moss_canvas("0:1"), None); + + let prompt = render_image_optimizer_prompt(OptimizePromptOptions { + variant_count: 1, + targets: ImageTargets::MOSS.with_moss_layout(true), + }) + .expect("render moss layout"); + assert!(prompt.contains("`aspect_ratio`: 1:1 is 1024 × 1024, 2:3 is 832 × 1248,")); + assert!(prompt.contains("1:2 is 720 × 1456, 2:1 is 1456 × 720.")); + assert!(prompt.contains( + "`bbox` is `[x_min, y_min, x_max, y_max]` in pixels from the top-left corner" + )); + assert!(prompt.contains( + "\"canvas_height\": 1184, \"canvas_width\": 880, \"items\": [{\"bbox\": [106, 83, 774, 391]" + )); + + let schema = optimize_prompt_terminator_definition(OptimizePromptOptions { + variant_count: 1, + targets: ImageTargets::MOSS.with_moss_layout(true), + }) + .input_schema; + let layout = &schema["properties"]["moss_layout"]["anyOf"][0]; + assert_eq!( + layout["required"], + json!(["canvas_width", "canvas_height", "summary", "items"]) + ); + assert_eq!( + layout["properties"]["items"]["maxItems"], + MOSS_LAYOUT_MAX_ITEMS + ); + assert_eq!( + layout["properties"]["summary"]["required"], + json!(["caption", "meta", "canvas"]) + ); + } + #[test] fn moss_fields_join_the_schema_only_with_a_moss_slot() { let prose = optimize_prompt_terminator_definition(OptimizePromptOptions { @@ -1225,7 +1398,7 @@ mod tests { "slot_moss_negative" ]) ); - assert!(prose["properties"]["slot_moss_layout"].is_null()); + assert!(prose["properties"]["moss_layout"].is_null()); // The layout flag of either side survives joining two generators. let vip = optimize_prompt_terminator_definition(OptimizePromptOptions { @@ -1244,20 +1417,38 @@ mod tests { "outputs", "nsfw_result", "aspect_ratio", - "slot_moss_design", - "slot_moss_layout", + "moss_kind", + "moss_layout", "slot_moss_negative" ]) ); - assert_eq!(vip["properties"]["slot_moss_design"]["type"], "boolean"); - let layout = &vip["properties"]["slot_moss_layout"]["anyOf"]; + assert_eq!(vip["properties"]["moss_kind"]["enum"], json!(MOSS_KINDS)); + assert!( + MOSS_LAYOUT_KINDS + .iter() + .all(|kind| MOSS_KINDS.contains(kind)) + ); + let layout = &vip["properties"]["moss_layout"]["anyOf"]; assert_eq!(layout[1], json!({ "type": "null" })); assert_eq!(layout[0]["additionalProperties"], false); - let bbox = &layout[0]["properties"]["items"]["items"]["properties"]["bbox"]; + let shapes = &layout[0]["properties"]["items"]["items"]["anyOf"]; + let bbox = &shapes[0]["properties"]["bbox"]; assert_eq!( (bbox["minItems"].clone(), bbox["maxItems"].clone()), (json!(4), json!(4)) ); + // Only a text item carries wording. + assert_eq!(shapes[0]["properties"]["label"]["enum"], json!(["text"])); + assert_eq!( + shapes[0]["required"], + json!(["label", "caption", "text", "bbox"]) + ); + assert_eq!( + shapes[1]["properties"]["label"]["enum"], + json!(["object", "character"]) + ); + assert!(shapes[1]["properties"]["text"].is_null()); + assert_eq!(shapes[1]["additionalProperties"], false); // The grammar writes keys in serialized order: the prompts before the MOSS fields. let keys: Vec<&String> = vip["properties"] .as_object() @@ -1265,15 +1456,16 @@ mod tests { .keys() .collect(); let position = |key: &str| keys.iter().position(|k| *k == key).expect("key"); - assert!(position("outputs") < position("slot_moss_design")); - assert!(position("slot_moss_design") < position("slot_moss_layout")); + assert!(position("moss_kind") < position("moss_layout")); + assert!(position("moss_layout") < position("outputs")); + assert!(position("outputs") < position("slot_moss_negative")); // Without a MOSS-Image slot the layout flag adds nothing. let flux3 = optimize_prompt_terminator_definition(OptimizePromptOptions { variant_count: 1, targets: ImageTargets::FLUX3.with_moss_layout(true), }); - assert!(flux3.input_schema["properties"]["slot_moss_layout"].is_null()); + assert!(flux3.input_schema["properties"]["moss_layout"].is_null()); assert!(flux3.input_schema["properties"]["slot_moss_negative"].is_null()); let edit = optimize_edit_prompt_terminator_definition(1); assert!(edit.input_schema["properties"]["slot_moss_negative"].is_null()); @@ -1326,19 +1518,19 @@ mod tests { "items": [{"bbox": [70, 120, 330, 880], "caption": "Bold cream letters.", "label": "text", "text": "Джаз\nу моря"}], "summary": {"caption": "A flat vector jazz poster."}, }); - let payload = |design: bool, layout: &Value| { + let payload = |kind: &str, layout: &Value| { json!({ "input": "постер", "outputs": ["A flat vector illustration poster for a jazz festival."], "nsfw_result": "safe", "aspect_ratio": "3:4", - "slot_moss_design": design, - "slot_moss_layout": layout, + "moss_kind": kind, + "moss_layout": layout, "slot_moss_negative": " people ", }) .to_string() }; - let designed = decode_image_optimize_payload(&payload(true, &layout), "постер", 1) + let designed = decode_image_optimize_payload(&payload("poster", &layout), "постер", 1) .expect("decode designed"); assert!(designed.moss_design); assert_eq!(designed.moss_layout.as_ref(), Some(&layout)); @@ -1351,13 +1543,47 @@ mod tests { Some(&json!("Джаз\nу моря")) ); - let scene = decode_image_optimize_payload(&payload(false, &layout), "постер", 1) - .expect("decode scene"); - assert_eq!(scene.moss_layout, None); + let quoted = json!({ + "items": [ + {"bbox": [0, 0, 10, 10], "caption": "c", "label": "text", "text": "\"Кино на крыше\""}, + {"bbox": [0, 0, 10, 10], "caption": "c", "label": "text", "text": "фильм «Солярис»"}, + {"bbox": [0, 0, 10, 10], "caption": "c", "label": "text", "text": "\"\""}, + ], + "summary": {"caption": "c"}, + }); + let unquoted = decode_image_optimize_payload(&payload("poster", "ed), "постер", 1) + .expect("decode quoted"); + let texts: Vec<&Value> = unquoted.moss_layout.as_ref().expect("layout")["items"] + .as_array() + .expect("items") + .iter() + .map(|item| &item["text"]) + .collect(); + assert_eq!( + texts, + [ + &json!("Кино на крыше"), + &json!("фильм «Солярис»"), + &json!("\"\"") + ] + ); + + // Only a layout kind keeps the layout, whatever the model wrote. + for kind in ["scene", "card", "menu", "unknown"] { + let other = decode_image_optimize_payload(&payload(kind, &layout), "постер", 1) + .expect("decode other kind"); + assert!(!other.moss_design, "{kind}"); + assert_eq!(other.moss_layout, None, "{kind}"); + } + for kind in MOSS_LAYOUT_KINDS { + let designed = decode_image_optimize_payload(&payload(kind, &layout), "постер", 1) + .expect("decode layout kind"); + assert!(designed.moss_design, "{kind}"); + } // A malformed layout degrades to the prose prompt instead of failing the payload. for broken in [json!("layout"), json!({"items": []}), json!(null)] { - let decoded = decode_image_optimize_payload(&payload(true, &broken), "постер", 1) + let decoded = decode_image_optimize_payload(&payload("poster", &broken), "постер", 1) .expect("decode broken layout"); assert_eq!(decoded.moss_layout, None); assert_eq!(decoded.outputs.len(), 1); @@ -1424,8 +1650,8 @@ mod tests { "input": "x", "outputs": ["Blocked: forbidden CSAM request."], "nsfw_result": "forbidden", - "slot_moss_design": true, - "slot_moss_layout": {"items": [{"bbox": [0, 0, 10, 10], "caption": "c", "label": "object"}], "summary": {"caption": "c"}}, + "moss_kind": "poster", + "moss_layout": {"items": [{"bbox": [0, 0, 10, 10], "caption": "c", "label": "object"}], "summary": {"caption": "c"}}, "slot_moss_negative": "hat", }) .to_string(); diff --git a/prompts/image/optimizer.prompt b/prompts/image/optimizer.prompt index f23c52e7..6393ad74 100644 --- a/prompts/image/optimizer.prompt +++ b/prompts/image/optimizer.prompt @@ -2,7 +2,7 @@ You are the prompt upsampler of image models. You turn one user request, written in any language, into English prompts for the image models named under Output Slots, and you classify the request for safety. You answer with one JSON object and nothing else. ### OUTPUT -1. Return exactly one JSON object with the fields `aspect_ratio`, `input`, `nsfw_result`, and `outputs`, in that order{{#if moss_layout}}, plus the MOSS-Image fields `slot_moss_design`, `slot_moss_layout`, and `slot_moss_negative`{{else}}{{#if moss}}, plus the MOSS-Image field `slot_moss_negative`{{/if}}{{/if}}, with no text, labels, or code fences around it. +1. Return exactly one JSON object with the fields {{#if moss_layout}}`aspect_ratio`, `input`, `moss_kind`, `moss_layout`, `nsfw_result`, `outputs`, and `slot_moss_negative`, in that order{{else}}`aspect_ratio`, `input`, `nsfw_result`, and `outputs`, in that order{{#if moss}}, plus the MOSS-Image field `slot_moss_negative`{{/if}}{{/if}}, with no text, labels, or code fences around it. 2. `input` repeats the user's request. When the input has a Context block, `input` repeats only the Request text. 3. `outputs` holds exactly `{{variant_count}}` prompts, one per slot below, each one paragraph of plain English prose. 4. Use straight double quotes and write a double quote inside a string as `\"`. @@ -69,7 +69,7 @@ MOSS-Image learned from detailed captions written by a vision-language recaption 2. Open with the medium and the composition, such as "A photograph of ..." or "A watercolour illustration of ...", then the key subjects and their actions, the setting, the colours, the lighting, and the style. 3. Place each subject with spatial words (left, right, center, foreground, background, above, behind) and keep one consistent order. 4. For every piece of text in the image, give the exact wording in double quotes and its layout: position, line order, alignment, size relative to other text, letterform, and colour. -5. Text set on several lines carries a line break at each break inside its quotes, written as `\n` in the JSON string, as in `\"Джаз\nу моря\"`. Keep the user's breaks. Set a title, slogan, caption, or sign of four or more words on two or three balanced lines, breaking between words where a designer would; keep shorter text on one line.{{#if moss_layout}} Use the same breaks in `slot_moss_layout`.{{/if}} +5. Text set on several lines carries a line break at each break inside its quotes, written as `\n` in the JSON string, as in `\"Джаз\nу моря\"`. Keep the user's breaks. Set a title, slogan, caption, or sign of four or more words on two or three balanced lines, breaking between words where a designer would; keep shorter text on one line.{{#if moss_layout}} Use the same breaks in `moss_layout`.{{/if}} 6. Write prose only: no tag lists, quality tags, or score tags. **MOSS-Image negative prompt** @@ -79,12 +79,13 @@ MOSS-Image also reads `slot_moss_negative`, a short list of things to steer away {{#if moss_layout}} **MOSS-Image layout** -MOSS-Image can also draw from `slot_moss_layout`: a summary of the whole picture plus a box for each block of text and each main object. -1. Set `slot_moss_design` to true only for a designed image where placement matters: a poster, flyer, banner, cover, card, invitation, menu, label or packaging, infographic, chart, diagram, screen or interface, comic or meme with captions, or a picture with two or more separate blocks of text. Set it to false for every other request: a photograph, painting, or illustration of a scene is not a designed image, even with a sign, a shop name, or a label in it. -2. When `slot_moss_design` is false, `slot_moss_layout` is null. Otherwise the layout shows the same picture as the MOSS-Image prompt in `outputs`: the same subjects, text, colours, and composition. -3. `summary.caption` describes the whole picture in 40 to 120 words: medium, style, background, palette, light, and the overall arrangement, without repeating the wording of the text. `summary.meta` holds one to three short medium or genre tags, such as "poster" or "flat vector illustration". `summary.canvas` is "single", "multi_panel" for comic panels, or "collage". -4. `items` lists every block of text and every main object or character, from top to bottom and left to right, at most twelve. Each has a `label` ("text", "object", or "character"), a `caption` of 10 to 40 words on its look (for text: weight, style, case, size, colour, and alignment), and a `bbox`. Only a text item has `text`: the exact wording with `\n` at each line break. Give each separately placed block of text its own item, and never repeat a text in two items. -5. `bbox` is `[y_min, x_min, y_max, x_max]` on a 0 to 1000 grid over the whole picture in the chosen `aspect_ratio`: y runs top to bottom, x left to right, and `[0, 0, 1000, 1000]` is the full frame. The grid stretches with the frame, so on a wide picture one unit of x is longer than one unit of y. Size each text box for its words at a readable size, larger for longer text, and keep margins from the edges. Boxes may overlap where elements overlap, such as a title over a background picture; a box marks placement and draws no frame. +MOSS-Image can also draw from `moss_layout`: the canvas in pixels, a summary, and a box for each visible unit. +1. `moss_kind` names the request: "poster" for a poster, flyer, banner, or advertisement, "comic" for a comic or storyboard, "meme", "infographic" for an infographic, chart, or diagram, "arrangement" for objects in a counted grid or an exact arrangement the request gives, "card" for a greeting card or invitation, "cover" for a book or album cover, "menu", "other_design" for another designed image such as a label, packaging, or a screen, and "scene" for everything else, also a photograph, painting, or illustration with a sign or label in it. +2. `moss_layout` is null unless the kind is "poster", "comic", "meme", "infographic", or "arrangement". Then the MOSS-Image prompt shows the same picture, with positions that agree with the boxes. +3. `canvas_width` and `canvas_height` are the canvas in pixels for the chosen `aspect_ratio`: {{#each moss_canvases}}{{this.ratio}} is {{this.width}} × {{this.height}}{{#unless @last}}, {{/unless}}{{/each}}. +4. `summary.caption` gives the whole picture and its global treatment: medium, style, background, palette, light, mood, arrangement, and reading order, leaving the exact wording to the text items. `summary.meta` holds medium or genre tags, such as "poster". `summary.canvas` is "multi_panel" for panels, "collage" for a collage, otherwise "single". +5. `items` lists the visible units in reading order: each block of text, panel, main object or character, and icon or chart part that needs its own place. Each has a `label` ("text", "object", or "character"), a `caption` that describes its look as fully as the picture needs (for text: weight, style, case, size, colour, alignment, and the surface under it), and a `bbox`. Only a text item has `text`: the exact wording with `\n` at each line break, never a placeholder and never repeated in another item. +6. `bbox` is `[x_min, y_min, x_max, y_max]` in pixels from the top-left corner, x from 0 to `canvas_width` and y from 0 to `canvas_height`. Size each text box so its words fit their lines at a readable size, and keep margins from the edges. Boxes may overlap where the picture does; a box marks placement and draws no border or panel. {{/if}} {{/if}} @@ -191,11 +192,11 @@ The next example shows the answer for a single MOSS-Image slot. Request: записка на холодильнике: купить молоко и хлеб -{"aspect_ratio": "3:4", "input": "записка на холодильнике: купить молоко и хлеб", "nsfw_result": "safe", "outputs": ["A photograph of a white refrigerator door in a home kitchen, taken at eye level in soft daylight from a window on the left. In the center of the door, a square yellow sticky note is held at its top edge by a round red magnet. The note carries one line of handwritten Cyrillic letters in blue ballpoint ink, \"купить молоко и хлеб\", written slightly uphill from left to right across the middle of the paper, the letters thin and rounded with a small loop on each \"л\". The lower right corner of the note curls away from the door and casts a thin grey shadow on the glossy white enamel. Below the note, a child's crayon drawing of a red house under a yellow sun is pinned by a blue magnet. On the right, the edge of a wooden counter holds a ceramic bowl of green apples, softly out of focus against a pale tiled wall."], {{#if moss_layout}}"slot_moss_design": false, "slot_moss_layout": null, {{/if}}"slot_moss_negative": ""} +{"aspect_ratio": "3:4", "input": "записка на холодильнике: купить молоко и хлеб", {{#if moss_layout}}"moss_kind": "scene", "moss_layout": null, {{/if}}"nsfw_result": "safe", "outputs": ["A photograph of a white refrigerator door in a home kitchen, taken at eye level in soft daylight from a window on the left. In the center of the door, a square yellow sticky note is held at its top edge by a round red magnet. The note carries one line of handwritten Cyrillic letters in blue ballpoint ink, \"купить молоко и хлеб\", written slightly uphill from left to right across the middle of the paper, the letters thin and rounded with a small loop on each \"л\". The lower right corner of the note curls away from the door and casts a thin grey shadow on the glossy white enamel. Below the note, a child's crayon drawing of a red house under a yellow sun is pinned by a blue magnet. On the right, the edge of a wooden counter holds a ceramic bowl of green apples, softly out of focus against a pale tiled wall."], "slot_moss_negative": ""} Request: постер джазового фестиваля «Джаз у моря», 12–14 июля, Сочи, без людей -{"aspect_ratio": "3:4", "input": "постер джазового фестиваля «Джаз у моря», 12–14 июля, Сочи, без людей", "nsfw_result": "safe", "outputs": ["A flat vector illustration poster for a jazz festival, with a calm sea at dusk filling the lower half under a warm orange and violet sky. Across the top, the title \"Джаз\nу моря\" runs in two centered lines of large cream Cyrillic letters in a rounded bold display face, the second line slightly smaller than the first. In the center, a golden saxophone stands upright on a low sand dune at the waterline, its curved bell turned to the right and its keys catching small highlights. Below it, the dates \"12–14 июля\" sit on one centered line of medium white Cyrillic letters in a clean sans-serif, and the city name \"Сочи\" sits beneath them in small widely spaced teal Cyrillic letters. The sea is drawn in flat horizontal bands of teal and deep blue with thin white wave lines, and a low setting sun glows just above the horizon behind the saxophone. Smooth gradients, crisp edges, and a limited palette of cream, orange, violet, teal, and gold, with generous empty space around the lettering."], {{#if moss_layout}}"slot_moss_design": true, "slot_moss_layout": {"items": [{"bbox": [70, 120, 330, 880], "caption": "Large bold rounded cream display letters on two centered lines, the second line slightly smaller.", "label": "text", "text": "Джаз\nу моря"}, {"bbox": [340, 300, 760, 700], "caption": "A golden saxophone standing upright on a low sand dune at the waterline, bell turned right, keys with small highlights.", "label": "object"}, {"bbox": [790, 230, 860, 770], "caption": "Medium white clean sans-serif letters on one centered line.", "label": "text", "text": "12–14 июля"}, {"bbox": [880, 380, 930, 620], "caption": "Small widely spaced teal letters on one centered line.", "label": "text", "text": "Сочи"}], "summary": {"canvas": "single", "caption": "A flat vector jazz festival poster: a calm teal and deep blue sea at dusk in the lower half, a warm orange and violet sky above, a low sun on the horizon, a golden saxophone on a sand dune in the center, smooth gradients, crisp edges, and generous empty space around large cream and white lettering.", "meta": ["poster", "flat vector illustration"]}}, {{/if}}"slot_moss_negative": "people"} +{"aspect_ratio": "3:4", "input": "постер джазового фестиваля «Джаз у моря», 12–14 июля, Сочи, без людей", {{#if moss_layout}}"moss_kind": "poster", "moss_layout": {"canvas_height": 1184, "canvas_width": 880, "items": [{"bbox": [106, 83, 774, 391], "caption": "Large bold cream Cyrillic letters in a rounded display face on two centered lines, the second slightly smaller, on the dusk sky.", "label": "text", "text": "Джаз\nу моря"}, {"bbox": [264, 403, 616, 900], "caption": "A golden saxophone standing upright on a low sand dune at the waterline, bell turned right, keys catching small highlights.", "label": "object"}, {"bbox": [202, 935, 678, 1018], "caption": "Medium white clean sans-serif letters on one centered line over the sea.", "label": "text", "text": "12–14 июля"}, {"bbox": [334, 1042, 546, 1101], "caption": "Small widely spaced teal letters on one centered line.", "label": "text", "text": "Сочи"}], "summary": {"canvas": "single", "caption": "A flat vector jazz festival poster at dusk: a calm teal and deep blue sea in the lower half, a warm orange and violet sky, a low sun on the horizon, and a golden saxophone on a sand dune in the center. Smooth gradients, crisp edges, and generous empty space around the lettering; the title reads first, then the dates and the city.", "meta": ["poster", "flat vector illustration"]}}, {{/if}}"nsfw_result": "safe", "outputs": ["A flat vector illustration poster for a jazz festival, with a calm sea at dusk filling the lower half under a warm orange and violet sky. Across the top, the title \"Джаз\nу моря\" runs in two centered lines of large cream Cyrillic letters in a rounded bold display face, the second line slightly smaller than the first. In the center, a golden saxophone stands upright on a low sand dune at the waterline, its curved bell turned to the right and its keys catching small highlights. Below it, the dates \"12–14 июля\" sit on one centered line of medium white Cyrillic letters in a clean sans-serif, and the city name \"Сочи\" sits beneath them in small widely spaced teal Cyrillic letters. The sea is drawn in flat horizontal bands of teal and deep blue with thin white wave lines, and a low setting sun glows just above the horizon behind the saxophone. Smooth gradients, crisp edges, and a limited palette of cream, orange, violet, teal, and gold, with generous empty space around the lettering."], "slot_moss_negative": "people"} {{/if}} From e6098052607632c22b4df6a8fc56f5feb1fa955c Mon Sep 17 00:00:00 2001 From: Valeriy Selitskiy <239034+iamwavecut@users.noreply.github.com> Date: Wed, 21 Oct 2026 01:15:32 +0200 Subject: [PATCH 2/2] Bump rustls-webpki to 0.103.16 and webpki-root-certs to 1.0.10 cargo audit fails on RUSTSEC-2026-0382 and RUSTSEC-2026-0383, and the production deploy requires a green CI run on the merge commit. --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c8fd224c..b6f49197 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3028,9 +3028,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.15" +version = "0.103.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +checksum = "4443b1b6d39d7c663139942cc4558262562d2c84123bb6282ef177009ca8b281" dependencies = [ "aws-lc-rs", "ring", @@ -4283,9 +4283,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.7" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +checksum = "58ec80e73fd8baf57bf0d9615554c75348bda990d6d5bbeab964dd068319fecb" dependencies = [ "rustls-pki-types", ]