@@ -83,12 +83,11 @@ use crate::{
|
||||
profile_identity::update_profile_identity,
|
||||
puzzle::{
|
||||
advance_local_puzzle_next_level, advance_puzzle_next_level, create_puzzle_agent_session,
|
||||
delete_puzzle_work, drag_puzzle_piece_or_group, execute_puzzle_agent_action,
|
||||
get_puzzle_agent_session, get_puzzle_gallery_detail, get_puzzle_run,
|
||||
get_puzzle_work_detail, get_puzzle_works, list_puzzle_gallery, put_puzzle_work,
|
||||
remix_puzzle_gallery_work, start_puzzle_run, stream_puzzle_agent_message,
|
||||
submit_puzzle_agent_message, submit_puzzle_leaderboard, swap_puzzle_pieces,
|
||||
update_puzzle_run_pause, use_puzzle_runtime_prop,
|
||||
delete_puzzle_work, execute_puzzle_agent_action, get_puzzle_agent_session,
|
||||
get_puzzle_gallery_detail, get_puzzle_run, get_puzzle_work_detail, get_puzzle_works,
|
||||
list_puzzle_gallery, put_puzzle_work, remix_puzzle_gallery_work, start_puzzle_run,
|
||||
stream_puzzle_agent_message, submit_puzzle_agent_message, submit_puzzle_leaderboard,
|
||||
swap_puzzle_pieces, update_puzzle_run_pause, use_puzzle_runtime_prop,
|
||||
},
|
||||
refresh_session::refresh_session,
|
||||
request_context::{attach_request_context, resolve_request_id},
|
||||
@@ -777,13 +776,6 @@ pub fn build_router(state: AppState) -> Router {
|
||||
require_bearer_auth,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/api/runtime/puzzle/runs/{run_id}/drag",
|
||||
post(drag_puzzle_piece_or_group).route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
require_bearer_auth,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/api/runtime/puzzle/runs/{run_id}/next-level",
|
||||
post(advance_puzzle_next_level).route_layer(middleware::from_fn_with_state(
|
||||
|
||||
@@ -9,10 +9,10 @@ pub(crate) const PUZZLE_DEFAULT_NEGATIVE_PROMPT: &str =
|
||||
pub(crate) fn build_puzzle_image_prompt(level_name: &str, prompt: &str) -> String {
|
||||
format!(
|
||||
concat!(
|
||||
"请生成一张适合 9:16 竖屏拼图关卡的高清插画。",
|
||||
"请生成一张适合 1:1 正方形拼图关卡的高清插画。",
|
||||
"关卡名:{level_name}。",
|
||||
"画面主体:{prompt}。",
|
||||
"画面要求:9:16 竖屏画布,适配 3x3 或 4x4 拼图切块,",
|
||||
"画面要求:1:1 正方形画布,适配 3x3 或 4x4 拼图切块,",
|
||||
"主体要清晰集中,前中后景层次明确,局部细节丰富但不要杂乱,",
|
||||
"避免文字、水印、边框和 UI 元素。"
|
||||
),
|
||||
@@ -31,7 +31,7 @@ mod tests {
|
||||
|
||||
assert!(prompt.contains("雨夜神庙"));
|
||||
assert!(prompt.contains("猫咪在发光遗迹前寻找线索"));
|
||||
assert!(prompt.contains("9:16 竖屏拼图关卡"));
|
||||
assert!(prompt.contains("1:1 正方形拼图关卡"));
|
||||
assert!(prompt.contains("3x3 或 4x4"));
|
||||
assert!(prompt.contains("避免文字、水印、边框和 UI 元素"));
|
||||
}
|
||||
|
||||
@@ -36,11 +36,11 @@ use shared_contracts::{
|
||||
},
|
||||
puzzle_gallery::{PuzzleGalleryDetailResponse, PuzzleGalleryResponse},
|
||||
puzzle_runtime::{
|
||||
AdvanceLocalPuzzleNextLevelRequest, DragPuzzlePieceRequest, PuzzleBoardSnapshotResponse,
|
||||
PuzzleCellPositionResponse, PuzzleLeaderboardEntryResponse, PuzzleMergedGroupStateResponse,
|
||||
PuzzlePieceStateResponse, PuzzleRunResponse, PuzzleRunSnapshotResponse,
|
||||
PuzzleRuntimeLevelSnapshotResponse, StartPuzzleRunRequest, SubmitPuzzleLeaderboardRequest,
|
||||
SwapPuzzlePiecesRequest, UpdatePuzzleRuntimePauseRequest, UsePuzzleRuntimePropRequest,
|
||||
AdvanceLocalPuzzleNextLevelRequest, PuzzleBoardSnapshotResponse, PuzzleCellPositionResponse,
|
||||
PuzzleLeaderboardEntryResponse, PuzzleMergedGroupStateResponse, PuzzlePieceStateResponse,
|
||||
PuzzleRunResponse, PuzzleRunSnapshotResponse, PuzzleRuntimeLevelSnapshotResponse,
|
||||
StartPuzzleRunRequest, SubmitPuzzleLeaderboardRequest, SwapPuzzlePiecesRequest,
|
||||
UpdatePuzzleRuntimePauseRequest, UsePuzzleRuntimePropRequest,
|
||||
},
|
||||
puzzle_works::{
|
||||
PutPuzzleWorkRequest, PuzzleWorkDetailResponse, PuzzleWorkMutationResponse,
|
||||
@@ -57,10 +57,10 @@ use spacetime_client::{
|
||||
PuzzleLeaderboardEntryRecord, PuzzleLeaderboardSubmitRecordInput, PuzzleMergedGroupRecord,
|
||||
PuzzlePieceStateRecord, PuzzlePublishRecordInput, PuzzleResultDraftRecord,
|
||||
PuzzleResultPreviewBlockerRecord, PuzzleResultPreviewFindingRecord, PuzzleResultPreviewRecord,
|
||||
PuzzleRunDragRecordInput, PuzzleRunPauseRecordInput, PuzzleRunPropRecordInput, PuzzleRunRecord,
|
||||
PuzzleRunStartRecordInput, PuzzleRunSwapRecordInput, PuzzleRuntimeLevelRecord,
|
||||
PuzzleSelectCoverImageRecordInput, PuzzleWorkProfileRecord, PuzzleWorkRemixRecordInput,
|
||||
PuzzleWorkUpsertRecordInput, SpacetimeClientError,
|
||||
PuzzleRunPauseRecordInput, PuzzleRunPropRecordInput, PuzzleRunRecord, PuzzleRunStartRecordInput,
|
||||
PuzzleRunSwapRecordInput, PuzzleRuntimeLevelRecord, PuzzleSelectCoverImageRecordInput,
|
||||
PuzzleWorkProfileRecord, PuzzleWorkRemixRecordInput, PuzzleWorkUpsertRecordInput,
|
||||
SpacetimeClientError,
|
||||
};
|
||||
use std::convert::Infallible;
|
||||
use tokio::time::sleep;
|
||||
@@ -86,7 +86,7 @@ const PUZZLE_GALLERY_PROVIDER: &str = "puzzle-gallery";
|
||||
const PUZZLE_RUNTIME_PROVIDER: &str = "puzzle-runtime";
|
||||
const PUZZLE_TEXT_TO_IMAGE_MODEL: &str = "wan2.2-t2i-flash";
|
||||
const PUZZLE_ENTITY_KIND: &str = "puzzle_work";
|
||||
const PUZZLE_GENERATED_IMAGE_SIZE: &str = "720*1280";
|
||||
const PUZZLE_GENERATED_IMAGE_SIZE: &str = "1024*1024";
|
||||
|
||||
pub async fn create_puzzle_agent_session(
|
||||
State(state): State<AppState>,
|
||||
@@ -1062,58 +1062,6 @@ pub async fn swap_puzzle_pieces(
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn drag_puzzle_piece_or_group(
|
||||
State(state): State<AppState>,
|
||||
AxumPath(run_id): AxumPath<String>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
Extension(authenticated): Extension<AuthenticatedAccessToken>,
|
||||
payload: Result<Json<DragPuzzlePieceRequest>, JsonRejection>,
|
||||
) -> Result<Json<Value>, Response> {
|
||||
let Json(payload) = payload.map_err(|error| {
|
||||
puzzle_error_response(
|
||||
&request_context,
|
||||
PUZZLE_RUNTIME_PROVIDER,
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": PUZZLE_RUNTIME_PROVIDER,
|
||||
"message": error.body_text(),
|
||||
})),
|
||||
)
|
||||
})?;
|
||||
ensure_non_empty(&request_context, PUZZLE_RUNTIME_PROVIDER, &run_id, "runId")?;
|
||||
ensure_non_empty(
|
||||
&request_context,
|
||||
PUZZLE_RUNTIME_PROVIDER,
|
||||
&payload.piece_id,
|
||||
"pieceId",
|
||||
)?;
|
||||
|
||||
let run = state
|
||||
.spacetime_client()
|
||||
.drag_puzzle_piece_or_group(PuzzleRunDragRecordInput {
|
||||
run_id,
|
||||
owner_user_id: authenticated.claims().user_id().to_string(),
|
||||
piece_id: payload.piece_id,
|
||||
target_row: payload.target_row,
|
||||
target_col: payload.target_col,
|
||||
dragged_at_micros: current_utc_micros(),
|
||||
})
|
||||
.await
|
||||
.map_err(|error| {
|
||||
puzzle_error_response(
|
||||
&request_context,
|
||||
PUZZLE_RUNTIME_PROVIDER,
|
||||
map_puzzle_client_error(error),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
PuzzleRunResponse {
|
||||
run: map_puzzle_run_response(run),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn advance_puzzle_next_level(
|
||||
State(state): State<AppState>,
|
||||
AxumPath(run_id): AxumPath<String>,
|
||||
@@ -1994,7 +1942,7 @@ async fn generate_puzzle_image_candidates(
|
||||
None => None,
|
||||
};
|
||||
// 中文注释:SpacetimeDB reducer 不能做外部 I/O,参考图读取与 DashScope 图生图都必须停留在 api-server。
|
||||
// 中文注释:拼图作品资产统一按 9:16 竖屏生成,运行时棋盘也按同一比例切块承载。
|
||||
// 中文注释:拼图作品资产统一按 1:1 正方形生成,前端运行时也按正方形棋盘切块承载。
|
||||
let generated = match reference_image.as_deref() {
|
||||
Some(reference_image) => {
|
||||
create_puzzle_image_to_image_generation(
|
||||
@@ -2411,8 +2359,8 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn puzzle_generated_image_size_is_portrait_9_16() {
|
||||
assert_eq!(PUZZLE_GENERATED_IMAGE_SIZE, "720*1280");
|
||||
fn puzzle_generated_image_size_is_square_1_1() {
|
||||
assert_eq!(PUZZLE_GENERATED_IMAGE_SIZE, "1024*1024");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1551,9 +1551,6 @@ fn submit_puzzle_leaderboard_entry_tx(
|
||||
.current_level
|
||||
.as_ref()
|
||||
.ok_or_else(|| "拼图关卡不存在".to_string())?;
|
||||
if current_level.status != PuzzleRuntimeLevelStatus::Cleared {
|
||||
return Err("当前关卡尚未通关".to_string());
|
||||
}
|
||||
if current_level.profile_id != input.profile_id {
|
||||
return Err("提交成绩的拼图作品与当前关卡不匹配".to_string());
|
||||
}
|
||||
@@ -1592,9 +1589,14 @@ fn submit_puzzle_leaderboard_entry_tx(
|
||||
10,
|
||||
);
|
||||
if let Some(level) = run.current_level.as_mut() {
|
||||
// 拼图拖动、合并与通关判定由前端运行态即时裁决;服务端只负责真实榜单。
|
||||
// 因此提交榜单时不能要求 SpacetimeDB 里的旧棋盘快照也已经通关。
|
||||
level.status = PuzzleRuntimeLevelStatus::Cleared;
|
||||
level.cleared_at_ms = Some(micros_to_millis(input.submitted_at_micros));
|
||||
level.elapsed_ms = Some(input.elapsed_ms.max(1_000));
|
||||
level.leaderboard_entries = leaderboard_entries.clone();
|
||||
}
|
||||
run.cleared_level_count = run.cleared_level_count.max(run.current_level_index);
|
||||
run.leaderboard_entries = leaderboard_entries;
|
||||
replace_puzzle_runtime_run(ctx, &row, &run, input.submitted_at_micros);
|
||||
Ok(run)
|
||||
|
||||
Reference in New Issue
Block a user