diff --git a/server-rs/crates/spacetime-module/src/wooden_fish.rs b/server-rs/crates/spacetime-module/src/wooden_fish.rs index ea60b99f..33482ac2 100644 --- a/server-rs/crates/spacetime-module/src/wooden_fish.rs +++ b/server-rs/crates/spacetime-module/src/wooden_fish.rs @@ -269,12 +269,13 @@ fn create_wooden_fish_agent_session_tx( .map(parse_config) .transpose()? .unwrap_or_else(|| default_config_from_input(&input)); - let draft = input + let mut draft = input .draft_json .as_deref() .map(parse_json) .transpose()? .unwrap_or_else(|| draft_from_config(&config, None, WOODEN_FISH_GENERATION_DRAFT)); + draft.generation_status = WOODEN_FISH_GENERATION_GENERATING.to_string(); ctx.db .wooden_fish_agent_session() @@ -282,8 +283,8 @@ fn create_wooden_fish_agent_session_tx( session_id: input.session_id.clone(), owner_user_id: input.owner_user_id.clone(), current_turn: 0, - progress_percent: 0, - stage: WOODEN_FISH_STAGE_COLLECTING.to_string(), + progress_percent: 1, + stage: WOODEN_FISH_STAGE_GENERATING.to_string(), config_json: to_json_string(&config), draft_json: to_json_string(&draft), published_profile_id: String::new(), diff --git a/server-rs/crates/spacetime-module/src/wooden_fish/types.rs b/server-rs/crates/spacetime-module/src/wooden_fish/types.rs index 2ab8c0e6..12ebbdca 100644 --- a/server-rs/crates/spacetime-module/src/wooden_fish/types.rs +++ b/server-rs/crates/spacetime-module/src/wooden_fish/types.rs @@ -4,11 +4,13 @@ use serde::{Deserialize, Serialize}; pub const WOODEN_FISH_TEMPLATE_ID: &str = "wooden-fish"; pub const WOODEN_FISH_TEMPLATE_NAME: &str = "敲木鱼"; pub const WOODEN_FISH_STAGE_COLLECTING: &str = "Collecting"; +pub const WOODEN_FISH_STAGE_GENERATING: &str = "Generating"; pub const WOODEN_FISH_STAGE_DRAFT_COMPILED: &str = "DraftCompiled"; pub const WOODEN_FISH_STAGE_PUBLISHED: &str = "Published"; pub const WOODEN_FISH_PUBLICATION_DRAFT: &str = "Draft"; pub const WOODEN_FISH_PUBLICATION_PUBLISHED: &str = "Published"; pub const WOODEN_FISH_GENERATION_DRAFT: &str = "draft"; +pub const WOODEN_FISH_GENERATION_GENERATING: &str = "generating"; pub const WOODEN_FISH_GENERATION_READY: &str = "ready"; pub const WOODEN_FISH_EVENT_RUN_STARTED: &str = "run-started"; pub const WOODEN_FISH_EVENT_RUN_CHECKPOINT: &str = "checkpoint"; diff --git a/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx b/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx index a9ebe6e4..c025338f 100644 --- a/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx +++ b/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx @@ -1966,13 +1966,65 @@ function buildWoodenFishCreationUrlState(params: { const profileId = normalizeCreationUrlValue( params.work?.summary.profileId ?? params.session?.draft?.profileId, ); + const draftId = profileId ?? sessionId; return { sessionId, profileId, + draftId, workId: normalizeCreationUrlValue(params.work?.summary.workId ?? profileId), }; } +function buildWoodenFishSessionFromWorkDetail( + work: WoodenFishWorkProfileResponse, + fallbackItem?: WoodenFishWorkSummaryResponse | null, +): WoodenFishSessionSnapshotResponse { + const sessionId = + normalizeCreationUrlValue(work.summary.sourceSessionId) ?? + normalizeCreationUrlValue(fallbackItem?.sourceSessionId) ?? + work.summary.profileId; + return { + sessionId, + ownerUserId: work.summary.ownerUserId, + status: work.summary.generationStatus, + draft: work.draft, + createdAt: work.summary.updatedAt, + updatedAt: work.summary.updatedAt, + }; +} + +function buildWoodenFishPendingSession( + item: WoodenFishWorkSummaryResponse, +): WoodenFishSessionSnapshotResponse { + const sessionId = + normalizeCreationUrlValue(item.sourceSessionId) ?? item.profileId; + return { + sessionId, + ownerUserId: item.ownerUserId, + status: item.generationStatus, + draft: { + templateId: 'wooden-fish', + templateName: '敲木鱼', + profileId: item.profileId, + workTitle: item.workTitle, + workDescription: item.workDescription, + themeTags: item.themeTags, + hitObjectPrompt: '', + hitObjectReferenceImageSrc: null, + hitSoundPrompt: null, + floatingWords: ['功德 +1'], + hitObjectAsset: null, + backgroundAsset: null, + backButtonAsset: null, + hitSoundAsset: null, + coverImageSrc: item.coverImageSrc, + generationStatus: item.generationStatus, + }, + createdAt: item.updatedAt, + updatedAt: item.updatedAt, + }; +} + function buildBarkBattleCreationUrlState( draft: BarkBattleDraftConfig | null, ): CreationUrlState { @@ -2480,6 +2532,37 @@ function buildPendingJumpHopWorks( })); } +function buildPendingWoodenFishWorks( + pending: Record | undefined, + existingItems: readonly WoodenFishWorkSummaryResponse[], +): WoodenFishWorkSummaryResponse[] { + if (!pending) { + return []; + } + + return Object.entries(pending) + .filter(([sessionId]) => + existingItems.every((item) => item.sourceSessionId !== sessionId), + ) + .map(([sessionId, state]) => ({ + runtimeKind: 'wooden-fish', + workId: `wooden-fish-work-${sessionId}`, + profileId: sessionId, + ownerUserId: '', + sourceSessionId: sessionId, + workTitle: '敲木鱼草稿', + workDescription: '正在生成敲木鱼草稿。', + themeTags: ['敲木鱼'], + coverImageSrc: null, + publicationStatus: 'draft', + playCount: 0, + updatedAt: state.updatedAt, + publishedAt: null, + publishReady: false, + generationStatus: state.status === 'generating' ? 'generating' : 'ready', + })); +} + function buildPendingMatch3DWorks( pending: Record | undefined, existingItems: readonly Match3DWorkSummary[], @@ -4512,8 +4595,14 @@ export function PlatformEntryFlowShellImpl({ [jumpHopWorks, pendingDraftShelfItems], ); const woodenFishShelfItems = useMemo( - () => woodenFishWorks, - [woodenFishWorks], + () => [ + ...buildPendingWoodenFishWorks( + pendingDraftShelfItems['wooden-fish'], + woodenFishWorks, + ), + ...woodenFishWorks, + ], + [pendingDraftShelfItems, woodenFishWorks], ); const match3dShelfItems = useMemo( () => [ @@ -8891,6 +8980,33 @@ export function PlatformEntryFlowShellImpl({ setSelectionStage('wooden-fish-generating'); markDraftGenerating('wooden-fish', [created.session.sessionId]); markPendingDraftGenerating('wooden-fish', created.session.sessionId); + const createdAt = created.session.updatedAt ?? created.session.createdAt; + setWoodenFishWorks((current) => [ + { + runtimeKind: 'wooden-fish', + workId: created.session.sessionId, + profileId: created.session.sessionId, + ownerUserId: created.session.ownerUserId, + sourceSessionId: created.session.sessionId, + workTitle: + payload?.workTitle ?? created.session.draft?.workTitle ?? '敲木鱼', + workDescription: + payload?.workDescription ?? + created.session.draft?.workDescription ?? + '', + themeTags: payload?.themeTags ?? created.session.draft?.themeTags ?? ['敲木鱼'], + coverImageSrc: created.session.draft?.coverImageSrc ?? null, + publicationStatus: 'draft', + playCount: 0, + updatedAt: createdAt, + publishedAt: null, + publishReady: false, + generationStatus: 'generating', + }, + ...current.filter( + (item) => item.sourceSessionId !== created.session.sessionId, + ), + ]); try { const response = await woodenFishClient.executeAction( @@ -8929,7 +9045,9 @@ export function PlatformEntryFlowShellImpl({ setWoodenFishWorks((current) => [ response.work!.summary, ...current.filter( - (item) => item.workId !== response.work!.summary.workId, + (item) => + item.workId !== response.work!.summary.workId && + item.sourceSessionId !== response.work!.summary.sourceSessionId, ), ]); markPendingDraftReady( @@ -11812,18 +11930,43 @@ export function PlatformEntryFlowShellImpl({ setWoodenFishError(null); setPublicWorkDetailError(null); setIsWoodenFishBusy(true); + if (item.generationStatus === 'generating') { + const pendingSession = buildWoodenFishPendingSession(item); + setWoodenFishSession(pendingSession); + setWoodenFishRun(null); + setWoodenFishWork(null); + writeCreationUrlState( + buildWoodenFishCreationUrlState({ session: pendingSession }), + ); + enterCreateTab(); + setSelectionStage('wooden-fish-generating'); + setIsWoodenFishBusy(false); + return; + } try { const detail = await woodenFishClient.getWorkDetail(item.profileId); - setWoodenFishSession(null); + const recoveredSession = buildWoodenFishSessionFromWorkDetail( + detail.item, + item, + ); + setWoodenFishSession(recoveredSession); setWoodenFishRun(null); setWoodenFishWork(detail.item); setWoodenFishRuntimeReturnStage('wooden-fish-result'); + writeCreationUrlState( + buildWoodenFishCreationUrlState({ + session: recoveredSession, + work: detail.item, + }), + ); enterCreateTab(); setSelectionStage('wooden-fish-result'); } catch (error) { setWoodenFishError( resolveRpgCreationErrorMessage(error, '读取敲木鱼草稿失败。'), ); + enterCreateTab(); + setSelectionStage('wooden-fish-generating'); } finally { setIsWoodenFishBusy(false); } @@ -11832,6 +11975,7 @@ export function PlatformEntryFlowShellImpl({ enterCreateTab, markDraftNoticeSeen, openWoodenFishPublicWorkDetail, + writeCreationUrlState, setSelectionStage, ], );