diff --git a/newIDE/app/src/AiGeneration/AiRequestChat/ChatMessages.js b/newIDE/app/src/AiGeneration/AiRequestChat/ChatMessages.js index ef42c8b3e4f9..6c8bface6948 100644 --- a/newIDE/app/src/AiGeneration/AiRequestChat/ChatMessages.js +++ b/newIDE/app/src/AiGeneration/AiRequestChat/ChatMessages.js @@ -460,9 +460,10 @@ export const ChatMessages = React.memo(function ChatMessages({ size="small" style={{ backgroundColor: !isPaused - ? getBackgroundColor(theme, 'medium') + ? getBackgroundColor(theme, 'light') : undefined, borderRadius: 4, + padding: 0, }} selected={isPaused} > diff --git a/newIDE/app/src/AiGeneration/AiRequestChat/FunctionCallRow.js b/newIDE/app/src/AiGeneration/AiRequestChat/FunctionCallRow.js index daad8f708638..fc163077a683 100644 --- a/newIDE/app/src/AiGeneration/AiRequestChat/FunctionCallRow.js +++ b/newIDE/app/src/AiGeneration/AiRequestChat/FunctionCallRow.js @@ -20,8 +20,7 @@ import { type EditorFunctionWithoutProject, type EditorCallbacks, } from '../../EditorFunctions'; -import Link from '../../UI/Link'; -import { LineStackLayout, ResponsiveLineStackLayout } from '../../UI/Layout'; +import { LineStackLayout } from '../../UI/Layout'; import ChevronArrowRight from '../../UI/CustomSvgIcons/ChevronArrowRight'; import ChevronArrowBottom from '../../UI/CustomSvgIcons/ChevronArrowBottom'; import Paper from '../../UI/Paper'; @@ -153,50 +152,39 @@ export const FunctionCallRow = React.memo(function FunctionCallRow({ )} - - - - {text || Working...} - - {hasDetailsToShow && ( - - setShowDetails(!showDetails)} - > - - Details - {details ? ( - - ) : ( - - )} - - - + {text || Working...} + + + {hasDetailsToShow && ( +
setShowDetails(!showDetails)} + role="button" + tabIndex={0} + onKeyDown={e => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + setShowDetails(!showDetails); + } + }} + > + + {showDetails ? ( + + ) : ( + )} + + Details + - - +
+ )} {details && (
diff --git a/newIDE/app/src/AiGeneration/AiRequestChat/FunctionCallRow.module.css b/newIDE/app/src/AiGeneration/AiRequestChat/FunctionCallRow.module.css index a3bd899a3f91..eadcdca6df12 100644 --- a/newIDE/app/src/AiGeneration/AiRequestChat/FunctionCallRow.module.css +++ b/newIDE/app/src/AiGeneration/AiRequestChat/FunctionCallRow.module.css @@ -32,7 +32,28 @@ } } +.detailsButtonContainer { + padding-left: 20px; + user-select: none; + padding-bottom: 4px; + display: flex; + align-items: center; + gap: 4px; + outline: none; +} + +.detailsButtonContainer:hover { + opacity: 0.7; +} + +.detailsButtonContainer:focus-visible { + outline: 2px solid; + outline-offset: 2px; + border-radius: 4px; +} + .detailsPaperContainer { transform-origin: top left; animation: details-paper-appear 0.1s; + padding-left: 20px; } diff --git a/newIDE/app/src/AiGeneration/AiRequestChat/FunctionCallsGroup.module.css b/newIDE/app/src/AiGeneration/AiRequestChat/FunctionCallsGroup.module.css index 27ff27bbf10e..0d9d4c59965f 100644 --- a/newIDE/app/src/AiGeneration/AiRequestChat/FunctionCallsGroup.module.css +++ b/newIDE/app/src/AiGeneration/AiRequestChat/FunctionCallsGroup.module.css @@ -22,7 +22,6 @@ } .header { - cursor: pointer; user-select: none; padding: 4px 0; display: flex; diff --git a/newIDE/app/src/AiGeneration/AiRequestChat/index.js b/newIDE/app/src/AiGeneration/AiRequestChat/index.js index 12a24f8ee8f0..370fad49e900 100644 --- a/newIDE/app/src/AiGeneration/AiRequestChat/index.js +++ b/newIDE/app/src/AiGeneration/AiRequestChat/index.js @@ -65,7 +65,7 @@ const styles = { }, }; -type Props = { +type Props = {| project: ?gdProject, i18n: I18nType, aiRequest: AiRequest | null, @@ -77,10 +77,7 @@ type Props = { mode: 'chat' | 'agent', aiConfigurationPresetId: string, |}) => void, - onSendMessage: (options: {| - userMessage: string, - createdSceneNames?: Array, - |}) => Promise, + onSendUserMessage: (userMessage: string) => Promise, onSendFeedback: ( aiRequestId: string, messageIndex: number, @@ -115,7 +112,7 @@ type Props = { availableCredits: number, standAloneForm?: boolean, -}; +|}; export type AiRequestChatInterface = {| resetUserInput: (aiRequestId: string | null) => void, @@ -324,7 +321,7 @@ export const AiRequestChat = React.forwardRef( aiRequestMode, isSending, onStartNewAiRequest, - onSendMessage, + onSendUserMessage, onSendFeedback, onStartOrOpenChat, quota, @@ -345,9 +342,7 @@ export const AiRequestChat = React.forwardRef( ) => { const { aiRequestHistory: { handleNavigateHistory, resetNavigation }, - editorFunctionCallResultsStorage, } = React.useContext(AiRequestContext); - const { getEditorFunctionCallResults } = editorFunctionCallResultsStorage; const { setAiState } = React.useContext(PreferencesContext); const [ @@ -396,16 +391,24 @@ export const AiRequestChat = React.forwardRef( ); const requiredGameId = (aiRequest && aiRequest.gameId) || null; - // Auto-scroll to bottom when content changes, if user is at the bottom + const scrollToBottom = React.useCallback(() => { + if (scrollViewRef.current) { + scrollViewRef.current.scrollToBottom({ behavior: 'smooth' }); + } + }, []); + + // Auto-scroll to bottom when content changes, if user is at the bottom. React.useEffect( () => { - if (shouldAutoScroll && scrollViewRef.current) { - scrollViewRef.current.scrollToBottom({ - behavior: 'smooth', - }); - } + if (shouldAutoScroll) scrollToBottom(); }, - [aiRequest, editorFunctionCallResults, lastSendError, shouldAutoScroll] + [ + scrollToBottom, + aiRequest, + editorFunctionCallResults, + lastSendError, + shouldAutoScroll, + ] ); const onScroll = React.useCallback( @@ -487,11 +490,7 @@ export const AiRequestChat = React.forwardRef( const aiRequestIdToReset: string = aiRequestId || ''; onUserRequestTextChange('', aiRequestIdToReset); - if (scrollViewRef.current) { - scrollViewRef.current.scrollToBottom({ - behavior: 'smooth', - }); - } + scrollToBottom(); }, })); @@ -526,7 +525,7 @@ export const AiRequestChat = React.forwardRef( aiRequest && getFunctionCallsToProcess({ aiRequest: aiRequest, - editorFunctionCallResults: getEditorFunctionCallResults(aiRequest.id), + editorFunctionCallResults, }).length > 0; const hasWorkingFunctionCalls = editorFunctionCallResults && @@ -536,7 +535,7 @@ export const AiRequestChat = React.forwardRef( const hasUnfinishedResult = aiRequest && getFunctionCallOutputsFromEditorFunctionCallResults( - getEditorFunctionCallResults(aiRequest.id) + editorFunctionCallResults ).hasUnfinishedResult; const hasWorkToProcess = hasUnfinishedResult || @@ -622,6 +621,7 @@ export const AiRequestChat = React.forwardRef( )}
{ + scrollToBottom(); onStartNewAiRequest({ mode: aiRequestMode, userRequest: userRequestTextPerAiRequestId[''], @@ -643,6 +643,7 @@ export const AiRequestChat = React.forwardRef( }} onNavigateHistory={handleNavigateHistory} onSubmit={() => { + scrollToBottom(); onStartNewAiRequest({ mode: aiRequestMode, userRequest: userRequestTextPerAiRequestId[''], @@ -685,6 +686,7 @@ export const AiRequestChat = React.forwardRef( !hasReachedLimitAndCannotUseCredits) } onClick={() => { + scrollToBottom(); onStartNewAiRequest({ mode: aiRequestMode, userRequest: userRequestTextPerAiRequestId[''], @@ -865,9 +867,7 @@ export const AiRequestChat = React.forwardRef( { setAutoProcessFunctionCalls(true); - onSendMessage({ - userMessage: userRequestTextPerAiRequestId[aiRequestId] || '', - }); + onSendUserMessage(userRequestTextPerAiRequestId[aiRequestId] || ''); }} className={classNames({ // Move the form up when the soft keyboard is open: @@ -900,10 +900,9 @@ export const AiRequestChat = React.forwardRef( maxRows={6} onSubmit={() => { setAutoProcessFunctionCalls(true); - onSendMessage({ - userMessage: - userRequestTextPerAiRequestId[aiRequestId] || '', - }); + onSendUserMessage( + userRequestTextPerAiRequestId[aiRequestId] || '' + ); }} controls={ @@ -923,10 +922,9 @@ export const AiRequestChat = React.forwardRef( label={sendButtonLabel} onClick={() => { setAutoProcessFunctionCalls(true); - onSendMessage({ - userMessage: - userRequestTextPerAiRequestId[aiRequestId] || '', - }); + onSendUserMessage( + userRequestTextPerAiRequestId[aiRequestId] || '' + ); }} /> diff --git a/newIDE/app/src/AiGeneration/AiRequestContext.js b/newIDE/app/src/AiGeneration/AiRequestContext.js index 54648430b01a..a64ec6f52e2f 100644 --- a/newIDE/app/src/AiGeneration/AiRequestContext.js +++ b/newIDE/app/src/AiGeneration/AiRequestContext.js @@ -21,7 +21,7 @@ type EditorFunctionCallResultsStorage = {| addEditorFunctionCallResults: ( aiRequestId: string, editorFunctionCallResults: EditorFunctionCallResult[] - ) => void, + ) => EditorFunctionCallResult[], clearEditorFunctionCallResults: (aiRequestId: string) => void, |}; @@ -44,32 +44,31 @@ const useEditorFunctionCallResultsStorage = (): EditorFunctionCallResultsStorage aiRequestId: string, editorFunctionCallResults: EditorFunctionCallResult[] ) => { + const existingEditorFunctionCallResults = ( + editorFunctionCallResultsPerRequest[aiRequestId] || [] + ).filter(existingEditorFunctionCallResult => { + return !editorFunctionCallResults.some(editorFunctionCallResult => { + return ( + editorFunctionCallResult.call_id === + existingEditorFunctionCallResult.call_id + ); + }); + }); + + const newEditorFunctionCallResultsPerRequest = { + ...editorFunctionCallResultsPerRequest, + [aiRequestId]: [ + ...existingEditorFunctionCallResults, + ...editorFunctionCallResults, + ], + }; setEditorFunctionCallResultsPerRequest( - editorFunctionCallResultsPerRequest => { - const existingEditorFunctionCallResults = ( - editorFunctionCallResultsPerRequest[aiRequestId] || [] - ).filter(existingEditorFunctionCallResult => { - return !editorFunctionCallResults.some( - editorFunctionCallResult => { - return ( - editorFunctionCallResult.call_id === - existingEditorFunctionCallResult.call_id - ); - } - ); - }); - - return { - ...editorFunctionCallResultsPerRequest, - [aiRequestId]: [ - ...existingEditorFunctionCallResults, - ...editorFunctionCallResults, - ], - }; - } + newEditorFunctionCallResultsPerRequest ); + + return newEditorFunctionCallResultsPerRequest[aiRequestId]; }, - [] + [editorFunctionCallResultsPerRequest] ), clearEditorFunctionCallResults: React.useCallback((aiRequestId: string) => { setEditorFunctionCallResultsPerRequest( @@ -435,7 +434,7 @@ export const initialAiRequestContextState: AiRequestContextState = { }, editorFunctionCallResultsStorage: { getEditorFunctionCallResults: () => [], - addEditorFunctionCallResults: () => {}, + addEditorFunctionCallResults: () => [], clearEditorFunctionCallResults: () => {}, }, getAiSettings: () => null, diff --git a/newIDE/app/src/AiGeneration/AskAiEditorContainer.js b/newIDE/app/src/AiGeneration/AskAiEditorContainer.js index 9f57b0ba07cb..8d1f50b87428 100644 --- a/newIDE/app/src/AiGeneration/AskAiEditorContainer.js +++ b/newIDE/app/src/AiGeneration/AskAiEditorContainer.js @@ -34,6 +34,7 @@ import { getFunctionCallOutputsFromEditorFunctionCallResults, getFunctionCallsToProcess, } from './AiRequestUtils'; +import { type EditorFunctionCallResult } from '../EditorFunctions/EditorFunctionCallRunner'; import { useStableUpToDateRef } from '../Utils/UseStableUpToDateCallback'; import { type NewProjectSetup, @@ -212,13 +213,6 @@ export const AskAiEditor = React.memo( projectName: name, storageProvider: UrlStorageProvider, saveAsLocation: null, - // Don't open scenes, as it will be done manually by the AI, - // detecting which scenes were created, - // allowing to send those messages to the AI, triggering the next steps. - dontOpenAnySceneOrProjectManager: true, - // Don't reposition the Ask AI editor, - // as it will be done manually after the project is created too. - dontRepositionAskAiEditor: true, creationSource: 'ai-agent-request', }; @@ -239,13 +233,9 @@ export const AskAiEditor = React.memo( // The example was not found - still create an empty project. } - const { createdProject } = await onCreateEmptyProject({ - projectName: name, - storageProvider: UrlStorageProvider, - saveAsLocation: null, - dontOpenAnySceneOrProjectManager: true, - creationSource: 'ai-agent-request', - }); + const { createdProject } = await onCreateEmptyProject( + newProjectSetup + ); return { exampleSlug: null, createdProject }; }, @@ -532,31 +522,23 @@ export const AskAiEditor = React.memo( ] ); - const hasFunctionsCallsToProcess = React.useMemo( - () => - selectedAiRequest - ? getFunctionCallsToProcess({ - aiRequest: selectedAiRequest, - editorFunctionCallResults: getEditorFunctionCallResults( - selectedAiRequest.id - ), - }).length > 0 - : false, - [selectedAiRequest, getEditorFunctionCallResults] - ); - // Send the results of the function call outputs, if any, and the user message (if any). const onSendMessage = React.useCallback( async ({ userMessage, createdSceneNames, + createdProject, + editorFunctionCallResults, }: {| userMessage: string, createdSceneNames?: Array, + createdProject?: ?gdProject, + editorFunctionCallResults: Array, |}) => { if ( !profile || !selectedAiRequestId || + !selectedAiRequest || isSendingAiRequest(selectedAiRequestId) ) return; @@ -567,9 +549,15 @@ export const AskAiEditor = React.memo( hasUnfinishedResult, functionCallOutputs, } = getFunctionCallOutputsFromEditorFunctionCallResults( - getEditorFunctionCallResults(selectedAiRequestId) + editorFunctionCallResults ); + const hasFunctionsCallsToProcess = + getFunctionCallsToProcess({ + aiRequest: selectedAiRequest, + editorFunctionCallResults, + }).length > 0; + // If anything is not finished yet, stop there (we only send all // results at once, AI do not support partial results). if (hasUnfinishedResult) return; @@ -611,16 +599,21 @@ export const AskAiEditor = React.memo( try { setSendingAiRequest(selectedAiRequestId, true); + const upToDateProject = createdProject || project; + const simplifiedProjectBuilder = makeSimplifiedProjectBuilder(gd); - const simplifiedProjectJson = project + const simplifiedProjectJson = upToDateProject ? JSON.stringify( - simplifiedProjectBuilder.getSimplifiedProject(project, {}) + simplifiedProjectBuilder.getSimplifiedProject( + upToDateProject, + {} + ) ) : null; - const projectSpecificExtensionsSummaryJson = project + const projectSpecificExtensionsSummaryJson = upToDateProject ? JSON.stringify( simplifiedProjectBuilder.getProjectSpecificExtensionsSummary( - project + upToDateProject ) ) : null; @@ -646,7 +639,9 @@ export const AskAiEditor = React.memo( aiRequestId: selectedAiRequestId, functionCallOutputs, ...preparedAiUserContent, - gameId: project ? project.getProjectUuid() : undefined, + gameId: upToDateProject + ? upToDateProject.getProjectUuid() + : undefined, payWithCredits, userMessage, paused, @@ -697,18 +692,6 @@ export const AskAiEditor = React.memo( createdSceneNames && createdSceneNames.length > 0 ) { - // We handle moving the pane here - // and not in the Mainframe afterCreatingProject function, - // as it gives time to the AI to send the created scenes messages, - // triggering the status change from 'ready' to 'working'. - onOpenAskAi({ - paneIdentifier: 'right', - // By default, function calls are paused on mount, - // to avoid resuming processing old requests automatically. - // In this case, we want to continue processing right away, as - // we're in the middle of a flow. - continueProcessingFunctionCallsOnMount: true, - }); createdSceneNames.forEach(sceneName => { onOpenLayout(sceneName, { openEventsEditor: true, @@ -722,7 +705,6 @@ export const AskAiEditor = React.memo( profile, selectedAiRequestId, isSendingAiRequest, - getEditorFunctionCallResults, quota, aiRequestPriceInCredits, availableCredits, @@ -734,8 +716,6 @@ export const AskAiEditor = React.memo( setLastSendError, onRefreshLimits, project, - hasFunctionsCallsToProcess, - onOpenAskAi, onOpenLayout, subscription, openSubscriptionDialog, @@ -743,10 +723,18 @@ export const AskAiEditor = React.memo( ] ); const onSendEditorFunctionCallResults = React.useCallback( - async (options: null | {| createdSceneNames: Array |}) => { + async ( + editorFunctionCallResults: Array, + options: {| + createdProject?: ?gdProject, + createdSceneNames?: Array, + |} + ) => { await onSendMessage({ userMessage: '', - createdSceneNames: options ? options.createdSceneNames : [], + createdProject: options.createdProject, + createdSceneNames: options.createdSceneNames, + editorFunctionCallResults, }); }, [onSendMessage] @@ -904,7 +892,14 @@ export const AskAiEditor = React.memo( aiRequest={selectedAiRequest} aiRequestMode={selectedAiRequestMode} onStartNewAiRequest={startNewAiRequest} - onSendMessage={onSendMessage} + onSendUserMessage={(userMessage: string) => + onSendMessage({ + userMessage, + editorFunctionCallResults: selectedAiRequest + ? getEditorFunctionCallResults(selectedAiRequest.id) || [] + : [], + }) + } isSending={isSendingAiRequest(selectedAiRequestId)} lastSendError={getLastSendError(selectedAiRequestId)} quota={quota} diff --git a/newIDE/app/src/AiGeneration/AskAiStandAloneForm.js b/newIDE/app/src/AiGeneration/AskAiStandAloneForm.js index 7d56aea5682e..a98763bb95e6 100644 --- a/newIDE/app/src/AiGeneration/AskAiStandAloneForm.js +++ b/newIDE/app/src/AiGeneration/AskAiStandAloneForm.js @@ -21,6 +21,7 @@ import { getFunctionCallOutputsFromEditorFunctionCallResults, getFunctionCallsToProcess, } from './AiRequestUtils'; +import { type EditorFunctionCallResult } from '../EditorFunctions/EditorFunctionCallRunner'; import { useStableUpToDateRef } from '../Utils/UseStableUpToDateCallback'; import { type NewProjectSetup, @@ -40,7 +41,6 @@ import { useAiRequestState, useProcessFunctionCalls, type NewAiRequestOptions, - type OpenAskAiOptions, AI_AGENT_TOOLS_VERSION, AI_CHAT_TOOLS_VERSION, } from './Utils'; @@ -81,7 +81,6 @@ type Props = {| ) => void, onWillInstallExtension: (extensionNames: Array) => void, onExtensionInstalled: (extensionNames: Array) => void, - onOpenAskAi: (?OpenAskAiOptions) => void, onCloseAskAi: () => void, dismissableIdentifier?: string, |}; @@ -95,7 +94,6 @@ export const AskAiStandAloneForm = ({ onCreateProjectFromExample, onCreateEmptyProject, onOpenLayout, - onOpenAskAi, onCloseAskAi, dismissableIdentifier, onWillInstallExtension, @@ -113,16 +111,9 @@ export const AskAiStandAloneForm = ({ projectName: name, storageProvider: UrlStorageProvider, saveAsLocation: null, - // Don't open scenes, as it will be done manually by the AI, - // detecting which scenes were created, - // allowing to send those messages to the AI, triggering the next steps. - dontOpenAnySceneOrProjectManager: true, - // Don't reposition the Ask AI editor, - // as it will be done manually after the project is created too. - dontRepositionAskAiEditor: true, - // Don't close the New project setup dialog, - // as it will be done manually after the project is created too. - dontCloseNewProjectSetupDialog: true, + // As the request is coming from the Standalone Ask AI form, + // ensure the Ask AI editor is opened once the project is created. + forceOpenAskAiEditor: true, creationSource: 'ai-agent-request', }; @@ -379,32 +370,24 @@ export const AskAiStandAloneForm = ({ ] ); - const hasFunctionsCallsToProcess = React.useMemo( - () => - aiRequestForForm - ? getFunctionCallsToProcess({ - aiRequest: aiRequestForForm, - editorFunctionCallResults: getEditorFunctionCallResults( - aiRequestForForm.id - ), - }).length > 0 - : false, - [aiRequestForForm, getEditorFunctionCallResults] - ); - const isLoading = isSendingAiRequest(aiRequestIdForForm); // Send the results of the function call outputs only. // In a standalone form, the only user message is sent when starting the request. const onSendMessage = React.useCallback( async ({ - createdSceneNames, userMessage, + createdSceneNames, + createdProject, + editorFunctionCallResults, }: {| - createdSceneNames?: Array, userMessage: string, + createdSceneNames?: Array, + createdProject?: ?gdProject, + editorFunctionCallResults: Array, |}) => { - if (!profile || !aiRequestIdForForm || isLoading) return; + if (!profile || !aiRequestIdForForm || !aiRequestForForm || isLoading) + return; // Read the results from the editor that applied the function calls. // and transform them into the output that will be stored on the AI request. @@ -412,9 +395,15 @@ export const AskAiStandAloneForm = ({ hasUnfinishedResult, functionCallOutputs, } = getFunctionCallOutputsFromEditorFunctionCallResults( - getEditorFunctionCallResults(aiRequestIdForForm) + editorFunctionCallResults ); + const hasFunctionsCallsToProcess = + getFunctionCallsToProcess({ + aiRequest: aiRequestForForm, + editorFunctionCallResults, + }).length > 0; + // If anything is not finished yet, stop there (we only send all // results at once, AI do not support partial results). if (hasUnfinishedResult) return; @@ -428,16 +417,18 @@ export const AskAiStandAloneForm = ({ try { setSendingAiRequest(aiRequestIdForForm, true); + const upToDateProject = createdProject || project; + const simplifiedProjectBuilder = makeSimplifiedProjectBuilder(gd); - const simplifiedProjectJson = project + const simplifiedProjectJson = upToDateProject ? JSON.stringify( - simplifiedProjectBuilder.getSimplifiedProject(project, {}) + simplifiedProjectBuilder.getSimplifiedProject(upToDateProject, {}) ) : null; - const projectSpecificExtensionsSummaryJson = project + const projectSpecificExtensionsSummaryJson = upToDateProject ? JSON.stringify( simplifiedProjectBuilder.getProjectSpecificExtensionsSummary( - project + upToDateProject ) ) : null; @@ -463,7 +454,9 @@ export const AskAiStandAloneForm = ({ aiRequestId: aiRequestIdForForm, functionCallOutputs, ...preparedAiUserContent, - gameId: project ? project.getProjectUuid() : undefined, + gameId: upToDateProject + ? upToDateProject.getProjectUuid() + : undefined, payWithCredits: false, userMessage: '', // No user message when sending only function call outputs. paused, @@ -485,44 +478,34 @@ export const AskAiStandAloneForm = ({ aiRequestChatRefCurrent.resetUserInput(aiRequestIdForForm); } setAiRequestIdForForm(''); - // We handle moving the pane here - // and not in the Mainframe afterCreatingProject function, - // as it gives time to the AI to send the created scenes messages, - // triggering the status change from 'ready' to 'working'. - onOpenAskAi(); - if (createdSceneNames && createdSceneNames.length > 0) { - createdSceneNames.forEach(sceneName => { - onOpenLayout(sceneName, { - openEventsEditor: true, - openSceneEditor: true, - focusWhenOpened: 'scene', - }); - }); - } } }, [ profile, aiRequestIdForForm, isLoading, - getEditorFunctionCallResults, setSendingAiRequest, updateAiRequest, clearEditorFunctionCallResults, getAuthorizationHeader, setLastSendError, project, - hasFunctionsCallsToProcess, - onOpenAskAi, - onOpenLayout, aiRequestForForm, ] ); const onSendEditorFunctionCallResults = React.useCallback( - async (options: null | {| createdSceneNames: Array |}) => { + async ( + editorFunctionCallResults: Array, + options: {| + createdSceneNames?: Array, + createdProject?: ?gdProject, + |} + ) => { await onSendMessage({ - createdSceneNames: options ? options.createdSceneNames : [], userMessage: '', + createdSceneNames: options.createdSceneNames, + createdProject: options.createdProject, + editorFunctionCallResults, }); }, [onSendMessage] @@ -598,7 +581,14 @@ export const AskAiStandAloneForm = ({ aiRequest={aiRequestForForm} aiRequestMode={aiRequestModeForForm} onStartNewAiRequest={startNewAiRequest} - onSendMessage={onSendMessage} + onSendUserMessage={(userMessage: string) => + onSendMessage({ + userMessage, + editorFunctionCallResults: aiRequestForForm + ? getEditorFunctionCallResults(aiRequestForForm.id) || [] + : [], + }) + } isSending={isLoading} lastSendError={getLastSendError(aiRequestIdForForm)} quota={quota} diff --git a/newIDE/app/src/AiGeneration/Utils.js b/newIDE/app/src/AiGeneration/Utils.js index abb2b08259fc..65ac1165bede 100644 --- a/newIDE/app/src/AiGeneration/Utils.js +++ b/newIDE/app/src/AiGeneration/Utils.js @@ -23,7 +23,6 @@ import { getFunctionCallOutputsFromEditorFunctionCallResults, getFunctionCallsToProcess, } from './AiRequestUtils'; -import { useTriggerAtNextRender } from '../Utils/useTriggerAtNextRender'; import { useEnsureExtensionInstalled } from './UseEnsureExtensionInstalled'; import { useGenerateEvents } from './UseGenerateEvents'; import { useSearchAndInstallAsset } from './UseSearchAndInstallAsset'; @@ -33,6 +32,7 @@ import PreferencesContext from '../MainFrame/Preferences/PreferencesContext'; import { useInterval } from '../Utils/UseInterval'; import { makeSimplifiedProjectBuilder } from '../EditorFunctions/SimplifiedProject/SimplifiedProject'; import { prepareAiUserContent } from './PrepareAiUserContent'; +import { extractGDevelopApiErrorStatusAndCode } from '../Utils/GDevelopServices/Errors'; const gd: libGDevelop = global.gd; @@ -62,13 +62,17 @@ export const useProcessFunctionCalls = ({ editorCallbacks: EditorCallbacks, selectedAiRequest: ?AiRequest, onSendEditorFunctionCallResults: ( - options: null | {| createdSceneNames: Array |} + editorFunctionCallResults: Array, + options: {| + createdSceneNames?: Array, + createdProject?: ?gdProject, + |} ) => Promise, getEditorFunctionCallResults: string => Array | null, addEditorFunctionCallResults: ( string, Array - ) => void, + ) => Array, onSceneEventsModifiedOutsideEditor: ( changes: SceneEventsOutsideEditorChanges ) => void, @@ -97,10 +101,6 @@ export const useProcessFunctionCalls = ({ }); const { generateEvents } = useGenerateEvents({ project }); - const triggerSendEditorFunctionCallResults = useTriggerAtNextRender( - onSendEditorFunctionCallResults - ); - const [ aiRequestAutoProcessState, setAiRequestAutoprocessState, @@ -142,9 +142,14 @@ export const useProcessFunctionCalls = ({ })) ); - const { results, createdSceneNames } = await processEditorFunctionCalls({ + const { + results, + createdSceneNames, + createdProject, + } = await processEditorFunctionCalls({ project, editorCallbacks, + i18n, functionCalls: functionCalls.map(functionCall => ({ name: functionCall.name, arguments: functionCall.arguments, @@ -167,15 +172,20 @@ export const useProcessFunctionCalls = ({ searchAndInstallAsset, }); - addEditorFunctionCallResults(selectedAiRequest.id, results); + const newResults = addEditorFunctionCallResults( + selectedAiRequest.id, + results + ); // We may have processed everything, so try to send the results // to the backend. - triggerSendEditorFunctionCallResults({ + await onSendEditorFunctionCallResults(newResults, { createdSceneNames, + createdProject, }); }, [ + i18n, selectedAiRequest, isReadyToProcessFunctionCalls, addEditorFunctionCallResults, @@ -189,8 +199,8 @@ export const useProcessFunctionCalls = ({ onWillInstallExtension, onExtensionInstalled, searchAndInstallAsset, - triggerSendEditorFunctionCallResults, generateEvents, + onSendEditorFunctionCallResults, ] ); @@ -371,6 +381,18 @@ export const useAiRequestState = ({ project }: {| project: ?gdProject |}) => { updateAiRequest(selectedAiRequest.id, aiRequestWithSuggestions); } catch (error) { + const extractedStatusAndCode = extractGDevelopApiErrorStatusAndCode( + error + ); + if ( + extractedStatusAndCode && + extractedStatusAndCode.status === 400 && + extractedStatusAndCode.code === 'ai-request/request-still-working' + ) { + // Don't log anything. + return; + } + console.error('Error getting AI request suggestions:', error); // Do not block updating the request if suggestions fetching fails. } diff --git a/newIDE/app/src/EditorFunctions/EditorFunctionCallRunner.js b/newIDE/app/src/EditorFunctions/EditorFunctionCallRunner.js index a2b8f6af2953..6f24a5ef436c 100644 --- a/newIDE/app/src/EditorFunctions/EditorFunctionCallRunner.js +++ b/newIDE/app/src/EditorFunctions/EditorFunctionCallRunner.js @@ -1,4 +1,5 @@ // @flow +import { type I18n as I18nType } from '@lingui/core'; import { type EventsGenerationResult } from '.'; import { editorFunctions, @@ -37,6 +38,7 @@ export type EditorFunctionCallResult = export type ProcessEditorFunctionCallsOptions = {| project: ?gdProject, functionCalls: Array, + i18n: I18nType, editorCallbacks: EditorCallbacks, ignore: boolean, generateEvents: ( @@ -67,6 +69,7 @@ export type ProcessEditorFunctionCallsOptions = {| export const processEditorFunctionCalls = async ({ functionCalls, project, + i18n, editorCallbacks, generateEvents, onSceneEventsModifiedOutsideEditor, @@ -81,9 +84,11 @@ export const processEditorFunctionCalls = async ({ }: ProcessEditorFunctionCallsOptions): Promise<{| results: Array, createdSceneNames: Array, + createdProject: ?gdProject, |}> => { const results: Array = []; const createdSceneNames: Array = []; + let createdProject: ?gdProject = null; for (const functionCall of functionCalls) { const call_id = functionCall.call_id; @@ -166,6 +171,7 @@ export const processEditorFunctionCalls = async ({ const argumentsWithoutProject = { args, + i18n, editorCallbacks, generateEvents, onSceneEventsModifiedOutsideEditor, @@ -214,6 +220,9 @@ export const processEditorFunctionCalls = async ({ if (meta && meta.newSceneNames) { createdSceneNames.push(...meta.newSceneNames); } + if (meta && meta.createdProject) { + createdProject = meta.createdProject; + } } catch (error) { results.push({ status: 'finished', @@ -224,5 +233,5 @@ export const processEditorFunctionCalls = async ({ } } - return { results, createdSceneNames }; + return { results, createdSceneNames, createdProject }; }; diff --git a/newIDE/app/src/EditorFunctions/index.js b/newIDE/app/src/EditorFunctions/index.js index 4d99822cdc2f..3183e1ab4748 100644 --- a/newIDE/app/src/EditorFunctions/index.js +++ b/newIDE/app/src/EditorFunctions/index.js @@ -16,7 +16,8 @@ import { applyEventsChanges, } from './ApplyEventsChanges'; import { isBehaviorDefaultCapability } from '../BehaviorsEditor/EnumerateBehaviorsMetadata'; -import { Trans } from '@lingui/macro'; +import { t, Trans } from '@lingui/macro'; +import { type I18n as I18nType } from '@lingui/core'; import Link from '../UI/Link'; import { hexNumberToRGBArray, @@ -66,6 +67,7 @@ export type EditorFunctionGenericOutput = {| success: boolean, meta?: { newSceneNames?: Array, + createdProject?: gdProject, }, message?: string, eventsAsText?: string, @@ -178,6 +180,7 @@ type RenderForEditorOptions = {| type LaunchFunctionOptionsWithoutProject = {| args: any, editorCallbacks: EditorCallbacks, + i18n: I18nType, generateEvents: ( options: EventsGenerationOptions ) => Promise, @@ -4435,7 +4438,7 @@ const initializeProject: EditorFunctionWithoutProject = { ), }; }, - launchFunction: async ({ args, editorCallbacks }) => { + launchFunction: async ({ args, editorCallbacks, i18n }) => { const project_name = extractRequiredString(args, 'project_name'); const template_slug = extractRequiredString(args, 'template_slug'); const also_read_existing_events = SafeExtractor.extractBooleanProperty( @@ -4462,6 +4465,14 @@ const initializeProject: EditorFunctionWithoutProject = { throw new Error('Unexpected null project after creation.'); } + if (!exampleSlug) { + // Created an empty project, let's add a default scene. + const layoutName = i18n._(t`Untitled scene`); + createdProject.insertNewLayout(layoutName, 0); + const layout = createdProject.getLayout(layoutName); + addDefaultLightToAllLayers(layout); + } + const output: EditorFunctionGenericOutput = { success: true, }; @@ -4487,17 +4498,17 @@ const initializeProject: EditorFunctionWithoutProject = { output.initializedFromTemplateSlug = exampleSlug; } else { if (template_slug) { - output.message = `Initialized project but this is an empty project.`; + output.message = `Initialized project but this is an empty project, with 1 scene.`; output.initializedProject = true; } else { - output.message = `Initialized empty project.`; + output.message = `Initialized empty project with 1 scene.`; output.initializedProject = true; } } output.meta = { - newSceneNames: mapFor(0, createdProject.getLayoutsCount(), i => - createdProject.getLayoutAt(i).getName() - ), + // Do not include the scene names, as the project will automatically + // open the scenes. + createdProject, }; return output; diff --git a/newIDE/app/src/MainFrame/EditorContainers/HomePage/CreateSection/index.js b/newIDE/app/src/MainFrame/EditorContainers/HomePage/CreateSection/index.js index 158a61093cf0..1d3009b3383f 100644 --- a/newIDE/app/src/MainFrame/EditorContainers/HomePage/CreateSection/index.js +++ b/newIDE/app/src/MainFrame/EditorContainers/HomePage/CreateSection/index.js @@ -55,7 +55,6 @@ import { deleteCloudProject } from '../../../../Utils/GDevelopServices/Project'; import { getDefaultRegisterGameProperties } from '../../../../Utils/UseGameAndBuildsManager'; import { type CreateProjectResult } from '../../../../Utils/UseCreateProject'; import { AskAiStandAloneForm } from '../../../../AiGeneration/AskAiStandAloneForm'; -import { type OpenAskAiOptions } from '../../../../AiGeneration/Utils'; import { AiRequestContext } from '../../../../AiGeneration/AiRequestContext'; const getExampleItemsColumns = ( @@ -100,7 +99,6 @@ type Props = {| ) => void, onWillInstallExtension: (extensionNames: Array) => void, onExtensionInstalled: (extensionNames: Array) => void, - onOpenAskAi: (?OpenAskAiOptions) => void, onCloseAskAi: () => void, closeProject: () => Promise, canOpen: boolean, @@ -139,7 +137,6 @@ const CreateSection = ({ onOpenLayout, onWillInstallExtension, onExtensionInstalled, - onOpenAskAi, onCloseAskAi, closeProject, canOpen, @@ -451,20 +448,6 @@ const CreateSection = ({ ] ); - const onOpenAskAiForStandAloneForm = React.useCallback( - () => { - onOpenAskAi({ - paneIdentifier: 'right', - // By default, function calls are paused on mount, - // to avoid resuming processing old requests automatically. - // In this case, we want to continue processing right away, as - // we're in the middle of a flow. - continueProcessingFunctionCallsOnMount: true, - }); - }, - [onOpenAskAi] - ); - const { aiRequestStorage } = React.useContext(AiRequestContext); const { isSendingAiRequest } = aiRequestStorage; const isLoadingAiRequest = isSendingAiRequest(''); @@ -524,7 +507,6 @@ const CreateSection = ({ onOpenLayout={onOpenLayout} onWillInstallExtension={onWillInstallExtension} onExtensionInstalled={onExtensionInstalled} - onOpenAskAi={onOpenAskAiForStandAloneForm} onCloseAskAi={onCloseAskAi} dismissableIdentifier="home-page-create-section" /> diff --git a/newIDE/app/src/MainFrame/EditorContainers/HomePage/index.js b/newIDE/app/src/MainFrame/EditorContainers/HomePage/index.js index 3485c9205890..36d8326991f3 100644 --- a/newIDE/app/src/MainFrame/EditorContainers/HomePage/index.js +++ b/newIDE/app/src/MainFrame/EditorContainers/HomePage/index.js @@ -608,7 +608,6 @@ export const HomePage = React.memo( onOpenLayout={onOpenLayout} onWillInstallExtension={onWillInstallExtension} onExtensionInstalled={onExtensionInstalled} - onOpenAskAi={onOpenAskAi} onCloseAskAi={onCloseAskAi} closeProject={closeProject} games={games} diff --git a/newIDE/app/src/MainFrame/UseNewProjectDialog.js b/newIDE/app/src/MainFrame/UseNewProjectDialog.js index b20c5701fe82..155467d06f7f 100644 --- a/newIDE/app/src/MainFrame/UseNewProjectDialog.js +++ b/newIDE/app/src/MainFrame/UseNewProjectDialog.js @@ -17,7 +17,6 @@ import { type FileMetadata, type StorageProvider } from '../ProjectsStorage'; import { type ResourceManagementProps } from '../ResourcesList/ResourceSource'; import RouterContext from './RouterContext'; import { type CreateProjectResult } from '../Utils/UseCreateProject'; -import { type OpenAskAiOptions } from '../AiGeneration/Utils'; type Props = {| project: ?gdProject, @@ -34,7 +33,6 @@ type Props = {| privateGameTemplateListingData: PrivateGameTemplateListingData, newProjectSetup: NewProjectSetup ) => Promise, - openAskAi: (?OpenAskAiOptions) => void, closeAskAi: () => void, storageProviders: Array, storageProvider: ?StorageProvider, @@ -64,7 +62,6 @@ const useNewProjectDialog = ({ createEmptyProject, createProjectFromExample, createProjectFromPrivateGameTemplate, - openAskAi, closeAskAi, storageProviders, storageProvider, @@ -204,21 +201,6 @@ const useNewProjectDialog = ({ [onSelectExampleShortHeader, removeRouteArguments] ); - const onOpenAskAi = React.useCallback( - () => { - closeNewProjectDialog(); - openAskAi({ - paneIdentifier: 'right', - // By default, function calls are paused on mount, - // to avoid resuming processing old requests automatically. - // In this case, we want to continue processing right away, as - // we're in the middle of a flow. - continueProcessingFunctionCallsOnMount: true, - }); - }, - [closeNewProjectDialog, openAskAi] - ); - const renderNewProjectDialog = () => { return ( <> @@ -235,7 +217,6 @@ const useNewProjectDialog = ({ onCreateProjectFromPrivateGameTemplate={ createProjectFromPrivateGameTemplate } - onOpenAskAi={onOpenAskAi} onCloseAskAi={closeAskAi} storageProviders={storageProviders} storageProvider={storageProvider} diff --git a/newIDE/app/src/MainFrame/index.js b/newIDE/app/src/MainFrame/index.js index 5db65945d132..28faded4a61a 100644 --- a/newIDE/app/src/MainFrame/index.js +++ b/newIDE/app/src/MainFrame/index.js @@ -1308,9 +1308,7 @@ const MainFrame = (props: Props) => { currentFileMetadata: newFileMetadata, })); } - if (!options.dontCloseNewProjectSetupDialog) { - setNewProjectSetupDialogOpen(false); - } + setNewProjectSetupDialogOpen(false); if (options.openQuickCustomizationDialog) { setQuickCustomizationDialogOpenedFromGameId(oldProjectId); } else { @@ -1319,25 +1317,22 @@ const MainFrame = (props: Props) => { openLeaderboardReplacerDialogIfNeeded(project, oldProjectId); configureMultiplayerLobbiesIfNeeded(project, oldProjectId); } - if (!options.dontOpenAnySceneOrProjectManager) { - options.openAllScenes || options.openQuickCustomizationDialog - ? openAllScenes({ - currentProject: project, - editorTabs, - }) - : openSceneOrProjectManager({ - currentProject: project, - editorTabs: editorTabs, - }); - } - if (!options.dontRepositionAskAiEditor) { - // If Ask AI editor was opened, reposition it. - const openedAskAIEditor = getOpenedAskAiEditor(state.editorTabs); - if (openedAskAIEditor) { - openAskAi({ - paneIdentifier: 'right', + options.openAllScenes || options.openQuickCustomizationDialog + ? openAllScenes({ + currentProject: project, + editorTabs, + }) + : openSceneOrProjectManager({ + currentProject: project, + editorTabs: editorTabs, }); - } + // If Ask AI editor was opened, reposition it. + const openedAskAIEditor = getOpenedAskAiEditor(state.editorTabs); + if (openedAskAIEditor || options.forceOpenAskAiEditor) { + openAskAi({ + paneIdentifier: 'right', + continueProcessingFunctionCallsOnMount: true, + }); } setIsProjectClosedSoAvoidReloadingExtensions(false); }, @@ -4583,7 +4578,6 @@ const MainFrame = (props: Props) => { createEmptyProject, createProjectFromExample, createProjectFromPrivateGameTemplate, - openAskAi, closeAskAi, storageProviders: props.storageProviders, storageProvider: getStorageProvider(), diff --git a/newIDE/app/src/ProjectCreation/NewProjectSetupDialog.js b/newIDE/app/src/ProjectCreation/NewProjectSetupDialog.js index f95697faf5d3..03862a7aa8b1 100644 --- a/newIDE/app/src/ProjectCreation/NewProjectSetupDialog.js +++ b/newIDE/app/src/ProjectCreation/NewProjectSetupDialog.js @@ -97,9 +97,7 @@ export type NewProjectSetup = {| orientation?: 'landscape' | 'portrait' | 'default', optimizeForPixelArt?: boolean, openQuickCustomizationDialog?: boolean, - dontOpenAnySceneOrProjectManager?: boolean, - dontRepositionAskAiEditor?: boolean, - dontCloseNewProjectSetupDialog?: boolean, + forceOpenAskAiEditor?: boolean, creationSource: NewProjectCreationSource, |}; @@ -126,7 +124,6 @@ type Props = {| newProjectSetup: NewProjectSetup, i18n: I18nType ) => Promise, - onOpenAskAi: () => void, onCloseAskAi: () => void, selectedExampleShortHeader: ?ExampleShortHeader, onSelectExampleShortHeader: (exampleShortHeader: ?ExampleShortHeader) => void, @@ -163,7 +160,6 @@ const NewProjectSetupDialog = ({ onCreateEmptyProject, onCreateFromExample, onCreateProjectFromPrivateGameTemplate, - onOpenAskAi, onCloseAskAi, selectedExampleShortHeader, onSelectExampleShortHeader, @@ -677,7 +673,6 @@ const NewProjectSetupDialog = ({ onOpenLayout={onOpenLayout} onWillInstallExtension={onWillInstallExtension} onExtensionInstalled={onExtensionInstalled} - onOpenAskAi={onOpenAskAi} onCloseAskAi={onCloseAskAi} /> Promise, onError: () => void, @@ -276,9 +274,7 @@ const useCreateProject = ({ options: { openAllScenes: !!options && options.openAllScenes, openQuickCustomizationDialog: !!newProjectSetup.openQuickCustomizationDialog, - dontOpenAnySceneOrProjectManager: !!newProjectSetup.dontOpenAnySceneOrProjectManager, - dontRepositionAskAiEditor: !!newProjectSetup.dontRepositionAskAiEditor, - dontCloseNewProjectSetupDialog: !!newProjectSetup.dontCloseNewProjectSetupDialog, + forceOpenAskAiEditor: !!newProjectSetup.forceOpenAskAiEditor, }, }); diff --git a/newIDE/app/src/Utils/useTriggerAtNextRender.js b/newIDE/app/src/Utils/useTriggerAtNextRender.js deleted file mode 100644 index 8eb577c5267d..000000000000 --- a/newIDE/app/src/Utils/useTriggerAtNextRender.js +++ /dev/null @@ -1,23 +0,0 @@ -// @flow -import * as React from 'react'; -import { useStableUpToDateCallback } from './UseStableUpToDateCallback'; - -export const useTriggerAtNextRender = ( - callback: (args: Args | null) => Promise -): ((args: Args) => void) => { - const stableUpToDateCallback = useStableUpToDateCallback(callback); - const [trigger, updateTrigger] = React.useState(0); - const [args, setArgs] = React.useState(null); - const triggerAtNextRender = React.useCallback((args: Args) => { - setArgs(args); - updateTrigger(trigger => trigger + 1); - }, []); - - React.useEffect( - () => { - stableUpToDateCallback(args); - }, - [trigger, args, stableUpToDateCallback] - ); - return triggerAtNextRender; -}; diff --git a/newIDE/app/src/stories/componentStories/ProjectCreation/NewProjectSetupDialog.stories.js b/newIDE/app/src/stories/componentStories/ProjectCreation/NewProjectSetupDialog.stories.js index 451636071b3b..7d20707f2d33 100644 --- a/newIDE/app/src/stories/componentStories/ProjectCreation/NewProjectSetupDialog.stories.js +++ b/newIDE/app/src/stories/componentStories/ProjectCreation/NewProjectSetupDialog.stories.js @@ -39,7 +39,6 @@ export const OpenAndNotAuthenticated = () => { onClose={() => action('click on close')()} onCreateEmptyProject={() => action('create empty')()} onCreateFromExample={() => action('create from example')()} - onOpenAskAi={() => action('open ask AI')()} onCloseAskAi={() => action('close ask AI')()} onOpenLayout={() => action('open layout')()} onWillInstallExtension={action('extension will be installed')} @@ -75,7 +74,6 @@ export const OpenAndAuthenticated = () => { resourceManagementProps={fakeResourceManagementProps} onCreateEmptyProject={() => action('create empty')()} onCreateFromExample={() => action('create from example')()} - onOpenAskAi={() => action('open ask AI')()} onCloseAskAi={() => action('close ask AI')()} onOpenLayout={() => action('open layout')()} onWillInstallExtension={action('extension will be installed')} @@ -112,7 +110,6 @@ export const Opening = () => { resourceManagementProps={fakeResourceManagementProps} onCreateEmptyProject={() => action('create empty')()} onCreateFromExample={() => action('create from example')()} - onOpenAskAi={() => action('open ask AI')()} onCloseAskAi={() => action('close ask AI')()} onOpenLayout={() => action('open layout')()} onWillInstallExtension={action('extension will be installed')} @@ -150,7 +147,6 @@ export const LimitsReached = () => { resourceManagementProps={fakeResourceManagementProps} onCreateEmptyProject={() => action('create empty')()} onCreateFromExample={() => action('create from example')()} - onOpenAskAi={() => action('open ask AI')()} onCloseAskAi={() => action('close ask AI')()} onOpenLayout={() => action('open layout')()} onWillInstallExtension={action('extension will be installed')} @@ -186,7 +182,6 @@ export const FromExample = () => { resourceManagementProps={fakeResourceManagementProps} onCreateEmptyProject={() => action('create empty')()} onCreateFromExample={() => action('create from example')()} - onOpenAskAi={() => action('open ask AI')()} onCloseAskAi={() => action('close ask AI')()} onOpenLayout={() => action('open layout')()} onWillInstallExtension={action('extension will be installed')} @@ -222,7 +217,6 @@ export const FromExampleWithoutGoingBack = () => { resourceManagementProps={fakeResourceManagementProps} onCreateEmptyProject={() => action('create empty')()} onCreateFromExample={() => action('create from example')()} - onOpenAskAi={() => action('open ask AI')()} onCloseAskAi={() => action('close ask AI')()} onOpenLayout={() => action('open layout')()} onWillInstallExtension={action('extension will be installed')} @@ -259,7 +253,6 @@ export const FromPrivateGameTemplate = () => { resourceManagementProps={fakeResourceManagementProps} onCreateEmptyProject={() => action('create empty')()} onCreateFromExample={() => action('create from example')()} - onOpenAskAi={() => action('open ask AI')()} onCloseAskAi={() => action('close ask AI')()} onOpenLayout={() => action('open layout')()} onWillInstallExtension={action('extension will be installed')} @@ -297,7 +290,6 @@ export const FromPrivateGameTemplateWithoutGoingBack = () => { resourceManagementProps={fakeResourceManagementProps} onCreateEmptyProject={() => action('create empty')()} onCreateFromExample={() => action('create from example')()} - onOpenAskAi={() => action('open ask AI')()} onCloseAskAi={() => action('close ask AI')()} onOpenLayout={() => action('open layout')()} onWillInstallExtension={action('extension will be installed')}