Merge branch 'codex/backend-rewrite-spacetimedb' of http://82.157.175.59:3000/GenarrativeAI/Genarrative into codex/backend-rewrite-spacetimedb
This commit is contained in:
@@ -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),
|
||||
|
||||
@@ -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 =
|
||||
"文字,水印,logo,UI界面,对话框,边框,多余肢体,畸形鱼体,低清晰度,模糊,压缩噪点,现代摄影棚,写实照片背景";
|
||||
|
||||
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");
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user