Increase VectorEngine timeouts and add image UI
Add VectorEngine image generation config and raise request timeouts (env + scripts) from 180000 to 1000000ms. Introduce a reusable CreativeImageInputPanel component with tests and wire up mobile keyboard-focus helpers; update generation views and related tests (CustomWorldGenerationView, BarkBattle editor, Match3D, Puzzle flows). Improve API error handling / VectorEngine request guidance (packages/shared http.ts and docs), and apply multiple backend/frontend fixes for puzzle/match3d/prompt handling. Also include extensive docs and decision-log updates describing UI/UX decisions and verification steps.
This commit is contained in:
@@ -473,6 +473,8 @@ const RECOMMEND_RUNTIME_BACKGROUND_AUTH_OPTIONS =
|
||||
BACKGROUND_AUTH_REQUEST_OPTIONS;
|
||||
const PUBLIC_PUZZLE_RUNTIME_AUTH_OPTIONS =
|
||||
RECOMMEND_RUNTIME_BACKGROUND_AUTH_OPTIONS;
|
||||
const PUZZLE_DRAFT_GENERATION_POINT_COST = 2;
|
||||
const MATCH3D_DRAFT_GENERATION_POINT_COST = 10;
|
||||
|
||||
function getPlatformPublicGalleryEntryTime(entry: PlatformPublicGalleryCard) {
|
||||
const rawTime = entry.publishedAt ?? entry.updatedAt;
|
||||
@@ -1576,6 +1578,7 @@ function getGenerationNoticeShelfKeys(item: CreationWorkShelfItem) {
|
||||
return collectDraftNoticeKeys('baby-object-match', [
|
||||
item.id,
|
||||
item.source.item.profileId,
|
||||
item.source.item.draftId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1588,6 +1591,15 @@ function isMiniGameDraftGenerating(state: MiniGameDraftGenerationState | null) {
|
||||
return Boolean(state && state.phase !== 'ready' && state.phase !== 'failed');
|
||||
}
|
||||
|
||||
function resolveProfileWalletBalance(
|
||||
dashboard: { walletBalance?: number | null } | null | undefined,
|
||||
) {
|
||||
const walletBalance = dashboard?.walletBalance;
|
||||
return typeof walletBalance === 'number' && Number.isFinite(walletBalance)
|
||||
? Math.max(0, Math.floor(walletBalance))
|
||||
: 0;
|
||||
}
|
||||
|
||||
function buildPendingBigFishWorks(
|
||||
pending: Record<string, PendingDraftShelfState> | undefined,
|
||||
existingItems: readonly BigFishWorkSummary[],
|
||||
@@ -1774,6 +1786,7 @@ function buildPuzzleCompileActionFromFormPayload(
|
||||
promptText: pictureDescription,
|
||||
...(pictureDescription ? { pictureDescription } : {}),
|
||||
referenceImageSrc: payload?.referenceImageSrc || null,
|
||||
referenceImageSrcs: payload?.referenceImageSrcs ?? [],
|
||||
imageModel: payload?.imageModel ?? null,
|
||||
aiRedraw: payload?.aiRedraw ?? true,
|
||||
candidateCount: 1,
|
||||
@@ -1795,6 +1808,7 @@ function buildPuzzleFormPayloadFromSession(
|
||||
seedText: pictureDescription,
|
||||
pictureDescription,
|
||||
referenceImageSrc: null,
|
||||
referenceImageSrcs: [],
|
||||
imageModel: null,
|
||||
aiRedraw: true,
|
||||
};
|
||||
@@ -1824,6 +1838,7 @@ function buildPuzzleFormPayloadFromAction(
|
||||
payload.action === 'compile_puzzle_draft'
|
||||
? (payload.referenceImageSrc ?? null)
|
||||
: (payload.referenceImageSrc ?? null),
|
||||
referenceImageSrcs: payload.referenceImageSrcs ?? [],
|
||||
imageModel:
|
||||
payload.action === 'compile_puzzle_draft'
|
||||
? (payload.imageModel ?? null)
|
||||
@@ -2649,6 +2664,35 @@ export function PlatformEntryFlowShellImpl({
|
||||
},
|
||||
[draftGenerationNotices],
|
||||
);
|
||||
const ensureEnoughDraftGenerationPointsFromServer = useCallback(
|
||||
async (pointsCost: number, setError: (message: string | null) => void) => {
|
||||
try {
|
||||
const latestDashboard = await getPlatformProfileDashboard(
|
||||
RECOMMEND_RUNTIME_BACKGROUND_AUTH_OPTIONS,
|
||||
);
|
||||
platformBootstrap.setProfileDashboard(latestDashboard);
|
||||
const walletBalance = resolveProfileWalletBalance(latestDashboard);
|
||||
if (walletBalance >= pointsCost) {
|
||||
return true;
|
||||
}
|
||||
|
||||
setError(
|
||||
`泥点不足,本次需要 ${pointsCost} 泥点,当前 ${walletBalance} 泥点。`,
|
||||
);
|
||||
enterCreateTab();
|
||||
selectionStageRef.current = 'platform';
|
||||
setSelectionStage('platform');
|
||||
return false;
|
||||
} catch {
|
||||
setError('读取泥点余额失败,请稍后重试。');
|
||||
enterCreateTab();
|
||||
selectionStageRef.current = 'platform';
|
||||
setSelectionStage('platform');
|
||||
return false;
|
||||
}
|
||||
},
|
||||
[enterCreateTab, platformBootstrap, setSelectionStage],
|
||||
);
|
||||
|
||||
const resolveBigFishErrorMessage = useCallback(
|
||||
(error: unknown, fallback: string) =>
|
||||
@@ -3262,8 +3306,15 @@ export function PlatformEntryFlowShellImpl({
|
||||
...visualNovelShelfItems.flatMap((item) =>
|
||||
collectDraftNoticeKeys('visual-novel', [item.profileId]),
|
||||
),
|
||||
...babyObjectMatchDrafts.flatMap((item) =>
|
||||
collectDraftNoticeKeys('baby-object-match', [
|
||||
item.profileId,
|
||||
item.draftId,
|
||||
]),
|
||||
),
|
||||
],
|
||||
[
|
||||
babyObjectMatchDrafts,
|
||||
bigFishShelfItems,
|
||||
creationHubItems,
|
||||
isSquareHoleCreationVisible,
|
||||
@@ -4318,6 +4369,28 @@ export function PlatformEntryFlowShellImpl({
|
||||
const persistRpgAgentUiState = sessionController.persistAgentUiState;
|
||||
const resetAutoSaveTrackingToIdle =
|
||||
autosaveCoordinator.resetAutoSaveTrackingToIdle;
|
||||
const preflightPuzzleDraftGeneration = useCallback(async () => {
|
||||
setPuzzleCreationError(null);
|
||||
setPuzzleError(null);
|
||||
return ensureEnoughDraftGenerationPointsFromServer(
|
||||
PUZZLE_DRAFT_GENERATION_POINT_COST,
|
||||
(message) => {
|
||||
setPuzzleCreationError(message);
|
||||
setPuzzleError(message);
|
||||
},
|
||||
);
|
||||
}, [
|
||||
ensureEnoughDraftGenerationPointsFromServer,
|
||||
setPuzzleCreationError,
|
||||
setPuzzleError,
|
||||
]);
|
||||
const preflightMatch3DDraftGeneration = useCallback(async () => {
|
||||
setMatch3DError(null);
|
||||
return ensureEnoughDraftGenerationPointsFromServer(
|
||||
MATCH3D_DRAFT_GENERATION_POINT_COST,
|
||||
setMatch3DError,
|
||||
);
|
||||
}, [ensureEnoughDraftGenerationPointsFromServer, setMatch3DError]);
|
||||
|
||||
const activeMatch3DGenerationSessionId =
|
||||
selectionStage === 'match3d-generating'
|
||||
@@ -4519,6 +4592,13 @@ export function PlatformEntryFlowShellImpl({
|
||||
setPuzzleCreationError(null);
|
||||
setPuzzleError(null);
|
||||
|
||||
if (
|
||||
payload.aiRedraw !== false &&
|
||||
!(await preflightPuzzleDraftGeneration())
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let nextSession: PuzzleAgentSessionSnapshot;
|
||||
try {
|
||||
const response = await createPuzzleAgentSession(payload);
|
||||
@@ -4669,6 +4749,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
markPendingDraftGenerating,
|
||||
markPendingDraftReady,
|
||||
isViewingPuzzleGeneration,
|
||||
preflightPuzzleDraftGeneration,
|
||||
puzzleFlow,
|
||||
refreshPuzzleShelf,
|
||||
resolvePuzzleErrorMessage,
|
||||
@@ -4684,6 +4765,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
setStreamingMatch3DReplyText('');
|
||||
setIsStreamingMatch3DReply(false);
|
||||
|
||||
if (!(await preflightMatch3DDraftGeneration())) {
|
||||
return;
|
||||
}
|
||||
|
||||
let nextSession: Match3DAgentSessionSnapshot;
|
||||
try {
|
||||
const response = await match3dCreationClient.createSession(payload);
|
||||
@@ -4869,6 +4954,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
markDraftReady,
|
||||
markPendingDraftGenerating,
|
||||
markPendingDraftReady,
|
||||
preflightMatch3DDraftGeneration,
|
||||
refreshMatch3DShelf,
|
||||
resolveMatch3DErrorMessage,
|
||||
setIsStreamingMatch3DReply,
|
||||
@@ -4970,6 +5056,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
setBabyObjectMatchGenerationState(
|
||||
createMiniGameDraftGenerationState('baby-object-match'),
|
||||
);
|
||||
selectionStageRef.current = 'baby-object-match-generating';
|
||||
setSelectionStage('baby-object-match-generating');
|
||||
|
||||
try {
|
||||
@@ -4987,7 +5074,16 @@ export function PlatformEntryFlowShellImpl({
|
||||
}
|
||||
: current,
|
||||
);
|
||||
setSelectionStage('baby-object-match-result');
|
||||
const openResult =
|
||||
selectionStageRef.current === 'baby-object-match-generating';
|
||||
markDraftReady(
|
||||
'baby-object-match',
|
||||
[response.draft.profileId, response.draft.draftId],
|
||||
openResult,
|
||||
);
|
||||
if (openResult) {
|
||||
setSelectionStage('baby-object-match-result');
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = resolvePuzzleErrorMessage(
|
||||
error,
|
||||
@@ -5008,7 +5104,12 @@ export function PlatformEntryFlowShellImpl({
|
||||
setIsBabyObjectMatchBusy(false);
|
||||
}
|
||||
},
|
||||
[refreshBabyObjectMatchShelf, resolvePuzzleErrorMessage, setSelectionStage],
|
||||
[
|
||||
markDraftReady,
|
||||
refreshBabyObjectMatchShelf,
|
||||
resolvePuzzleErrorMessage,
|
||||
setSelectionStage,
|
||||
],
|
||||
);
|
||||
|
||||
const savePuzzleFormDraft = useCallback(
|
||||
@@ -5026,6 +5127,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
promptText: payload.pictureDescription ?? null,
|
||||
pictureDescription: payload.pictureDescription ?? '',
|
||||
referenceImageSrc: payload.referenceImageSrc ?? null,
|
||||
referenceImageSrcs: payload.referenceImageSrcs ?? [],
|
||||
imageModel: payload.imageModel ?? null,
|
||||
aiRedraw: payload.aiRedraw ?? true,
|
||||
});
|
||||
@@ -5234,7 +5336,6 @@ export function PlatformEntryFlowShellImpl({
|
||||
setShowCreationTypeModal(false);
|
||||
setActiveCreationFormType('bark-battle');
|
||||
setBarkBattleError(null);
|
||||
setSelectionStage('bark-battle-config');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5272,7 +5373,6 @@ export function PlatformEntryFlowShellImpl({
|
||||
setMatch3DError,
|
||||
setPuzzleCreationError,
|
||||
setPuzzleError,
|
||||
setSelectionStage,
|
||||
setVisualNovelError,
|
||||
],
|
||||
);
|
||||
@@ -5306,6 +5406,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
setBarkBattlePublishedConfig(null);
|
||||
setBarkBattleError(null);
|
||||
setIsBarkBattleBusy(false);
|
||||
selectionStageRef.current = 'platform';
|
||||
setSelectionStage('platform');
|
||||
}, [setSelectionStage]);
|
||||
|
||||
@@ -5361,6 +5462,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
setBabyObjectMatchGenerationPhase('generating');
|
||||
setBabyObjectMatchError(null);
|
||||
enterCreateTab();
|
||||
selectionStageRef.current = 'platform';
|
||||
setSelectionStage('platform');
|
||||
}, [enterCreateTab, setSelectionStage]);
|
||||
|
||||
@@ -8316,19 +8418,24 @@ export function PlatformEntryFlowShellImpl({
|
||||
|
||||
const openPuzzleDraft = useCallback(
|
||||
async (item: PuzzleWorkSummary) => {
|
||||
const noticeKeys = collectDraftNoticeKeys('puzzle', [
|
||||
item.workId,
|
||||
item.profileId,
|
||||
item.sourceSessionId,
|
||||
buildPuzzleResultWorkId(item.sourceSessionId),
|
||||
buildPuzzleResultProfileId(item.sourceSessionId),
|
||||
]);
|
||||
const isMarkedGenerating = isDraftNoticeGenerating('puzzle', [
|
||||
item.workId,
|
||||
item.profileId,
|
||||
item.sourceSessionId,
|
||||
buildPuzzleResultWorkId(item.sourceSessionId),
|
||||
buildPuzzleResultProfileId(item.sourceSessionId),
|
||||
]);
|
||||
setPuzzleOperation(null);
|
||||
setPuzzleRun(null);
|
||||
setPuzzleRuntimeAuthMode('default');
|
||||
setSelectedPuzzleDetail(null);
|
||||
markDraftNoticeSeen(
|
||||
collectDraftNoticeKeys('puzzle', [
|
||||
item.workId,
|
||||
item.profileId,
|
||||
item.sourceSessionId,
|
||||
buildPuzzleResultWorkId(item.sourceSessionId),
|
||||
buildPuzzleResultProfileId(item.sourceSessionId),
|
||||
]),
|
||||
);
|
||||
if (!item.sourceSessionId?.trim()) {
|
||||
if (item.publicationStatus === 'published') {
|
||||
await openPuzzleDetail(item.profileId, { tab: 'create' });
|
||||
@@ -8373,6 +8480,30 @@ export function PlatformEntryFlowShellImpl({
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMarkedGenerating) {
|
||||
try {
|
||||
const { session: latestSession } = await getPuzzleAgentSession(
|
||||
item.sourceSessionId,
|
||||
);
|
||||
puzzleFlow.setSession(latestSession);
|
||||
setPuzzleFormDraftPayload(buildPuzzleFormPayloadFromSession(latestSession));
|
||||
setPuzzleGenerationState(createMiniGameDraftGenerationState('puzzle'));
|
||||
enterCreateTab();
|
||||
selectionStageRef.current = 'puzzle-generating';
|
||||
activePuzzleGenerationSessionIdRef.current = item.sourceSessionId;
|
||||
setSelectionStage('puzzle-generating');
|
||||
return;
|
||||
} catch (error) {
|
||||
setPuzzleError(
|
||||
resolvePuzzleErrorMessage(error, '读取拼图创作草稿失败。'),
|
||||
);
|
||||
await refreshPuzzleShelf().catch(() => undefined);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
markDraftNoticeSeen(noticeKeys);
|
||||
|
||||
const restoredSession = await puzzleFlow.restoreDraft(
|
||||
item.sourceSessionId,
|
||||
);
|
||||
@@ -8393,12 +8524,14 @@ export function PlatformEntryFlowShellImpl({
|
||||
[
|
||||
enterCreateTab,
|
||||
getPuzzleBackgroundCompileTask,
|
||||
isDraftNoticeGenerating,
|
||||
markDraftNoticeSeen,
|
||||
openPuzzleDetail,
|
||||
puzzleFlow,
|
||||
puzzleGenerationViewState,
|
||||
puzzleSession?.sessionId,
|
||||
refreshPuzzleShelf,
|
||||
resolvePuzzleErrorMessage,
|
||||
setPuzzleError,
|
||||
setSelectionStage,
|
||||
],
|
||||
@@ -8746,6 +8879,12 @@ export function PlatformEntryFlowShellImpl({
|
||||
|
||||
const openBabyObjectMatchDraft = useCallback(
|
||||
(draft: BabyObjectMatchDraft) => {
|
||||
markDraftNoticeSeen(
|
||||
collectDraftNoticeKeys('baby-object-match', [
|
||||
draft.profileId,
|
||||
draft.draftId,
|
||||
]),
|
||||
);
|
||||
setBabyObjectMatchDraft(draft);
|
||||
setBabyObjectMatchFormPayload({
|
||||
itemAName: draft.itemNames[0],
|
||||
@@ -8757,7 +8896,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
enterCreateTab();
|
||||
setSelectionStage('baby-object-match-result');
|
||||
},
|
||||
[enterCreateTab, setSelectionStage],
|
||||
[enterCreateTab, markDraftNoticeSeen, setSelectionStage],
|
||||
);
|
||||
|
||||
const startBigFishRunFromWork = useCallback(
|
||||
@@ -10601,6 +10740,21 @@ export function PlatformEntryFlowShellImpl({
|
||||
title={null}
|
||||
/>
|
||||
</Suspense>
|
||||
) : activeCreationFormType === 'bark-battle' ? (
|
||||
<Suspense
|
||||
fallback={<LazyPanelFallback label="正在加载汪汪声浪创作..." />}
|
||||
>
|
||||
<BarkBattleConfigEditor
|
||||
isBusy={isBarkBattleBusy}
|
||||
error={barkBattleError}
|
||||
onBack={leaveBarkBattleFlow}
|
||||
onPublish={(payload) => {
|
||||
void publishBarkBattleConfig(payload);
|
||||
}}
|
||||
showBackButton={false}
|
||||
title={null}
|
||||
/>
|
||||
</Suspense>
|
||||
) : (
|
||||
<Suspense
|
||||
fallback={<LazyPanelFallback label="正在加载拼图创作..." />}
|
||||
@@ -11182,6 +11336,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
activeBadgeLabel="素材生成中"
|
||||
pausedBadgeLabel="素材生成已暂停"
|
||||
idleBadgeLabel="等待返回工作区"
|
||||
hideBatchModule
|
||||
/>
|
||||
</Suspense>
|
||||
</motion.div>
|
||||
@@ -11848,6 +12003,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
activeBadgeLabel="草稿生成中"
|
||||
pausedBadgeLabel="草稿生成已暂停"
|
||||
idleBadgeLabel="等待返回工作区"
|
||||
hideBatchModule
|
||||
/>
|
||||
</Suspense>
|
||||
</motion.div>
|
||||
@@ -12215,33 +12371,6 @@ export function PlatformEntryFlowShellImpl({
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{selectionStage === 'bark-battle-config' && (
|
||||
<motion.div
|
||||
key="bark-battle-config"
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -12 }}
|
||||
className="flex h-full min-h-0 flex-col"
|
||||
>
|
||||
<Suspense
|
||||
fallback={<LazyPanelFallback label="正在加载汪汪声浪配置..." />}
|
||||
>
|
||||
<BarkBattleConfigEditor
|
||||
isBusy={isBarkBattleBusy}
|
||||
onBack={leaveBarkBattleFlow}
|
||||
onPublish={(payload) => {
|
||||
void publishBarkBattleConfig(payload);
|
||||
}}
|
||||
/>
|
||||
{barkBattleError ? (
|
||||
<div className="platform-subpanel mx-auto mt-3 max-w-5xl rounded-2xl px-4 py-3 text-sm text-rose-200">
|
||||
{barkBattleError}
|
||||
</div>
|
||||
) : null}
|
||||
</Suspense>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{selectionStage === 'bark-battle-runtime' && barkBattlePublishedConfig && (
|
||||
<motion.div
|
||||
key="bark-battle-runtime"
|
||||
@@ -12258,7 +12387,9 @@ export function PlatformEntryFlowShellImpl({
|
||||
workId={barkBattlePublishedConfig.workId}
|
||||
publishedConfig={barkBattlePublishedConfig}
|
||||
onExit={() => {
|
||||
setSelectionStage('bark-battle-config');
|
||||
enterCreateTab();
|
||||
setActiveCreationFormType('bark-battle');
|
||||
setSelectionStage('platform');
|
||||
}}
|
||||
/>
|
||||
</Suspense>
|
||||
|
||||
@@ -31,7 +31,6 @@ export type SelectionStage =
|
||||
| 'square-hole-generating'
|
||||
| 'square-hole-result'
|
||||
| 'square-hole-runtime'
|
||||
| 'bark-battle-config'
|
||||
| 'bark-battle-runtime'
|
||||
| 'creative-agent-workspace'
|
||||
| 'visual-novel-agent-workspace'
|
||||
|
||||
Reference in New Issue
Block a user