Merge remote-tracking branch 'origin/master' into codex/ddd

# Conflicts:
#	docs/technical/README.md
#	docs/technical/RUST_API_SERVER_ROUTE_INDEX_2026-04-22.md
#	docs/technical/SPACETIMEDB_TABLE_CATALOG.md
#	scripts/generate-spacetime-bindings.mjs
#	server-rs/crates/api-server/src/app.rs
#	server-rs/crates/api-server/src/assets.rs
#	server-rs/crates/api-server/src/big_fish.rs
#	server-rs/crates/api-server/src/custom_world_ai.rs
#	server-rs/crates/api-server/src/llm.rs
#	server-rs/crates/api-server/src/main.rs
#	server-rs/crates/api-server/src/puzzle.rs
#	server-rs/crates/api-server/src/runtime_profile.rs
#	server-rs/crates/api-server/src/runtime_story/compat/ai.rs
#	server-rs/crates/api-server/src/runtime_story/compat/npc_actions.rs
#	server-rs/crates/api-server/src/runtime_story/compat/presentation.rs
#	server-rs/crates/api-server/src/runtime_story/compat/tests.rs
#	server-rs/crates/api-server/src/state.rs
#	server-rs/crates/module-auth/src/lib.rs
#	server-rs/crates/module-big-fish/src/lib.rs
#	server-rs/crates/module-custom-world/src/lib.rs
#	server-rs/crates/module-puzzle/src/lib.rs
#	server-rs/crates/module-runtime/src/lib.rs
#	server-rs/crates/spacetime-client/src/big_fish.rs
#	server-rs/crates/spacetime-client/src/lib.rs
#	server-rs/crates/spacetime-client/src/mapper.rs
#	server-rs/crates/spacetime-client/src/module_bindings/admin_disable_profile_redeem_code_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/admin_upsert_profile_redeem_code_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/advance_puzzle_next_level_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/append_ai_text_chunk_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/apply_chapter_progression_ledger_entry_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/attach_ai_result_reference_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/authorize_database_migration_operator_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/begin_story_session_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/big_fish_runtime_run_type.rs
#	server-rs/crates/spacetime-client/src/module_bindings/bind_asset_object_to_entity_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/cancel_ai_task_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/clear_platform_browse_history_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/compile_big_fish_draft_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/compile_custom_world_published_profile_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/compile_puzzle_agent_draft_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/complete_ai_stage_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/complete_ai_task_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/confirm_asset_object_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/consume_profile_wallet_points_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/continue_story_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/create_ai_task_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/create_battle_state_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/create_big_fish_session_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/create_custom_world_agent_session_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/create_profile_recharge_order_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/create_puzzle_agent_session_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/delete_big_fish_work_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/delete_custom_world_agent_session_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/delete_custom_world_profile_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/delete_puzzle_work_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/delete_runtime_snapshot_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/drag_puzzle_piece_or_group_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/execute_custom_world_agent_action_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/export_auth_store_snapshot_from_tables_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/export_database_migration_to_file_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/fail_ai_task_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/finalize_big_fish_agent_message_turn_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/finalize_custom_world_agent_message_turn_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/finalize_puzzle_agent_message_turn_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/generate_big_fish_asset_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_auth_store_snapshot_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_battle_state_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_big_fish_session_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_chapter_progression_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_agent_card_detail_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_agent_operation_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_agent_session_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_gallery_detail_by_code_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_gallery_detail_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_library_detail_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_player_progression_or_default_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_profile_dashboard_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_profile_play_stats_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_profile_recharge_center_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_profile_referral_invite_center_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_agent_session_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_gallery_detail_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_run_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_work_detail_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_runtime_inventory_state_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_runtime_setting_or_default_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_runtime_snapshot_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_story_session_state_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/grant_player_progression_experience_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/import_auth_store_snapshot_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/import_database_migration_from_file_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/import_database_migration_incremental_from_file_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/list_asset_history_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/list_big_fish_works_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/list_custom_world_gallery_entries_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/list_custom_world_profiles_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/list_custom_world_works_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/list_platform_browse_history_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/list_profile_save_archives_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/list_profile_wallet_ledger_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/list_puzzle_gallery_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/list_puzzle_works_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/mod.rs
#	server-rs/crates/spacetime-client/src/module_bindings/publish_big_fish_game_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/publish_custom_world_profile_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/publish_custom_world_world_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/publish_puzzle_work_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/record_big_fish_play_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/redeem_profile_referral_invite_code_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/redeem_profile_reward_code_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/refund_profile_wallet_points_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/resolve_combat_action_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_battle_interaction_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_interaction_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_social_action_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/resolve_treasure_interaction_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/resume_profile_save_archive_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/revoke_database_migration_operator_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/save_puzzle_generated_images_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/select_puzzle_cover_image_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/start_puzzle_run_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/submit_big_fish_message_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/submit_custom_world_agent_message_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/submit_puzzle_agent_message_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/submit_puzzle_leaderboard_entry_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/swap_puzzle_pieces_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/unpublish_custom_world_profile_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/update_puzzle_work_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/upsert_auth_store_snapshot_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/upsert_chapter_progression_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/upsert_custom_world_agent_operation_progress_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/upsert_custom_world_profile_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/upsert_npc_state_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/upsert_platform_browse_history_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/upsert_runtime_setting_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/upsert_runtime_snapshot_and_return_procedure.rs
#	server-rs/crates/spacetime-module/src/auth/procedures.rs
#	server-rs/crates/spacetime-module/src/custom_world/mod.rs
#	server-rs/crates/spacetime-module/src/lib.rs
#	server-rs/crates/spacetime-module/src/migration.rs
#	server-rs/crates/spacetime-module/src/puzzle.rs
#	server-rs/crates/spacetime-module/src/runtime/profile.rs
#	src/components/platform-entry/PlatformEntryFlowShellImpl.tsx
#	src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx
#	src/services/aiService.ts
#	src/services/puzzle-runtime/puzzleRuntimeClient.ts
This commit is contained in:
kdletters
2026-05-02 03:35:59 +08:00
513 changed files with 52813 additions and 6013 deletions

View File

@@ -124,6 +124,9 @@ pub(crate) fn generate_big_fish_asset_tx(
last_assistant_reply: Some(reply.clone()),
publish_ready: readiness.readiness.publish_ready,
play_count: session.play_count,
remix_count: session.remix_count,
like_count: session.like_count,
published_at: session.published_at,
created_at: session.created_at,
updated_at,
};
@@ -192,6 +195,9 @@ pub(crate) fn publish_big_fish_game_tx(
last_assistant_reply: Some("玩法已发布,可以进入测试运行态。".to_string()),
publish_ready: true,
play_count: session.play_count,
remix_count: session.remix_count,
like_count: session.like_count,
published_at: Some(published_at),
created_at: session.created_at,
updated_at: published_at,
};

View File

@@ -2,7 +2,9 @@ use crate::big_fish::tables::{
big_fish_agent_message, big_fish_creation_session, big_fish_runtime_run,
};
use crate::runtime::{
ProfilePlayedWorkUpsertInput, add_profile_observed_play_time, upsert_profile_played_work,
ProfilePlayedWorkUpsertInput, PublicWorkLikeRecordInput, PublicWorkPlayRecordInput,
add_profile_observed_play_time, count_recent_public_work_plays, record_public_work_like,
record_public_work_play, upsert_profile_played_work,
};
use crate::*;
use module_big_fish::{EvaluateBigFishPublishReadinessCommand, evaluate_publish_readiness};
@@ -125,6 +127,51 @@ pub fn record_big_fish_play(
}
}
#[spacetimedb::procedure]
pub fn record_big_fish_like(
ctx: &mut ProcedureContext,
input: BigFishWorkLikeRecordInput,
) -> BigFishWorksProcedureResult {
match ctx.try_with_tx(|tx| record_big_fish_like_tx(tx, input.clone())) {
Ok(items) => match serde_json::to_string(&items) {
Ok(items_json) => BigFishWorksProcedureResult {
ok: true,
items_json: Some(items_json),
error_message: None,
},
Err(error) => BigFishWorksProcedureResult {
ok: false,
items_json: None,
error_message: Some(error.to_string()),
},
},
Err(message) => BigFishWorksProcedureResult {
ok: false,
items_json: None,
error_message: Some(message),
},
}
}
#[spacetimedb::procedure]
pub fn remix_big_fish_work(
ctx: &mut ProcedureContext,
input: BigFishWorkRemixInput,
) -> BigFishSessionProcedureResult {
match ctx.try_with_tx(|tx| remix_big_fish_work_tx(tx, input.clone())) {
Ok(session) => BigFishSessionProcedureResult {
ok: true,
session: Some(session),
error_message: None,
},
Err(message) => BigFishSessionProcedureResult {
ok: false,
session: None,
error_message: Some(message),
},
}
}
#[spacetimedb::procedure]
pub fn submit_big_fish_message(
ctx: &mut ProcedureContext,
@@ -227,6 +274,9 @@ pub(crate) fn create_big_fish_session_tx(
last_assistant_reply: Some(input.welcome_message_text.clone()),
publish_ready: false,
play_count: 0,
remix_count: 0,
like_count: 0,
published_at: None,
created_at,
updated_at: created_at,
});
@@ -269,6 +319,7 @@ pub(crate) fn list_big_fish_works_tx(
input: BigFishWorksListInput,
) -> Result<Vec<BigFishWorkSummarySnapshot>, String> {
validate_works_list_input(&input).map_err(|error| error.to_string())?;
let now_micros = ctx.timestamp.to_micros_since_unix_epoch();
let mut items = ctx
.db
@@ -281,7 +332,7 @@ pub(crate) fn list_big_fish_works_tx(
row.owner_user_id == input.owner_user_id && should_include_big_fish_work(ctx, row)
})
.map(|row| build_big_fish_work_summary(ctx, &row))
.map(|row| build_big_fish_work_summary(ctx, &row, now_micros))
.collect::<Result<Vec<_>, _>>()?;
items.sort_by(|left, right| {
@@ -426,6 +477,9 @@ pub(crate) fn submit_big_fish_message_tx(
last_assistant_reply: session.last_assistant_reply.clone(),
publish_ready: session.publish_ready,
play_count: session.play_count,
remix_count: session.remix_count,
like_count: session.like_count,
published_at: session.published_at,
created_at: session.created_at,
updated_at: submitted_at,
};
@@ -473,6 +527,9 @@ pub(crate) fn finalize_big_fish_agent_message_turn_tx(
last_assistant_reply: session.last_assistant_reply.clone(),
publish_ready: session.publish_ready,
play_count: session.play_count,
remix_count: session.remix_count,
like_count: session.like_count,
published_at: session.published_at,
created_at: session.created_at,
updated_at,
};
@@ -528,6 +585,9 @@ pub(crate) fn finalize_big_fish_agent_message_turn_tx(
last_assistant_reply: Some(assistant_reply_text),
publish_ready: session.publish_ready,
play_count: session.play_count,
remix_count: session.remix_count,
like_count: session.like_count,
published_at: session.published_at,
created_at: session.created_at,
updated_at,
};
@@ -592,6 +652,9 @@ pub(crate) fn compile_big_fish_draft_tx(
last_assistant_reply: Some(reply.clone()),
publish_ready: readiness.readiness.publish_ready,
play_count: session.play_count,
remix_count: session.remix_count,
like_count: session.like_count,
published_at: session.published_at,
created_at: session.created_at,
updated_at: compiled_at,
};
@@ -667,6 +730,15 @@ pub(crate) fn record_big_fish_play_tx(
input.elapsed_ms,
input.played_at_micros,
)?;
record_public_work_play(
ctx,
PublicWorkPlayRecordInput {
source_type: "big-fish".to_string(),
owner_user_id: session.owner_user_id.clone(),
profile_id: session.session_id.clone(),
played_at_micros: input.played_at_micros,
},
)?;
let next_session = BigFishCreationSession {
session_id: session.session_id.clone(),
owner_user_id: session.owner_user_id.clone(),
@@ -681,16 +753,184 @@ pub(crate) fn record_big_fish_play_tx(
publish_ready: session.publish_ready,
// 中文注释:正式进入已发布作品时同时累加作品播放数,用户侧去重由 profile_played_world 保证。
play_count: session.play_count.saturating_add(1),
remix_count: session.remix_count,
like_count: session.like_count,
published_at: session.published_at,
created_at: session.created_at,
updated_at: played_at,
};
replace_big_fish_session(ctx, &session, next_session);
list_big_fish_works_tx(
list_big_fish_works_tx(ctx, build_public_big_fish_gallery_list_input())
}
pub(crate) fn record_big_fish_like_tx(
ctx: &ReducerContext,
input: BigFishWorkLikeRecordInput,
) -> Result<Vec<BigFishWorkSummarySnapshot>, String> {
let session_id = input.session_id.trim();
let user_id = input.user_id.trim();
if session_id.is_empty() || user_id.is_empty() {
return Err("big_fish like 参数不能为空".to_string());
}
let session = ctx
.db
.big_fish_creation_session()
.session_id()
.find(&session_id.to_string())
.filter(|row| row.stage == BigFishCreationStage::Published)
.ok_or_else(|| "big_fish 已发布作品不存在,无法点赞".to_string())?;
let inserted_like = record_public_work_like(
ctx,
BigFishWorksListInput {
owner_user_id: String::new(),
published_only: true,
PublicWorkLikeRecordInput {
source_type: "big-fish".to_string(),
owner_user_id: session.owner_user_id.clone(),
profile_id: session.session_id.clone(),
user_id: user_id.to_string(),
liked_at_micros: input.liked_at_micros,
},
)?;
if inserted_like {
let liked_at = Timestamp::from_micros_since_unix_epoch(input.liked_at_micros);
let next_session = BigFishCreationSession {
session_id: session.session_id.clone(),
owner_user_id: session.owner_user_id.clone(),
seed_text: session.seed_text.clone(),
current_turn: session.current_turn,
progress_percent: session.progress_percent,
stage: session.stage,
anchor_pack_json: session.anchor_pack_json.clone(),
draft_json: session.draft_json.clone(),
asset_coverage_json: session.asset_coverage_json.clone(),
last_assistant_reply: session.last_assistant_reply.clone(),
publish_ready: session.publish_ready,
play_count: session.play_count,
remix_count: session.remix_count,
like_count: session.like_count.saturating_add(1),
published_at: session.published_at,
created_at: session.created_at,
updated_at: liked_at,
};
replace_big_fish_session(ctx, &session, next_session);
}
list_big_fish_works_tx(ctx, build_public_big_fish_gallery_list_input())
}
fn remix_big_fish_work_tx(
ctx: &ReducerContext,
input: BigFishWorkRemixInput,
) -> Result<BigFishSessionSnapshot, String> {
let source_session_id = input.source_session_id.trim();
let target_session_id = input.target_session_id.trim();
let target_owner_user_id = input.target_owner_user_id.trim();
let welcome_message_id = input.welcome_message_id.trim();
if source_session_id.is_empty()
|| target_session_id.is_empty()
|| target_owner_user_id.is_empty()
|| welcome_message_id.is_empty()
{
return Err("big_fish remix 参数不能为空".to_string());
}
if ctx
.db
.big_fish_creation_session()
.session_id()
.find(&target_session_id.to_string())
.is_some()
{
return Err("big_fish remix 目标 session 已存在".to_string());
}
if ctx
.db
.big_fish_agent_message()
.message_id()
.find(&welcome_message_id.to_string())
.is_some()
{
return Err("big_fish remix 消息已存在".to_string());
}
let source = ctx
.db
.big_fish_creation_session()
.session_id()
.find(&source_session_id.to_string())
.filter(|row| row.stage == BigFishCreationStage::Published)
.ok_or_else(|| "big_fish 已发布源作品不存在".to_string())?;
let remixed_at = Timestamp::from_micros_since_unix_epoch(input.remixed_at_micros);
let next_source = BigFishCreationSession {
session_id: source.session_id.clone(),
owner_user_id: source.owner_user_id.clone(),
seed_text: source.seed_text.clone(),
current_turn: source.current_turn,
progress_percent: source.progress_percent,
stage: source.stage,
anchor_pack_json: source.anchor_pack_json.clone(),
draft_json: source.draft_json.clone(),
asset_coverage_json: source.asset_coverage_json.clone(),
last_assistant_reply: source.last_assistant_reply.clone(),
publish_ready: source.publish_ready,
play_count: source.play_count,
remix_count: source.remix_count.saturating_add(1),
like_count: source.like_count,
published_at: source.published_at,
created_at: source.created_at,
updated_at: remixed_at,
};
replace_big_fish_session(ctx, &source, next_source);
let target_session = BigFishCreationSession {
session_id: target_session_id.to_string(),
owner_user_id: target_owner_user_id.to_string(),
seed_text: source.seed_text.clone(),
current_turn: 1,
progress_percent: 80,
stage: BigFishCreationStage::DraftReady,
anchor_pack_json: source.anchor_pack_json.clone(),
draft_json: source.draft_json.clone(),
asset_coverage_json: source.asset_coverage_json.clone(),
last_assistant_reply: Some("已从公开作品 Remix 出新的大鱼吃小鱼草稿。".to_string()),
publish_ready: source.publish_ready,
play_count: 0,
remix_count: 0,
like_count: 0,
published_at: None,
created_at: remixed_at,
updated_at: remixed_at,
};
ctx.db.big_fish_creation_session().insert(target_session);
ctx.db.big_fish_agent_message().insert(BigFishAgentMessage {
message_id: welcome_message_id.to_string(),
session_id: target_session_id.to_string(),
role: BigFishAgentMessageRole::Assistant,
kind: BigFishAgentMessageKind::Summary,
text: "已复制公开作品为你的草稿。".to_string(),
created_at: remixed_at,
});
for slot in list_big_fish_asset_slots(ctx, &source.session_id) {
upsert_big_fish_asset_slot(
ctx,
BigFishAssetSlotSnapshot {
slot_id: slot.slot_id.replace(&source.session_id, target_session_id),
session_id: target_session_id.to_string(),
asset_kind: slot.asset_kind,
level: slot.level,
motion_key: slot.motion_key,
status: slot.status,
asset_url: slot.asset_url,
prompt_snapshot: slot.prompt_snapshot,
updated_at_micros: input.remixed_at_micros,
},
);
}
get_big_fish_session_tx(
ctx,
BigFishSessionGetInput {
session_id: target_session_id.to_string(),
owner_user_id: target_owner_user_id.to_string(),
},
)
}
@@ -747,6 +987,7 @@ pub(crate) fn build_big_fish_session_snapshot(
pub(crate) fn build_big_fish_work_summary(
ctx: &ReducerContext,
row: &BigFishCreationSession,
now_micros: i64,
) -> Result<BigFishWorkSummarySnapshot, String> {
let draft = row
.draft_json
@@ -809,9 +1050,29 @@ pub(crate) fn build_big_fish_work_summary(
level_motion_ready_count: coverage.level_motion_ready_count,
background_ready: coverage.background_ready,
play_count: row.play_count,
remix_count: row.remix_count,
like_count: row.like_count,
recent_play_count_7d: count_recent_public_work_plays(
ctx,
"big-fish",
&row.session_id,
now_micros,
),
published_at_micros: row
.published_at
.or_else(|| (row.stage == BigFishCreationStage::Published).then_some(row.updated_at))
.map(|value| value.to_micros_since_unix_epoch()),
})
}
fn build_public_big_fish_gallery_list_input() -> BigFishWorksListInput {
BigFishWorksListInput {
// 中文注释published_only 分支不会按 owner 过滤;非空占位用于兼容旧部署模块的前置校验。
owner_user_id: PUBLIC_BIG_FISH_GALLERY_OWNER_USER_ID.to_string(),
published_only: true,
}
}
pub(crate) fn replace_big_fish_session(
ctx: &ReducerContext,
current: &BigFishCreationSession,
@@ -846,6 +1107,13 @@ mod tests {
last_assistant_reply: Some("欢迎来到大鱼吃小鱼共创。".to_string()),
publish_ready: false,
play_count: 0,
remix_count: 0,
like_count: 0,
published_at: if stage == BigFishCreationStage::Published {
Some(Timestamp::from_micros_since_unix_epoch(1))
} else {
None
},
created_at: Timestamp::from_micros_since_unix_epoch(1),
updated_at: Timestamp::from_micros_since_unix_epoch(1),
}

View File

@@ -17,9 +17,16 @@ pub struct BigFishCreationSession {
pub(crate) asset_coverage_json: String,
pub(crate) last_assistant_reply: Option<String>,
pub(crate) publish_ready: bool,
pub(crate) play_count: u32,
pub(crate) created_at: Timestamp,
pub(crate) updated_at: Timestamp,
#[default(0)]
pub(crate) play_count: u32,
#[default(0)]
pub(crate) remix_count: u32,
#[default(0)]
pub(crate) like_count: u32,
#[default(None::<Timestamp>)]
pub(crate) published_at: Option<Timestamp>,
}
#[spacetimedb::table(