再次合并 master

合入 origin/master 最新订阅消息与计费相关更新

保留作品架 actions 收口并接入统一分享弹窗

修复创作生成泥点预检与本地余额扣减回归
This commit is contained in:
2026-06-08 17:18:38 +08:00
342 changed files with 4153 additions and 2483 deletions

View File

@@ -589,6 +589,7 @@ pub async fn execute_puzzle_agent_action(
let now = current_utc_micros();
let action = payload.action.trim().to_string();
let billing_asset_id = format!("{session_id}:{now}");
let mut operation_consumed_points = 0;
tracing::info!(
provider = PUZZLE_AGENT_API_BASE_PROVIDER,
session_id = %session_id,
@@ -617,13 +618,14 @@ pub async fn execute_puzzle_agent_action(
let log_session_id = session_id.clone();
let log_owner_user_id = owner_user_id.clone();
async move {
let failed_at_micros = current_utc_micros();
let result = state
.spacetime_client()
.mark_puzzle_draft_generation_failed(PuzzleDraftCompileFailureRecordInput {
session_id,
owner_user_id,
owner_user_id: owner_user_id.clone(),
error_message,
failed_at_micros: current_utc_micros(),
failed_at_micros,
})
.await;
if let Err(error) = result {
@@ -634,6 +636,19 @@ pub async fn execute_puzzle_agent_action(
message = %error,
"拼图草稿失败态回写失败,继续返回原始错误"
);
} else {
send_generation_result_subscribe_message_after_completion(
state.root_state(),
GenerationResultSubscribeMessage {
owner_user_id,
work_name: None,
status: GenerationResultSubscribeMessageStatus::Failed,
consumed_points: 0,
completed_at_micros: failed_at_micros,
page: Some("/pages/web-view/index".to_string()),
},
)
.await;
}
}
};
@@ -641,6 +656,17 @@ pub async fn execute_puzzle_agent_action(
let (operation_type, phase_label, phase_detail, session) = match action.as_str() {
"compile_puzzle_draft" => {
let ai_redraw = payload.ai_redraw.unwrap_or(true);
let puzzle_draft_generation_points_cost = if ai_redraw {
crate::creation_entry_config::resolve_creation_entry_mud_point_cost(
state.root_state(),
"puzzle",
PUZZLE_IMAGE_GENERATION_POINTS_COST,
)
.await
} else {
0
};
operation_consumed_points = puzzle_draft_generation_points_cost;
let reference_image_sources = collect_puzzle_reference_image_sources(
payload.reference_image_src.as_deref(),
payload.reference_image_srcs.as_slice(),
@@ -677,10 +703,7 @@ pub async fn execute_puzzle_agent_action(
);
state
.spacetime_client()
.get_puzzle_agent_session(
compile_session_id.clone(),
owner_user_id.clone(),
)
.get_puzzle_agent_session(compile_session_id.clone(), owner_user_id.clone())
.await
.map(mark_puzzle_initial_generation_started_snapshot)
.map_err(map_puzzle_client_error)
@@ -696,10 +719,9 @@ pub async fn execute_puzzle_agent_action(
.map_err(map_puzzle_compile_error);
match compiled_session {
Ok(compiled_session) => {
let response_session =
mark_puzzle_initial_generation_started_snapshot(
compiled_session.clone(),
);
let response_session = mark_puzzle_initial_generation_started_snapshot(
compiled_session.clone(),
);
let background_state = state.clone();
let background_request_context = request_context.clone();
let background_session_id = compile_session_id.clone();
@@ -708,20 +730,23 @@ pub async fn execute_puzzle_agent_action(
let background_reference_image_src =
primary_reference_image_src.map(str::to_string);
let background_image_model = payload.image_model.clone();
let background_points_cost = puzzle_draft_generation_points_cost;
let background_work_name = compiled_session
.draft
.as_ref()
.map(|draft| draft.work_title.clone());
let background_billing_asset_id =
format!("{background_session_id}:compile_puzzle_draft");
tokio::spawn(async move {
let operation_owner_user_id =
background_owner_user_id.clone();
let background_root_state =
background_state.root_state().clone();
let operation_owner_user_id = background_owner_user_id.clone();
let background_root_state = background_state.root_state().clone();
let operation_state = background_state.clone();
let result = execute_billable_asset_operation_with_cost(
&background_root_state,
&background_owner_user_id,
"puzzle_initial_image",
&background_billing_asset_id,
PUZZLE_IMAGE_GENERATION_POINTS_COST,
background_points_cost,
async move {
generate_puzzle_initial_cover_from_compiled_session(
&operation_state,
@@ -739,6 +764,22 @@ pub async fn execute_puzzle_agent_action(
.await;
match result {
Ok(session) => {
send_generation_result_subscribe_message_after_completion(
&background_root_state,
GenerationResultSubscribeMessage {
owner_user_id: background_owner_user_id.clone(),
work_name: session
.draft
.as_ref()
.map(|draft| draft.work_title.clone()),
status:
GenerationResultSubscribeMessageStatus::Succeeded,
consumed_points: background_points_cost,
completed_at_micros: current_utc_micros(),
page: Some("/pages/web-view/index".to_string()),
},
)
.await;
tracing::info!(
provider = PUZZLE_AGENT_API_BASE_PROVIDER,
session_id = %session.session_id,
@@ -748,15 +789,15 @@ pub async fn execute_puzzle_agent_action(
}
Err(error) => {
let error_message = error.body_text();
let failed_at_micros = current_utc_micros();
let failure_result = background_state
.spacetime_client()
.mark_puzzle_draft_generation_failed(
PuzzleDraftCompileFailureRecordInput {
session_id: background_session_id.clone(),
owner_user_id: background_owner_user_id
.clone(),
owner_user_id: background_owner_user_id.clone(),
error_message: error_message.clone(),
failed_at_micros: current_utc_micros(),
failed_at_micros,
},
)
.await;
@@ -768,6 +809,20 @@ pub async fn execute_puzzle_agent_action(
message = %mark_error,
"拼图首图后台生成失败态回写失败"
);
} else {
send_generation_result_subscribe_message_after_completion(
&background_root_state,
GenerationResultSubscribeMessage {
owner_user_id: background_owner_user_id.clone(),
work_name: background_work_name.clone(),
status:
GenerationResultSubscribeMessageStatus::Failed,
consumed_points: 0,
completed_at_micros: failed_at_micros,
page: Some("/pages/web-view/index".to_string()),
},
)
.await;
}
tracing::warn!(
provider = PUZZLE_AGENT_API_BASE_PROVIDER,
@@ -778,9 +833,7 @@ pub async fn execute_puzzle_agent_action(
);
}
}
unregister_puzzle_background_compile_task(
&background_session_id,
);
unregister_puzzle_background_compile_task(&background_session_id);
});
Ok(response_session)
}
@@ -1428,6 +1481,25 @@ pub async fn execute_puzzle_agent_action(
};
let session = session?;
if operation_type == "compile_puzzle_draft"
&& session
.draft
.as_ref()
.is_some_and(|draft| draft.generation_status == "ready")
{
send_generation_result_subscribe_message_after_completion(
state.root_state(),
GenerationResultSubscribeMessage {
owner_user_id: owner_user_id.clone(),
work_name: session.draft.as_ref().map(|draft| draft.work_title.clone()),
status: GenerationResultSubscribeMessageStatus::Succeeded,
consumed_points: operation_consumed_points,
completed_at_micros: current_utc_micros(),
page: Some("/pages/web-view/index".to_string()),
},
)
.await;
}
Ok(json_success_body(
Some(&request_context),