再次合并 master
合入 origin/master 最新订阅消息与计费相关更新 保留作品架 actions 收口并接入统一分享弹窗 修复创作生成泥点预检与本地余额扣减回归
This commit is contained in:
@@ -68,7 +68,6 @@ import type {
|
||||
PuzzleAgentSessionSnapshot,
|
||||
SendPuzzleAgentMessageRequest,
|
||||
} from '../../../packages/shared/src/contracts/puzzleAgentSession';
|
||||
import { isPuzzleCompileActionReady } from './puzzleDraftGenerationState';
|
||||
import type { PuzzleCreativeTemplateSelection } from '../../../packages/shared/src/contracts/puzzleCreativeTemplate';
|
||||
import type {
|
||||
PuzzleRunSnapshot,
|
||||
@@ -159,6 +158,7 @@ import {
|
||||
} from '../../services/big-fish-works';
|
||||
import {
|
||||
type CreationEntryConfig,
|
||||
DEFAULT_UNIFIED_CREATION_MUD_POINT_COST,
|
||||
fetchCreationEntryConfig,
|
||||
} from '../../services/creationEntryConfigService';
|
||||
import {
|
||||
@@ -351,6 +351,7 @@ import {
|
||||
publishVisualNovelWork,
|
||||
updateVisualNovelWork,
|
||||
} from '../../services/visual-novel-works';
|
||||
import { requestGenerationResultSubscribePermission } from '../../services/wechatMiniProgramSubscribe';
|
||||
import {
|
||||
woodenFishClient,
|
||||
type WoodenFishGalleryCardResponse,
|
||||
@@ -495,7 +496,6 @@ import {
|
||||
EDUTAINMENT_HIDDEN_MESSAGE,
|
||||
filterGeneralPublicWorks,
|
||||
} from './platformEdutainmentVisibility';
|
||||
import { buildPlatformRecommendedEntries } from './platformRecommendation';
|
||||
import { PlatformEntryCreationTypeModal } from './PlatformEntryCreationTypeModal';
|
||||
import type { PlatformCreationTypeId } from './platformEntryCreationTypes';
|
||||
import {
|
||||
@@ -525,6 +525,7 @@ import { PlatformEntryWorldDetailView } from './PlatformEntryWorldDetailView';
|
||||
import { PlatformErrorDialog } from './PlatformErrorDialog';
|
||||
import { PlatformFeedbackView } from './PlatformFeedbackView';
|
||||
import { resolvePlatformGenerationProgressTickDecision } from './platformGenerationProgressTickModel';
|
||||
import { buildPlatformRecommendedEntries } from './platformRecommendation';
|
||||
import {
|
||||
buildMatch3DProfileFromSession,
|
||||
hasMatch3DRuntimeAsset,
|
||||
@@ -649,6 +650,7 @@ import {
|
||||
} from './platformSelectionStageModel';
|
||||
import { PlatformTaskCompletionDialog } from './PlatformTaskCompletionDialog';
|
||||
import { PlatformWorkDetailView } from './PlatformWorkDetailView';
|
||||
import { isPuzzleCompileActionReady } from './puzzleDraftGenerationState';
|
||||
import { usePlatformCreationAgentFlowController } from './usePlatformCreationAgentFlowController';
|
||||
import { usePlatformEntryBootstrap } from './usePlatformEntryBootstrap';
|
||||
import { usePlatformEntryLibraryDetail } from './usePlatformEntryLibraryDetail';
|
||||
@@ -1732,6 +1734,18 @@ export function PlatformEntryFlowShellImpl({
|
||||
creationEntryTypes,
|
||||
'visual-novel',
|
||||
);
|
||||
const resolveCreationEntryMudPointCost = useCallback(
|
||||
(id: PlatformCreationTypeId) =>
|
||||
creationEntryTypes.find((item) => item.id === id)?.mudPointCost ??
|
||||
DEFAULT_UNIFIED_CREATION_MUD_POINT_COST,
|
||||
[creationEntryTypes],
|
||||
);
|
||||
const puzzleDraftGenerationPointCost =
|
||||
resolveCreationEntryMudPointCost('puzzle');
|
||||
const match3DDraftGenerationPointCost =
|
||||
resolveCreationEntryMudPointCost('match3d');
|
||||
const barkBattleDraftGenerationPointCost =
|
||||
resolveCreationEntryMudPointCost('bark-battle');
|
||||
const [profilePlayStats, setProfilePlayStats] =
|
||||
useState<ProfilePlayStatsResponse | null>(null);
|
||||
const [profilePlayStatsError, setProfilePlayStatsError] = useState<
|
||||
@@ -2046,9 +2060,8 @@ export function PlatformEntryFlowShellImpl({
|
||||
lastProfileDashboardSnapshotRef.current = null;
|
||||
}, [authUi?.user?.id]);
|
||||
|
||||
const getPlatformProfileDashboardWithLocalWalletDelta = useCallback(
|
||||
async (options?: Parameters<typeof getPlatformProfileDashboard>[0]) => {
|
||||
const latestDashboard = await getPlatformProfileDashboard(options);
|
||||
const applyProfileWalletLocalDeltaToDashboard = useCallback(
|
||||
(latestDashboard: ProfileDashboardSummary | null) => {
|
||||
const reconciledDelta =
|
||||
reconcileProfileWalletLocalDeltaWithServerDashboard(
|
||||
lastProfileDashboardSnapshotRef.current,
|
||||
@@ -2064,6 +2077,13 @@ export function PlatformEntryFlowShellImpl({
|
||||
},
|
||||
[],
|
||||
);
|
||||
const getPlatformProfileDashboardWithLocalWalletDelta = useCallback(
|
||||
async (options?: Parameters<typeof getPlatformProfileDashboard>[0]) => {
|
||||
const latestDashboard = await getPlatformProfileDashboard(options);
|
||||
return applyProfileWalletLocalDeltaToDashboard(latestDashboard);
|
||||
},
|
||||
[applyProfileWalletLocalDeltaToDashboard],
|
||||
);
|
||||
|
||||
const platformBootstrap = usePlatformEntryBootstrap({
|
||||
user: authUi?.user,
|
||||
@@ -2175,10 +2195,12 @@ export function PlatformEntryFlowShellImpl({
|
||||
const ensureEnoughDraftGenerationPointsFromServer = useCallback(
|
||||
async (pointsCost: number) => {
|
||||
try {
|
||||
const latestDashboard = await getPlatformProfileDashboardWithLocalWalletDelta(
|
||||
const latestDashboard = await getPlatformProfileDashboard(
|
||||
RECOMMEND_RUNTIME_BACKGROUND_AUTH_OPTIONS,
|
||||
);
|
||||
platformBootstrap.setProfileDashboard(latestDashboard);
|
||||
const dashboardWithLocalDelta =
|
||||
applyProfileWalletLocalDeltaToDashboard(latestDashboard);
|
||||
platformBootstrap.setProfileDashboard(dashboardWithLocalDelta);
|
||||
const walletBalance = resolveProfileWalletBalance(latestDashboard);
|
||||
if (walletBalance >= pointsCost) {
|
||||
setDraftGenerationPointNotice(null);
|
||||
@@ -2198,7 +2220,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
return false;
|
||||
}
|
||||
},
|
||||
[getPlatformProfileDashboardWithLocalWalletDelta, platformBootstrap],
|
||||
[applyProfileWalletLocalDeltaToDashboard, platformBootstrap],
|
||||
);
|
||||
|
||||
const resolveBigFishErrorMessage = useCallback(
|
||||
@@ -4063,7 +4085,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
});
|
||||
}
|
||||
},
|
||||
beforeExecuteAction: ({ payload, session }) => {
|
||||
beforeExecuteAction: async ({ payload, session }) => {
|
||||
const formPayload = buildPuzzleFormPayloadFromAction(payload);
|
||||
if (formPayload) {
|
||||
setPuzzleFormDraftPayload(formPayload);
|
||||
@@ -4102,6 +4124,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
error: null,
|
||||
},
|
||||
}));
|
||||
void requestGenerationResultSubscribePermission();
|
||||
},
|
||||
onActionError: async ({ payload, errorMessage, session, setSession }) => {
|
||||
if (payload.action !== 'compile_puzzle_draft') {
|
||||
@@ -4267,21 +4290,30 @@ export function PlatformEntryFlowShellImpl({
|
||||
setPuzzleCreationError(null);
|
||||
setPuzzleError(null);
|
||||
return ensureEnoughDraftGenerationPointsFromServer(
|
||||
PUZZLE_DRAFT_GENERATION_POINT_COST,
|
||||
puzzleDraftGenerationPointCost,
|
||||
);
|
||||
}, [ensureEnoughDraftGenerationPointsFromServer]);
|
||||
}, [
|
||||
ensureEnoughDraftGenerationPointsFromServer,
|
||||
puzzleDraftGenerationPointCost,
|
||||
]);
|
||||
const preflightMatch3DDraftGeneration = useCallback(async () => {
|
||||
setMatch3DError(null);
|
||||
return ensureEnoughDraftGenerationPointsFromServer(
|
||||
MATCH3D_DRAFT_GENERATION_POINT_COST,
|
||||
match3DDraftGenerationPointCost,
|
||||
);
|
||||
}, [ensureEnoughDraftGenerationPointsFromServer]);
|
||||
}, [
|
||||
ensureEnoughDraftGenerationPointsFromServer,
|
||||
match3DDraftGenerationPointCost,
|
||||
]);
|
||||
const preflightBarkBattleDraftGeneration = useCallback(async () => {
|
||||
setBarkBattleError(null);
|
||||
return ensureEnoughDraftGenerationPointsFromServer(
|
||||
BARK_BATTLE_DRAFT_GENERATION_POINT_COST,
|
||||
barkBattleDraftGenerationPointCost,
|
||||
);
|
||||
}, [ensureEnoughDraftGenerationPointsFromServer]);
|
||||
}, [
|
||||
barkBattleDraftGenerationPointCost,
|
||||
ensureEnoughDraftGenerationPointsFromServer,
|
||||
]);
|
||||
const draftGenerationPointNoticeDescription = draftGenerationPointNotice
|
||||
? draftGenerationPointNotice.title === '读取泥点余额失败'
|
||||
? '当前表单不会丢失,关闭后可继续编辑,稍后再试。'
|
||||
@@ -5263,6 +5295,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
|
||||
try {
|
||||
const actionPayload = buildPuzzleCompileActionFromFormPayload(payload);
|
||||
void requestGenerationResultSubscribePermission();
|
||||
const response = await executePuzzleAgentAction(
|
||||
nextSession.sessionId,
|
||||
actionPayload,
|
||||
@@ -5430,6 +5463,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
isViewingPuzzleGeneration,
|
||||
preflightPuzzleDraftGeneration,
|
||||
puzzleFlow,
|
||||
puzzleDraftGenerationPointCost,
|
||||
refreshPuzzleShelf,
|
||||
recoverCompletedPuzzleDraftGeneration,
|
||||
refreshPlatformDashboardSilently,
|
||||
@@ -5707,6 +5741,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
[
|
||||
adjustProfileWalletBalanceLocally,
|
||||
match3dRuntimeAdapter,
|
||||
match3DDraftGenerationPointCost,
|
||||
isViewingMatch3DGeneration,
|
||||
markDraftGenerating,
|
||||
markDraftFailed,
|
||||
@@ -14293,6 +14328,9 @@ export function PlatformEntryFlowShellImpl({
|
||||
onOpenShelfItem={(item) => {
|
||||
markDraftNoticeSeen(getGenerationNoticeShelfKeys(item));
|
||||
}}
|
||||
onShareWork={(payload) => {
|
||||
openPublishShareModal(payload);
|
||||
}}
|
||||
deletingWorkId={deletingCreationWorkId}
|
||||
claimingPuzzleProfileId={claimingPuzzlePointIncentiveProfileId}
|
||||
/>
|
||||
@@ -14833,7 +14871,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
>
|
||||
<UnifiedCreationWorkspace
|
||||
playId="match3d"
|
||||
spec={getUnifiedSpec('match3d')}
|
||||
spec={{
|
||||
...getUnifiedSpec('match3d'),
|
||||
mudPointCost: match3DDraftGenerationPointCost,
|
||||
}}
|
||||
session={match3dSession}
|
||||
isBusy={isStreamingMatch3DReply}
|
||||
error={match3dError}
|
||||
@@ -15946,7 +15987,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
>
|
||||
<UnifiedCreationWorkspace
|
||||
playId="puzzle"
|
||||
spec={getUnifiedSpec('puzzle')}
|
||||
spec={{
|
||||
...getUnifiedSpec('puzzle'),
|
||||
mudPointCost: puzzleDraftGenerationPointCost,
|
||||
}}
|
||||
session={puzzleSession}
|
||||
isBusy={isStreamingPuzzleReply}
|
||||
error={puzzleError}
|
||||
|
||||
@@ -16,6 +16,7 @@ export type PlatformCreationTypeCard = {
|
||||
subtitle: string;
|
||||
badge: string;
|
||||
imageSrc: string;
|
||||
mudPointCost: number;
|
||||
mudPointCostLabel: string;
|
||||
locked: boolean;
|
||||
categoryId: string;
|
||||
@@ -144,6 +145,9 @@ export function derivePlatformCreationTypes(
|
||||
subtitle: item.subtitle,
|
||||
badge: item.badge,
|
||||
imageSrc: item.imageSrc,
|
||||
mudPointCost: normalizeMudPointCost(
|
||||
item.unifiedCreationSpec?.mudPointCost,
|
||||
),
|
||||
mudPointCostLabel: formatMudPointCostText(
|
||||
item.unifiedCreationSpec?.mudPointCost,
|
||||
),
|
||||
|
||||
@@ -300,6 +300,91 @@ function ActionCompleteHarness({
|
||||
);
|
||||
}
|
||||
|
||||
function BeforeActionHarness({ events }: { events: string[] }) {
|
||||
const hasOpenedRef = useRef(false);
|
||||
const flow = usePlatformCreationAgentFlowController<
|
||||
ActionTestSession,
|
||||
Record<string, never>,
|
||||
{ session: ActionTestSession },
|
||||
TestMessagePayload,
|
||||
{ action: string },
|
||||
{ session: ActionTestSession }
|
||||
>({
|
||||
client: {
|
||||
createSession: async () => ({
|
||||
session: {
|
||||
sessionId: 'session-1',
|
||||
messages: [],
|
||||
draft: { profileId: 'profile-draft-1' },
|
||||
},
|
||||
}),
|
||||
getSession: async () => ({
|
||||
session: {
|
||||
sessionId: 'session-1',
|
||||
messages: [],
|
||||
draft: { profileId: 'profile-draft-1' },
|
||||
},
|
||||
}),
|
||||
streamMessage: async () => ({
|
||||
sessionId: 'session-1',
|
||||
messages: [],
|
||||
draft: { profileId: 'profile-draft-1' },
|
||||
}),
|
||||
executeAction: async () => {
|
||||
events.push('executeAction');
|
||||
return {
|
||||
session: {
|
||||
sessionId: 'session-1',
|
||||
messages: [],
|
||||
draft: { profileId: 'profile-ready-1' },
|
||||
},
|
||||
};
|
||||
},
|
||||
selectSession: (response) => response.session,
|
||||
},
|
||||
createPayload: {},
|
||||
workspaceStage: 'match3d-agent-workspace',
|
||||
resultStage: 'match3d-result',
|
||||
platformStage: 'platform',
|
||||
isCompileAction: () => true,
|
||||
resolveErrorMessage: (error, fallback) =>
|
||||
error instanceof Error ? error.message : fallback,
|
||||
errorMessages: {
|
||||
open: '打开失败',
|
||||
restoreMissingSession: '缺少会话',
|
||||
restore: '恢复失败',
|
||||
submit: '发送失败',
|
||||
execute: '执行失败',
|
||||
},
|
||||
enterCreateTab: () => {},
|
||||
setSelectionStage: () => {},
|
||||
beforeExecuteAction: async () => {
|
||||
events.push('beforeExecuteAction');
|
||||
await Promise.resolve();
|
||||
events.push('permissionResolved');
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (hasOpenedRef.current) {
|
||||
return;
|
||||
}
|
||||
hasOpenedRef.current = true;
|
||||
void flow.openWorkspace({});
|
||||
}, [flow]);
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
void flow.executeAction({ action: 'match3d_compile_draft' });
|
||||
}}
|
||||
>
|
||||
执行
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function SessionChangeHarness({
|
||||
onSessionChanged,
|
||||
}: {
|
||||
@@ -547,6 +632,28 @@ test('creation agent flow suppresses compile result stage for background complet
|
||||
);
|
||||
});
|
||||
|
||||
test('creation agent flow waits for beforeExecuteAction before network action', async () => {
|
||||
const events: string[] = [];
|
||||
|
||||
render(<BeforeActionHarness events={events} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('button', { name: '执行' })).toBeTruthy();
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
screen.getByRole('button', { name: '执行' }).click();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(events).toEqual([
|
||||
'beforeExecuteAction',
|
||||
'permissionResolved',
|
||||
'executeAction',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
test('creation agent flow notifies session changes after open restore and compile', async () => {
|
||||
const onSessionChanged = vi.fn();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import type { Dispatch, SetStateAction } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import type { TextStreamOptions } from '../../services/aiTypes';
|
||||
import type { SelectionStage } from './platformEntryTypes';
|
||||
@@ -90,7 +90,7 @@ type PlatformCreationAgentFlowControllerOptions<
|
||||
beforeExecuteAction?: (params: {
|
||||
payload: TActionPayload;
|
||||
session: TSession;
|
||||
}) => void;
|
||||
}) => void | Promise<void>;
|
||||
onActionError?: (params: {
|
||||
payload: TActionPayload;
|
||||
error: unknown;
|
||||
@@ -211,7 +211,7 @@ export function usePlatformCreationAgentFlowController<
|
||||
setIsBusy(false);
|
||||
}
|
||||
},
|
||||
[isBusy, options, resetStreamingReply],
|
||||
[isBusy, options, resetStreamingReply, setSession],
|
||||
);
|
||||
|
||||
const restoreDraft = useCallback(
|
||||
@@ -249,7 +249,7 @@ export function usePlatformCreationAgentFlowController<
|
||||
setIsBusy(false);
|
||||
}
|
||||
},
|
||||
[options, resetStreamingReply],
|
||||
[options, resetStreamingReply, setSession],
|
||||
);
|
||||
|
||||
const submitMessage = useCallback(
|
||||
@@ -309,7 +309,13 @@ export function usePlatformCreationAgentFlowController<
|
||||
setIsStreamingReply(false);
|
||||
}
|
||||
},
|
||||
[isStreamingReply, options, session, updateStreamingReplyText],
|
||||
[
|
||||
isStreamingReply,
|
||||
options,
|
||||
session,
|
||||
setSession,
|
||||
updateStreamingReplyText,
|
||||
],
|
||||
);
|
||||
|
||||
const executeAction = useCallback(
|
||||
@@ -323,7 +329,7 @@ export function usePlatformCreationAgentFlowController<
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
options.beforeExecuteAction?.({ payload, session: targetSession });
|
||||
await options.beforeExecuteAction?.({ payload, session: targetSession });
|
||||
const response = await options.client.executeAction(
|
||||
targetSession.sessionId,
|
||||
payload,
|
||||
@@ -358,7 +364,7 @@ export function usePlatformCreationAgentFlowController<
|
||||
setIsBusy(false);
|
||||
}
|
||||
},
|
||||
[isBusy, options, session],
|
||||
[isBusy, options, session, setSession],
|
||||
);
|
||||
|
||||
const leaveFlow = useCallback(() => {
|
||||
|
||||
Reference in New Issue
Block a user