1
This commit is contained in:
@@ -26,37 +26,35 @@ function CreationTypeCard(props: {
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
onClick={onSelect}
|
||||
className={`platform-interactive-card relative overflow-hidden rounded-[1.65rem] border px-4 py-4 text-left ${
|
||||
className={`platform-interactive-card relative flex min-h-[8.25rem] flex-col overflow-hidden rounded-[1.65rem] border px-4 py-4 text-left ${
|
||||
item.locked
|
||||
? 'cursor-not-allowed border-[var(--platform-subpanel-border)] bg-[var(--platform-subpanel-fill)] text-[var(--platform-text-soft)]'
|
||||
: 'border-[var(--platform-cool-border)] bg-[radial-gradient(circle_at_top_left,rgba(255,255,255,0.24),transparent_34%),linear-gradient(135deg,rgba(255,79,139,0.96),rgba(255,145,110,0.9))] text-white'
|
||||
} ${busy && !item.locked ? 'opacity-70' : ''}`}
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<span
|
||||
className={`platform-pill px-3 ${
|
||||
item.locked
|
||||
? 'platform-pill--neutral text-[var(--platform-text-soft)]'
|
||||
: 'platform-pill--neutral border-white/30 bg-white/18 text-white'
|
||||
}`}
|
||||
>
|
||||
{item.locked ? item.badge : busy ? '正在开启' : item.badge}
|
||||
</span>
|
||||
<div className="flex min-h-6 items-start justify-end gap-3">
|
||||
{item.locked ? (
|
||||
<span className="platform-pill platform-pill--neutral px-3 text-[var(--platform-text-soft)]">
|
||||
{item.badge}
|
||||
</span>
|
||||
) : null}
|
||||
{item.locked ? (
|
||||
<span className="text-lg leading-none text-white/45">·</span>
|
||||
) : (
|
||||
<ArrowRight className="h-4 w-4 text-white/80" />
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-8 text-xl font-black leading-tight text-inherit">
|
||||
{item.title}
|
||||
</div>
|
||||
<div
|
||||
className={`mt-2 text-sm ${
|
||||
item.locked ? 'text-zinc-500' : 'text-zinc-200/82'
|
||||
}`}
|
||||
>
|
||||
{item.subtitle}
|
||||
<div className="mt-auto pt-4">
|
||||
<div className="text-xl font-black leading-tight text-inherit">
|
||||
{item.title}
|
||||
</div>
|
||||
<div
|
||||
className={`mt-2 text-sm ${
|
||||
item.locked ? 'text-zinc-500' : 'text-zinc-200/82'
|
||||
}`}
|
||||
>
|
||||
{item.subtitle}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -25,6 +25,7 @@ import type {
|
||||
} from '../../../packages/shared/src/contracts/puzzleAgentActions';
|
||||
import type { PuzzleResultDraft } from '../../../packages/shared/src/contracts/puzzleAgentDraft';
|
||||
import type {
|
||||
CreatePuzzleAgentSessionRequest,
|
||||
PuzzleAgentSessionSnapshot,
|
||||
SendPuzzleAgentMessageRequest,
|
||||
} from '../../../packages/shared/src/contracts/puzzleAgentSession';
|
||||
@@ -98,12 +99,20 @@ import {
|
||||
} from '../../services/puzzle-gallery';
|
||||
import {
|
||||
advanceLocalPuzzleNextLevel,
|
||||
dragPuzzlePieceOrGroup,
|
||||
getPuzzleRun,
|
||||
startPuzzleRun,
|
||||
submitPuzzleLeaderboard,
|
||||
swapPuzzlePieces,
|
||||
updatePuzzleRunPause,
|
||||
usePuzzleRuntimeProp as consumePuzzleRuntimeProp,
|
||||
} from '../../services/puzzle-runtime';
|
||||
import {
|
||||
applyLocalPuzzleFreezeTime,
|
||||
dragLocalPuzzlePiece,
|
||||
isLocalPuzzleRun,
|
||||
refreshLocalPuzzleTimer,
|
||||
setLocalPuzzlePaused,
|
||||
startLocalPuzzleRun,
|
||||
submitLocalPuzzleLeaderboard,
|
||||
swapLocalPuzzlePieces,
|
||||
@@ -114,8 +123,8 @@ import { rpgCreationPreviewAdapter } from '../../services/rpg-creation/rpgCreati
|
||||
import {
|
||||
deleteRpgEntryWorldProfile,
|
||||
getRpgEntryWorldGalleryDetailByCode,
|
||||
remixRpgEntryWorldGallery,
|
||||
recordRpgEntryWorldGalleryPlay,
|
||||
remixRpgEntryWorldGallery,
|
||||
} from '../../services/rpg-entry/rpgEntryLibraryClient';
|
||||
import { getRpgProfilePlayStats } from '../../services/rpg-entry/rpgProfileClient';
|
||||
import type { CustomWorldProfile } from '../../types';
|
||||
@@ -230,6 +239,33 @@ function mapBigFishWorkToPublicWorkDetail(
|
||||
return mapBigFishWorkToPlatformGalleryCard(item);
|
||||
}
|
||||
|
||||
function mapPublicWorkDetailToPuzzleWork(
|
||||
entry: PlatformPublicGalleryCard,
|
||||
): PuzzleWorkSummary | null {
|
||||
if (!isPuzzleGalleryEntry(entry)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
workId: entry.workId,
|
||||
profileId: entry.profileId,
|
||||
ownerUserId: entry.ownerUserId,
|
||||
sourceSessionId: null,
|
||||
authorDisplayName: entry.authorDisplayName,
|
||||
levelName: entry.worldName,
|
||||
summary: entry.summaryText,
|
||||
themeTags: entry.themeTags,
|
||||
coverImageSrc: entry.coverImageSrc,
|
||||
publicationStatus: 'published',
|
||||
updatedAt: entry.updatedAt,
|
||||
publishedAt: entry.publishedAt,
|
||||
playCount: entry.playCount ?? 0,
|
||||
remixCount: entry.remixCount ?? 0,
|
||||
likeCount: entry.likeCount ?? 0,
|
||||
publishReady: true,
|
||||
};
|
||||
}
|
||||
|
||||
function mapPublicWorkDetailToBigFishWork(
|
||||
entry: PlatformPublicGalleryCard,
|
||||
): BigFishWorkSummary | null {
|
||||
@@ -265,6 +301,26 @@ function mapPublicWorkDetailToBigFishWork(
|
||||
};
|
||||
}
|
||||
|
||||
async function resolvePublicWorkAuthorSummary(
|
||||
entry: PlatformPublicGalleryCard,
|
||||
): Promise<PublicUserSummary | null> {
|
||||
if ('authorPublicUserCode' in entry && entry.authorPublicUserCode?.trim()) {
|
||||
try {
|
||||
return await getPublicAuthUserByCode(entry.authorPublicUserCode);
|
||||
} catch {
|
||||
if (!entry.ownerUserId.trim()) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (entry.ownerUserId.trim()) {
|
||||
return getPublicAuthUserById(entry.ownerUserId);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function readProfileTextField(
|
||||
profile: CustomWorldProfile | null,
|
||||
paths: string[],
|
||||
@@ -400,6 +456,18 @@ function buildPuzzleResultProfileId(sessionId: string | null | undefined) {
|
||||
return `puzzle-profile-${stableSuffix}`;
|
||||
}
|
||||
|
||||
function buildPuzzleCompileActionFromFormPayload(
|
||||
payload: CreatePuzzleAgentSessionRequest | null,
|
||||
): PuzzleAgentActionRequest {
|
||||
return {
|
||||
action: 'compile_puzzle_draft',
|
||||
promptText:
|
||||
payload?.pictureDescription?.trim() || payload?.seedText?.trim(),
|
||||
referenceImageSrc: payload?.referenceImageSrc || null,
|
||||
candidateCount: 1,
|
||||
};
|
||||
}
|
||||
|
||||
const CustomWorldGenerationView = lazy(async () => {
|
||||
const module = await import('../CustomWorldGenerationView');
|
||||
return {
|
||||
@@ -505,6 +573,9 @@ export function PlatformEntryFlowShellImpl({
|
||||
useState<CustomWorldLibraryEntry<CustomWorldProfile> | null>(null);
|
||||
const [selectedPublicWorkDetail, setSelectedPublicWorkDetail] =
|
||||
useState<PlatformPublicGalleryCard | null>(null);
|
||||
const [selectedPublicWorkAuthor, setSelectedPublicWorkAuthor] =
|
||||
useState<PublicUserSummary | null>(null);
|
||||
const publicWorkAuthorRequestKeyRef = useRef(0);
|
||||
const [publicWorkDetailError, setPublicWorkDetailError] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
@@ -529,8 +600,9 @@ export function PlatformEntryFlowShellImpl({
|
||||
const [isBigFishLoadingLibrary, setIsBigFishLoadingLibrary] = useState(false);
|
||||
const [bigFishGenerationState, setBigFishGenerationState] =
|
||||
useState<MiniGameDraftGenerationState | null>(null);
|
||||
const [puzzleOperation, setPuzzleOperation] =
|
||||
useState<PuzzleAgentOperationRecord | null>(null);
|
||||
const [, setPuzzleOperation] = useState<PuzzleAgentOperationRecord | null>(
|
||||
null,
|
||||
);
|
||||
const [puzzleWorks, setPuzzleWorks] = useState<PuzzleWorkSummary[]>([]);
|
||||
const [puzzleGalleryEntries, setPuzzleGalleryEntries] = useState<
|
||||
PuzzleWorkSummary[]
|
||||
@@ -544,9 +616,12 @@ export function PlatformEntryFlowShellImpl({
|
||||
const [isPuzzleLeaderboardBusy, setIsPuzzleLeaderboardBusy] = useState(false);
|
||||
const submittedPuzzleLeaderboardKeysRef = useRef(new Set<string>());
|
||||
const [puzzleRun, setPuzzleRun] = useState<PuzzleRunSnapshot | null>(null);
|
||||
const puzzleRunRef = useRef<PuzzleRunSnapshot | null>(null);
|
||||
const [isPuzzleLoadingLibrary, setIsPuzzleLoadingLibrary] = useState(false);
|
||||
const [puzzleGenerationState, setPuzzleGenerationState] =
|
||||
useState<MiniGameDraftGenerationState | null>(null);
|
||||
const [puzzleFormDraftPayload, setPuzzleFormDraftPayload] =
|
||||
useState<CreatePuzzleAgentSessionRequest | null>(null);
|
||||
const [isPuzzleNextLevelGenerating, setIsPuzzleNextLevelGenerating] =
|
||||
useState(false);
|
||||
const [isSearchingPublicCode, setIsSearchingPublicCode] = useState(false);
|
||||
@@ -984,7 +1059,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
|
||||
const puzzleFlow = usePlatformCreationAgentFlowController<
|
||||
PuzzleAgentSessionSnapshot,
|
||||
Record<string, never>,
|
||||
CreatePuzzleAgentSessionRequest,
|
||||
{ session: PuzzleAgentSessionSnapshot },
|
||||
SendPuzzleAgentMessageRequest,
|
||||
PuzzleAgentActionRequest,
|
||||
@@ -1097,7 +1172,6 @@ export function PlatformEntryFlowShellImpl({
|
||||
const setPuzzleError = puzzleFlow.setError;
|
||||
const isPuzzleBusy = puzzleFlow.isBusy;
|
||||
const setIsPuzzleBusy = puzzleFlow.setIsBusy;
|
||||
const streamingPuzzleReplyText = puzzleFlow.streamingReplyText;
|
||||
const isStreamingPuzzleReply = puzzleFlow.isStreamingReply;
|
||||
const resetRpgSessionViewState = sessionController.resetSessionViewState;
|
||||
const setRpgGeneratedCustomWorldProfile =
|
||||
@@ -1106,6 +1180,11 @@ export function PlatformEntryFlowShellImpl({
|
||||
const persistRpgAgentUiState = sessionController.persistAgentUiState;
|
||||
const resetAutoSaveTrackingToIdle =
|
||||
autosaveCoordinator.resetAutoSaveTrackingToIdle;
|
||||
|
||||
useEffect(() => {
|
||||
puzzleRunRef.current = puzzleRun;
|
||||
}, [puzzleRun]);
|
||||
|
||||
const openBigFishAgentWorkspace = useCallback(async () => {
|
||||
setBigFishRun(null);
|
||||
await bigFishFlow.openWorkspace();
|
||||
@@ -1114,8 +1193,32 @@ export function PlatformEntryFlowShellImpl({
|
||||
const openPuzzleAgentWorkspace = useCallback(async () => {
|
||||
setPuzzleRun(null);
|
||||
setPuzzleOperation(null);
|
||||
await puzzleFlow.openWorkspace();
|
||||
}, [puzzleFlow]);
|
||||
setPuzzleGenerationState(null);
|
||||
setPuzzleFormDraftPayload(null);
|
||||
puzzleFlow.setSession(null);
|
||||
puzzleFlow.setError(null);
|
||||
puzzleFlow.setStreamingReplyText('');
|
||||
puzzleFlow.setIsStreamingReply(false);
|
||||
enterCreateTab();
|
||||
setShowCreationTypeModal(false);
|
||||
setSelectionStage('puzzle-agent-workspace');
|
||||
}, [enterCreateTab, puzzleFlow, setSelectionStage]);
|
||||
|
||||
const createPuzzleDraftFromForm = useCallback(
|
||||
async (payload: CreatePuzzleAgentSessionRequest) => {
|
||||
setPuzzleFormDraftPayload(payload);
|
||||
const nextSession = await puzzleFlow.openWorkspace(payload);
|
||||
if (!nextSession) {
|
||||
return;
|
||||
}
|
||||
|
||||
await puzzleFlow.executeAction(
|
||||
buildPuzzleCompileActionFromFormPayload(payload),
|
||||
nextSession,
|
||||
);
|
||||
},
|
||||
[puzzleFlow],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (platformBootstrap.canReadProtectedData) {
|
||||
@@ -1325,6 +1428,8 @@ export function PlatformEntryFlowShellImpl({
|
||||
async (
|
||||
profileId: string,
|
||||
returnStage: PuzzleRuntimeReturnStage = 'work-detail',
|
||||
detailItem?: PuzzleWorkSummary,
|
||||
mirrorErrorToPublicDetail = false,
|
||||
) => {
|
||||
if (isPuzzleBusy) {
|
||||
return;
|
||||
@@ -1334,7 +1439,8 @@ export function PlatformEntryFlowShellImpl({
|
||||
setPuzzleError(null);
|
||||
|
||||
try {
|
||||
const { item } = await getPuzzleGalleryDetail(profileId);
|
||||
const item =
|
||||
detailItem ?? (await getPuzzleGalleryDetail(profileId)).item;
|
||||
const { run } = await startPuzzleRun({ profileId: item.profileId });
|
||||
setSelectedPuzzleDetail(item);
|
||||
setPuzzleRun(run);
|
||||
@@ -1347,12 +1453,22 @@ export function PlatformEntryFlowShellImpl({
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
setPuzzleError(resolvePuzzleErrorMessage(error, '启动拼图玩法失败。'));
|
||||
const message = resolvePuzzleErrorMessage(error, '启动拼图玩法失败。');
|
||||
setPuzzleError(message);
|
||||
if (mirrorErrorToPublicDetail) {
|
||||
setPublicWorkDetailError(message);
|
||||
}
|
||||
} finally {
|
||||
setIsPuzzleBusy(false);
|
||||
}
|
||||
},
|
||||
[isPuzzleBusy, resolvePuzzleErrorMessage, setSelectionStage],
|
||||
[
|
||||
isPuzzleBusy,
|
||||
resolvePuzzleErrorMessage,
|
||||
setIsPuzzleBusy,
|
||||
setPuzzleError,
|
||||
setSelectionStage,
|
||||
],
|
||||
);
|
||||
|
||||
const buildPuzzleTestWork = useCallback(
|
||||
@@ -1449,9 +1565,20 @@ export function PlatformEntryFlowShellImpl({
|
||||
}
|
||||
|
||||
setPuzzleError(null);
|
||||
setPuzzleRun(swapLocalPuzzlePieces(puzzleRun, payload));
|
||||
if (isLocalPuzzleRun(puzzleRun)) {
|
||||
setPuzzleRun(swapLocalPuzzlePieces(puzzleRun, payload));
|
||||
return;
|
||||
}
|
||||
|
||||
void swapPuzzlePieces(puzzleRun.runId, payload)
|
||||
.then(({ run }) => {
|
||||
setPuzzleRun(run);
|
||||
})
|
||||
.catch((error) => {
|
||||
setPuzzleError(resolvePuzzleErrorMessage(error, '交换拼图块失败。'));
|
||||
});
|
||||
},
|
||||
[isPuzzleBusy, puzzleRun],
|
||||
[isPuzzleBusy, puzzleRun, resolvePuzzleErrorMessage, setPuzzleError],
|
||||
);
|
||||
|
||||
const dragPuzzlePiece = useCallback(
|
||||
@@ -1461,9 +1588,126 @@ export function PlatformEntryFlowShellImpl({
|
||||
}
|
||||
|
||||
setPuzzleError(null);
|
||||
setPuzzleRun(dragLocalPuzzlePiece(puzzleRun, payload));
|
||||
if (isLocalPuzzleRun(puzzleRun)) {
|
||||
setPuzzleRun(dragLocalPuzzlePiece(puzzleRun, payload));
|
||||
return;
|
||||
}
|
||||
|
||||
void dragPuzzlePieceOrGroup(puzzleRun.runId, payload)
|
||||
.then(({ run }) => {
|
||||
setPuzzleRun(run);
|
||||
})
|
||||
.catch((error) => {
|
||||
setPuzzleError(resolvePuzzleErrorMessage(error, '拖动拼图块失败。'));
|
||||
});
|
||||
},
|
||||
[isPuzzleBusy, puzzleRun],
|
||||
[isPuzzleBusy, puzzleRun, resolvePuzzleErrorMessage, setPuzzleError],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectionStage !== 'puzzle-runtime' || !puzzleRun?.currentLevel) {
|
||||
return;
|
||||
}
|
||||
if (puzzleRun.currentLevel.status !== 'playing') {
|
||||
return;
|
||||
}
|
||||
|
||||
const timerId = window.setInterval(() => {
|
||||
if (!isLocalPuzzleRun(puzzleRun)) {
|
||||
return;
|
||||
}
|
||||
setPuzzleRun((currentRun) =>
|
||||
currentRun ? refreshLocalPuzzleTimer(currentRun) : currentRun,
|
||||
);
|
||||
}, 250);
|
||||
|
||||
return () => window.clearInterval(timerId);
|
||||
}, [puzzleRun, selectionStage]);
|
||||
|
||||
const setPuzzleRuntimePaused = useCallback(
|
||||
async (paused: boolean) => {
|
||||
if (!puzzleRun?.currentLevel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isLocalPuzzleRun(puzzleRun)) {
|
||||
setPuzzleRun((currentRun) =>
|
||||
currentRun ? setLocalPuzzlePaused(currentRun, paused) : currentRun,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { run } = await updatePuzzleRunPause(puzzleRun.runId, {
|
||||
paused,
|
||||
});
|
||||
setPuzzleRun(run);
|
||||
void platformBootstrap.refreshProfileDashboard();
|
||||
} catch (error) {
|
||||
setPuzzleError(
|
||||
resolvePuzzleErrorMessage(error, '更新拼图计时状态失败。'),
|
||||
);
|
||||
}
|
||||
},
|
||||
[platformBootstrap, puzzleRun, resolvePuzzleErrorMessage, setPuzzleError],
|
||||
);
|
||||
|
||||
const syncPuzzleRuntimeTimeout = useCallback(async () => {
|
||||
if (
|
||||
!puzzleRun?.currentLevel ||
|
||||
puzzleRun.currentLevel.status !== 'playing'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isLocalPuzzleRun(puzzleRun)) {
|
||||
setPuzzleRun((currentRun) =>
|
||||
currentRun ? refreshLocalPuzzleTimer(currentRun) : currentRun,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { run } = await getPuzzleRun(puzzleRun.runId);
|
||||
setPuzzleRun(run);
|
||||
} catch (error) {
|
||||
setPuzzleError(
|
||||
resolvePuzzleErrorMessage(error, '同步拼图失败状态失败。'),
|
||||
);
|
||||
}
|
||||
}, [puzzleRun, resolvePuzzleErrorMessage, setPuzzleError]);
|
||||
|
||||
const usePuzzleProp = useCallback(
|
||||
async (propKind: 'hint' | 'reference' | 'freezeTime') => {
|
||||
if (
|
||||
!puzzleRun?.currentLevel ||
|
||||
puzzleRun.currentLevel.status !== 'playing'
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isLocalPuzzleRun(puzzleRun)) {
|
||||
const currentRun = puzzleRunRef.current ?? puzzleRun;
|
||||
if (!currentRun.currentLevel) {
|
||||
return null;
|
||||
}
|
||||
const nextRun =
|
||||
propKind === 'freezeTime'
|
||||
? applyLocalPuzzleFreezeTime(currentRun)
|
||||
: setLocalPuzzlePaused(currentRun, propKind === 'reference');
|
||||
puzzleRunRef.current = nextRun;
|
||||
setPuzzleRun(nextRun);
|
||||
return nextRun;
|
||||
}
|
||||
|
||||
const { run } = await consumePuzzleRuntimeProp(puzzleRun.runId, {
|
||||
propKind,
|
||||
});
|
||||
setPuzzleRun(run);
|
||||
void platformBootstrap.refreshProfileDashboard();
|
||||
return run;
|
||||
},
|
||||
[platformBootstrap, puzzleRun],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -1622,34 +1866,6 @@ export function PlatformEntryFlowShellImpl({
|
||||
});
|
||||
}, [handleCustomWorldSelect, runProtectedAction, selectedDetailEntry]);
|
||||
|
||||
const handleExperienceRpgWork = useCallback(
|
||||
(work: (typeof creationHubItems)[number]) => {
|
||||
if (!work.profileId) {
|
||||
return;
|
||||
}
|
||||
|
||||
runProtectedAction(() => {
|
||||
const matchedEntry = platformBootstrap.savedCustomWorldEntries.find(
|
||||
(entry) => entry.profileId === work.profileId,
|
||||
);
|
||||
if (!matchedEntry) {
|
||||
platformBootstrap.setPlatformError(
|
||||
'未找到可体验的作品,请刷新后重试。',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
handleCustomWorldSelect(matchedEntry.profile);
|
||||
});
|
||||
},
|
||||
[
|
||||
handleCustomWorldSelect,
|
||||
platformBootstrap,
|
||||
platformBootstrap.savedCustomWorldEntries,
|
||||
runProtectedAction,
|
||||
],
|
||||
);
|
||||
|
||||
const handleDeleteLibraryEntry = useCallback(
|
||||
(entry: CustomWorldLibraryEntry<CustomWorldProfile>) => {
|
||||
if (!entry.profileId || deletingCreationWorkId) {
|
||||
@@ -1815,6 +2031,32 @@ export function PlatformEntryFlowShellImpl({
|
||||
],
|
||||
);
|
||||
|
||||
const clearSelectedPublicWorkAuthor = useCallback(() => {
|
||||
publicWorkAuthorRequestKeyRef.current += 1;
|
||||
setSelectedPublicWorkAuthor(null);
|
||||
}, []);
|
||||
|
||||
const loadSelectedPublicWorkAuthor = useCallback(
|
||||
(entry: PlatformPublicGalleryCard) => {
|
||||
const requestKey = publicWorkAuthorRequestKeyRef.current + 1;
|
||||
publicWorkAuthorRequestKeyRef.current = requestKey;
|
||||
setSelectedPublicWorkAuthor(null);
|
||||
|
||||
void resolvePublicWorkAuthorSummary(entry)
|
||||
.then((author) => {
|
||||
if (publicWorkAuthorRequestKeyRef.current === requestKey) {
|
||||
setSelectedPublicWorkAuthor(author);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
if (publicWorkAuthorRequestKeyRef.current === requestKey) {
|
||||
setSelectedPublicWorkAuthor(null);
|
||||
}
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const openPublicWorkDetail = useCallback(
|
||||
(entry: PlatformPublicGalleryCard) => {
|
||||
setSelectedPublicWorkDetail(entry);
|
||||
@@ -1829,19 +2071,44 @@ export function PlatformEntryFlowShellImpl({
|
||||
[setSelectionStage],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const detailEntry =
|
||||
selectionStage === 'work-detail'
|
||||
? selectedPublicWorkDetail
|
||||
: selectionStage === 'detail' &&
|
||||
selectedDetailEntry &&
|
||||
selectedDetailEntry.visibility !== 'draft'
|
||||
? mapRpgGalleryCardToPublicWorkDetail(selectedDetailEntry)
|
||||
: null;
|
||||
|
||||
if (!detailEntry) {
|
||||
clearSelectedPublicWorkAuthor();
|
||||
return;
|
||||
}
|
||||
|
||||
loadSelectedPublicWorkAuthor(detailEntry);
|
||||
}, [
|
||||
clearSelectedPublicWorkAuthor,
|
||||
loadSelectedPublicWorkAuthor,
|
||||
selectedDetailEntry,
|
||||
selectedPublicWorkDetail,
|
||||
selectionStage,
|
||||
]);
|
||||
|
||||
const openRpgPublicWorkDetail = useCallback(
|
||||
async (entry: CustomWorldGalleryCard) => {
|
||||
setIsPublicWorkDetailBusy(true);
|
||||
setPublicWorkDetailError(null);
|
||||
clearSelectedPublicWorkAuthor();
|
||||
setSelectedPublicWorkDetail(entry);
|
||||
setSelectionStage('work-detail');
|
||||
|
||||
try {
|
||||
const detailEntry =
|
||||
await detailNavigation.loadGalleryDetailEntry(entry);
|
||||
setSelectedDetailEntry(detailEntry);
|
||||
setSelectedPublicWorkDetail(
|
||||
mapRpgGalleryCardToPublicWorkDetail(detailEntry),
|
||||
);
|
||||
const detailCard = mapRpgGalleryCardToPublicWorkDetail(detailEntry);
|
||||
setSelectedPublicWorkDetail(detailCard);
|
||||
if (detailEntry.publicWorkCode?.trim()) {
|
||||
pushAppHistoryPath(
|
||||
buildPublicWorkStagePath('work-detail', detailEntry.publicWorkCode),
|
||||
@@ -1856,7 +2123,12 @@ export function PlatformEntryFlowShellImpl({
|
||||
setIsPublicWorkDetailBusy(false);
|
||||
}
|
||||
},
|
||||
[detailNavigation, setSelectedDetailEntry, setSelectionStage],
|
||||
[
|
||||
clearSelectedPublicWorkAuthor,
|
||||
detailNavigation,
|
||||
setSelectedDetailEntry,
|
||||
setSelectionStage,
|
||||
],
|
||||
);
|
||||
|
||||
const openPuzzlePublicWorkDetail = useCallback(
|
||||
@@ -2004,7 +2276,13 @@ export function PlatformEntryFlowShellImpl({
|
||||
}
|
||||
|
||||
if (isPuzzleGalleryEntry(selectedPublicWorkDetail)) {
|
||||
void startPuzzleRunFromProfile(selectedPublicWorkDetail.profileId);
|
||||
const work = mapPublicWorkDetailToPuzzleWork(selectedPublicWorkDetail);
|
||||
if (!work) {
|
||||
setPublicWorkDetailError('当前拼图作品信息不完整,暂时无法进入玩法。');
|
||||
return;
|
||||
}
|
||||
setPublicWorkDetailError(null);
|
||||
void startPuzzleRunFromProfile(work.profileId, 'work-detail', work, true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2106,7 +2384,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
(entry) => entry.profileId !== nextEntry.profileId,
|
||||
),
|
||||
]);
|
||||
detailNavigation.openSavedCustomWorldEditor(nextEntry);
|
||||
void detailNavigation.openSavedCustomWorldEditor(nextEntry);
|
||||
})
|
||||
.catch((error) => {
|
||||
setPublicWorkDetailError(
|
||||
@@ -2272,7 +2550,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
setSearchedPublicUser(user);
|
||||
} catch (error) {
|
||||
setPublicSearchError(
|
||||
resolveRpgCreationErrorMessage(error, '未找到对应的叙世号或作品号。'),
|
||||
resolveRpgCreationErrorMessage(error, '未找到对应的陶泥号或作品号。'),
|
||||
);
|
||||
} finally {
|
||||
setIsSearchingPublicCode(false);
|
||||
@@ -2539,9 +2817,6 @@ export function PlatformEntryFlowShellImpl({
|
||||
handleDeletePublishedWork(item);
|
||||
}}
|
||||
deletingWorkId={deletingCreationWorkId}
|
||||
onExperienceRpg={(item) => {
|
||||
handleExperienceRpgWork(item);
|
||||
}}
|
||||
rpgLibraryEntries={platformBootstrap.savedCustomWorldEntries}
|
||||
bigFishItems={isBigFishCreationVisible ? bigFishWorks : []}
|
||||
onOpenBigFishDetail={
|
||||
@@ -2553,15 +2828,6 @@ export function PlatformEntryFlowShellImpl({
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onExperienceBigFish={
|
||||
isBigFishCreationVisible
|
||||
? (item) => {
|
||||
runProtectedAction(() => {
|
||||
void startBigFishRunFromWork(item, 'platform');
|
||||
});
|
||||
}
|
||||
: null
|
||||
}
|
||||
onDeleteBigFish={
|
||||
isBigFishCreationVisible
|
||||
? (item) => {
|
||||
@@ -2575,11 +2841,6 @@ export function PlatformEntryFlowShellImpl({
|
||||
void openPuzzleDraft(item);
|
||||
});
|
||||
}}
|
||||
onExperiencePuzzle={(profileId) => {
|
||||
runProtectedAction(() => {
|
||||
void startPuzzleRunFromProfile(profileId, 'platform');
|
||||
});
|
||||
}}
|
||||
onDeletePuzzle={(item) => {
|
||||
handleDeletePuzzleWork(item);
|
||||
}}
|
||||
@@ -2648,7 +2909,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
}}
|
||||
onOpenLibraryDetail={(entry) => {
|
||||
runProtectedAction(() => {
|
||||
detailNavigation.openLibraryDetail(entry);
|
||||
void detailNavigation.openLibraryDetail(entry);
|
||||
});
|
||||
}}
|
||||
onDeleteLibraryEntry={(entry) => {
|
||||
@@ -2691,10 +2952,12 @@ export function PlatformEntryFlowShellImpl({
|
||||
>
|
||||
<PlatformWorkDetailView
|
||||
entry={selectedPublicWorkDetail}
|
||||
authorAvatarUrl={selectedPublicWorkAuthor?.avatarUrl ?? null}
|
||||
isBusy={isPublicWorkDetailBusy || isPuzzleBusy || isBigFishBusy}
|
||||
error={publicWorkDetailError}
|
||||
onBack={() => {
|
||||
setPublicWorkDetailError(null);
|
||||
clearSelectedPublicWorkAuthor();
|
||||
setSelectionStage('platform');
|
||||
}}
|
||||
onStart={startSelectedPublicWork}
|
||||
@@ -2720,10 +2983,12 @@ export function PlatformEntryFlowShellImpl({
|
||||
) : selectedDetailEntry.visibility !== 'draft' ? (
|
||||
<PlatformWorkDetailView
|
||||
entry={mapRpgGalleryCardToPublicWorkDetail(selectedDetailEntry)}
|
||||
authorAvatarUrl={selectedPublicWorkAuthor?.avatarUrl ?? null}
|
||||
isBusy={detailNavigation.isMutatingDetail}
|
||||
error={detailNavigation.detailError}
|
||||
onBack={() => {
|
||||
detailNavigation.setDetailError(null);
|
||||
clearSelectedPublicWorkAuthor();
|
||||
entryNavigation.backToPlatformHome();
|
||||
}}
|
||||
onStart={handleStartSelectedWorld}
|
||||
@@ -2747,7 +3012,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
detailNavigation.isSelectedWorldOwned
|
||||
? () => {
|
||||
runProtectedAction(() => {
|
||||
detailNavigation.openSavedCustomWorldEditor(
|
||||
void detailNavigation.openSavedCustomWorldEditor(
|
||||
selectedDetailEntry,
|
||||
);
|
||||
});
|
||||
@@ -2988,9 +3253,6 @@ export function PlatformEntryFlowShellImpl({
|
||||
>
|
||||
<PuzzleAgentWorkspace
|
||||
session={puzzleSession}
|
||||
activeOperation={puzzleOperation}
|
||||
streamingReplyText={streamingPuzzleReplyText}
|
||||
isStreamingReply={isStreamingPuzzleReply}
|
||||
isBusy={isPuzzleBusy || isStreamingPuzzleReply}
|
||||
error={puzzleError}
|
||||
onBack={leavePuzzleFlow}
|
||||
@@ -3000,6 +3262,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
onExecuteAction={(payload) => {
|
||||
void executePuzzleAction(payload);
|
||||
}}
|
||||
initialFormPayload={puzzleFormDraftPayload}
|
||||
onCreateFromForm={(payload) => {
|
||||
void createPuzzleDraftFromForm(payload);
|
||||
}}
|
||||
/>
|
||||
</Suspense>
|
||||
</motion.div>
|
||||
@@ -3033,7 +3299,11 @@ export function PlatformEntryFlowShellImpl({
|
||||
setSelectionStage('puzzle-agent-workspace');
|
||||
}}
|
||||
onRetry={() => {
|
||||
void executePuzzleAction({ action: 'compile_puzzle_draft' });
|
||||
void executePuzzleAction(
|
||||
buildPuzzleCompileActionFromFormPayload(
|
||||
puzzleFormDraftPayload,
|
||||
),
|
||||
);
|
||||
}}
|
||||
onInterrupt={undefined}
|
||||
backLabel="返回创作中心"
|
||||
@@ -3117,6 +3387,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
void startPuzzleRunFromProfile(
|
||||
selectedPuzzleDetail.profileId,
|
||||
'puzzle-gallery-detail',
|
||||
selectedPuzzleDetail,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
@@ -3155,6 +3426,9 @@ export function PlatformEntryFlowShellImpl({
|
||||
onAdvanceNextLevel={() => {
|
||||
void advancePuzzleLevel();
|
||||
}}
|
||||
onPauseChange={setPuzzleRuntimePaused}
|
||||
onUseProp={usePuzzleProp}
|
||||
onTimeExpired={syncPuzzleRuntimeTimeout}
|
||||
/>
|
||||
</Suspense>
|
||||
{isPuzzleNextLevelGenerating ? (
|
||||
@@ -3474,7 +3748,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
{searchedPublicUser.displayName}
|
||||
</div>
|
||||
<div className="mt-2 text-sm text-[var(--platform-text-soft)]">
|
||||
叙世号 {searchedPublicUser.publicUserCode}
|
||||
陶泥号 {searchedPublicUser.publicUserCode}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/* @vitest-environment jsdom */
|
||||
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { expect, test, vi } from 'vitest';
|
||||
|
||||
import type { PlatformPublicGalleryCard } from '../rpg-entry/rpgEntryWorldPresentation';
|
||||
import { PlatformWorkDetailView } from './PlatformWorkDetailView';
|
||||
|
||||
function createPuzzleEntry(): PlatformPublicGalleryCard {
|
||||
return {
|
||||
sourceType: 'puzzle',
|
||||
workId: 'work-1',
|
||||
profileId: 'profile-1',
|
||||
publicWorkCode: 'PZ-001',
|
||||
ownerUserId: 'user-1',
|
||||
authorDisplayName: '137****6613',
|
||||
worldName: '关键词:逍遥游拼图',
|
||||
subtitle: '拼图关卡',
|
||||
summaryText: '适合公开游玩的拼图作品。',
|
||||
coverImageSrc: null,
|
||||
themeTags: ['拼图'],
|
||||
playCount: 12,
|
||||
remixCount: 3,
|
||||
likeCount: 4,
|
||||
recentPlayCount7d: 0,
|
||||
visibility: 'published',
|
||||
publishedAt: '2026-04-20T10:00:00.000Z',
|
||||
updatedAt: '2026-04-25T12:00:00.000Z',
|
||||
};
|
||||
}
|
||||
|
||||
test('PlatformWorkDetailView renders compact stats and recent update time', () => {
|
||||
render(
|
||||
<PlatformWorkDetailView
|
||||
entry={createPuzzleEntry()}
|
||||
isBusy={false}
|
||||
error={null}
|
||||
onBack={vi.fn()}
|
||||
onStart={vi.fn()}
|
||||
onRemix={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText('改造')).toBeTruthy();
|
||||
expect(screen.getByText('游玩')).toBeTruthy();
|
||||
expect(screen.getByText('点赞')).toBeTruthy();
|
||||
expect(screen.getByText('最近更新')).toBeTruthy();
|
||||
expect(screen.queryByText('改造次数')).toBeNull();
|
||||
expect(screen.queryByText('游玩次数')).toBeNull();
|
||||
expect(screen.queryByText('上线日期')).toBeNull();
|
||||
expect(screen.getByText('2026-04-25')).toBeTruthy();
|
||||
expect(screen.getAllByText('次')).toHaveLength(2);
|
||||
expect(screen.getByText('赞')).toBeTruthy();
|
||||
});
|
||||
@@ -1,20 +1,32 @@
|
||||
import { ArrowLeft, Copy, GitFork, Play, Share2 } from 'lucide-react';
|
||||
import {
|
||||
ArrowLeft,
|
||||
Clock3,
|
||||
Copy,
|
||||
Gamepad2,
|
||||
GitFork,
|
||||
Heart,
|
||||
Play,
|
||||
Share2,
|
||||
} from 'lucide-react';
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
import { buildPublicWorkDetailUrl } from '../../routing/appPageRoutes';
|
||||
import { copyTextToClipboard } from '../../services/clipboard';
|
||||
import { ResolvedAssetImage } from '../ResolvedAssetImage';
|
||||
import {
|
||||
buildPlatformWorldTags,
|
||||
buildPlatformWorldDisplayTags,
|
||||
formatPlatformWorkDisplayName,
|
||||
formatPlatformWorkDisplayTags,
|
||||
formatPlatformWorldTime,
|
||||
type PlatformPublicGalleryCard,
|
||||
resolvePlatformPublicWorkCode,
|
||||
resolvePlatformWorldCoverImage,
|
||||
resolvePlatformWorldStats,
|
||||
type PlatformPublicGalleryCard,
|
||||
} from '../rpg-entry/rpgEntryWorldPresentation';
|
||||
|
||||
export interface PlatformWorkDetailViewProps {
|
||||
entry: PlatformPublicGalleryCard;
|
||||
authorAvatarUrl?: string | null;
|
||||
isBusy: boolean;
|
||||
error: string | null;
|
||||
onBack: () => void;
|
||||
@@ -40,8 +52,13 @@ function getSourceLabel(entry: PlatformPublicGalleryCard) {
|
||||
return 'RPG';
|
||||
}
|
||||
|
||||
function getAuthorAvatarLabel(authorDisplayName: string) {
|
||||
return Array.from(authorDisplayName.trim() || '作')[0] ?? '作';
|
||||
}
|
||||
|
||||
export function PlatformWorkDetailView({
|
||||
entry,
|
||||
authorAvatarUrl,
|
||||
isBusy,
|
||||
error,
|
||||
onBack,
|
||||
@@ -50,30 +67,51 @@ export function PlatformWorkDetailView({
|
||||
}: PlatformWorkDetailViewProps) {
|
||||
const coverImage = resolvePlatformWorldCoverImage(entry);
|
||||
const publicWorkCode = resolvePlatformPublicWorkCode(entry);
|
||||
const normalizedAuthorAvatarUrl = authorAvatarUrl?.trim() ?? '';
|
||||
const [copyState, setCopyState] = useState<'idle' | 'copied' | 'failed'>(
|
||||
'idle',
|
||||
);
|
||||
const [shareState, setShareState] = useState<'idle' | 'copied' | 'failed'>(
|
||||
'idle',
|
||||
);
|
||||
const displayName = formatPlatformWorkDisplayName(entry.worldName);
|
||||
const tags = useMemo(
|
||||
() =>
|
||||
[
|
||||
getSourceLabel(entry),
|
||||
...buildPlatformWorldTags(entry).map((tag) => tag.trim()),
|
||||
]
|
||||
.filter(Boolean)
|
||||
.slice(0, 4),
|
||||
formatPlatformWorkDisplayTags(
|
||||
[getSourceLabel(entry), ...buildPlatformWorldDisplayTags(entry, 3)],
|
||||
4,
|
||||
),
|
||||
[entry],
|
||||
);
|
||||
const stats = resolvePlatformWorldStats(entry);
|
||||
const statItems = [
|
||||
{ label: '改造次数', value: formatCompactCount(stats.remixCount) },
|
||||
{ label: '游玩次数', value: formatCompactCount(stats.playCount) },
|
||||
{ label: '点赞次数', value: formatCompactCount(stats.likeCount) },
|
||||
{
|
||||
label: '上线日期',
|
||||
value: formatPlatformWorldTime(stats.publishedAt),
|
||||
label: '改造',
|
||||
value: formatCompactCount(stats.remixCount),
|
||||
unit: '次',
|
||||
icon: GitFork,
|
||||
tone: 'remix',
|
||||
},
|
||||
{
|
||||
label: '游玩',
|
||||
value: formatCompactCount(stats.playCount),
|
||||
unit: '次',
|
||||
icon: Gamepad2,
|
||||
tone: 'play',
|
||||
},
|
||||
{
|
||||
label: '点赞',
|
||||
value: formatCompactCount(stats.likeCount),
|
||||
unit: '赞',
|
||||
icon: Heart,
|
||||
tone: 'like',
|
||||
},
|
||||
{
|
||||
label: '最近更新',
|
||||
value: formatPlatformWorldTime(stats.updatedAt ?? stats.publishedAt),
|
||||
icon: Clock3,
|
||||
tone: 'time',
|
||||
isTime: true,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -162,10 +200,26 @@ export function PlatformWorkDetailView({
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="platform-work-detail__name">
|
||||
{entry.worldName}
|
||||
{displayName}
|
||||
</div>
|
||||
<div className="platform-work-detail__author">
|
||||
{entry.authorDisplayName}
|
||||
<span className="platform-work-detail__author-avatar">
|
||||
{normalizedAuthorAvatarUrl ? (
|
||||
<ResolvedAssetImage
|
||||
src={normalizedAuthorAvatarUrl}
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
className="platform-work-detail__author-avatar-image"
|
||||
/>
|
||||
) : (
|
||||
<span className="platform-work-detail__author-avatar-label">
|
||||
{getAuthorAvatarLabel(entry.authorDisplayName)}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<span className="platform-work-detail__author-name">
|
||||
{entry.authorDisplayName}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@@ -175,18 +229,37 @@ export function PlatformWorkDetailView({
|
||||
disabled={isBusy}
|
||||
>
|
||||
<GitFork className="h-5 w-5" />
|
||||
Remix
|
||||
作品改造
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="platform-work-detail__stats">
|
||||
{statItems.map((item) => (
|
||||
<div key={item.label} className="platform-work-detail__stat">
|
||||
<div className="platform-work-detail__stat-label">
|
||||
{item.label}
|
||||
<div
|
||||
key={item.label}
|
||||
className={`platform-work-detail__stat platform-work-detail__stat--${item.tone}`}
|
||||
>
|
||||
<div className="platform-work-detail__stat-head">
|
||||
<span className="platform-work-detail__stat-icon">
|
||||
<item.icon className="h-3.5 w-3.5" />
|
||||
</span>
|
||||
<span className="platform-work-detail__stat-label">
|
||||
{item.label}
|
||||
</span>
|
||||
</div>
|
||||
<div className="platform-work-detail__stat-value">
|
||||
{item.value}
|
||||
<div
|
||||
className={`platform-work-detail__stat-value${
|
||||
item.isTime ? ' platform-work-detail__stat-value--time' : ''
|
||||
}`}
|
||||
>
|
||||
<span className="platform-work-detail__stat-number">
|
||||
{item.value}
|
||||
</span>
|
||||
{item.unit ? (
|
||||
<span className="platform-work-detail__stat-unit">
|
||||
{item.unit}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -36,8 +36,8 @@ export function isPlatformCreationTypeVisible(id: PlatformCreationTypeId) {
|
||||
export const PLATFORM_CREATION_TYPES: PlatformCreationTypeCard[] = [
|
||||
{
|
||||
id: 'rpg',
|
||||
title: '角色扮演 RPG',
|
||||
subtitle: 'Agent 共创',
|
||||
title: '角色扮演',
|
||||
subtitle: '剧情演绎,冒险成长',
|
||||
badge: '可创建',
|
||||
locked: false,
|
||||
},
|
||||
@@ -51,8 +51,8 @@ export const PLATFORM_CREATION_TYPES: PlatformCreationTypeCard[] = [
|
||||
},
|
||||
{
|
||||
id: 'puzzle',
|
||||
title: '拼图玩法',
|
||||
subtitle: '图像锚点共创',
|
||||
title: '拼图',
|
||||
subtitle: '创意礼物,生活分享',
|
||||
badge: '可创建',
|
||||
locked: false,
|
||||
},
|
||||
@@ -60,14 +60,14 @@ export const PLATFORM_CREATION_TYPES: PlatformCreationTypeCard[] = [
|
||||
id: 'airp',
|
||||
title: 'AIRP',
|
||||
subtitle: '敬请期待',
|
||||
badge: '锁定',
|
||||
badge: '敬请期待',
|
||||
locked: true,
|
||||
},
|
||||
{
|
||||
id: 'visual-novel',
|
||||
title: '视觉小说',
|
||||
subtitle: '敬请期待',
|
||||
badge: '锁定',
|
||||
badge: '敬请期待',
|
||||
locked: true,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -131,9 +131,9 @@ export function usePlatformCreationAgentFlowController<
|
||||
const [streamingReplyText, setStreamingReplyText] = useState('');
|
||||
const [isStreamingReply, setIsStreamingReply] = useState(false);
|
||||
|
||||
const openWorkspace = useCallback(async () => {
|
||||
const openWorkspace = useCallback(async (createPayload?: TCreatePayload) => {
|
||||
if (isBusy) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
setIsBusy(true);
|
||||
@@ -142,15 +142,20 @@ export function usePlatformCreationAgentFlowController<
|
||||
setIsStreamingReply(false);
|
||||
|
||||
try {
|
||||
const response = await options.client.createSession(options.createPayload);
|
||||
setSession(options.client.selectSession(response));
|
||||
const response = await options.client.createSession(
|
||||
createPayload ?? options.createPayload,
|
||||
);
|
||||
const nextSession = options.client.selectSession(response);
|
||||
setSession(nextSession);
|
||||
options.enterCreateTab();
|
||||
options.onSessionOpened?.();
|
||||
options.setSelectionStage(options.workspaceStage);
|
||||
return nextSession;
|
||||
} catch (caughtError) {
|
||||
setError(
|
||||
options.resolveErrorMessage(caughtError, options.errorMessages.open),
|
||||
);
|
||||
return null;
|
||||
} finally {
|
||||
setIsBusy(false);
|
||||
}
|
||||
@@ -235,8 +240,9 @@ export function usePlatformCreationAgentFlowController<
|
||||
);
|
||||
|
||||
const executeAction = useCallback(
|
||||
async (payload: TActionPayload) => {
|
||||
if (!session || isBusy) {
|
||||
async (payload: TActionPayload, sessionOverride?: TSession | null) => {
|
||||
const targetSession = sessionOverride ?? session;
|
||||
if (!targetSession || isBusy) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -244,15 +250,15 @@ export function usePlatformCreationAgentFlowController<
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
options.beforeExecuteAction?.({ payload, session });
|
||||
options.beforeExecuteAction?.({ payload, session: targetSession });
|
||||
const response = await options.client.executeAction(
|
||||
session.sessionId,
|
||||
targetSession.sessionId,
|
||||
payload,
|
||||
);
|
||||
await options.onActionComplete?.({
|
||||
payload,
|
||||
response,
|
||||
session,
|
||||
session: targetSession,
|
||||
setSession,
|
||||
});
|
||||
if (options.isCompileAction(payload)) {
|
||||
|
||||
Reference in New Issue
Block a user