use super::error::GeneratedAssetSheetError; #[derive(Clone, Debug, PartialEq, Eq)] pub struct GeneratedAssetSheetPromptInput<'a> { pub subject_text: &'a str, pub item_names: &'a [String], pub grid_size: usize, pub item_name_prompt_template: Option<&'a str>, pub special_prompt: Option<&'a str>, } pub fn build_generated_asset_sheet_prompt( input: &GeneratedAssetSheetPromptInput<'_>, ) -> Result { let grid_size = input.grid_size; if grid_size == 0 { return Err(GeneratedAssetSheetError::invalid_request( "系列素材图集的 n 必须大于 0。", )); } if input.item_names.len() > grid_size { return Err(GeneratedAssetSheetError::invalid_request(format!( "系列素材图集的物品行数不能超过 n。gridSize={grid_size}, itemCount={}", input.item_names.len() ))); } let subject_text = input.subject_text.trim(); let subject_text = if subject_text.is_empty() { "系列素材" } else { subject_text }; let item_rows = input .item_names .iter() .enumerate() .map(|(index, item_name)| { let row_index = index + 1; let item_name = item_name.trim(); if let Some(template) = input .item_name_prompt_template .map(str::trim) .filter(|value| !value.is_empty()) { return template .replace("{row_index}", row_index.to_string().as_str()) .replace("{item_name}", item_name) .replace("{view_count}", grid_size.to_string().as_str()); } format!("第{row_index}行:{item_name} 的 {grid_size} 个不同视图") }) .collect::>() .join(";"); let special_prompt = input .special_prompt .map(str::trim) .filter(|value| !value.is_empty()) .map(str::to_string) .unwrap_or_else(|| format!("每个物品生成 {grid_size} 个不同视图。")); Ok(format!( "生成一张1:1图片。固定生成{grid_size}行*{grid_size}列网格素材图,画面是{subject_text}。严格{grid_size}*{grid_size}均匀排布,严格按行组织:{item_rows}。{special_prompt}每个格子一个独立居中的完整素材,每格背景必须是统一纯绿色绿幕背景(高饱和亮绿色,接近 #00FF00),背景平整无纹理、无渐变、无阴影、无道具,方便后续抠成透明。素材本身不得使用与绿幕相同的纯绿色;若素材天然含绿色,必须使用更深、更黄或更蓝的绿色并用清晰描边与绿幕区分。统一柔和光照,清晰轮廓,适合直接切割成游戏2D素材。请让每个素材完整落在自己的格子中央,四周保留留白,相邻素材主体之间必须至少保留单个素材格宽度的1/4空白间距(约25%单格宽度),包含左右相邻格和上下相邻行,素材主体不得占满格子。禁止主体跨格、贴边或越界,禁止任何内容进入相邻格子影响裁剪后的效果。不要出现文字、水印、UI、边框、网格线、标签、底座、场景或其他物体。" )) }