1
This commit is contained in:
@@ -48,6 +48,12 @@ const CHARACTER_VISUAL_ENTITY_KIND: &str = "character";
|
||||
const CHARACTER_VISUAL_SLOT: &str = "primary_visual";
|
||||
const CHARACTER_VISUAL_TASK_POLL_INTERVAL_MS: u64 = 2_500;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct GeneratedCharacterPrimaryVisual {
|
||||
pub image_src: String,
|
||||
pub asset_id: String,
|
||||
}
|
||||
|
||||
pub async fn generate_character_visual(
|
||||
State(state): State<AppState>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
@@ -277,6 +283,90 @@ pub async fn generate_character_visual(
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) async fn generate_character_primary_visual_for_profile(
|
||||
state: &AppState,
|
||||
owner_user_id: &str,
|
||||
character_id: &str,
|
||||
prompt_text: &str,
|
||||
character_brief_text: Option<&str>,
|
||||
) -> Result<GeneratedCharacterPrimaryVisual, AppError> {
|
||||
let payload = CharacterVisualGenerateRequest {
|
||||
character_id: character_id.to_string(),
|
||||
source_mode: shared_contracts::assets::CharacterVisualSourceMode::TextToImage,
|
||||
prompt_text: prompt_text.to_string(),
|
||||
character_brief_text: character_brief_text.map(ToOwned::to_owned),
|
||||
reference_image_data_urls: Vec::new(),
|
||||
candidate_count: 1,
|
||||
image_model: CHARACTER_VISUAL_MODEL.to_string(),
|
||||
size: "1024*1024".to_string(),
|
||||
};
|
||||
let task_id = generate_ai_task_id(current_utc_micros());
|
||||
let prompt = build_character_visual_prompt(
|
||||
payload.prompt_text.as_str(),
|
||||
payload.character_brief_text.as_deref(),
|
||||
);
|
||||
let character_id = normalize_required_text(payload.character_id.as_str(), "character");
|
||||
let model = normalize_required_text(payload.image_model.as_str(), CHARACTER_VISUAL_MODEL);
|
||||
let size = normalize_required_text(payload.size.as_str(), "1024*1024");
|
||||
create_visual_task(
|
||||
state,
|
||||
&task_id,
|
||||
owner_user_id,
|
||||
&character_id,
|
||||
&model,
|
||||
&prompt,
|
||||
)?;
|
||||
let settings = require_dashscope_settings(state)?;
|
||||
let http_client = build_dashscope_http_client(&settings)?;
|
||||
state
|
||||
.ai_task_service()
|
||||
.start_task(task_id.as_str(), current_utc_micros())
|
||||
.map_err(map_ai_task_error)?;
|
||||
let generated = create_character_visual_generation(
|
||||
&http_client,
|
||||
&settings,
|
||||
model.as_str(),
|
||||
prompt.as_str(),
|
||||
size.as_str(),
|
||||
1,
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
let drafts = persist_visual_drafts(
|
||||
state,
|
||||
owner_user_id,
|
||||
&character_id,
|
||||
&task_id,
|
||||
generated.images,
|
||||
size.as_str(),
|
||||
)
|
||||
.await?;
|
||||
let draft = drafts.into_iter().next().ok_or_else(|| {
|
||||
AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({
|
||||
"provider": "character-visual",
|
||||
"message": "角色主形象生成没有返回候选图。",
|
||||
}))
|
||||
})?;
|
||||
let asset_id = format!("visual-{character_id}-{task_id}");
|
||||
let image_src = persist_published_visual(
|
||||
state,
|
||||
owner_user_id,
|
||||
&character_id,
|
||||
asset_id.as_str(),
|
||||
draft.image_src.as_str(),
|
||||
Some(prompt.as_str()),
|
||||
)
|
||||
.await?;
|
||||
state
|
||||
.ai_task_service()
|
||||
.complete_task(task_id.as_str(), current_utc_micros())
|
||||
.map_err(map_ai_task_error)?;
|
||||
Ok(GeneratedCharacterPrimaryVisual {
|
||||
image_src,
|
||||
asset_id,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_character_visual_job(
|
||||
State(state): State<AppState>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
|
||||
Reference in New Issue
Block a user