Files
Genarrative/server-rs/crates/api-server/src/prompt/scene_background.rs
kdletters cbc27bad4a
Some checks failed
CI / verify (push) Has been cancelled
init with react+axum+spacetimedb
2026-04-26 18:06:23 +08:00

167 lines
6.8 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#[derive(Clone, Debug, Default)]
pub(crate) struct SceneImagePromptProfile<'a> {
pub name: &'a str,
pub subtitle: &'a str,
pub tone: &'a str,
pub player_goal: &'a str,
pub summary: &'a str,
pub setting_text: &'a str,
}
#[derive(Clone, Debug, Default)]
pub(crate) struct SceneImagePromptLandmark<'a> {
pub name: &'a str,
pub description: &'a str,
}
#[derive(Clone, Debug)]
pub(crate) struct SceneImagePromptParams<'a> {
pub profile: SceneImagePromptProfile<'a>,
pub landmark: SceneImagePromptLandmark<'a>,
pub user_prompt: &'a str,
pub has_reference_image: bool,
pub fallback_landmark_name: Option<&'a str>,
pub fallback_world_name: &'a str,
}
#[derive(Clone, Debug)]
pub(crate) struct SceneActBackgroundPromptParams<'a> {
pub world_name: &'a str,
pub world_tone: &'a str,
pub scene_name: &'a str,
pub title: &'a str,
pub summary: &'a str,
pub act_goal: &'a str,
pub transition_hook: &'a str,
pub primary_role_name: &'a str,
pub support_role_names: Vec<String>,
pub prompt_text: &'a str,
}
pub(crate) const DEFAULT_CUSTOM_WORLD_SCENE_IMAGE_NEGATIVE_PROMPT: &str = "文字水印logoUI界面对话框边框人物近景特写多人合照模糊低清晰度畸形建筑现代车辆监控摄像头";
pub(crate) fn build_custom_world_scene_image_prompt(params: SceneImagePromptParams<'_>) -> String {
let world_name = clamp_scene_image_text(
if params.profile.name.trim().is_empty() {
params.fallback_world_name
} else {
params.profile.name
},
18,
);
let world_subtitle = clamp_scene_image_text(params.profile.subtitle, 18);
let world_tone = clamp_scene_image_text(params.profile.tone, 48);
let world_goal = clamp_scene_image_text(params.profile.player_goal, 48);
let world_summary = clamp_scene_image_text(params.profile.summary, 72);
let world_setting = clamp_scene_image_text(params.profile.setting_text, 72);
let landmark_name = clamp_scene_image_text(
if params.landmark.name.trim().is_empty() {
params.fallback_landmark_name.unwrap_or("未命名场景")
} else {
params.landmark.name
},
18,
);
let landmark_description = clamp_scene_image_text(params.landmark.description, 96);
let requested_visual = clamp_scene_image_text(params.user_prompt, 120);
vec![
"为横版 16:9 2D RPG 生成高完成度像素风场景背景,适合作为剧情探索与战斗底图。".to_string(),
"画面构图必须严格按上下 1:1 分区:上半部分严格控制在整张图的 1/2 高度内,只描绘场景远景与中远景轮廓,不要让背景内容向下侵占超过半屏。".to_string(),
"下半部分严格占据整张图的 1/2 高度,用于玩家角色站位与展示,必须是模拟 3D 游戏视角的地面近景,有明确的透视延伸和近大远小关系,不是平铺的 2D 侧视地面。".to_string(),
"下半部分的内容必须是明确可站立的地面本体,例如道路、石板、平台、广场、甲板、沙地或草地,要有连续、稳定、可落脚的站位逻辑,不能只是装饰性前景、坑洞、障碍堆、栏杆带或不可通行的景物。".to_string(),
"下半部分地面近景要保持相对简洁、低细节、轮廓清楚、便于角色站立,不要堆满道具、植被、碎石、栏杆或复杂装饰。".to_string(),
if params.has_reference_image {
"已提供一张自定义参考图,请沿用其构图、镜头或氛围线索,同时继续满足本次场景需求。".to_string()
} else {
String::new()
},
format!(
"世界:{}{}",
if world_name.is_empty() {
"未命名世界"
} else {
world_name.as_str()
},
if world_subtitle.is_empty() {
String::new()
} else {
format!("{world_subtitle}")
}
),
conditional_prompt_line("玩家设定", world_setting.as_str()),
conditional_prompt_line("世界概述", world_summary.as_str()),
conditional_prompt_line("整体基调", world_tone.as_str()),
conditional_prompt_line("玩家目标关联", world_goal.as_str()),
format!(
"场景名称:{}",
if landmark_name.is_empty() {
"未命名场景"
} else {
landmark_name.as_str()
}
),
conditional_prompt_line("场景描述", landmark_description.as_str()),
conditional_prompt_line("本次想要生成的画面内容", requested_visual.as_str()),
"不要出现 UI、字幕、文字、水印、logo 或装饰边框,人物仅可作为很小的远景剪影,画面重点放在场景本身,不要遮挡下半部分的角色展示区域。".to_string(),
]
.into_iter()
.filter(|line| !line.is_empty())
.collect::<Vec<_>>()
.join("")
}
pub(crate) fn build_scene_act_background_image_prompt(
params: SceneActBackgroundPromptParams<'_>,
) -> String {
// 幕背景图不是普通地点图,必须把世界、幕目标、过渡钩子和角色关系一起写入图像提示词,
// 同时明确禁止角色立绘和 UI 元素进入背景资产。
[
format!("这是世界《{}》中的场景幕背景图。", params.world_name),
format!("场景:{}", params.scene_name),
format!("幕标题:{}", params.title),
format!("幕摘要:{}", params.summary),
format!("幕目标:{}", params.act_goal),
format!("过渡钩子:{}", params.transition_hook),
format!(
"主角色:{}",
if params.primary_role_name.trim().is_empty() {
"待补主角色"
} else {
params.primary_role_name.trim()
}
),
if params.support_role_names.is_empty() {
String::new()
} else {
format!("辅助角色:{}", params.support_role_names.join(""))
},
format!("世界气质:{}", params.world_tone),
format!("背景描述:{}", params.prompt_text),
"要求:只生成环境背景,不出现角色立绘、站位 UI、对白框、按钮或文字。".to_string(),
]
.into_iter()
.filter(|line| !line.trim().is_empty())
.collect::<Vec<_>>()
.join("\n")
}
fn clamp_scene_image_text(value: &str, max_length: usize) -> String {
value
.trim()
.replace(char::is_whitespace, " ")
.chars()
.take(max_length)
.collect::<String>()
.trim()
.to_string()
}
fn conditional_prompt_line(prefix: &str, value: &str) -> String {
if value.is_empty() {
String::new()
} else {
format!("{prefix}{value}")
}
}