mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Compare commits
1 Commits
v5.5.242
...
cursor/ini
Author | SHA1 | Date | |
---|---|---|---|
![]() |
568e64a2dc |
@@ -50,11 +50,11 @@ import {
|
||||
sendAiRequestMessageSent,
|
||||
sendAiRequestStarted,
|
||||
} from '../Utils/Analytics/EventSender';
|
||||
import { useCreateAiProjectDialog } from './UseCreateAiProjectDialog';
|
||||
import { type ExampleShortHeader } from '../Utils/GDevelopServices/Example';
|
||||
import { prepareAiUserContent } from './PrepareAiUserContent';
|
||||
import { AiRequestContext } from './AiRequestContext';
|
||||
import { getAiConfigurationPresetsWithAvailability } from './AiConfiguration';
|
||||
import useAlertDialog from '../UI/Alert/useAlertDialog';
|
||||
|
||||
const gd: libGDevelop = global.gd;
|
||||
|
||||
@@ -238,11 +238,11 @@ export const useSelectedAiRequest = ({
|
||||
}: {|
|
||||
initialAiRequestId: string | null,
|
||||
|}) => {
|
||||
const { profile, getAuthorizationHeader } = React.useContext(
|
||||
AuthenticatedUserContext
|
||||
);
|
||||
const { aiRequestStorage } = React.useContext(AiRequestContext);
|
||||
const { aiRequests, updateAiRequest } = aiRequestStorage;
|
||||
const { profile, getAuthorizationHeader } = React.useContext(
|
||||
AuthenticatedUserContext
|
||||
);
|
||||
const { aiRequestStorage } = React.useContext(AiRequestContext);
|
||||
const { aiRequests, updateAiRequest } = aiRequestStorage;
|
||||
|
||||
const [selectedAiRequestId, setSelectedAiRequestId] = React.useState<
|
||||
string | null
|
||||
@@ -332,6 +332,7 @@ type Props = {|
|
||||
resourceManagementProps: ResourceManagementProps,
|
||||
fileMetadata: ?FileMetadata,
|
||||
storageProvider: ?StorageProvider,
|
||||
getStorageProvider: () => StorageProvider,
|
||||
setToolbar: (?React.Node) => void,
|
||||
i18n: I18nType,
|
||||
onCreateEmptyProject: (newProjectSetup: NewProjectSetup) => Promise<void>,
|
||||
@@ -409,6 +410,7 @@ export const AskAiEditor = React.memo<Props>(
|
||||
resourceManagementProps,
|
||||
fileMetadata,
|
||||
storageProvider,
|
||||
getStorageProvider,
|
||||
i18n,
|
||||
onCreateEmptyProject,
|
||||
onCreateProjectFromExample,
|
||||
@@ -425,8 +427,34 @@ export const AskAiEditor = React.memo<Props>(
|
||||
const editorCallbacks: EditorCallbacks = React.useMemo(
|
||||
() => ({
|
||||
onOpenLayout,
|
||||
onCreateProjectFromExample: async (exampleName: string, exampleSlug: string) => {
|
||||
// Find the example by slug
|
||||
const { listAllExamples } = await import('../Utils/GDevelopServices/Example');
|
||||
const allExamples = await listAllExamples();
|
||||
const exampleShortHeader = allExamples.exampleShortHeaders.find(
|
||||
example => example.slug === exampleSlug
|
||||
);
|
||||
|
||||
if (!exampleShortHeader) {
|
||||
throw new Error(`Example with slug "${exampleSlug}" not found`);
|
||||
}
|
||||
|
||||
const newProjectSetup: NewProjectSetup = {
|
||||
projectName: exampleName,
|
||||
storageProvider: project ? getStorageProvider() : null,
|
||||
saveAsLocation: null,
|
||||
dontOpenAnySceneOrProjectManager: false,
|
||||
};
|
||||
|
||||
await onCreateProjectFromExample(
|
||||
exampleShortHeader,
|
||||
newProjectSetup,
|
||||
i18n,
|
||||
false // isQuickCustomization
|
||||
);
|
||||
},
|
||||
}),
|
||||
[onOpenLayout]
|
||||
[onOpenLayout, onCreateProjectFromExample, project, i18n, getStorageProvider]
|
||||
);
|
||||
|
||||
const {
|
||||
@@ -502,10 +530,6 @@ export const AskAiEditor = React.memo<Props>(
|
||||
setLastSendError,
|
||||
} = aiRequestStorage;
|
||||
|
||||
const {
|
||||
createAiProject,
|
||||
renderCreateAiProjectDialog,
|
||||
} = useCreateAiProjectDialog();
|
||||
|
||||
const updateToolbar = React.useCallback(
|
||||
() => {
|
||||
@@ -543,6 +567,7 @@ export const AskAiEditor = React.memo<Props>(
|
||||
const { openCreditsPackageDialog } = React.useContext(
|
||||
CreditsPackageStoreContext
|
||||
);
|
||||
const { showAlert } = useAlertDialog();
|
||||
|
||||
const {
|
||||
profile,
|
||||
@@ -597,26 +622,12 @@ export const AskAiEditor = React.memo<Props>(
|
||||
} = newAiRequestOptions;
|
||||
startNewAiRequest(null);
|
||||
|
||||
// If no project is opened, create a new empty one if the request is for
|
||||
// the AI agent.
|
||||
// If no project is opened for agent mode, show an error
|
||||
if (mode === 'agent' && !project) {
|
||||
try {
|
||||
console.info(
|
||||
'No project opened, opening the dialog to create a new project.'
|
||||
);
|
||||
const result = await createAiProject();
|
||||
if (result === 'canceled') {
|
||||
return;
|
||||
}
|
||||
console.info('New project created - starting AI request.');
|
||||
startNewAiRequest({
|
||||
mode,
|
||||
userRequest,
|
||||
aiConfigurationPresetId,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error creating a new empty project:', error);
|
||||
}
|
||||
showAlert({
|
||||
title: t`No project opened`,
|
||||
message: t`Please open or create a project before using the AI agent. The AI can help you create a project by using the "initialize_project" command.`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -734,9 +745,9 @@ export const AskAiEditor = React.memo<Props>(
|
||||
setSendingAiRequest,
|
||||
upToDateSelectedAiRequestId,
|
||||
updateAiRequest,
|
||||
createAiProject,
|
||||
newAiRequestOptions,
|
||||
onOpenAskAi,
|
||||
showAlert,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -1017,10 +1028,6 @@ export const AskAiEditor = React.memo<Props>(
|
||||
/>
|
||||
</div>
|
||||
</Paper>
|
||||
{renderCreateAiProjectDialog({
|
||||
onCreateEmptyProject,
|
||||
onCreateProjectFromExample,
|
||||
})}
|
||||
<AskAiHistory
|
||||
open={isHistoryOpen}
|
||||
onClose={onCloseHistory}
|
||||
@@ -1053,6 +1060,7 @@ export const renderAskAiEditorContainer = (
|
||||
resourceManagementProps={props.resourceManagementProps}
|
||||
fileMetadata={props.fileMetadata}
|
||||
storageProvider={props.storageProvider}
|
||||
getStorageProvider={props.getStorageProvider}
|
||||
setToolbar={props.setToolbar}
|
||||
isActive={props.isActive}
|
||||
onCreateEmptyProject={props.onCreateEmptyProject}
|
||||
|
@@ -146,13 +146,35 @@ export const processEditorFunctionCalls = async ({
|
||||
searchAndInstallAsset,
|
||||
}
|
||||
);
|
||||
const { success, ...output } = result;
|
||||
results.push({
|
||||
status: 'finished',
|
||||
call_id,
|
||||
success,
|
||||
output,
|
||||
});
|
||||
|
||||
// Handle special case for initialize_project
|
||||
if (result._requiresProjectInitialization && editorCallbacks.onCreateProjectFromExample) {
|
||||
const { name, exampleSlug } = result._projectInitializationData;
|
||||
try {
|
||||
await editorCallbacks.onCreateProjectFromExample(name, exampleSlug);
|
||||
results.push({
|
||||
status: 'finished',
|
||||
call_id,
|
||||
success: true,
|
||||
output: { message: `Project "${name}" initialized from example "${exampleSlug}".` },
|
||||
});
|
||||
} catch (error) {
|
||||
results.push({
|
||||
status: 'finished',
|
||||
call_id,
|
||||
success: false,
|
||||
output: { message: `Failed to initialize project: ${error.message}` },
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const { success, ...output } = result;
|
||||
results.push({
|
||||
status: 'finished',
|
||||
call_id,
|
||||
success,
|
||||
output,
|
||||
});
|
||||
}
|
||||
|
||||
if (success && args) {
|
||||
if (name === 'create_scene' && typeof args.scene_name === 'string') {
|
||||
|
@@ -123,6 +123,10 @@ export type EditorCallbacks = {|
|
||||
| 'none',
|
||||
|}
|
||||
) => void,
|
||||
onCreateProjectFromExample?: (
|
||||
exampleName: string,
|
||||
exampleSlug: string
|
||||
) => Promise<void>,
|
||||
|};
|
||||
|
||||
export type SceneEventsOutsideEditorChanges = {|
|
||||
@@ -3461,6 +3465,45 @@ const addOrEditVariable: EditorFunction = {
|
||||
},
|
||||
};
|
||||
|
||||
const initializeProject: EditorFunction = {
|
||||
renderForEditor: ({ project, args, editorCallbacks, shouldShowDetails }) => {
|
||||
const name = SafeExtractor.extractStringProperty(args, 'name');
|
||||
const exampleSlug = SafeExtractor.extractStringProperty(args, 'example_slug');
|
||||
|
||||
if (!name && !exampleSlug) {
|
||||
return {
|
||||
text: <Trans>Initialize project (missing required arguments)</Trans>,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
text: (
|
||||
<Trans>
|
||||
Initialize project{name && ` "${name}"`}
|
||||
{exampleSlug && ` from example "${exampleSlug}"`}
|
||||
</Trans>
|
||||
),
|
||||
};
|
||||
},
|
||||
launchFunction: async ({ project, args }) => {
|
||||
const name = extractRequiredString(args, 'name');
|
||||
const exampleSlug = extractRequiredString(args, 'example_slug');
|
||||
|
||||
// This function requires special handling in the AskAiEditorContainer
|
||||
// because project initialization needs to be done through the MainFrame callbacks
|
||||
return {
|
||||
success: true,
|
||||
message: `Project initialization requested: name="${name}", example_slug="${exampleSlug}"`,
|
||||
// Signal that this needs special handling
|
||||
_requiresProjectInitialization: true,
|
||||
_projectInitializationData: {
|
||||
name,
|
||||
exampleSlug,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export const editorFunctions: { [string]: EditorFunction } = {
|
||||
create_object: createObject,
|
||||
inspect_object_properties: inspectObjectProperties,
|
||||
@@ -3479,4 +3522,5 @@ export const editorFunctions: { [string]: EditorFunction } = {
|
||||
inspect_scene_properties_layers_effects: inspectScenePropertiesLayersEffects,
|
||||
change_scene_properties_layers_effects: changeScenePropertiesLayersEffects,
|
||||
add_or_edit_variable: addOrEditVariable,
|
||||
initialize_project: initializeProject,
|
||||
};
|
||||
|
@@ -47,6 +47,7 @@ export type RenderEditorContainerProps = {|
|
||||
project: ?gdProject,
|
||||
fileMetadata: ?FileMetadata,
|
||||
storageProvider: StorageProvider,
|
||||
getStorageProvider: () => StorageProvider,
|
||||
setToolbar: (?React.Node) => void,
|
||||
setGamesPlatformFrameShown: ({| shown: boolean, isMobile: boolean |}) => void,
|
||||
|
||||
|
@@ -578,6 +578,7 @@ const EditorTabsPane = React.forwardRef<Props, {||}>((props, ref) => {
|
||||
project: currentProject,
|
||||
fileMetadata: currentFileMetadata,
|
||||
storageProvider: getStorageProvider(),
|
||||
getStorageProvider,
|
||||
ref: editorRef => (editorTab.editorRef = editorRef),
|
||||
setToolbar: editorToolbar =>
|
||||
setEditorToolbar(editorToolbar, isCurrentTab),
|
||||
|
55
test_initialize_project.md
Normal file
55
test_initialize_project.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Test Initialize Project Command
|
||||
|
||||
## Summary of Changes
|
||||
|
||||
### 1. Added `initialize_project` function to EditorFunctions/index.js
|
||||
- Takes `name` and `example_slug` as arguments
|
||||
- Returns a special response that signals project initialization is needed
|
||||
|
||||
### 2. Updated EditorCallbacks type
|
||||
- Added optional `onCreateProjectFromExample` callback
|
||||
|
||||
### 3. Modified EditorFunctionCallRunner
|
||||
- Handles the special case when `_requiresProjectInitialization` is true
|
||||
- Calls the `onCreateProjectFromExample` callback with the project name and example slug
|
||||
|
||||
### 4. Updated AskAiEditorContainer
|
||||
- Removed automatic project creation when no project is open
|
||||
- Added implementation of `onCreateProjectFromExample` callback that:
|
||||
- Fetches all examples to find the one matching the slug
|
||||
- Creates appropriate project setup
|
||||
- Calls the main `onCreateProjectFromExample` from props
|
||||
- Shows an alert if agent mode is used without a project
|
||||
|
||||
### 5. Updated BaseEditor and EditorTabsPane
|
||||
- Added `getStorageProvider` to props type
|
||||
- Passed through to editor containers
|
||||
|
||||
## How it Works
|
||||
|
||||
1. AI sends `initialize_project` command with project name and example slug
|
||||
2. The command is processed by EditorFunctionCallRunner
|
||||
3. Special handling detects the initialization request
|
||||
4. Calls the callback to create project from example
|
||||
5. The callback in AskAiEditorContainer:
|
||||
- Finds the example by slug
|
||||
- Sets up project configuration
|
||||
- Triggers actual project creation through MainFrame
|
||||
|
||||
## Testing Instructions
|
||||
|
||||
To test this implementation:
|
||||
|
||||
1. Open GDevelop without any project
|
||||
2. Use AI chat/agent mode
|
||||
3. AI should be able to use the `initialize_project` command
|
||||
4. Command should create a new project based on the specified example
|
||||
|
||||
## Example AI Command
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "initialize_project",
|
||||
"arguments": "{\"name\": \"My Platformer Game\", \"example_slug\": \"geometry-monster\"}"
|
||||
}
|
||||
```
|
Reference in New Issue
Block a user