This commit is contained in:
2026-04-22 22:01:07 +08:00
parent d8716d70b0
commit b317c2a8ea
37 changed files with 1821 additions and 515 deletions

View File

@@ -30,15 +30,16 @@ use shared_contracts::{
SwapPuzzlePiecesRequest,
},
puzzle_works::{
PuzzleWorkDetailResponse, PuzzleWorkMutationResponse, PuzzleWorkProfileResponse,
PuzzleWorkSummaryResponse, PuzzleWorksResponse, PutPuzzleWorkRequest,
PutPuzzleWorkRequest, PuzzleWorkDetailResponse, PuzzleWorkMutationResponse,
PuzzleWorkProfileResponse, PuzzleWorkSummaryResponse, PuzzleWorksResponse,
},
};
use shared_kernel::build_prefixed_uuid_id;
use spacetime_client::{
PuzzleAgentMessageRecord, PuzzleAgentMessageSubmitRecordInput, PuzzleAgentSessionCreateRecordInput,
PuzzleAgentSessionRecord, PuzzleAgentSuggestedActionRecord, PuzzleAnchorItemRecord,
PuzzleAnchorPackRecord, PuzzleCreatorIntentRecord, PuzzleGeneratedImageCandidateRecord,
PuzzleAgentMessageRecord, PuzzleAgentMessageSubmitRecordInput,
PuzzleAgentSessionCreateRecordInput, PuzzleAgentSessionRecord,
PuzzleAgentSuggestedActionRecord, PuzzleAnchorItemRecord, PuzzleAnchorPackRecord,
PuzzleCreatorIntentRecord, PuzzleGeneratedImageCandidateRecord,
PuzzleGeneratedImagesSaveRecordInput, PuzzlePublishRecordInput, PuzzleResultDraftRecord,
PuzzleResultPreviewBlockerRecord, PuzzleResultPreviewFindingRecord, PuzzleResultPreviewRecord,
PuzzleRunDragRecordInput, PuzzleRunRecord, PuzzleRunStartRecordInput, PuzzleRunSwapRecordInput,
@@ -47,11 +48,8 @@ use spacetime_client::{
};
use crate::{
api_response::json_success_body,
auth::AuthenticatedAccessToken,
http_error::AppError,
request_context::RequestContext,
state::AppState,
api_response::json_success_body, auth::AuthenticatedAccessToken, http_error::AppError,
request_context::RequestContext, state::AppState,
};
const PUZZLE_AGENT_API_BASE_PROVIDER: &str = "puzzle-agent";
@@ -110,14 +108,16 @@ pub async fn get_puzzle_agent_session(
Extension(request_context): Extension<RequestContext>,
Extension(authenticated): Extension<AuthenticatedAccessToken>,
) -> Result<Json<Value>, Response> {
ensure_non_empty(&request_context, PUZZLE_AGENT_API_BASE_PROVIDER, &session_id, "sessionId")?;
ensure_non_empty(
&request_context,
PUZZLE_AGENT_API_BASE_PROVIDER,
&session_id,
"sessionId",
)?;
let session = state
.spacetime_client()
.get_puzzle_agent_session(
session_id,
authenticated.claims().user_id().to_string(),
)
.get_puzzle_agent_session(session_id, authenticated.claims().user_id().to_string())
.await
.map_err(|error| {
puzzle_error_response(
@@ -152,7 +152,12 @@ pub async fn submit_puzzle_agent_message(
})),
)
})?;
ensure_non_empty(&request_context, PUZZLE_AGENT_API_BASE_PROVIDER, &session_id, "sessionId")?;
ensure_non_empty(
&request_context,
PUZZLE_AGENT_API_BASE_PROVIDER,
&session_id,
"sessionId",
)?;
let client_message_id = payload.client_message_id.trim().to_string();
let message_text = payload.text.trim().to_string();
@@ -207,7 +212,12 @@ pub async fn stream_puzzle_agent_message(
})),
)
})?;
ensure_non_empty(&request_context, PUZZLE_AGENT_API_BASE_PROVIDER, &session_id, "sessionId")?;
ensure_non_empty(
&request_context,
PUZZLE_AGENT_API_BASE_PROVIDER,
&session_id,
"sessionId",
)?;
let session = state
.spacetime_client()
@@ -245,7 +255,12 @@ pub async fn stream_puzzle_agent_message(
"session",
&json!({ "session": session_response }),
)?;
append_sse_event(&request_context, &mut sse_body, "done", &json!({ "ok": true }))?;
append_sse_event(
&request_context,
&mut sse_body,
"done",
&json!({ "ok": true }),
)?;
Ok(build_event_stream_response(sse_body))
}
@@ -266,7 +281,12 @@ pub async fn execute_puzzle_agent_action(
})),
)
})?;
ensure_non_empty(&request_context, PUZZLE_AGENT_API_BASE_PROVIDER, &session_id, "sessionId")?;
ensure_non_empty(
&request_context,
PUZZLE_AGENT_API_BASE_PROVIDER,
&session_id,
"sessionId",
)?;
let owner_user_id = authenticated.claims().user_id().to_string();
let now = current_utc_micros();
@@ -327,11 +347,11 @@ pub async fn execute_puzzle_agent_action(
})
.collect::<Vec<_>>(),
)
.map_err(|error| {
SpacetimeClientError::Runtime(format!(
"拼图候选图序列化失败:{error}"
))
});
.map_err(|error| {
SpacetimeClientError::Runtime(format!(
"拼图候选图序列化失败:{error}"
))
});
match candidates_json {
Ok(candidates_json) => {
state
@@ -497,7 +517,12 @@ pub async fn get_puzzle_work_detail(
Extension(request_context): Extension<RequestContext>,
Extension(_authenticated): Extension<AuthenticatedAccessToken>,
) -> Result<Json<Value>, Response> {
ensure_non_empty(&request_context, PUZZLE_WORKS_PROVIDER, &profile_id, "profileId")?;
ensure_non_empty(
&request_context,
PUZZLE_WORKS_PROVIDER,
&profile_id,
"profileId",
)?;
let item = state
.spacetime_client()
@@ -536,7 +561,12 @@ pub async fn put_puzzle_work(
})),
)
})?;
ensure_non_empty(&request_context, PUZZLE_WORKS_PROVIDER, &profile_id, "profileId")?;
ensure_non_empty(
&request_context,
PUZZLE_WORKS_PROVIDER,
&profile_id,
"profileId",
)?;
let item = state
.spacetime_client()
@@ -599,7 +629,12 @@ pub async fn get_puzzle_gallery_detail(
AxumPath(profile_id): AxumPath<String>,
Extension(request_context): Extension<RequestContext>,
) -> Result<Json<Value>, Response> {
ensure_non_empty(&request_context, PUZZLE_GALLERY_PROVIDER, &profile_id, "profileId")?;
ensure_non_empty(
&request_context,
PUZZLE_GALLERY_PROVIDER,
&profile_id,
"profileId",
)?;
let item = state
.spacetime_client()
@@ -637,7 +672,12 @@ pub async fn start_puzzle_run(
})),
)
})?;
ensure_non_empty(&request_context, PUZZLE_RUNTIME_PROVIDER, &payload.profile_id, "profileId")?;
ensure_non_empty(
&request_context,
PUZZLE_RUNTIME_PROVIDER,
&payload.profile_id,
"profileId",
)?;
let run = state
.spacetime_client()
@@ -767,7 +807,12 @@ pub async fn drag_puzzle_piece_or_group(
)
})?;
ensure_non_empty(&request_context, PUZZLE_RUNTIME_PROVIDER, &run_id, "runId")?;
ensure_non_empty(&request_context, PUZZLE_RUNTIME_PROVIDER, &payload.piece_id, "pieceId")?;
ensure_non_empty(
&request_context,
PUZZLE_RUNTIME_PROVIDER,
&payload.piece_id,
"pieceId",
)?;
let run = state
.spacetime_client()
@@ -850,12 +895,16 @@ fn map_puzzle_agent_session_response(
.into_iter()
.map(map_puzzle_suggested_action_response)
.collect(),
result_preview: session.result_preview.map(map_puzzle_result_preview_response),
result_preview: session
.result_preview
.map(map_puzzle_result_preview_response),
updated_at: session.updated_at,
}
}
fn map_puzzle_anchor_pack_response(anchor_pack: PuzzleAnchorPackRecord) -> PuzzleAnchorPackResponse {
fn map_puzzle_anchor_pack_response(
anchor_pack: PuzzleAnchorPackRecord,
) -> PuzzleAnchorPackResponse {
PuzzleAnchorPackResponse {
theme_promise: map_puzzle_anchor_item_response(anchor_pack.theme_promise),
visual_subject: map_puzzle_anchor_item_response(anchor_pack.visual_subject),
@@ -1098,8 +1147,7 @@ fn resolve_author_display_name(
fn build_puzzle_welcome_text(seed_text: &str) -> String {
if seed_text.trim().is_empty() {
return "先告诉我你想做一张什么样的拼图图像,我会帮你收束成可发布的题材锚点。"
.to_string();
return "先告诉我你想做一张什么样的拼图图像,我会帮你收束成可发布的题材锚点。".to_string();
}
"我先接住你的画面灵感,再一起把它收束成正式拼图关卡。".to_string()
@@ -1124,11 +1172,7 @@ fn ensure_non_empty(
Ok(())
}
fn puzzle_bad_request(
request_context: &RequestContext,
provider: &str,
message: &str,
) -> Response {
fn puzzle_bad_request(request_context: &RequestContext, provider: &str, message: &str) -> Response {
puzzle_error_response(
request_context,
provider,
@@ -1279,7 +1323,11 @@ fn save_placeholder_puzzle_asset(
fs::write(output_dir.join(&file_name), svg).map_err(io_error)?;
Ok(GeneratedPuzzleAssetResponse {
image_src: format!("/{}/{}", relative_dir.to_string_lossy().replace('\\', "/"), file_name),
image_src: format!(
"/{}/{}",
relative_dir.to_string_lossy().replace('\\', "/"),
file_name
),
asset_id,
})
}