1
This commit is contained in:
106
server-rs/crates/api-server/src/prompt/puzzle/image.rs
Normal file
106
server-rs/crates/api-server/src/prompt/puzzle/image.rs
Normal file
@@ -0,0 +1,106 @@
|
||||
/// 拼图图片生成的默认反向提示词。
|
||||
///
|
||||
/// 这里单独收口拼图图片提示词,避免图片生成链路、候选图持久化和 DashScope 请求编排
|
||||
/// 混在同一个脚本里,后续调画风或资产约束时只需要改这一处。
|
||||
pub(crate) const PUZZLE_DEFAULT_NEGATIVE_PROMPT: &str =
|
||||
"低清晰度,低质量,文字水印,畸形构图,过度模糊,重复肢体,画面脏污";
|
||||
|
||||
/// wan2.2 / wan2.1 文生图旧协议的正向 prompt 上限。
|
||||
///
|
||||
/// 中文注释:DashScope 旧 text2image 接口会把超长 prompt 判成请求参数不合法,
|
||||
/// 所以这里先在拼图提示词模块内压缩,保证固定玩法约束不会被用户长描述挤掉。
|
||||
pub(crate) const PUZZLE_TEXT_TO_IMAGE_PROMPT_MAX_CHARS: usize = 500;
|
||||
|
||||
const PUZZLE_IMAGE_LEVEL_NAME_MAX_CHARS: usize = 40;
|
||||
const PUZZLE_IMAGE_PROMPT_FALLBACK: &str = "清晰、有辨识度的拼图画面";
|
||||
|
||||
/// 根据拼图关卡名和陶泥主输入构造最终发给图片模型的提示词。
|
||||
pub(crate) fn build_puzzle_image_prompt(level_name: &str, prompt: &str) -> String {
|
||||
let level_name =
|
||||
truncate_puzzle_prompt_segment(level_name.trim(), PUZZLE_IMAGE_LEVEL_NAME_MAX_CHARS);
|
||||
let prompt = prompt.trim();
|
||||
let prompt = if prompt.is_empty() {
|
||||
PUZZLE_IMAGE_PROMPT_FALLBACK
|
||||
} else {
|
||||
prompt
|
||||
};
|
||||
let template_chars = build_puzzle_image_prompt_text(level_name.as_str(), "")
|
||||
.chars()
|
||||
.count();
|
||||
let prompt_max_chars = PUZZLE_TEXT_TO_IMAGE_PROMPT_MAX_CHARS.saturating_sub(template_chars);
|
||||
let prompt = truncate_puzzle_prompt_segment(prompt, prompt_max_chars);
|
||||
let image_prompt = build_puzzle_image_prompt_text(level_name.as_str(), prompt.as_str());
|
||||
|
||||
debug_assert!(
|
||||
image_prompt.chars().count() <= PUZZLE_TEXT_TO_IMAGE_PROMPT_MAX_CHARS,
|
||||
"puzzle image prompt should fit DashScope wan2.2 limit"
|
||||
);
|
||||
image_prompt
|
||||
}
|
||||
|
||||
fn build_puzzle_image_prompt_text(level_name: &str, prompt: &str) -> String {
|
||||
format!(
|
||||
concat!(
|
||||
"请生成一张高清插画。",
|
||||
"关卡名:{level_name}。",
|
||||
"画面主体:{prompt}。",
|
||||
"画面要求:1:1 正方形拼图关卡,适配 3x3 或 4x4 拼图切块,",
|
||||
"主体要清晰集中,前中后景层次明确,局部细节丰富但不要杂乱,",
|
||||
"避免文字、水印、边框和 UI 元素。"
|
||||
),
|
||||
level_name = level_name,
|
||||
prompt = prompt,
|
||||
)
|
||||
}
|
||||
|
||||
fn truncate_puzzle_prompt_segment(value: &str, max_chars: usize) -> String {
|
||||
if value.chars().count() <= max_chars {
|
||||
return value.to_string();
|
||||
}
|
||||
|
||||
const MARKER: &str = "...";
|
||||
if max_chars <= MARKER.chars().count() {
|
||||
return value.chars().take(max_chars).collect();
|
||||
}
|
||||
|
||||
let keep_chars = max_chars - MARKER.chars().count();
|
||||
format!(
|
||||
"{}{MARKER}",
|
||||
value.chars().take(keep_chars).collect::<String>()
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn build_puzzle_image_prompt_keeps_puzzle_asset_constraints() {
|
||||
let prompt = build_puzzle_image_prompt("雨夜神庙", "猫咪在发光遗迹前寻找线索");
|
||||
|
||||
assert!(prompt.contains("雨夜神庙"));
|
||||
assert!(prompt.contains("猫咪在发光遗迹前寻找线索"));
|
||||
assert!(prompt.contains("1:1 正方形拼图关卡"));
|
||||
assert!(prompt.contains("3x3 或 4x4"));
|
||||
assert!(prompt.contains("避免文字、水印、边框和 UI 元素"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_puzzle_image_prompt_trims_long_user_description_for_wan22() {
|
||||
let long_level_name = "雨夜神庙".repeat(20);
|
||||
let long_description =
|
||||
"发光遗迹、猫咪、漂浮碎片、雨水反光、远处灯塔、适合拼图切块。".repeat(50);
|
||||
let prompt = build_puzzle_image_prompt(long_level_name.as_str(), long_description.as_str());
|
||||
|
||||
assert!(prompt.chars().count() <= PUZZLE_TEXT_TO_IMAGE_PROMPT_MAX_CHARS);
|
||||
assert!(prompt.contains("1:1 正方形拼图关卡"));
|
||||
assert!(prompt.contains("3x3 或 4x4"));
|
||||
assert!(prompt.contains("避免文字、水印、边框和 UI 元素"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_negative_prompt_blocks_text_and_low_quality_assets() {
|
||||
assert!(PUZZLE_DEFAULT_NEGATIVE_PROMPT.contains("低清晰度"));
|
||||
assert!(PUZZLE_DEFAULT_NEGATIVE_PROMPT.contains("文字水印"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user