This commit is contained in:
2026-05-15 06:36:48 +08:00
620 changed files with 126075 additions and 6322 deletions

View File

@@ -0,0 +1,529 @@
use super::*;
pub(super) fn map_match3d_agent_session_response(
session: Match3DAgentSessionRecord,
) -> Match3DAgentSessionSnapshotResponse {
Match3DAgentSessionSnapshotResponse {
session_id: session.session_id,
current_turn: session.current_turn,
progress_percent: session.progress_percent,
stage: session.stage.clone(),
anchor_pack: map_match3d_anchor_pack_response_for_turn(
session.anchor_pack,
session.current_turn,
session.stage.as_str(),
),
config: session.config.map(map_match3d_config_response),
draft: session.draft.map(map_match3d_draft_response),
messages: session
.messages
.into_iter()
.map(map_match3d_message_response)
.collect(),
last_assistant_reply: session.last_assistant_reply,
published_profile_id: session.published_profile_id,
updated_at: session.updated_at,
}
}
pub(super) fn map_match3d_agent_session_response_with_assets(
session: Match3DAgentSessionRecord,
generated_item_assets: &[Match3DGeneratedItemAsset],
) -> Match3DAgentSessionSnapshotResponse {
let mut response = map_match3d_agent_session_response(session);
if let Some(draft) = response.draft.as_mut() {
draft.generated_item_assets = generated_item_assets
.iter()
.cloned()
.map(map_match3d_generated_item_asset_for_agent)
.collect();
if draft
.cover_image_src
.as_deref()
.map(str::trim)
.unwrap_or_default()
.is_empty()
{
draft.cover_image_src = resolve_match3d_default_cover_image_src(generated_item_assets);
}
let background_asset = find_match3d_generated_background_asset(generated_item_assets);
apply_match3d_background_asset_to_agent_draft(draft, background_asset);
}
response
}
pub(super) fn map_match3d_anchor_pack_response_for_turn(
anchor: Match3DAnchorPackRecord,
current_turn: u32,
stage: &str,
) -> Match3DAnchorPackResponse {
let is_ready = matches!(
stage,
"ReadyToCompile"
| "ready_to_compile"
| "DraftCompiled"
| "draft_compiled"
| "draft_ready"
| "ReadyToPublish"
| "ready_to_publish"
| "Published"
| "published"
);
let collected_count = if is_ready { 3 } else { current_turn.min(3) };
Match3DAnchorPackResponse {
theme: map_match3d_anchor_item_response_for_collected(anchor.theme, collected_count >= 1),
clear_count: map_match3d_anchor_item_response_for_collected(
anchor.clear_count,
collected_count >= 2,
),
difficulty: map_match3d_anchor_item_response_for_collected(
anchor.difficulty,
collected_count >= 3,
),
}
}
pub(super) fn map_match3d_anchor_item_response(
anchor: Match3DAnchorItemRecord,
) -> Match3DAnchorItemResponse {
Match3DAnchorItemResponse {
key: anchor.key,
label: anchor.label,
value: anchor.value,
status: anchor.status,
}
}
pub(super) fn map_match3d_anchor_item_response_for_collected(
anchor: Match3DAnchorItemRecord,
collected: bool,
) -> Match3DAnchorItemResponse {
if collected {
return map_match3d_anchor_item_response(anchor);
}
Match3DAnchorItemResponse {
key: anchor.key,
label: anchor.label,
value: String::new(),
status: "missing".to_string(),
}
}
pub(super) fn map_match3d_config_response(
config: Match3DCreatorConfigRecord,
) -> Match3DCreatorConfigResponse {
Match3DCreatorConfigResponse {
theme_text: config.theme_text,
reference_image_src: config.reference_image_src,
clear_count: config.clear_count,
difficulty: config.difficulty,
asset_style_id: config.asset_style_id,
asset_style_label: config.asset_style_label,
asset_style_prompt: config.asset_style_prompt,
generate_click_sound: config.generate_click_sound,
}
}
pub(super) fn map_match3d_draft_response(
draft: Match3DResultDraftRecord,
) -> Match3DResultDraftResponse {
Match3DResultDraftResponse {
profile_id: draft.profile_id,
game_name: draft.game_name,
theme_text: draft.theme_text,
summary_text: Some(draft.summary_text.clone()),
summary: draft.summary_text,
tags: draft.tags,
cover_image_src: draft.cover_image_src,
reference_image_src: draft.reference_image_src,
clear_count: draft.clear_count,
difficulty: draft.difficulty,
total_item_count: draft.total_item_count,
publish_ready: draft.publish_ready,
blockers: draft.blockers,
background_prompt: None,
background_image_src: None,
background_image_object_key: None,
generated_background_asset: None,
generated_item_assets: Vec::new(),
}
}
pub(super) fn map_match3d_generated_item_asset_for_agent(
asset: Match3DGeneratedItemAsset,
) -> Match3DAgentGeneratedItemAssetResponse {
Match3DAgentGeneratedItemAssetResponse {
item_id: asset.item_id,
item_name: asset.item_name,
item_size: asset.item_size,
image_src: asset.image_src,
image_object_key: asset.image_object_key,
image_views: asset
.image_views
.into_iter()
.map(map_match3d_image_view_for_agent)
.collect(),
model_src: asset.model_src,
model_object_key: asset.model_object_key,
model_file_name: asset.model_file_name,
task_uuid: asset.task_uuid,
subscription_key: asset.subscription_key,
sound_prompt: asset.sound_prompt,
background_music_title: asset.background_music_title,
background_music_style: asset.background_music_style,
background_music_prompt: asset.background_music_prompt,
background_music: asset.background_music,
click_sound: asset.click_sound,
background_asset: asset
.background_asset
.map(map_match3d_background_asset_for_agent),
status: asset.status,
error: asset.error,
}
}
pub(super) fn map_match3d_generated_item_asset_for_work(
asset: Match3DGeneratedItemAssetJson,
) -> shared_contracts::match3d_works::Match3DGeneratedItemAssetResponse {
shared_contracts::match3d_works::Match3DGeneratedItemAssetResponse {
item_id: asset.item_id,
item_name: asset.item_name,
item_size: asset
.item_size
.or_else(|| Some(MATCH3D_ITEM_SIZE_LARGE.to_string())),
image_src: asset.image_src,
image_object_key: asset.image_object_key,
image_views: asset
.image_views
.into_iter()
.map(map_match3d_image_view_for_work)
.collect(),
model_src: asset.model_src,
model_object_key: asset.model_object_key,
model_file_name: asset.model_file_name,
task_uuid: asset.task_uuid,
subscription_key: asset.subscription_key,
sound_prompt: asset.sound_prompt,
background_music_title: asset.background_music_title,
background_music_style: asset.background_music_style,
background_music_prompt: asset.background_music_prompt,
background_music: asset.background_music,
click_sound: asset.click_sound,
background_asset: asset
.background_asset
.map(map_match3d_background_asset_for_work),
status: asset.status,
error: asset.error,
}
}
pub(super) fn map_match3d_image_view_for_agent(
view: Match3DGeneratedItemImageView,
) -> shared_contracts::match3d_agent::Match3DGeneratedItemImageViewResponse {
shared_contracts::match3d_agent::Match3DGeneratedItemImageViewResponse {
view_id: view.view_id,
view_index: view.view_index,
image_src: view.image_src,
image_object_key: view.image_object_key,
}
}
pub(super) fn map_match3d_image_view_for_work(
view: Match3DGeneratedItemImageView,
) -> shared_contracts::match3d_works::Match3DGeneratedItemImageViewResponse {
shared_contracts::match3d_works::Match3DGeneratedItemImageViewResponse {
view_id: view.view_id,
view_index: view.view_index,
image_src: view.image_src,
image_object_key: view.image_object_key,
}
}
pub(super) fn map_match3d_image_view_from_work(
view: shared_contracts::match3d_works::Match3DGeneratedItemImageViewResponse,
) -> Match3DGeneratedItemImageView {
Match3DGeneratedItemImageView {
view_id: view.view_id,
view_index: view.view_index,
image_src: view.image_src,
image_object_key: view.image_object_key,
}
}
pub(super) fn map_match3d_background_asset_for_agent(
asset: Match3DGeneratedBackgroundAsset,
) -> shared_contracts::match3d_agent::Match3DGeneratedBackgroundAssetResponse {
shared_contracts::match3d_agent::Match3DGeneratedBackgroundAssetResponse {
prompt: asset.prompt,
image_src: asset.image_src,
image_object_key: asset.image_object_key,
container_prompt: asset.container_prompt,
container_image_src: asset.container_image_src,
container_image_object_key: asset.container_image_object_key,
status: asset.status,
error: asset.error,
}
}
pub(super) fn map_match3d_background_asset_for_work(
asset: Match3DGeneratedBackgroundAsset,
) -> shared_contracts::match3d_works::Match3DGeneratedBackgroundAssetResponse {
shared_contracts::match3d_works::Match3DGeneratedBackgroundAssetResponse {
prompt: asset.prompt,
image_src: asset.image_src,
image_object_key: asset.image_object_key,
container_prompt: asset.container_prompt,
container_image_src: asset.container_image_src,
container_image_object_key: asset.container_image_object_key,
status: asset.status,
error: asset.error,
}
}
pub(super) fn find_match3d_generated_background_asset(
assets: &[Match3DGeneratedItemAsset],
) -> Option<Match3DGeneratedBackgroundAsset> {
assets
.iter()
.find_map(|asset| asset.background_asset.clone())
}
pub(super) fn resolve_match3d_default_cover_image_src(
assets: &[Match3DGeneratedItemAsset],
) -> Option<String> {
find_match3d_generated_background_asset(assets).and_then(|asset| {
asset
.container_image_src
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
.map(str::to_string)
.or_else(|| {
asset
.container_image_object_key
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
.map(str::to_string)
})
.or_else(|| {
asset
.image_src
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
.map(str::to_string)
})
.or_else(|| {
asset
.image_object_key
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
.map(str::to_string)
})
})
}
pub(super) fn find_match3d_generated_background_asset_json(
assets: &[Match3DGeneratedItemAssetJson],
) -> Option<Match3DGeneratedBackgroundAsset> {
assets
.iter()
.find_map(|asset| asset.background_asset.clone())
}
pub(super) fn apply_match3d_background_asset_to_agent_draft(
draft: &mut Match3DResultDraftResponse,
background_asset: Option<Match3DGeneratedBackgroundAsset>,
) {
if let Some(asset) = background_asset {
draft.background_prompt = Some(asset.prompt.clone());
draft.background_image_src = asset.image_src.clone();
draft.background_image_object_key = asset.image_object_key.clone();
draft.generated_background_asset = Some(map_match3d_background_asset_for_agent(asset));
}
}
pub(super) fn build_match3d_work_profile_record_with_assets(
mut item: Match3DWorkProfileRecord,
assets: &[Match3DGeneratedItemAsset],
) -> Match3DWorkProfileRecord {
item.generated_item_assets_json = serialize_match3d_generated_item_assets(assets);
if let Some(background_asset) = find_match3d_generated_background_asset(assets) {
item.cover_image_src = item.cover_image_src.or_else(|| {
background_asset
.container_image_src
.clone()
.or(background_asset.container_image_object_key.clone())
.or(background_asset.image_src.clone())
.or(background_asset.image_object_key.clone())
});
}
item
}
pub(super) fn map_match3d_message_response(
message: Match3DAgentMessageRecord,
) -> Match3DAgentMessageResponse {
Match3DAgentMessageResponse {
id: message.message_id,
role: message.role,
kind: message.kind,
text: message.text,
created_at: message.created_at,
}
}
pub(super) fn map_match3d_work_summary_response(
item: Match3DWorkProfileRecord,
) -> Match3DWorkSummaryResponse {
let generated_item_asset_json =
parse_match3d_generated_item_assets(item.generated_item_assets_json.as_deref());
let background_asset = find_match3d_generated_background_asset_json(&generated_item_asset_json);
let generated_background_asset = background_asset
.clone()
.map(map_match3d_background_asset_for_work);
let generated_item_assets = generated_item_asset_json
.into_iter()
.map(map_match3d_generated_item_asset_for_work)
.collect();
Match3DWorkSummaryResponse {
work_id: item.work_id,
profile_id: item.profile_id,
owner_user_id: item.owner_user_id,
source_session_id: item.source_session_id,
game_name: item.game_name,
theme_text: item.theme_text,
summary: item.summary,
tags: item.tags,
cover_image_src: item.cover_image_src,
reference_image_src: item.reference_image_src,
clear_count: item.clear_count,
difficulty: item.difficulty,
publication_status: item.publication_status,
play_count: item.play_count,
updated_at: item.updated_at,
published_at: item.published_at,
publish_ready: item.publish_ready,
background_prompt: background_asset.as_ref().map(|asset| asset.prompt.clone()),
background_image_src: background_asset
.as_ref()
.and_then(|asset| asset.image_src.clone()),
background_image_object_key: background_asset
.as_ref()
.and_then(|asset| asset.image_object_key.clone()),
generated_background_asset,
generated_item_assets,
}
}
pub(super) fn match3d_bad_gateway(message: impl Into<String>) -> AppError {
AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({
"provider": MATCH3D_AGENT_PROVIDER,
"message": message.into(),
}))
}
pub(super) fn match3d_background_music_missing_error(message: impl Into<String>) -> AppError {
AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({
"provider": MATCH3D_AGENT_PROVIDER,
"message": message.into(),
"missingAssets": ["背景音乐"],
}))
}
pub(super) fn require_match3d_background_music_title(
plan: &Match3DGeneratedBackgroundMusicPlan,
) -> Result<String, AppError> {
let title = normalize_match3d_audio_title(plan.title.as_str());
if title.is_empty() {
return Err(match3d_background_music_missing_error(
"抓大鹅草稿背景音乐名称为空,无法完成背景音乐生成",
));
}
Ok(title)
}
pub(super) fn map_match3d_work_profile_response(
item: Match3DWorkProfileRecord,
) -> Match3DWorkProfileResponse {
Match3DWorkProfileResponse {
summary: map_match3d_work_summary_response(item),
}
}
pub(super) fn map_match3d_run_response(run: Match3DRunRecord) -> Match3DRunSnapshotResponse {
Match3DRunSnapshotResponse {
run_id: run.run_id,
profile_id: run.profile_id,
owner_user_id: run.owner_user_id,
status: normalize_match3d_run_status(run.status.as_str()).to_string(),
snapshot_version: run.snapshot_version,
started_at_ms: run.started_at_ms,
duration_limit_ms: run.duration_limit_ms,
server_now_ms: run.server_now_ms,
remaining_ms: run.remaining_ms,
clear_count: run.clear_count,
total_item_count: run.total_item_count,
cleared_item_count: run.cleared_item_count,
items: run
.items
.into_iter()
.map(map_match3d_item_response)
.collect(),
tray_slots: run
.tray_slots
.into_iter()
.map(map_match3d_tray_slot_response)
.collect(),
failure_reason: run
.failure_reason
.map(|reason| normalize_match3d_failure_reason(reason.as_str()).to_string()),
last_confirmed_action_id: run.last_confirmed_action_id,
}
}
pub(super) fn map_match3d_item_response(
item: Match3DItemSnapshotRecord,
) -> Match3DItemSnapshotResponse {
Match3DItemSnapshotResponse {
item_instance_id: item.item_instance_id,
item_type_id: item.item_type_id,
visual_key: item.visual_key,
x: item.x,
y: item.y,
radius: item.radius,
layer: item.layer,
state: normalize_match3d_item_state(item.state.as_str()).to_string(),
clickable: item.clickable,
tray_slot_index: item.tray_slot_index,
}
}
pub(super) fn map_match3d_tray_slot_response(
slot: Match3DTraySlotRecord,
) -> Match3DTraySlotResponse {
Match3DTraySlotResponse {
slot_index: slot.slot_index,
item_instance_id: slot.item_instance_id,
item_type_id: slot.item_type_id,
visual_key: slot.visual_key,
}
}
pub(super) fn map_match3d_click_confirmation_response(
confirmation: Match3DClickConfirmationRecord,
) -> Match3DClickConfirmationResponse {
Match3DClickConfirmationResponse {
accepted: confirmation.accepted,
reject_reason: confirmation
.reject_reason
.map(|reason| normalize_match3d_click_reject_reason(reason.as_str()).to_string()),
entered_slot_index: confirmation.entered_slot_index,
cleared_item_instance_ids: confirmation.cleared_item_instance_ids,
run: map_match3d_run_response(confirmation.run),
}
}