This commit is contained in:
2026-04-23 06:01:00 +08:00
parent 9dc56f0fc0
commit f6046ef658
123 changed files with 7752 additions and 436 deletions

View File

@@ -57,7 +57,8 @@ use crate::{
error_middleware::normalize_error_response,
health::health_check,
legacy_generated_assets::{
proxy_generated_animations, proxy_generated_character_drafts, proxy_generated_characters,
proxy_generated_animations, proxy_generated_big_fish_assets,
proxy_generated_character_drafts, proxy_generated_characters,
proxy_generated_custom_world_covers, proxy_generated_custom_world_scenes,
proxy_generated_qwen_sprites,
},
@@ -137,6 +138,10 @@ pub fn build_router(state: AppState) -> Router {
"/generated-animations/{*path}",
get(proxy_generated_animations),
)
.route(
"/generated-big-fish-assets/{*path}",
get(proxy_generated_big_fish_assets),
)
.route(
"/generated-custom-world-scenes/{*path}",
get(proxy_generated_custom_world_scenes),

View File

@@ -1,10 +1,22 @@
use std::{
collections::BTreeMap,
time::{Duration, Instant, SystemTime, UNIX_EPOCH},
};
use axum::{
Json,
extract::{Extension, Path, State, rejection::JsonRejection},
http::{HeaderName, StatusCode, header},
response::{IntoResponse, Response},
};
use serde_json::{Value, json};
use module_assets::{
AssetObjectAccessPolicy, AssetObjectFieldError, build_asset_entity_binding_input,
build_asset_object_upsert_input, generate_asset_binding_id, generate_asset_object_id,
};
use platform_oss::{
LegacyAssetPrefix, OssHeadObjectRequest, OssObjectAccess, OssPutObjectRequest,
};
use serde_json::{Map, Value, json};
use shared_contracts::big_fish::{
BigFishActionResponse, BigFishAgentMessageResponse, BigFishAnchorItemResponse,
BigFishAnchorPackResponse, BigFishAssetCoverageResponse, BigFishAssetSlotResponse,
@@ -24,6 +36,7 @@ use spacetime_client::{
BigFishSessionCreateRecordInput, BigFishSessionRecord, BigFishVector2Record,
SpacetimeClientError,
};
use tokio::time::sleep;
use crate::{
api_response::json_success_body, auth::AuthenticatedAccessToken, http_error::AppError,
@@ -233,6 +246,17 @@ pub async fn execute_big_fish_action(
.await
}
"big_fish_generate_level_main_image" => {
let asset_url = generate_big_fish_formal_asset(
&state,
&owner_user_id,
&session_id,
"level_main_image",
payload.level,
None,
now,
)
.await
.map_err(|error| big_fish_error_response(&request_context, error))?;
state
.spacetime_client()
.generate_big_fish_asset(BigFishAssetGenerateRecordInput {
@@ -241,11 +265,23 @@ pub async fn execute_big_fish_action(
asset_kind: "level_main_image".to_string(),
level: payload.level,
motion_key: None,
asset_url: Some(asset_url),
generated_at_micros: now,
})
.await
}
"big_fish_generate_level_motion" => {
let asset_url = generate_big_fish_formal_asset(
&state,
&owner_user_id,
&session_id,
"level_motion",
payload.level,
payload.motion_key.as_deref(),
now,
)
.await
.map_err(|error| big_fish_error_response(&request_context, error))?;
state
.spacetime_client()
.generate_big_fish_asset(BigFishAssetGenerateRecordInput {
@@ -254,11 +290,23 @@ pub async fn execute_big_fish_action(
asset_kind: "level_motion".to_string(),
level: payload.level,
motion_key: payload.motion_key,
asset_url: Some(asset_url),
generated_at_micros: now,
})
.await
}
"big_fish_generate_stage_background" => {
let asset_url = generate_big_fish_formal_asset(
&state,
&owner_user_id,
&session_id,
"stage_background",
None,
None,
now,
)
.await
.map_err(|error| big_fish_error_response(&request_context, error))?;
state
.spacetime_client()
.generate_big_fish_asset(BigFishAssetGenerateRecordInput {
@@ -267,6 +315,7 @@ pub async fn execute_big_fish_action(
asset_kind: "stage_background".to_string(),
level: None,
motion_key: None,
asset_url: Some(asset_url),
generated_at_micros: now,
})
.await
@@ -588,6 +637,747 @@ fn build_big_fish_welcome_text(seed_text: &str) -> String {
"我已经收到你的玩法起点,会先把它整理成锚点并准备结果页草稿。".to_string()
}
struct BigFishDashScopeSettings {
base_url: String,
api_key: String,
request_timeout_ms: u64,
}
struct BigFishGeneratedImage {
image_url: String,
task_id: String,
}
struct BigFishDownloadedImage {
mime_type: String,
extension: String,
bytes: Vec<u8>,
}
struct BigFishFormalAssetContext {
entity_id: String,
prompt: String,
negative_prompt: String,
size: String,
asset_object_kind: String,
binding_slot: String,
path_segments: Vec<String>,
}
const BIG_FISH_TEXT_TO_IMAGE_MODEL: &str = "wan2.2-t2i-flash";
const BIG_FISH_ENTITY_KIND: &str = "big_fish_session";
const BIG_FISH_DEFAULT_NEGATIVE_PROMPT: &str =
"文字水印logoUI界面对话框边框多余肢体畸形鱼体低清晰度模糊压缩噪点现代摄影棚写实照片背景";
async fn generate_big_fish_formal_asset(
state: &AppState,
owner_user_id: &str,
session_id: &str,
asset_kind: &str,
level: Option<u32>,
motion_key: Option<&str>,
generated_at_micros: i64,
) -> Result<String, AppError> {
let session = state
.spacetime_client()
.get_big_fish_session(session_id.to_string(), owner_user_id.to_string())
.await
.map_err(map_big_fish_client_error)?;
let draft = session.draft.as_ref().ok_or_else(|| {
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
"provider": "big-fish",
"message": "玩法草稿尚未编译,不能生成正式图片。",
}))
})?;
let context = build_big_fish_formal_asset_context(
&session,
draft,
asset_kind,
level,
motion_key,
generated_at_micros,
)?;
let settings = require_big_fish_dashscope_settings(state)?;
let http_client = build_big_fish_dashscope_http_client(&settings)?;
let generated = create_big_fish_text_to_image_generation(
&http_client,
&settings,
context.prompt.as_str(),
context.negative_prompt.as_str(),
context.size.as_str(),
)
.await?;
let downloaded = download_big_fish_remote_image(
&http_client,
generated.image_url.as_str(),
"下载 Big Fish 正式图片失败",
)
.await?;
persist_big_fish_formal_asset(
state,
owner_user_id,
&context,
generated,
downloaded,
generated_at_micros,
)
.await
}
fn build_big_fish_formal_asset_context(
session: &BigFishSessionRecord,
draft: &BigFishGameDraftRecord,
asset_kind: &str,
level: Option<u32>,
motion_key: Option<&str>,
generated_at_micros: i64,
) -> Result<BigFishFormalAssetContext, AppError> {
let asset_id = format!("asset-{generated_at_micros}");
match asset_kind {
"level_main_image" => {
let level = find_big_fish_level_blueprint(draft, level)?;
let level_part = build_big_fish_level_part(Some(level.level));
Ok(BigFishFormalAssetContext {
entity_id: session.session_id.clone(),
prompt: build_big_fish_level_main_image_prompt(draft, level),
negative_prompt: BIG_FISH_DEFAULT_NEGATIVE_PROMPT.to_string(),
size: "1024*1024".to_string(),
asset_object_kind: "big_fish_level_main_image".to_string(),
binding_slot: format!("level_main_image:{level_part}"),
path_segments: vec![
sanitize_big_fish_path_segment(session.session_id.as_str(), "session"),
"level-main-image".to_string(),
level_part,
asset_id,
],
})
}
"level_motion" => {
let level = find_big_fish_level_blueprint(draft, level)?;
let motion_key = motion_key
.map(str::trim)
.filter(|value| matches!(*value, "idle_float" | "move_swim"))
.ok_or_else(|| {
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
"provider": "big-fish",
"message": "motionKey 必须是 idle_float 或 move_swim。",
}))
})?;
let level_part = build_big_fish_level_part(Some(level.level));
Ok(BigFishFormalAssetContext {
entity_id: session.session_id.clone(),
prompt: build_big_fish_level_motion_prompt(draft, level, motion_key),
negative_prompt: BIG_FISH_DEFAULT_NEGATIVE_PROMPT.to_string(),
size: "1024*1024".to_string(),
asset_object_kind: "big_fish_level_motion".to_string(),
binding_slot: format!("level_motion:{level_part}:{motion_key}"),
path_segments: vec![
sanitize_big_fish_path_segment(session.session_id.as_str(), "session"),
"level-motion".to_string(),
level_part,
sanitize_big_fish_path_segment(motion_key, "motion"),
asset_id,
],
})
}
"stage_background" => Ok(BigFishFormalAssetContext {
entity_id: session.session_id.clone(),
prompt: build_big_fish_stage_background_prompt(draft),
negative_prompt: BIG_FISH_DEFAULT_NEGATIVE_PROMPT.to_string(),
size: "720*1280".to_string(),
asset_object_kind: "big_fish_stage_background".to_string(),
binding_slot: "stage_background".to_string(),
path_segments: vec![
sanitize_big_fish_path_segment(session.session_id.as_str(), "session"),
"stage-background".to_string(),
asset_id,
],
}),
_ => Err(AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
"provider": "big-fish",
"message": format!("assetKind `{asset_kind}` 不支持正式图片生成。"),
}))),
}
}
fn find_big_fish_level_blueprint(
draft: &BigFishGameDraftRecord,
level: Option<u32>,
) -> Result<&BigFishLevelBlueprintRecord, AppError> {
let level = level.ok_or_else(|| {
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
"provider": "big-fish",
"message": "level 是等级资产生成的必填项。",
}))
})?;
draft
.levels
.iter()
.find(|blueprint| blueprint.level == level)
.ok_or_else(|| {
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
"provider": "big-fish",
"message": format!("level `{level}` 不存在于当前 Big Fish 草稿。"),
}))
})
}
fn build_big_fish_level_main_image_prompt(
draft: &BigFishGameDraftRecord,
level: &BigFishLevelBlueprintRecord,
) -> String {
vec![
format!(
"为竖屏移动游戏《{}》生成一张等级生物主图。",
draft.title
),
format!(
"生态主题:{}。核心乐趣:{}",
draft.ecology_theme, draft.core_fun
),
format!(
"等级Lv.{},名称:{},幻想描述:{}",
level.level, level.name, level.one_line_fantasy
),
format!("轮廓方向:{}", level.silhouette_direction),
format!("视觉提示词种子:{}", level.visual_prompt_seed),
"画面要求单体游戏生物完整入镜轮廓清晰适合作为大鱼吃小鱼等级角色主图2D 高完成度游戏插画,深海发光质感,中央构图。".to_string(),
"不要出现 UI、文字、logo、水印、对话框或边框背景保持干净的深海渐变或透明感不要出现多只主体。".to_string(),
]
.join("")
}
fn build_big_fish_level_motion_prompt(
draft: &BigFishGameDraftRecord,
level: &BigFishLevelBlueprintRecord,
motion_key: &str,
) -> String {
let motion_text = match motion_key {
"move_swim" => "向右游动的关键帧预览,身体与尾鳍有清晰推进姿态,带轻微水流拖尾。",
_ => "待机漂浮的关键帧预览,身体轻微摆动,姿态稳定,适合作为 idle 状态。",
};
vec![
format!(
"为竖屏移动游戏《{}》生成一张等级生物动作关键帧静态预览图。",
draft.title
),
format!("生态主题:{}", draft.ecology_theme),
format!(
"等级Lv.{},名称:{},幻想描述:{}",
level.level, level.name, level.one_line_fantasy
),
format!("动作提示词种子:{}", level.motion_prompt_seed),
format!("动作要求:{motion_text}"),
"画面要求单体生物完整入镜轮廓清晰动作方向明确2D 高完成度游戏插画,适合作为 Big Fish 动作槽位的静态 keyframe。".to_string(),
"不要出现 UI、文字、logo、水印、对话框或边框不要生成序列帧拼图不要出现多只主体。".to_string(),
]
.join("")
}
fn build_big_fish_stage_background_prompt(draft: &BigFishGameDraftRecord) -> String {
let background = &draft.background;
vec![
format!(
"为竖屏移动游戏《{}》生成一张 9:16 全屏活动区域背景。",
draft.title
),
format!("生态主题:{}", draft.ecology_theme),
format!("背景主题:{}。色彩氛围:{}", background.theme, background.color_mood),
format!("前景提示:{}", background.foreground_hints),
format!("中景构图:{}", background.midground_composition),
format!("背景纵深:{}", background.background_depth),
format!("安全操作区:{}", background.safe_play_area_hint),
format!("出生边缘:{}", background.spawn_edge_hint),
format!("背景提示词种子:{}", background.background_prompt_seed),
"画面要求:竖屏 9:16中央 70% 保持清爽可读,边缘有深海生态层次和微弱生物光,适合作为大鱼吃小鱼运行态背景。".to_string(),
"不要出现 UI、文字、logo、水印、对话框、边框或巨大主体遮挡不要把中央操作区画得过暗或过复杂。".to_string(),
]
.join("")
}
fn require_big_fish_dashscope_settings(
state: &AppState,
) -> Result<BigFishDashScopeSettings, AppError> {
let base_url = state.config.dashscope_base_url.trim().trim_end_matches('/');
if base_url.is_empty() {
return Err(
AppError::from_status(StatusCode::SERVICE_UNAVAILABLE).with_details(json!({
"provider": "dashscope",
"reason": "DASHSCOPE_BASE_URL 未配置",
})),
);
}
let api_key = state
.config
.dashscope_api_key
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
.ok_or_else(|| {
AppError::from_status(StatusCode::SERVICE_UNAVAILABLE).with_details(json!({
"provider": "dashscope",
"reason": "DASHSCOPE_API_KEY 未配置",
}))
})?;
Ok(BigFishDashScopeSettings {
base_url: base_url.to_string(),
api_key: api_key.to_string(),
request_timeout_ms: state.config.dashscope_image_request_timeout_ms.max(1),
})
}
fn build_big_fish_dashscope_http_client(
settings: &BigFishDashScopeSettings,
) -> Result<reqwest::Client, AppError> {
reqwest::Client::builder()
.timeout(Duration::from_millis(settings.request_timeout_ms))
.build()
.map_err(|error| {
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR).with_details(json!({
"provider": "dashscope",
"message": format!("构造 DashScope HTTP 客户端失败:{error}"),
}))
})
}
async fn create_big_fish_text_to_image_generation(
http_client: &reqwest::Client,
settings: &BigFishDashScopeSettings,
prompt: &str,
negative_prompt: &str,
size: &str,
) -> Result<BigFishGeneratedImage, AppError> {
let mut parameters = Map::from_iter([
("n".to_string(), json!(1)),
("size".to_string(), Value::String(size.to_string())),
("prompt_extend".to_string(), Value::Bool(true)),
("watermark".to_string(), Value::Bool(false)),
]);
if !negative_prompt.trim().is_empty() {
parameters.insert(
"negative_prompt".to_string(),
Value::String(negative_prompt.trim().to_string()),
);
}
let response = http_client
.post(format!(
"{}/services/aigc/text2image/image-synthesis",
settings.base_url
))
.header(
reqwest::header::AUTHORIZATION,
format!("Bearer {}", settings.api_key),
)
.header(reqwest::header::CONTENT_TYPE, "application/json")
.header("X-DashScope-Async", "enable")
.json(&json!({
"model": BIG_FISH_TEXT_TO_IMAGE_MODEL,
"input": {
"prompt": prompt,
},
"parameters": parameters,
}))
.send()
.await
.map_err(|error| map_big_fish_dashscope_request_error(format!(
"创建 Big Fish 图片生成任务失败:{error}"
)))?;
let status = response.status();
let response_text = response.text().await.map_err(|error| {
map_big_fish_dashscope_request_error(format!("读取 Big Fish 图片生成响应失败:{error}"))
})?;
if !status.is_success() {
return Err(map_big_fish_dashscope_upstream_error(
response_text.as_str(),
"创建 Big Fish 图片生成任务失败",
));
}
let payload = parse_big_fish_json_payload(response_text.as_str(), "解析 Big Fish 图片生成响应失败")?;
let task_id = extract_big_fish_task_id(&payload).ok_or_else(|| {
AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({
"provider": "dashscope",
"message": "Big Fish 图片生成任务未返回 task_id",
}))
})?;
let deadline = Instant::now() + Duration::from_millis(settings.request_timeout_ms);
while Instant::now() < deadline {
let poll_response = http_client
.get(format!("{}/tasks/{}", settings.base_url, task_id))
.header(
reqwest::header::AUTHORIZATION,
format!("Bearer {}", settings.api_key),
)
.send()
.await
.map_err(|error| map_big_fish_dashscope_request_error(format!(
"查询 Big Fish 图片生成任务失败:{error}"
)))?;
let poll_status = poll_response.status();
let poll_text = poll_response.text().await.map_err(|error| {
map_big_fish_dashscope_request_error(format!(
"读取 Big Fish 图片生成任务响应失败:{error}"
))
})?;
if !poll_status.is_success() {
return Err(map_big_fish_dashscope_upstream_error(
poll_text.as_str(),
"查询 Big Fish 图片生成任务失败",
));
}
let poll_payload =
parse_big_fish_json_payload(poll_text.as_str(), "解析 Big Fish 图片生成任务响应失败")?;
let task_status = find_first_big_fish_string_by_key(&poll_payload, "task_status")
.unwrap_or_default()
.trim()
.to_string();
if task_status == "SUCCEEDED" {
let image_url = extract_big_fish_image_urls(&poll_payload)
.into_iter()
.next()
.ok_or_else(|| {
AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({
"provider": "dashscope",
"message": "Big Fish 图片生成成功但未返回图片地址",
}))
})?;
return Ok(BigFishGeneratedImage { image_url, task_id });
}
if matches!(task_status.as_str(), "FAILED" | "UNKNOWN") {
return Err(map_big_fish_dashscope_upstream_error(
poll_text.as_str(),
"Big Fish 图片生成任务失败",
));
}
sleep(Duration::from_secs(2)).await;
}
Err(
AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({
"provider": "dashscope",
"message": "Big Fish 图片生成超时或未返回图片地址",
})),
)
}
async fn download_big_fish_remote_image(
http_client: &reqwest::Client,
image_url: &str,
fallback_message: &str,
) -> Result<BigFishDownloadedImage, AppError> {
let response = http_client
.get(image_url)
.send()
.await
.map_err(|error| map_big_fish_dashscope_request_error(format!("{fallback_message}{error}")))?;
let status = response.status();
let content_type = response
.headers()
.get(reqwest::header::CONTENT_TYPE)
.and_then(|value| value.to_str().ok())
.unwrap_or("image/jpeg")
.to_string();
let bytes = response
.bytes()
.await
.map_err(|error| map_big_fish_dashscope_request_error(format!("{fallback_message}{error}")))?;
if !status.is_success() {
return Err(
AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({
"provider": "dashscope",
"message": fallback_message,
"status": status.as_u16(),
})),
);
}
let mime_type = normalize_big_fish_downloaded_image_mime_type(content_type.as_str());
Ok(BigFishDownloadedImage {
extension: big_fish_mime_to_extension(mime_type.as_str()).to_string(),
mime_type,
bytes: bytes.to_vec(),
})
}
async fn persist_big_fish_formal_asset(
state: &AppState,
owner_user_id: &str,
context: &BigFishFormalAssetContext,
generated: BigFishGeneratedImage,
downloaded: BigFishDownloadedImage,
generated_at_micros: i64,
) -> Result<String, AppError> {
let oss_client = state.oss_client().ok_or_else(|| {
AppError::from_status(StatusCode::SERVICE_UNAVAILABLE).with_details(json!({
"provider": "aliyun-oss",
"reason": "OSS 未完成环境变量配置",
}))
})?;
let http_client = reqwest::Client::new();
let put_result = oss_client
.put_object(
&http_client,
OssPutObjectRequest {
prefix: LegacyAssetPrefix::BigFishAssets,
path_segments: context.path_segments.clone(),
file_name: format!("image.{}", downloaded.extension),
content_type: Some(downloaded.mime_type.clone()),
access: OssObjectAccess::Private,
metadata: build_big_fish_asset_metadata(
context.asset_object_kind.as_str(),
owner_user_id,
BIG_FISH_ENTITY_KIND,
context.entity_id.as_str(),
context.binding_slot.as_str(),
),
body: downloaded.bytes,
},
)
.await
.map_err(map_big_fish_asset_oss_error)?;
let head = oss_client
.head_object(
&http_client,
OssHeadObjectRequest {
object_key: put_result.object_key.clone(),
},
)
.await
.map_err(map_big_fish_asset_oss_error)?;
let asset_object = state
.spacetime_client()
.confirm_asset_object(
build_asset_object_upsert_input(
generate_asset_object_id(generated_at_micros),
head.bucket,
head.object_key,
AssetObjectAccessPolicy::Private,
head.content_type.or(Some(downloaded.mime_type)),
head.content_length,
head.etag,
context.asset_object_kind.clone(),
Some(generated.task_id),
Some(owner_user_id.to_string()),
None,
Some(context.entity_id.clone()),
generated_at_micros,
)
.map_err(map_big_fish_asset_object_prepare_error)?,
)
.await
.map_err(map_big_fish_asset_spacetime_error)?;
state
.spacetime_client()
.bind_asset_object_to_entity(
build_asset_entity_binding_input(
generate_asset_binding_id(generated_at_micros),
asset_object.asset_object_id,
BIG_FISH_ENTITY_KIND.to_string(),
context.entity_id.clone(),
context.binding_slot.clone(),
context.asset_object_kind.clone(),
Some(owner_user_id.to_string()),
None,
generated_at_micros,
)
.map_err(map_big_fish_asset_binding_prepare_error)?,
)
.await
.map_err(map_big_fish_asset_spacetime_error)?;
Ok(put_result.legacy_public_path)
}
fn build_big_fish_asset_metadata(
asset_kind: &str,
owner_user_id: &str,
entity_kind: &str,
entity_id: &str,
slot: &str,
) -> BTreeMap<String, String> {
BTreeMap::from([
("asset_kind".to_string(), asset_kind.to_string()),
("owner_user_id".to_string(), owner_user_id.to_string()),
("entity_kind".to_string(), entity_kind.to_string()),
("entity_id".to_string(), entity_id.to_string()),
("slot".to_string(), slot.to_string()),
])
}
fn parse_big_fish_json_payload(raw_text: &str, fallback_message: &str) -> Result<Value, AppError> {
serde_json::from_str::<Value>(raw_text).map_err(|error| {
AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({
"provider": "dashscope",
"message": format!("{fallback_message}{error}"),
}))
})
}
fn extract_big_fish_task_id(payload: &Value) -> Option<String> {
find_first_big_fish_string_by_key(payload, "task_id")
}
fn extract_big_fish_image_urls(payload: &Value) -> Vec<String> {
let mut urls = Vec::new();
collect_big_fish_strings_by_key(payload, "image", &mut urls);
collect_big_fish_strings_by_key(payload, "url", &mut urls);
let mut deduped = Vec::new();
for url in urls {
if !deduped.contains(&url) {
deduped.push(url);
}
}
deduped
}
fn find_first_big_fish_string_by_key(payload: &Value, target_key: &str) -> Option<String> {
let mut results = Vec::new();
collect_big_fish_strings_by_key(payload, target_key, &mut results);
results.into_iter().next()
}
fn collect_big_fish_strings_by_key(payload: &Value, target_key: &str, results: &mut Vec<String>) {
match payload {
Value::Array(entries) => {
for entry in entries {
collect_big_fish_strings_by_key(entry, target_key, results);
}
}
Value::Object(object) => {
for (key, value) in object {
if key == target_key
&& let Some(text) = value.as_str()
{
results.push(text.to_string());
}
collect_big_fish_strings_by_key(value, target_key, results);
}
}
_ => {}
}
}
fn normalize_big_fish_downloaded_image_mime_type(content_type: &str) -> String {
let mime_type = content_type
.split(';')
.next()
.map(str::trim)
.unwrap_or("image/jpeg");
match mime_type {
"image/png" | "image/webp" | "image/jpeg" | "image/jpg" | "image/gif" => {
mime_type.to_string()
}
_ => "image/jpeg".to_string(),
}
}
fn big_fish_mime_to_extension(mime_type: &str) -> &str {
match mime_type {
"image/png" => "png",
"image/webp" => "webp",
"image/gif" => "gif",
_ => "jpg",
}
}
fn map_big_fish_dashscope_request_error(message: String) -> AppError {
AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({
"provider": "dashscope",
"message": message,
}))
}
fn map_big_fish_dashscope_upstream_error(raw_text: &str, fallback_message: &str) -> AppError {
AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({
"provider": "dashscope",
"message": parse_big_fish_api_error_message(raw_text, fallback_message),
}))
}
fn parse_big_fish_api_error_message(raw_text: &str, fallback_message: &str) -> String {
let trimmed = raw_text.trim();
if trimmed.is_empty() {
return fallback_message.to_string();
}
if let Ok(payload) = serde_json::from_str::<Value>(trimmed)
&& let Some(message) = find_first_big_fish_string_by_key(&payload, "message")
.or_else(|| find_first_big_fish_string_by_key(&payload, "code"))
{
return message;
}
let excerpt = trimmed.chars().take(240).collect::<String>();
format!("{fallback_message}{excerpt}")
}
fn map_big_fish_asset_object_prepare_error(error: AssetObjectFieldError) -> AppError {
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
"provider": "asset-object",
"message": error.to_string(),
}))
}
fn map_big_fish_asset_binding_prepare_error(error: AssetObjectFieldError) -> AppError {
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
"provider": "asset-entity-binding",
"message": error.to_string(),
}))
}
fn map_big_fish_asset_spacetime_error(error: SpacetimeClientError) -> AppError {
AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({
"provider": "spacetimedb",
"message": error.to_string(),
}))
}
fn map_big_fish_asset_oss_error(error: platform_oss::OssError) -> AppError {
let status = match error {
platform_oss::OssError::InvalidConfig(_) | platform_oss::OssError::InvalidRequest(_) => {
StatusCode::BAD_REQUEST
}
platform_oss::OssError::ObjectNotFound(_) => StatusCode::NOT_FOUND,
platform_oss::OssError::Request(_)
| platform_oss::OssError::SerializePolicy(_)
| platform_oss::OssError::Sign(_) => StatusCode::BAD_GATEWAY,
};
AppError::from_status(status).with_details(json!({
"provider": "aliyun-oss",
"message": error.to_string(),
}))
}
fn build_big_fish_level_part(level: Option<u32>) -> String {
level
.map(|value| format!("level-{value}"))
.unwrap_or_else(|| "stage".to_string())
}
fn sanitize_big_fish_path_segment(value: &str, fallback: &str) -> String {
let sanitized = value
.trim()
.chars()
.map(|ch| {
if ch.is_ascii_alphanumeric() || ch == '-' || ch == '_' {
ch
} else {
'-'
}
})
.collect::<String>()
.trim_matches('-')
.to_string();
if sanitized.is_empty() {
fallback.to_string()
} else {
sanitized
}
}
fn ensure_non_empty(
request_context: &RequestContext,
value: &str,
@@ -680,8 +1470,6 @@ fn big_fish_error_response(request_context: &RequestContext, error: AppError) ->
}
fn current_utc_micros() -> i64 {
use std::time::{SystemTime, UNIX_EPOCH};
let duration = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system clock should be after unix epoch");

View File

@@ -33,6 +33,13 @@ pub async fn proxy_generated_animations(
proxy_legacy_generated_asset(state, LegacyAssetPrefix::Animations, path).await
}
pub async fn proxy_generated_big_fish_assets(
State(state): State<AppState>,
Path(path): Path<String>,
) -> Response {
proxy_legacy_generated_asset(state, LegacyAssetPrefix::BigFishAssets, path).await
}
pub async fn proxy_generated_custom_world_scenes(
State(state): State<AppState>,
Path(path): Path<String>,
@@ -200,6 +207,20 @@ mod tests {
);
}
#[test]
fn build_generated_object_key_supports_big_fish_assets() {
let object_key = build_generated_object_key(
LegacyAssetPrefix::BigFishAssets,
"big-fish-session-1/level-main-image/level-1/image.png",
)
.expect("object key should build");
assert_eq!(
object_key,
"generated-big-fish-assets/big-fish-session-1/level-main-image/level-1/image.png"
);
}
#[test]
fn build_generated_object_key_rejects_parent_segment() {
assert!(

View File

@@ -305,6 +305,7 @@ pub struct BigFishAssetGenerateInput {
pub asset_kind: BigFishAssetKind,
pub level: Option<u32>,
pub motion_key: Option<String>,
pub asset_url: Option<String>,
pub generated_at_micros: i64,
}
@@ -593,12 +594,16 @@ pub fn build_generated_asset_slot(
asset_kind: BigFishAssetKind,
level: Option<u32>,
motion_key: Option<String>,
asset_url: Option<String>,
updated_at_micros: i64,
) -> Result<BigFishAssetSlotSnapshot, BigFishFieldError> {
let session_id =
normalize_required_string(session_id).ok_or(BigFishFieldError::MissingSessionId)?;
let prompt_snapshot = build_asset_prompt_snapshot(draft, asset_kind, level, motion_key.as_deref())?;
let prompt_snapshot =
build_asset_prompt_snapshot(draft, asset_kind, level, motion_key.as_deref())?;
let slot_id = build_asset_slot_id(&session_id, asset_kind, level, motion_key.as_deref());
let resolved_asset_url = normalize_required_string(asset_url.as_deref().unwrap_or_default())
.unwrap_or_else(|| build_placeholder_asset_url(asset_kind, level, updated_at_micros));
Ok(BigFishAssetSlotSnapshot {
slot_id,
@@ -607,7 +612,7 @@ pub fn build_generated_asset_slot(
level,
motion_key,
status: BigFishAssetStatus::Ready,
asset_url: Some(build_placeholder_asset_url(asset_kind, level, updated_at_micros)),
asset_url: Some(resolved_asset_url),
prompt_snapshot,
updated_at_micros,
})

View File

@@ -17,10 +17,11 @@ pub const DEFAULT_POST_MAX_SIZE_BYTES: u64 = 20 * 1024 * 1024;
pub const DEFAULT_SUCCESS_ACTION_STATUS: u16 = 200;
pub const DEFAULT_METADATA_TOTAL_BYTES_LIMIT: usize = 8 * 1024;
pub const LEGACY_PUBLIC_PREFIXES: [&str; 6] = [
pub const LEGACY_PUBLIC_PREFIXES: [&str; 7] = [
"generated-character-drafts",
"generated-characters",
"generated-animations",
"generated-big-fish-assets",
"generated-custom-world-scenes",
"generated-custom-world-covers",
"generated-qwen-sprites",
@@ -38,6 +39,7 @@ pub enum LegacyAssetPrefix {
CharacterDrafts,
Characters,
Animations,
BigFishAssets,
CustomWorldScenes,
CustomWorldCovers,
QwenSprites,
@@ -206,6 +208,7 @@ impl LegacyAssetPrefix {
"generated-character-drafts" => Some(Self::CharacterDrafts),
"generated-characters" => Some(Self::Characters),
"generated-animations" => Some(Self::Animations),
"generated-big-fish-assets" => Some(Self::BigFishAssets),
"generated-custom-world-scenes" => Some(Self::CustomWorldScenes),
"generated-custom-world-covers" => Some(Self::CustomWorldCovers),
"generated-qwen-sprites" => Some(Self::QwenSprites),
@@ -218,6 +221,7 @@ impl LegacyAssetPrefix {
Self::CharacterDrafts => "generated-character-drafts",
Self::Characters => "generated-characters",
Self::Animations => "generated-animations",
Self::BigFishAssets => "generated-big-fish-assets",
Self::CustomWorldScenes => "generated-custom-world-scenes",
Self::CustomWorldCovers => "generated-custom-world-covers",
Self::QwenSprites => "generated-qwen-sprites",

View File

@@ -1487,6 +1487,7 @@ impl SpacetimeClient {
asset_kind: map_big_fish_asset_kind_input(input.asset_kind.as_str())?,
level: input.level,
motion_key: input.motion_key,
asset_url: input.asset_url,
generated_at_micros: input.generated_at_micros,
};
@@ -6137,6 +6138,7 @@ pub struct BigFishAssetGenerateRecordInput {
pub asset_kind: String,
pub level: Option<u32>,
pub motion_key: Option<String>,
pub asset_url: Option<String>,
pub generated_at_micros: i64,
}

View File

@@ -19,6 +19,7 @@ pub struct BigFishAssetGenerateInput {
pub asset_kind: BigFishAssetKind,
pub level: Option::<u32>,
pub motion_key: Option::<String>,
pub asset_url: Option::<String>,
pub generated_at_micros: i64,
}

View File

@@ -328,44 +328,7 @@ pub mod unpublish_custom_world_profile_reducer;
pub mod upsert_chapter_progression_reducer;
pub mod upsert_custom_world_profile_reducer;
pub mod upsert_npc_state_reducer;
pub mod ai_result_reference_table;
pub mod ai_task_table;
pub mod ai_task_stage_table;
pub mod ai_text_chunk_table;
pub mod asset_entity_binding_table;
pub mod asset_object_table;
pub mod battle_state_table;
pub mod big_fish_agent_message_table;
pub mod big_fish_asset_slot_table;
pub mod big_fish_creation_session_table;
pub mod big_fish_runtime_run_table;
pub mod chapter_progression_table;
pub mod custom_world_agent_message_table;
pub mod custom_world_agent_operation_table;
pub mod custom_world_agent_session_table;
pub mod custom_world_draft_card_table;
pub mod custom_world_gallery_entry_table;
pub mod custom_world_profile_table;
pub mod custom_world_session_table;
pub mod inventory_slot_table;
pub mod npc_state_table;
pub mod player_progression_table;
pub mod profile_dashboard_state_table;
pub mod profile_played_world_table;
pub mod profile_save_archive_table;
pub mod profile_wallet_ledger_table;
pub mod puzzle_agent_message_table;
pub mod puzzle_agent_session_table;
pub mod puzzle_runtime_run_table;
pub mod puzzle_work_profile_table;
pub mod quest_log_table;
pub mod quest_record_table;
pub mod runtime_setting_table;
pub mod runtime_snapshot_table;
pub mod story_event_table;
pub mod story_session_table;
pub mod treasure_record_table;
pub mod user_browse_history_table;
pub mod advance_puzzle_next_level_procedure;
pub mod append_ai_text_chunk_and_return_procedure;
pub mod apply_chapter_progression_ledger_entry_and_return_procedure;
@@ -743,44 +706,7 @@ pub use treasure_record_snapshot_type::TreasureRecordSnapshot;
pub use treasure_resolve_input_type::TreasureResolveInput;
pub use unequip_inventory_item_input_type::UnequipInventoryItemInput;
pub use user_browse_history_type::UserBrowseHistory;
pub use ai_result_reference_table::*;
pub use ai_task_table::*;
pub use ai_task_stage_table::*;
pub use ai_text_chunk_table::*;
pub use asset_entity_binding_table::*;
pub use asset_object_table::*;
pub use battle_state_table::*;
pub use big_fish_agent_message_table::*;
pub use big_fish_asset_slot_table::*;
pub use big_fish_creation_session_table::*;
pub use big_fish_runtime_run_table::*;
pub use chapter_progression_table::*;
pub use custom_world_agent_message_table::*;
pub use custom_world_agent_operation_table::*;
pub use custom_world_agent_session_table::*;
pub use custom_world_draft_card_table::*;
pub use custom_world_gallery_entry_table::*;
pub use custom_world_profile_table::*;
pub use custom_world_session_table::*;
pub use inventory_slot_table::*;
pub use npc_state_table::*;
pub use player_progression_table::*;
pub use profile_dashboard_state_table::*;
pub use profile_played_world_table::*;
pub use profile_save_archive_table::*;
pub use profile_wallet_ledger_table::*;
pub use puzzle_agent_message_table::*;
pub use puzzle_agent_session_table::*;
pub use puzzle_runtime_run_table::*;
pub use puzzle_work_profile_table::*;
pub use quest_log_table::*;
pub use quest_record_table::*;
pub use runtime_setting_table::*;
pub use runtime_snapshot_table::*;
pub use story_event_table::*;
pub use story_session_table::*;
pub use treasure_record_table::*;
pub use user_browse_history_table::*;
pub use accept_quest_reducer::accept_quest;
pub use acknowledge_quest_completion_reducer::acknowledge_quest_completion;
pub use apply_chapter_progression_ledger_entry_reducer::apply_chapter_progression_ledger_entry;
@@ -1138,44 +1064,7 @@ fn args_bsatn(&self) -> Result<Vec<u8>, __sats::bsatn::EncodeError> {
#[allow(non_snake_case)]
#[doc(hidden)]
pub struct DbUpdate {
ai_result_reference: __sdk::TableUpdate<AiResultReference>,
ai_task: __sdk::TableUpdate<AiTask>,
ai_task_stage: __sdk::TableUpdate<AiTaskStage>,
ai_text_chunk: __sdk::TableUpdate<AiTextChunk>,
asset_entity_binding: __sdk::TableUpdate<AssetEntityBinding>,
asset_object: __sdk::TableUpdate<AssetObject>,
battle_state: __sdk::TableUpdate<BattleState>,
big_fish_agent_message: __sdk::TableUpdate<BigFishAgentMessage>,
big_fish_asset_slot: __sdk::TableUpdate<BigFishAssetSlot>,
big_fish_creation_session: __sdk::TableUpdate<BigFishCreationSession>,
big_fish_runtime_run: __sdk::TableUpdate<BigFishRuntimeRun>,
chapter_progression: __sdk::TableUpdate<ChapterProgression>,
custom_world_agent_message: __sdk::TableUpdate<CustomWorldAgentMessage>,
custom_world_agent_operation: __sdk::TableUpdate<CustomWorldAgentOperation>,
custom_world_agent_session: __sdk::TableUpdate<CustomWorldAgentSession>,
custom_world_draft_card: __sdk::TableUpdate<CustomWorldDraftCard>,
custom_world_gallery_entry: __sdk::TableUpdate<CustomWorldGalleryEntry>,
custom_world_profile: __sdk::TableUpdate<CustomWorldProfile>,
custom_world_session: __sdk::TableUpdate<CustomWorldSession>,
inventory_slot: __sdk::TableUpdate<InventorySlot>,
npc_state: __sdk::TableUpdate<NpcState>,
player_progression: __sdk::TableUpdate<PlayerProgression>,
profile_dashboard_state: __sdk::TableUpdate<ProfileDashboardState>,
profile_played_world: __sdk::TableUpdate<ProfilePlayedWorld>,
profile_save_archive: __sdk::TableUpdate<ProfileSaveArchive>,
profile_wallet_ledger: __sdk::TableUpdate<ProfileWalletLedger>,
puzzle_agent_message: __sdk::TableUpdate<PuzzleAgentMessageRow>,
puzzle_agent_session: __sdk::TableUpdate<PuzzleAgentSessionRow>,
puzzle_runtime_run: __sdk::TableUpdate<PuzzleRuntimeRunRow>,
puzzle_work_profile: __sdk::TableUpdate<PuzzleWorkProfileRow>,
quest_log: __sdk::TableUpdate<QuestLog>,
quest_record: __sdk::TableUpdate<QuestRecord>,
runtime_setting: __sdk::TableUpdate<RuntimeSetting>,
runtime_snapshot: __sdk::TableUpdate<RuntimeSnapshotRow>,
story_event: __sdk::TableUpdate<StoryEvent>,
story_session: __sdk::TableUpdate<StorySession>,
treasure_record: __sdk::TableUpdate<TreasureRecord>,
user_browse_history: __sdk::TableUpdate<UserBrowseHistory>,
custom_world_gallery_entry: __sdk::TableUpdate<CustomWorldGalleryEntry>,
}
@@ -1186,44 +1075,7 @@ impl TryFrom<__ws::v2::TransactionUpdate> for DbUpdate {
for table_update in __sdk::transaction_update_iter_table_updates(raw) {
match &table_update.table_name[..] {
"ai_result_reference" => db_update.ai_result_reference.append(ai_result_reference_table::parse_table_update(table_update)?),
"ai_task" => db_update.ai_task.append(ai_task_table::parse_table_update(table_update)?),
"ai_task_stage" => db_update.ai_task_stage.append(ai_task_stage_table::parse_table_update(table_update)?),
"ai_text_chunk" => db_update.ai_text_chunk.append(ai_text_chunk_table::parse_table_update(table_update)?),
"asset_entity_binding" => db_update.asset_entity_binding.append(asset_entity_binding_table::parse_table_update(table_update)?),
"asset_object" => db_update.asset_object.append(asset_object_table::parse_table_update(table_update)?),
"battle_state" => db_update.battle_state.append(battle_state_table::parse_table_update(table_update)?),
"big_fish_agent_message" => db_update.big_fish_agent_message.append(big_fish_agent_message_table::parse_table_update(table_update)?),
"big_fish_asset_slot" => db_update.big_fish_asset_slot.append(big_fish_asset_slot_table::parse_table_update(table_update)?),
"big_fish_creation_session" => db_update.big_fish_creation_session.append(big_fish_creation_session_table::parse_table_update(table_update)?),
"big_fish_runtime_run" => db_update.big_fish_runtime_run.append(big_fish_runtime_run_table::parse_table_update(table_update)?),
"chapter_progression" => db_update.chapter_progression.append(chapter_progression_table::parse_table_update(table_update)?),
"custom_world_agent_message" => db_update.custom_world_agent_message.append(custom_world_agent_message_table::parse_table_update(table_update)?),
"custom_world_agent_operation" => db_update.custom_world_agent_operation.append(custom_world_agent_operation_table::parse_table_update(table_update)?),
"custom_world_agent_session" => db_update.custom_world_agent_session.append(custom_world_agent_session_table::parse_table_update(table_update)?),
"custom_world_draft_card" => db_update.custom_world_draft_card.append(custom_world_draft_card_table::parse_table_update(table_update)?),
"custom_world_gallery_entry" => db_update.custom_world_gallery_entry.append(custom_world_gallery_entry_table::parse_table_update(table_update)?),
"custom_world_profile" => db_update.custom_world_profile.append(custom_world_profile_table::parse_table_update(table_update)?),
"custom_world_session" => db_update.custom_world_session.append(custom_world_session_table::parse_table_update(table_update)?),
"inventory_slot" => db_update.inventory_slot.append(inventory_slot_table::parse_table_update(table_update)?),
"npc_state" => db_update.npc_state.append(npc_state_table::parse_table_update(table_update)?),
"player_progression" => db_update.player_progression.append(player_progression_table::parse_table_update(table_update)?),
"profile_dashboard_state" => db_update.profile_dashboard_state.append(profile_dashboard_state_table::parse_table_update(table_update)?),
"profile_played_world" => db_update.profile_played_world.append(profile_played_world_table::parse_table_update(table_update)?),
"profile_save_archive" => db_update.profile_save_archive.append(profile_save_archive_table::parse_table_update(table_update)?),
"profile_wallet_ledger" => db_update.profile_wallet_ledger.append(profile_wallet_ledger_table::parse_table_update(table_update)?),
"puzzle_agent_message" => db_update.puzzle_agent_message.append(puzzle_agent_message_table::parse_table_update(table_update)?),
"puzzle_agent_session" => db_update.puzzle_agent_session.append(puzzle_agent_session_table::parse_table_update(table_update)?),
"puzzle_runtime_run" => db_update.puzzle_runtime_run.append(puzzle_runtime_run_table::parse_table_update(table_update)?),
"puzzle_work_profile" => db_update.puzzle_work_profile.append(puzzle_work_profile_table::parse_table_update(table_update)?),
"quest_log" => db_update.quest_log.append(quest_log_table::parse_table_update(table_update)?),
"quest_record" => db_update.quest_record.append(quest_record_table::parse_table_update(table_update)?),
"runtime_setting" => db_update.runtime_setting.append(runtime_setting_table::parse_table_update(table_update)?),
"runtime_snapshot" => db_update.runtime_snapshot.append(runtime_snapshot_table::parse_table_update(table_update)?),
"story_event" => db_update.story_event.append(story_event_table::parse_table_update(table_update)?),
"story_session" => db_update.story_session.append(story_session_table::parse_table_update(table_update)?),
"treasure_record" => db_update.treasure_record.append(treasure_record_table::parse_table_update(table_update)?),
"user_browse_history" => db_update.user_browse_history.append(user_browse_history_table::parse_table_update(table_update)?),
"custom_world_gallery_entry" => db_update.custom_world_gallery_entry.append(custom_world_gallery_entry_table::parse_table_update(table_update)?),
unknown => {
return Err(__sdk::InternalError::unknown_name(
@@ -1246,44 +1098,7 @@ impl __sdk::DbUpdate for DbUpdate {
fn apply_to_client_cache(&self, cache: &mut __sdk::ClientCache<RemoteModule>) -> AppliedDiff<'_> {
let mut diff = AppliedDiff::default();
diff.ai_result_reference = cache.apply_diff_to_table::<AiResultReference>("ai_result_reference", &self.ai_result_reference).with_updates_by_pk(|row| &row.result_reference_row_id);
diff.ai_task = cache.apply_diff_to_table::<AiTask>("ai_task", &self.ai_task).with_updates_by_pk(|row| &row.task_id);
diff.ai_task_stage = cache.apply_diff_to_table::<AiTaskStage>("ai_task_stage", &self.ai_task_stage).with_updates_by_pk(|row| &row.task_stage_id);
diff.ai_text_chunk = cache.apply_diff_to_table::<AiTextChunk>("ai_text_chunk", &self.ai_text_chunk).with_updates_by_pk(|row| &row.text_chunk_row_id);
diff.asset_entity_binding = cache.apply_diff_to_table::<AssetEntityBinding>("asset_entity_binding", &self.asset_entity_binding).with_updates_by_pk(|row| &row.binding_id);
diff.asset_object = cache.apply_diff_to_table::<AssetObject>("asset_object", &self.asset_object).with_updates_by_pk(|row| &row.asset_object_id);
diff.battle_state = cache.apply_diff_to_table::<BattleState>("battle_state", &self.battle_state).with_updates_by_pk(|row| &row.battle_state_id);
diff.big_fish_agent_message = cache.apply_diff_to_table::<BigFishAgentMessage>("big_fish_agent_message", &self.big_fish_agent_message).with_updates_by_pk(|row| &row.message_id);
diff.big_fish_asset_slot = cache.apply_diff_to_table::<BigFishAssetSlot>("big_fish_asset_slot", &self.big_fish_asset_slot).with_updates_by_pk(|row| &row.slot_id);
diff.big_fish_creation_session = cache.apply_diff_to_table::<BigFishCreationSession>("big_fish_creation_session", &self.big_fish_creation_session).with_updates_by_pk(|row| &row.session_id);
diff.big_fish_runtime_run = cache.apply_diff_to_table::<BigFishRuntimeRun>("big_fish_runtime_run", &self.big_fish_runtime_run).with_updates_by_pk(|row| &row.run_id);
diff.chapter_progression = cache.apply_diff_to_table::<ChapterProgression>("chapter_progression", &self.chapter_progression).with_updates_by_pk(|row| &row.chapter_progression_id);
diff.custom_world_agent_message = cache.apply_diff_to_table::<CustomWorldAgentMessage>("custom_world_agent_message", &self.custom_world_agent_message).with_updates_by_pk(|row| &row.message_id);
diff.custom_world_agent_operation = cache.apply_diff_to_table::<CustomWorldAgentOperation>("custom_world_agent_operation", &self.custom_world_agent_operation).with_updates_by_pk(|row| &row.operation_id);
diff.custom_world_agent_session = cache.apply_diff_to_table::<CustomWorldAgentSession>("custom_world_agent_session", &self.custom_world_agent_session).with_updates_by_pk(|row| &row.session_id);
diff.custom_world_draft_card = cache.apply_diff_to_table::<CustomWorldDraftCard>("custom_world_draft_card", &self.custom_world_draft_card).with_updates_by_pk(|row| &row.card_id);
diff.custom_world_gallery_entry = cache.apply_diff_to_table::<CustomWorldGalleryEntry>("custom_world_gallery_entry", &self.custom_world_gallery_entry).with_updates_by_pk(|row| &row.profile_id);
diff.custom_world_profile = cache.apply_diff_to_table::<CustomWorldProfile>("custom_world_profile", &self.custom_world_profile).with_updates_by_pk(|row| &row.profile_id);
diff.custom_world_session = cache.apply_diff_to_table::<CustomWorldSession>("custom_world_session", &self.custom_world_session).with_updates_by_pk(|row| &row.session_id);
diff.inventory_slot = cache.apply_diff_to_table::<InventorySlot>("inventory_slot", &self.inventory_slot).with_updates_by_pk(|row| &row.slot_id);
diff.npc_state = cache.apply_diff_to_table::<NpcState>("npc_state", &self.npc_state).with_updates_by_pk(|row| &row.npc_state_id);
diff.player_progression = cache.apply_diff_to_table::<PlayerProgression>("player_progression", &self.player_progression).with_updates_by_pk(|row| &row.user_id);
diff.profile_dashboard_state = cache.apply_diff_to_table::<ProfileDashboardState>("profile_dashboard_state", &self.profile_dashboard_state).with_updates_by_pk(|row| &row.user_id);
diff.profile_played_world = cache.apply_diff_to_table::<ProfilePlayedWorld>("profile_played_world", &self.profile_played_world).with_updates_by_pk(|row| &row.played_world_id);
diff.profile_save_archive = cache.apply_diff_to_table::<ProfileSaveArchive>("profile_save_archive", &self.profile_save_archive).with_updates_by_pk(|row| &row.archive_id);
diff.profile_wallet_ledger = cache.apply_diff_to_table::<ProfileWalletLedger>("profile_wallet_ledger", &self.profile_wallet_ledger).with_updates_by_pk(|row| &row.wallet_ledger_id);
diff.puzzle_agent_message = cache.apply_diff_to_table::<PuzzleAgentMessageRow>("puzzle_agent_message", &self.puzzle_agent_message).with_updates_by_pk(|row| &row.message_id);
diff.puzzle_agent_session = cache.apply_diff_to_table::<PuzzleAgentSessionRow>("puzzle_agent_session", &self.puzzle_agent_session).with_updates_by_pk(|row| &row.session_id);
diff.puzzle_runtime_run = cache.apply_diff_to_table::<PuzzleRuntimeRunRow>("puzzle_runtime_run", &self.puzzle_runtime_run).with_updates_by_pk(|row| &row.run_id);
diff.puzzle_work_profile = cache.apply_diff_to_table::<PuzzleWorkProfileRow>("puzzle_work_profile", &self.puzzle_work_profile).with_updates_by_pk(|row| &row.profile_id);
diff.quest_log = cache.apply_diff_to_table::<QuestLog>("quest_log", &self.quest_log).with_updates_by_pk(|row| &row.log_id);
diff.quest_record = cache.apply_diff_to_table::<QuestRecord>("quest_record", &self.quest_record).with_updates_by_pk(|row| &row.quest_id);
diff.runtime_setting = cache.apply_diff_to_table::<RuntimeSetting>("runtime_setting", &self.runtime_setting).with_updates_by_pk(|row| &row.user_id);
diff.runtime_snapshot = cache.apply_diff_to_table::<RuntimeSnapshotRow>("runtime_snapshot", &self.runtime_snapshot).with_updates_by_pk(|row| &row.user_id);
diff.story_event = cache.apply_diff_to_table::<StoryEvent>("story_event", &self.story_event).with_updates_by_pk(|row| &row.event_id);
diff.story_session = cache.apply_diff_to_table::<StorySession>("story_session", &self.story_session).with_updates_by_pk(|row| &row.story_session_id);
diff.treasure_record = cache.apply_diff_to_table::<TreasureRecord>("treasure_record", &self.treasure_record).with_updates_by_pk(|row| &row.treasure_record_id);
diff.user_browse_history = cache.apply_diff_to_table::<UserBrowseHistory>("user_browse_history", &self.user_browse_history).with_updates_by_pk(|row| &row.browse_history_id);
diff.custom_world_gallery_entry = cache.apply_diff_to_table::<CustomWorldGalleryEntry>("custom_world_gallery_entry", &self.custom_world_gallery_entry).with_updates_by_pk(|row| &row.profile_id);
diff
}
@@ -1291,44 +1106,7 @@ fn parse_initial_rows(raw: __ws::v2::QueryRows) -> __sdk::Result<Self> {
let mut db_update = DbUpdate::default();
for table_rows in raw.tables {
match &table_rows.table[..] {
"ai_result_reference" => db_update.ai_result_reference.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"ai_task" => db_update.ai_task.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"ai_task_stage" => db_update.ai_task_stage.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"ai_text_chunk" => db_update.ai_text_chunk.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"asset_entity_binding" => db_update.asset_entity_binding.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"asset_object" => db_update.asset_object.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"battle_state" => db_update.battle_state.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"big_fish_agent_message" => db_update.big_fish_agent_message.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"big_fish_asset_slot" => db_update.big_fish_asset_slot.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"big_fish_creation_session" => db_update.big_fish_creation_session.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"big_fish_runtime_run" => db_update.big_fish_runtime_run.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"chapter_progression" => db_update.chapter_progression.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"custom_world_agent_message" => db_update.custom_world_agent_message.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"custom_world_agent_operation" => db_update.custom_world_agent_operation.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"custom_world_agent_session" => db_update.custom_world_agent_session.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"custom_world_draft_card" => db_update.custom_world_draft_card.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"custom_world_gallery_entry" => db_update.custom_world_gallery_entry.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"custom_world_profile" => db_update.custom_world_profile.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"custom_world_session" => db_update.custom_world_session.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"inventory_slot" => db_update.inventory_slot.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"npc_state" => db_update.npc_state.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"player_progression" => db_update.player_progression.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"profile_dashboard_state" => db_update.profile_dashboard_state.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"profile_played_world" => db_update.profile_played_world.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"profile_save_archive" => db_update.profile_save_archive.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"profile_wallet_ledger" => db_update.profile_wallet_ledger.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"puzzle_agent_message" => db_update.puzzle_agent_message.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"puzzle_agent_session" => db_update.puzzle_agent_session.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"puzzle_runtime_run" => db_update.puzzle_runtime_run.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"puzzle_work_profile" => db_update.puzzle_work_profile.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"quest_log" => db_update.quest_log.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"quest_record" => db_update.quest_record.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"runtime_setting" => db_update.runtime_setting.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"runtime_snapshot" => db_update.runtime_snapshot.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"story_event" => db_update.story_event.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"story_session" => db_update.story_session.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"treasure_record" => db_update.treasure_record.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"user_browse_history" => db_update.user_browse_history.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"custom_world_gallery_entry" => db_update.custom_world_gallery_entry.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
unknown => { return Err(__sdk::InternalError::unknown_name("table", unknown, "QueryRows").into()); }
}} Ok(db_update)
}
@@ -1336,44 +1114,7 @@ fn parse_unsubscribe_rows(raw: __ws::v2::QueryRows) -> __sdk::Result<Self> {
let mut db_update = DbUpdate::default();
for table_rows in raw.tables {
match &table_rows.table[..] {
"ai_result_reference" => db_update.ai_result_reference.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"ai_task" => db_update.ai_task.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"ai_task_stage" => db_update.ai_task_stage.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"ai_text_chunk" => db_update.ai_text_chunk.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"asset_entity_binding" => db_update.asset_entity_binding.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"asset_object" => db_update.asset_object.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"battle_state" => db_update.battle_state.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"big_fish_agent_message" => db_update.big_fish_agent_message.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"big_fish_asset_slot" => db_update.big_fish_asset_slot.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"big_fish_creation_session" => db_update.big_fish_creation_session.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"big_fish_runtime_run" => db_update.big_fish_runtime_run.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"chapter_progression" => db_update.chapter_progression.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"custom_world_agent_message" => db_update.custom_world_agent_message.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"custom_world_agent_operation" => db_update.custom_world_agent_operation.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"custom_world_agent_session" => db_update.custom_world_agent_session.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"custom_world_draft_card" => db_update.custom_world_draft_card.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"custom_world_gallery_entry" => db_update.custom_world_gallery_entry.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"custom_world_profile" => db_update.custom_world_profile.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"custom_world_session" => db_update.custom_world_session.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"inventory_slot" => db_update.inventory_slot.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"npc_state" => db_update.npc_state.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"player_progression" => db_update.player_progression.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"profile_dashboard_state" => db_update.profile_dashboard_state.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"profile_played_world" => db_update.profile_played_world.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"profile_save_archive" => db_update.profile_save_archive.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"profile_wallet_ledger" => db_update.profile_wallet_ledger.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"puzzle_agent_message" => db_update.puzzle_agent_message.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"puzzle_agent_session" => db_update.puzzle_agent_session.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"puzzle_runtime_run" => db_update.puzzle_runtime_run.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"puzzle_work_profile" => db_update.puzzle_work_profile.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"quest_log" => db_update.quest_log.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"quest_record" => db_update.quest_record.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"runtime_setting" => db_update.runtime_setting.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"runtime_snapshot" => db_update.runtime_snapshot.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"story_event" => db_update.story_event.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"story_session" => db_update.story_session.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"treasure_record" => db_update.treasure_record.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"user_browse_history" => db_update.user_browse_history.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"custom_world_gallery_entry" => db_update.custom_world_gallery_entry.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
unknown => { return Err(__sdk::InternalError::unknown_name("table", unknown, "QueryRows").into()); }
}} Ok(db_update)
}
@@ -1383,44 +1124,7 @@ for table_rows in raw.tables {
#[allow(non_snake_case)]
#[doc(hidden)]
pub struct AppliedDiff<'r> {
ai_result_reference: __sdk::TableAppliedDiff<'r, AiResultReference>,
ai_task: __sdk::TableAppliedDiff<'r, AiTask>,
ai_task_stage: __sdk::TableAppliedDiff<'r, AiTaskStage>,
ai_text_chunk: __sdk::TableAppliedDiff<'r, AiTextChunk>,
asset_entity_binding: __sdk::TableAppliedDiff<'r, AssetEntityBinding>,
asset_object: __sdk::TableAppliedDiff<'r, AssetObject>,
battle_state: __sdk::TableAppliedDiff<'r, BattleState>,
big_fish_agent_message: __sdk::TableAppliedDiff<'r, BigFishAgentMessage>,
big_fish_asset_slot: __sdk::TableAppliedDiff<'r, BigFishAssetSlot>,
big_fish_creation_session: __sdk::TableAppliedDiff<'r, BigFishCreationSession>,
big_fish_runtime_run: __sdk::TableAppliedDiff<'r, BigFishRuntimeRun>,
chapter_progression: __sdk::TableAppliedDiff<'r, ChapterProgression>,
custom_world_agent_message: __sdk::TableAppliedDiff<'r, CustomWorldAgentMessage>,
custom_world_agent_operation: __sdk::TableAppliedDiff<'r, CustomWorldAgentOperation>,
custom_world_agent_session: __sdk::TableAppliedDiff<'r, CustomWorldAgentSession>,
custom_world_draft_card: __sdk::TableAppliedDiff<'r, CustomWorldDraftCard>,
custom_world_gallery_entry: __sdk::TableAppliedDiff<'r, CustomWorldGalleryEntry>,
custom_world_profile: __sdk::TableAppliedDiff<'r, CustomWorldProfile>,
custom_world_session: __sdk::TableAppliedDiff<'r, CustomWorldSession>,
inventory_slot: __sdk::TableAppliedDiff<'r, InventorySlot>,
npc_state: __sdk::TableAppliedDiff<'r, NpcState>,
player_progression: __sdk::TableAppliedDiff<'r, PlayerProgression>,
profile_dashboard_state: __sdk::TableAppliedDiff<'r, ProfileDashboardState>,
profile_played_world: __sdk::TableAppliedDiff<'r, ProfilePlayedWorld>,
profile_save_archive: __sdk::TableAppliedDiff<'r, ProfileSaveArchive>,
profile_wallet_ledger: __sdk::TableAppliedDiff<'r, ProfileWalletLedger>,
puzzle_agent_message: __sdk::TableAppliedDiff<'r, PuzzleAgentMessageRow>,
puzzle_agent_session: __sdk::TableAppliedDiff<'r, PuzzleAgentSessionRow>,
puzzle_runtime_run: __sdk::TableAppliedDiff<'r, PuzzleRuntimeRunRow>,
puzzle_work_profile: __sdk::TableAppliedDiff<'r, PuzzleWorkProfileRow>,
quest_log: __sdk::TableAppliedDiff<'r, QuestLog>,
quest_record: __sdk::TableAppliedDiff<'r, QuestRecord>,
runtime_setting: __sdk::TableAppliedDiff<'r, RuntimeSetting>,
runtime_snapshot: __sdk::TableAppliedDiff<'r, RuntimeSnapshotRow>,
story_event: __sdk::TableAppliedDiff<'r, StoryEvent>,
story_session: __sdk::TableAppliedDiff<'r, StorySession>,
treasure_record: __sdk::TableAppliedDiff<'r, TreasureRecord>,
user_browse_history: __sdk::TableAppliedDiff<'r, UserBrowseHistory>,
custom_world_gallery_entry: __sdk::TableAppliedDiff<'r, CustomWorldGalleryEntry>,
__unused: std::marker::PhantomData<&'r ()>,
}
@@ -1431,44 +1135,7 @@ impl __sdk::InModule for AppliedDiff<'_> {
impl<'r> __sdk::AppliedDiff<'r> for AppliedDiff<'r> {
fn invoke_row_callbacks(&self, event: &EventContext, callbacks: &mut __sdk::DbCallbacks<RemoteModule>) {
callbacks.invoke_table_row_callbacks::<AiResultReference>("ai_result_reference", &self.ai_result_reference, event);
callbacks.invoke_table_row_callbacks::<AiTask>("ai_task", &self.ai_task, event);
callbacks.invoke_table_row_callbacks::<AiTaskStage>("ai_task_stage", &self.ai_task_stage, event);
callbacks.invoke_table_row_callbacks::<AiTextChunk>("ai_text_chunk", &self.ai_text_chunk, event);
callbacks.invoke_table_row_callbacks::<AssetEntityBinding>("asset_entity_binding", &self.asset_entity_binding, event);
callbacks.invoke_table_row_callbacks::<AssetObject>("asset_object", &self.asset_object, event);
callbacks.invoke_table_row_callbacks::<BattleState>("battle_state", &self.battle_state, event);
callbacks.invoke_table_row_callbacks::<BigFishAgentMessage>("big_fish_agent_message", &self.big_fish_agent_message, event);
callbacks.invoke_table_row_callbacks::<BigFishAssetSlot>("big_fish_asset_slot", &self.big_fish_asset_slot, event);
callbacks.invoke_table_row_callbacks::<BigFishCreationSession>("big_fish_creation_session", &self.big_fish_creation_session, event);
callbacks.invoke_table_row_callbacks::<BigFishRuntimeRun>("big_fish_runtime_run", &self.big_fish_runtime_run, event);
callbacks.invoke_table_row_callbacks::<ChapterProgression>("chapter_progression", &self.chapter_progression, event);
callbacks.invoke_table_row_callbacks::<CustomWorldAgentMessage>("custom_world_agent_message", &self.custom_world_agent_message, event);
callbacks.invoke_table_row_callbacks::<CustomWorldAgentOperation>("custom_world_agent_operation", &self.custom_world_agent_operation, event);
callbacks.invoke_table_row_callbacks::<CustomWorldAgentSession>("custom_world_agent_session", &self.custom_world_agent_session, event);
callbacks.invoke_table_row_callbacks::<CustomWorldDraftCard>("custom_world_draft_card", &self.custom_world_draft_card, event);
callbacks.invoke_table_row_callbacks::<CustomWorldGalleryEntry>("custom_world_gallery_entry", &self.custom_world_gallery_entry, event);
callbacks.invoke_table_row_callbacks::<CustomWorldProfile>("custom_world_profile", &self.custom_world_profile, event);
callbacks.invoke_table_row_callbacks::<CustomWorldSession>("custom_world_session", &self.custom_world_session, event);
callbacks.invoke_table_row_callbacks::<InventorySlot>("inventory_slot", &self.inventory_slot, event);
callbacks.invoke_table_row_callbacks::<NpcState>("npc_state", &self.npc_state, event);
callbacks.invoke_table_row_callbacks::<PlayerProgression>("player_progression", &self.player_progression, event);
callbacks.invoke_table_row_callbacks::<ProfileDashboardState>("profile_dashboard_state", &self.profile_dashboard_state, event);
callbacks.invoke_table_row_callbacks::<ProfilePlayedWorld>("profile_played_world", &self.profile_played_world, event);
callbacks.invoke_table_row_callbacks::<ProfileSaveArchive>("profile_save_archive", &self.profile_save_archive, event);
callbacks.invoke_table_row_callbacks::<ProfileWalletLedger>("profile_wallet_ledger", &self.profile_wallet_ledger, event);
callbacks.invoke_table_row_callbacks::<PuzzleAgentMessageRow>("puzzle_agent_message", &self.puzzle_agent_message, event);
callbacks.invoke_table_row_callbacks::<PuzzleAgentSessionRow>("puzzle_agent_session", &self.puzzle_agent_session, event);
callbacks.invoke_table_row_callbacks::<PuzzleRuntimeRunRow>("puzzle_runtime_run", &self.puzzle_runtime_run, event);
callbacks.invoke_table_row_callbacks::<PuzzleWorkProfileRow>("puzzle_work_profile", &self.puzzle_work_profile, event);
callbacks.invoke_table_row_callbacks::<QuestLog>("quest_log", &self.quest_log, event);
callbacks.invoke_table_row_callbacks::<QuestRecord>("quest_record", &self.quest_record, event);
callbacks.invoke_table_row_callbacks::<RuntimeSetting>("runtime_setting", &self.runtime_setting, event);
callbacks.invoke_table_row_callbacks::<RuntimeSnapshotRow>("runtime_snapshot", &self.runtime_snapshot, event);
callbacks.invoke_table_row_callbacks::<StoryEvent>("story_event", &self.story_event, event);
callbacks.invoke_table_row_callbacks::<StorySession>("story_session", &self.story_session, event);
callbacks.invoke_table_row_callbacks::<TreasureRecord>("treasure_record", &self.treasure_record, event);
callbacks.invoke_table_row_callbacks::<UserBrowseHistory>("user_browse_history", &self.user_browse_history, event);
callbacks.invoke_table_row_callbacks::<CustomWorldGalleryEntry>("custom_world_gallery_entry", &self.custom_world_gallery_entry, event);
}
}
@@ -2120,83 +1787,9 @@ impl __sdk::SpacetimeModule for RemoteModule {
type QueryBuilder = __sdk::QueryBuilder;
fn register_tables(client_cache: &mut __sdk::ClientCache<Self>) {
ai_result_reference_table::register_table(client_cache);
ai_task_table::register_table(client_cache);
ai_task_stage_table::register_table(client_cache);
ai_text_chunk_table::register_table(client_cache);
asset_entity_binding_table::register_table(client_cache);
asset_object_table::register_table(client_cache);
battle_state_table::register_table(client_cache);
big_fish_agent_message_table::register_table(client_cache);
big_fish_asset_slot_table::register_table(client_cache);
big_fish_creation_session_table::register_table(client_cache);
big_fish_runtime_run_table::register_table(client_cache);
chapter_progression_table::register_table(client_cache);
custom_world_agent_message_table::register_table(client_cache);
custom_world_agent_operation_table::register_table(client_cache);
custom_world_agent_session_table::register_table(client_cache);
custom_world_draft_card_table::register_table(client_cache);
custom_world_gallery_entry_table::register_table(client_cache);
custom_world_profile_table::register_table(client_cache);
custom_world_session_table::register_table(client_cache);
inventory_slot_table::register_table(client_cache);
npc_state_table::register_table(client_cache);
player_progression_table::register_table(client_cache);
profile_dashboard_state_table::register_table(client_cache);
profile_played_world_table::register_table(client_cache);
profile_save_archive_table::register_table(client_cache);
profile_wallet_ledger_table::register_table(client_cache);
puzzle_agent_message_table::register_table(client_cache);
puzzle_agent_session_table::register_table(client_cache);
puzzle_runtime_run_table::register_table(client_cache);
puzzle_work_profile_table::register_table(client_cache);
quest_log_table::register_table(client_cache);
quest_record_table::register_table(client_cache);
runtime_setting_table::register_table(client_cache);
runtime_snapshot_table::register_table(client_cache);
story_event_table::register_table(client_cache);
story_session_table::register_table(client_cache);
treasure_record_table::register_table(client_cache);
user_browse_history_table::register_table(client_cache);
custom_world_gallery_entry_table::register_table(client_cache);
}
const ALL_TABLE_NAMES: &'static [&'static str] = &[
"ai_result_reference",
"ai_task",
"ai_task_stage",
"ai_text_chunk",
"asset_entity_binding",
"asset_object",
"battle_state",
"big_fish_agent_message",
"big_fish_asset_slot",
"big_fish_creation_session",
"big_fish_runtime_run",
"chapter_progression",
"custom_world_agent_message",
"custom_world_agent_operation",
"custom_world_agent_session",
"custom_world_draft_card",
"custom_world_gallery_entry",
"custom_world_profile",
"custom_world_session",
"inventory_slot",
"npc_state",
"player_progression",
"profile_dashboard_state",
"profile_played_world",
"profile_save_archive",
"profile_wallet_ledger",
"puzzle_agent_message",
"puzzle_agent_session",
"puzzle_runtime_run",
"puzzle_work_profile",
"quest_log",
"quest_record",
"runtime_setting",
"runtime_snapshot",
"story_event",
"story_session",
"treasure_record",
"user_browse_history",
"custom_world_gallery_entry",
];
}

View File

@@ -2178,6 +2178,7 @@ fn generate_big_fish_asset_tx(
input.asset_kind,
input.level,
input.motion_key.clone(),
input.asset_url.clone(),
input.generated_at_micros,
)
.map_err(|error| error.to_string())?;
@@ -2186,10 +2187,18 @@ fn generate_big_fish_asset_tx(
let asset_slots = list_big_fish_asset_slots(ctx, &session.session_id);
let coverage = build_asset_coverage(Some(&draft), &asset_slots);
let updated_at = Timestamp::from_micros_since_unix_epoch(input.generated_at_micros);
let reply = match input.asset_kind {
BigFishAssetKind::LevelMainImage => "本级主图已生成并设为正式资产。",
BigFishAssetKind::LevelMotion => "本级动作已生成并设为正式资产。",
BigFishAssetKind::StageBackground => "活动区域背景已生成并设为正式资产。",
let uses_placeholder = input
.asset_url
.as_deref()
.map(str::trim)
.is_none_or(str::is_empty);
let reply = match (input.asset_kind, uses_placeholder) {
(BigFishAssetKind::LevelMainImage, true) => "本级主图占位图已生成,可在结果页继续预览。",
(BigFishAssetKind::LevelMainImage, false) => "本级主图已正式生成,可在结果页继续预览。",
(BigFishAssetKind::LevelMotion, true) => "本级动作占位图已生成,可在结果页继续预览。",
(BigFishAssetKind::LevelMotion, false) => "本级动作图已正式生成,可在结果页继续预览。",
(BigFishAssetKind::StageBackground, true) => "活动区域背景占位图已生成,可在结果页继续预览。",
(BigFishAssetKind::StageBackground, false) => "活动区域背景已正式生成,可在结果页继续预览。",
}
.to_string();
let next_stage = if coverage.publish_ready {