import { useCallback, useState } from 'react'; import type { TextStreamOptions } from '../../services/aiTypes'; import type { SelectionStage } from './platformEntryTypes'; type CreationAgentMessageLike = { clientMessageId: string; text: string; }; type CreationAgentSessionLike = { sessionId: string; draft?: unknown; messages: Array<{ id: string; role: string; kind?: string; text: string; createdAt?: string; }>; updatedAt?: string; }; type CreationAgentClientAdapter< TSession extends CreationAgentSessionLike, TCreatePayload, TCreateResponse, TMessagePayload extends CreationAgentMessageLike, TActionPayload, TActionResponse, > = { createSession: (payload: TCreatePayload) => Promise; getSession: (sessionId: string) => Promise; streamMessage: ( sessionId: string, payload: TMessagePayload, options?: TextStreamOptions, ) => Promise; executeAction: ( sessionId: string, payload: TActionPayload, ) => Promise; selectSession: (response: TCreateResponse) => TSession; }; type PlatformCreationAgentFlowControllerOptions< TSession extends CreationAgentSessionLike, TCreatePayload, TCreateResponse, TMessagePayload extends CreationAgentMessageLike, TActionPayload, TActionResponse, > = { client: CreationAgentClientAdapter< TSession, TCreatePayload, TCreateResponse, TMessagePayload, TActionPayload, TActionResponse >; createPayload: TCreatePayload; workspaceStage: SelectionStage; resultStage: SelectionStage; platformStage: SelectionStage; isCompileAction: (payload: TActionPayload) => boolean; resolveErrorMessage: (error: unknown, fallback: string) => string; errorMessages: { open: string; restoreMissingSession: string; restore: string; submit: string; execute: string; }; enterCreateTab: () => void; setSelectionStage: (stage: SelectionStage) => void; onSessionOpened?: () => void; onActionComplete?: (params: { payload: TActionPayload; response: TActionResponse; session: TSession; setSession: (session: TSession) => void; }) => Promise | void; beforeExecuteAction?: (params: { payload: TActionPayload; session: TSession; }) => void; onActionError?: (params: { payload: TActionPayload; error: unknown; errorMessage: string; }) => void; }; function buildOptimisticMessage( payload: TMessagePayload, ) { return { id: payload.clientMessageId, role: 'user', kind: 'chat', text: payload.text.trim(), createdAt: new Date().toISOString(), }; } /** * 轻量作品 Agent 创作流程的通用前端控制器。 * 这里只处理跨玩法一致的会话、流式消息、忙碌态与草稿恢复,玩法结果页和运行态动作留给外层。 */ export function usePlatformCreationAgentFlowController< TSession extends CreationAgentSessionLike, TCreatePayload, TCreateResponse, TMessagePayload extends CreationAgentMessageLike, TActionPayload, TActionResponse, >( options: PlatformCreationAgentFlowControllerOptions< TSession, TCreatePayload, TCreateResponse, TMessagePayload, TActionPayload, TActionResponse >, ) { const [session, setSession] = useState(null); const [error, setError] = useState(null); const [isBusy, setIsBusy] = useState(false); const [streamingReplyText, setStreamingReplyText] = useState(''); const [isStreamingReply, setIsStreamingReply] = useState(false); const openWorkspace = useCallback(async () => { if (isBusy) { return; } setIsBusy(true); setError(null); setStreamingReplyText(''); setIsStreamingReply(false); try { const response = await options.client.createSession(options.createPayload); setSession(options.client.selectSession(response)); options.enterCreateTab(); options.onSessionOpened?.(); options.setSelectionStage(options.workspaceStage); } catch (caughtError) { setError( options.resolveErrorMessage(caughtError, options.errorMessages.open), ); } finally { setIsBusy(false); } }, [isBusy, options]); const restoreDraft = useCallback( async (sessionId: string | null | undefined) => { const normalizedSessionId = sessionId?.trim(); if (!normalizedSessionId) { setError(options.errorMessages.restoreMissingSession); return null; } setIsBusy(true); setError(null); setStreamingReplyText(''); setIsStreamingReply(false); try { const response = await options.client.getSession(normalizedSessionId); const nextSession = options.client.selectSession(response); setSession(nextSession); options.enterCreateTab(); options.setSelectionStage( nextSession.draft ? options.resultStage : options.workspaceStage, ); return nextSession; } catch (caughtError) { setError( options.resolveErrorMessage(caughtError, options.errorMessages.restore), ); options.enterCreateTab(); options.setSelectionStage(options.platformStage); return null; } finally { setIsBusy(false); } }, [options], ); const submitMessage = useCallback( async (payload: TMessagePayload) => { if (!session || isStreamingReply) { return; } const optimisticMessage = buildOptimisticMessage(payload); setError(null); setStreamingReplyText(''); setIsStreamingReply(true); setSession((current) => current ? { ...current, messages: [...current.messages, optimisticMessage], updatedAt: optimisticMessage.createdAt, } : current, ); try { const nextSession = await options.client.streamMessage( session.sessionId, payload, { onUpdate: setStreamingReplyText, }, ); setSession(nextSession); setStreamingReplyText(''); } catch (caughtError) { setError( options.resolveErrorMessage(caughtError, options.errorMessages.submit), ); } finally { setIsStreamingReply(false); } }, [isStreamingReply, options, session], ); const executeAction = useCallback( async (payload: TActionPayload) => { if (!session || isBusy) { return; } setIsBusy(true); setError(null); try { options.beforeExecuteAction?.({ payload, session }); const response = await options.client.executeAction( session.sessionId, payload, ); await options.onActionComplete?.({ payload, response, session, setSession, }); if (options.isCompileAction(payload)) { options.setSelectionStage(options.resultStage); } } catch (caughtError) { const errorMessage = options.resolveErrorMessage( caughtError, options.errorMessages.execute, ); setError(errorMessage); options.onActionError?.({ payload, error: caughtError, errorMessage, }); } finally { setIsBusy(false); } }, [isBusy, options, session], ); const leaveFlow = useCallback(() => { setError(null); setStreamingReplyText(''); setIsStreamingReply(false); options.enterCreateTab(); options.setSelectionStage(options.platformStage); }, [options]); const resetTransientState = useCallback(() => { setError(null); setStreamingReplyText(''); setIsStreamingReply(false); }, []); return { session, setSession, error, setError, isBusy, setIsBusy, streamingReplyText, setStreamingReplyText, isStreamingReply, setIsStreamingReply, openWorkspace, restoreDraft, submitMessage, executeAction, leaveFlow, resetTransientState, }; }