Files
Genarrative/server-rs/crates/api-server/src/editor_generation_config.rs
T
kdletters d8dd2f1857 补齐外部OpenAPI编辑器素材能力
移除外部OpenAPI中的API Key管理接口暴露
补齐编辑器项目列表、最近项目、重命名和删除外部接口
补齐素材直传凭证、素材对象确认和签名读取外部接口
复用站内编辑器生成、素材库和项目资源链路并按API Key账号归属
更新外部OpenAPI、后端契约和项目共享记忆文档
2026-06-21 13:40:09 +08:00

235 lines
8.8 KiB
Rust

/// 图片画布编辑器生成类能力的泥点配置。
///
/// 中文注释:先用 api-server 静态配置收口价格事实源,避免继续把价格散落在
/// 前端常量和具体 handler 内;后续若接后台配置,可只替换本模块读取来源。
const EDITOR_IMAGE_MODEL_GPT_IMAGE_2: &str = "gpt-image-2";
const EDITOR_IMAGE_MODEL_NANOBANANA2: &str = "gemini-3.1-flash-image-preview";
const EDITOR_IMAGE_MODEL_NANOBANANA2_DISPLAY_ALIAS: &str = "nanobanana2";
const EDITOR_IMAGE_MODEL_NANOBANANA_LEGACY_ALIAS: &str = "nano-banana";
const EDITOR_IMAGE_MODEL_GENERATION_MUD_POINTS: u32 = 12;
const EDITOR_IMAGE_MODEL_GPT_IMAGE_2_MUD_POINTS: u32 = 20;
const EDITOR_SPEC_MODEL_GPT_IMAGE_2_MUD_POINTS: u32 = 5;
pub(crate) const EDITOR_VIDEO_GENERATION_480P_MUD_POINTS_PER_SECOND: u32 = 10;
pub(crate) const EDITOR_VIDEO_GENERATION_720P_MUD_POINTS_PER_SECOND: u32 = 20;
pub(crate) const EDITOR_CHARACTER_ANIMATION_480P_MUD_POINTS_PER_SECOND: u32 = 10;
pub(crate) const EDITOR_CHARACTER_ANIMATION_720P_MUD_POINTS_PER_SECOND: u32 = 20;
const EDITOR_VIDEO_MODEL_SEEDANCE_2: &str = "seedance2.0";
const EDITOR_VIDEO_MODEL_SEEDANCE_2_FAST: &str = "seedance2.0-fast";
const EDITOR_VIDEO_MODEL_KLING_3: &str = "kling3.0";
const EDITOR_VIDEO_MODEL_KLING_3_OMNI: &str = "kling3.0-omni";
const EDITOR_VIDEO_MODEL_VEO_3_1: &str = "veo3.1";
const EDITOR_VIDEO_MODEL_VEO_3_1_FAST: &str = "veo3.1-fast";
const EDITOR_CHARACTER_ANIMATION_MODEL_SEEDANCE_2_FAST: &str = "seedance2.0-fast";
pub(crate) const EDITOR_SOUND_EFFECT_MODEL_VIDU: &str = "audio1.0";
pub(crate) const EDITOR_BACKGROUND_MUSIC_MODEL_SUNO: &str = "chirp-v5";
const EDITOR_SOUND_EFFECT_MODEL_VIDU_MUD_POINTS: u32 = 10;
const EDITOR_BACKGROUND_MUSIC_MODEL_SUNO_MUD_POINTS: u32 = 5;
pub(crate) fn editor_image_model_generation_mud_points(model: Option<&str>) -> u32 {
match model.map(str::trim).filter(|value| !value.is_empty()) {
Some(EDITOR_IMAGE_MODEL_GPT_IMAGE_2) => EDITOR_IMAGE_MODEL_GPT_IMAGE_2_MUD_POINTS,
Some(EDITOR_IMAGE_MODEL_NANOBANANA2)
| Some(EDITOR_IMAGE_MODEL_NANOBANANA2_DISPLAY_ALIAS)
| Some(EDITOR_IMAGE_MODEL_NANOBANANA_LEGACY_ALIAS) => {
EDITOR_IMAGE_MODEL_GENERATION_MUD_POINTS
}
_ => EDITOR_IMAGE_MODEL_GENERATION_MUD_POINTS,
}
}
pub(crate) fn editor_spec_model_generation_mud_points(model: Option<&str>) -> u32 {
match model.map(str::trim).filter(|value| !value.is_empty()) {
Some(EDITOR_IMAGE_MODEL_GPT_IMAGE_2) => EDITOR_SPEC_MODEL_GPT_IMAGE_2_MUD_POINTS,
_ => EDITOR_SPEC_MODEL_GPT_IMAGE_2_MUD_POINTS,
}
}
pub(crate) fn editor_image_generation_mud_points(kind: Option<&str>, model: Option<&str>) -> u32 {
match kind.map(str::trim) {
Some("spec") => editor_spec_model_generation_mud_points(model),
Some("character" | "icon" | "ui-design" | "publication-material") => {
editor_image_model_generation_mud_points(model)
}
_ => editor_image_model_generation_mud_points(model),
}
}
fn editor_video_model_resolution_mud_points_per_second(
model: Option<&str>,
resolution: &str,
) -> u32 {
let normalized_model = match model.map(str::trim).filter(|value| !value.is_empty()) {
Some(
EDITOR_VIDEO_MODEL_SEEDANCE_2
| EDITOR_VIDEO_MODEL_SEEDANCE_2_FAST
| EDITOR_VIDEO_MODEL_KLING_3
| EDITOR_VIDEO_MODEL_KLING_3_OMNI
| EDITOR_VIDEO_MODEL_VEO_3_1
| EDITOR_VIDEO_MODEL_VEO_3_1_FAST,
) => model.unwrap_or_default().trim(),
_ => EDITOR_VIDEO_MODEL_SEEDANCE_2_FAST,
};
let (rate_480p, rate_720p) = match normalized_model {
EDITOR_VIDEO_MODEL_SEEDANCE_2 => (12, 24),
EDITOR_VIDEO_MODEL_KLING_3 => (15, 30),
EDITOR_VIDEO_MODEL_KLING_3_OMNI => (20, 40),
_ => (
EDITOR_VIDEO_GENERATION_480P_MUD_POINTS_PER_SECOND,
EDITOR_VIDEO_GENERATION_720P_MUD_POINTS_PER_SECOND,
),
};
if resolution == "720p" {
rate_720p
} else {
rate_480p
}
}
pub(crate) fn editor_video_model_generation_mud_points(
model: Option<&str>,
resolution: &str,
duration_seconds: u32,
) -> u32 {
let per_second = editor_video_model_resolution_mud_points_per_second(model, resolution);
per_second * duration_seconds
}
pub(crate) fn editor_video_generation_mud_points(resolution: &str, duration_seconds: u32) -> u32 {
let per_second = editor_video_model_resolution_mud_points_per_second(None, resolution);
per_second * duration_seconds
}
pub(crate) fn editor_character_animation_model_mud_points(
model: Option<&str>,
resolution: &str,
duration_seconds: u32,
) -> u32 {
let _normalized_model = match model.map(str::trim).filter(|value| !value.is_empty()) {
Some(EDITOR_CHARACTER_ANIMATION_MODEL_SEEDANCE_2_FAST) => {
EDITOR_CHARACTER_ANIMATION_MODEL_SEEDANCE_2_FAST
}
_ => EDITOR_CHARACTER_ANIMATION_MODEL_SEEDANCE_2_FAST,
};
let per_second = if resolution == "720p" {
EDITOR_CHARACTER_ANIMATION_720P_MUD_POINTS_PER_SECOND
} else {
EDITOR_CHARACTER_ANIMATION_480P_MUD_POINTS_PER_SECOND
};
per_second * duration_seconds
}
pub(crate) fn editor_character_animation_mud_points(
resolution: &str,
duration_seconds: u32,
) -> u32 {
editor_character_animation_model_mud_points(None, resolution, duration_seconds)
}
pub(crate) fn editor_sound_effect_model_generation_mud_points(model: Option<&str>) -> u32 {
match model.map(str::trim).filter(|value| !value.is_empty()) {
Some(EDITOR_SOUND_EFFECT_MODEL_VIDU) => EDITOR_SOUND_EFFECT_MODEL_VIDU_MUD_POINTS,
_ => EDITOR_SOUND_EFFECT_MODEL_VIDU_MUD_POINTS,
}
}
pub(crate) fn editor_background_music_model_generation_mud_points(model: Option<&str>) -> u32 {
match model.map(str::trim).filter(|value| !value.is_empty()) {
Some(EDITOR_BACKGROUND_MUSIC_MODEL_SUNO) => EDITOR_BACKGROUND_MUSIC_MODEL_SUNO_MUD_POINTS,
_ => EDITOR_BACKGROUND_MUSIC_MODEL_SUNO_MUD_POINTS,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn editor_character_animation_price_uses_configured_resolution_rates() {
assert_eq!(editor_character_animation_mud_points("480p", 4), 40);
assert_eq!(editor_character_animation_mud_points("720p", 6), 120);
}
#[test]
fn editor_video_generation_price_uses_configured_resolution_rates() {
assert_eq!(editor_video_generation_mud_points("480p", 4), 40);
assert_eq!(editor_video_generation_mud_points("720p", 5), 100);
}
#[test]
fn editor_video_generation_price_uses_configured_model_rates() {
assert_eq!(
editor_video_model_generation_mud_points(Some("seedance2.0-fast"), "480p", 4),
40
);
assert_eq!(
editor_video_model_generation_mud_points(Some("seedance2.0"), "720p", 5),
120
);
assert_eq!(
editor_video_model_generation_mud_points(Some("kling3.0"), "480p", 5),
75
);
assert_eq!(
editor_video_model_generation_mud_points(Some("kling3.0-omni"), "720p", 4),
160
);
assert_eq!(
editor_video_model_generation_mud_points(Some("veo3.1"), "480p", 4),
40
);
assert_eq!(
editor_video_model_generation_mud_points(Some("veo3.1-fast"), "720p", 4),
80
);
}
#[test]
fn editor_audio_generation_price_uses_configured_model_rates() {
assert_eq!(
editor_sound_effect_model_generation_mud_points(Some("audio1.0")),
10
);
assert_eq!(
editor_background_music_model_generation_mud_points(Some("chirp-v5")),
5
);
}
#[test]
fn editor_image_generation_price_uses_configured_kind_rates() {
assert_eq!(editor_image_generation_mud_points(None, None), 12);
assert_eq!(editor_image_generation_mud_points(Some("spec"), None), 5);
assert_eq!(
editor_image_generation_mud_points(Some("character"), None),
12
);
assert_eq!(editor_image_generation_mud_points(Some("icon"), None), 12);
assert_eq!(
editor_image_generation_mud_points(Some("ui-design"), None),
12
);
assert_eq!(
editor_image_generation_mud_points(
Some("publication-material"),
Some("gemini-3.1-flash-image-preview")
),
12
);
assert_eq!(
editor_image_generation_mud_points(Some("publication-material"), Some("nanobanana2")),
12
);
assert_eq!(
editor_image_generation_mud_points(Some("publication-material"), Some("nano-banana")),
12
);
assert_eq!(
editor_image_generation_mud_points(Some("publication-material"), Some("gpt-image-2")),
20
);
}
}