1
This commit is contained in:
@@ -0,0 +1,294 @@
|
||||
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<TCreateResponse>;
|
||||
getSession: (sessionId: string) => Promise<TCreateResponse>;
|
||||
streamMessage: (
|
||||
sessionId: string,
|
||||
payload: TMessagePayload,
|
||||
options?: TextStreamOptions,
|
||||
) => Promise<TSession>;
|
||||
executeAction: (
|
||||
sessionId: string,
|
||||
payload: TActionPayload,
|
||||
) => Promise<TActionResponse>;
|
||||
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> | void;
|
||||
};
|
||||
|
||||
function buildOptimisticMessage<TMessagePayload extends CreationAgentMessageLike>(
|
||||
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<TSession | null>(null);
|
||||
const [error, setError] = useState<string | null>(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 {
|
||||
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) {
|
||||
setError(
|
||||
options.resolveErrorMessage(caughtError, options.errorMessages.execute),
|
||||
);
|
||||
} 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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user