mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Compare commits
6 Commits
v5.5.233
...
cursor/enh
Author | SHA1 | Date | |
---|---|---|---|
![]() |
366bfca4cd | ||
![]() |
d58cd9c340 | ||
![]() |
08b05c13b6 | ||
![]() |
eb55c85f4e | ||
![]() |
8a243440db | ||
![]() |
b3e4e6b89c |
130
AI_PROJECT_RESTORE_IMPLEMENTATION.md
Normal file
130
AI_PROJECT_RESTORE_IMPLEMENTATION.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# AI Project Restore Implementation
|
||||
|
||||
## Overview
|
||||
This implementation provides automatic project saving and restoration capabilities for AI agent requests, leveraging GDevelop's existing cloud save features and version history system.
|
||||
|
||||
## Key Features
|
||||
|
||||
### 1. Automatic Project Save Before AI Agent Requests
|
||||
- When a user starts a new AI agent request, the project is automatically saved to create a cloud version
|
||||
- This only works for cloud projects (projects saved to GDevelop Cloud)
|
||||
- The current version ID is captured and stored with the AI request
|
||||
|
||||
### 2. Version-Based Restoration
|
||||
- Uses GDevelop's existing cloud version system instead of custom serialization
|
||||
- Leverages the `onOpenCloudProjectOnSpecificVersion` function from MainFrame
|
||||
- Provides seamless restoration to the exact state before AI agent modifications
|
||||
|
||||
### 3. Smart UI Integration
|
||||
- Restore button appears at the top of AI agent chats that have a stored initial version
|
||||
- Only visible for cloud projects with stored version information
|
||||
- Button shows loading state during restoration process
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Backend API Changes
|
||||
- Extended `AiRequest` type to include `initialProjectVersionId?: string | null`
|
||||
- This field stores the cloud project version ID captured before starting the agent
|
||||
|
||||
### Frontend Changes
|
||||
|
||||
#### 1. AskAiEditorContainer.js
|
||||
- Added `onOpenCloudProjectOnSpecificVersion` prop to enable version restoration
|
||||
- Modified AI request creation to save project and capture version ID for cloud projects
|
||||
- Added `onRestoreInitialProject` callback that uses cloud version system
|
||||
- Only attempts version capture for cloud projects (`storageProvider.internalName === 'Cloud'`)
|
||||
|
||||
#### 2. AiRequestChat/index.js
|
||||
- Added restore button UI at the top of agent chats
|
||||
- Added `isCloudProject` prop to control button visibility
|
||||
- Added loading state for restore operation
|
||||
- Proper error handling during restoration
|
||||
|
||||
#### 3. EditorFunctions/index.js
|
||||
- Extended `EditorCallbacks` type to include optional `onSave` function
|
||||
- Enables AI components to trigger project saves when needed
|
||||
|
||||
#### 4. MainFrame/index.js
|
||||
- Added `onOpenCloudProjectOnSpecificVersion` to editor props
|
||||
- This connects the AI editor to the existing version restoration system
|
||||
|
||||
## User Experience
|
||||
|
||||
### Starting an Agent Request
|
||||
1. User opens AI agent and submits a request
|
||||
2. System automatically saves the current project (creates a new version)
|
||||
3. Version ID is stored with the AI request for later restoration
|
||||
4. AI agent proceeds with modifications
|
||||
|
||||
### Restoring to Initial State
|
||||
1. User sees "Click here to restore the project as it was at the beginning" button
|
||||
2. Clicking the button triggers cloud version restoration
|
||||
3. Project is restored to the exact state before AI agent started
|
||||
4. All changes made by the AI agent are discarded
|
||||
|
||||
## Technical Advantages
|
||||
|
||||
### 1. Leverages Existing Infrastructure
|
||||
- Uses GDevelop's mature cloud save and version system
|
||||
- No custom serialization/deserialization code needed
|
||||
- Inherits all cloud storage reliability and error handling
|
||||
|
||||
### 2. Scalable and Reliable
|
||||
- Cloud versions are professionally managed and backed up
|
||||
- No local storage limitations or browser storage issues
|
||||
- Consistent across different devices and sessions
|
||||
|
||||
### 3. Version History Integration
|
||||
- Restored versions appear in the project's version history
|
||||
- Users can access version history features for AI-generated content
|
||||
- Seamless integration with existing version management workflow
|
||||
|
||||
## Limitations and Considerations
|
||||
|
||||
### 1. Cloud Projects Only
|
||||
- Feature only works for projects saved to GDevelop Cloud
|
||||
- Local projects cannot use this restoration feature
|
||||
- Clear messaging is provided when feature is unavailable
|
||||
|
||||
### 2. Version Storage
|
||||
- Relies on cloud project version creation during save
|
||||
- Version IDs are stored locally in the AI request metadata
|
||||
- If local storage is cleared, version reference may be lost
|
||||
|
||||
### 3. Network Dependency
|
||||
- Restoration requires internet connection for cloud access
|
||||
- Standard cloud storage network limitations apply
|
||||
|
||||
## Error Handling
|
||||
|
||||
### 1. Save Failures
|
||||
- If initial save fails, AI request continues without version storage
|
||||
- User is informed that restoration won't be available
|
||||
- Graceful degradation ensures AI functionality remains available
|
||||
|
||||
### 2. Restoration Failures
|
||||
- Comprehensive error logging for debugging
|
||||
- UI provides feedback during restoration process
|
||||
- Button disabled during restoration to prevent conflicts
|
||||
|
||||
### 3. Non-Cloud Projects
|
||||
- Restore functionality hidden for non-cloud projects
|
||||
- Clear console warnings when attempting unsupported operations
|
||||
- No impact on existing AI functionality for local projects
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
### 1. Backend Integration
|
||||
- Could extend backend API to store `initialProjectVersionId` server-side
|
||||
- Would enable restoration across sessions and devices
|
||||
- Currently relies on local client-side storage
|
||||
|
||||
### 2. Local Project Support
|
||||
- Could implement local project snapshots using browser storage
|
||||
- Would require custom serialization for non-cloud projects
|
||||
- Currently prioritizes cloud projects for reliability
|
||||
|
||||
### 3. Enhanced UI
|
||||
- Could add confirmation dialogs for restoration
|
||||
- Might include preview of changes before restoration
|
||||
- Could integrate with version history UI components
|
@@ -72,7 +72,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
|
||||
extension
|
||||
.AddExpression("normalize",
|
||||
_("Normalize a value between `min` and `max` to a value between 0 and 1."),
|
||||
_("Normalize a value between `min` and `max` to a value "
|
||||
"between 0 and 1."),
|
||||
_("Remap a value between 0 and 1."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
@@ -124,7 +125,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
extension
|
||||
.AddExpression("mod",
|
||||
_("Modulo"),
|
||||
_("x mod y"),
|
||||
_("Compute \"x mod y\". GDevelop does NOT support the \% "
|
||||
"operator. Use this mod(x, y) function instead."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddParameter("expression", _("x (as in x mod y)"))
|
||||
@@ -184,11 +186,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("asinh",
|
||||
_("Arcsine"),
|
||||
_("Arcsine"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"asinh", _("Arcsine"), _("Arcsine"), "", "res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
@@ -218,11 +217,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("cbrt",
|
||||
_("Cube root"),
|
||||
_("Cube root"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"cbrt", _("Cube root"), _("Cube root"), "", "res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
@@ -260,12 +256,13 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("Expression"), "", true);
|
||||
|
||||
extension
|
||||
.AddExpression("cos",
|
||||
_("Cosine"),
|
||||
_("Cosine of an angle (in radian). "
|
||||
"If you want to use degrees, use`ToRad`: `sin(ToRad(45))`."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"cos",
|
||||
_("Cosine"),
|
||||
_("Cosine of an angle (in radian). "
|
||||
"If you want to use degrees, use`ToRad`: `sin(ToRad(45))`."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
@@ -293,29 +290,20 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("int",
|
||||
_("Round"),
|
||||
_("Round a number"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"int", _("Round"), _("Round a number"), "", "res/mathfunction.png")
|
||||
.SetHidden()
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("rint",
|
||||
_("Round"),
|
||||
_("Round a number"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"rint", _("Round"), _("Round a number"), "", "res/mathfunction.png")
|
||||
.SetHidden()
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("round",
|
||||
_("Round"),
|
||||
_("Round a number"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"round", _("Round"), _("Round a number"), "", "res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
@@ -324,8 +312,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
_("Round a number to the Nth decimal place"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"))
|
||||
.AddParameter("expression", _("Expression"), "", true);
|
||||
.AddParameter("expression", _("Number to Round"))
|
||||
.AddParameter("expression", _("Decimal Places"), "", true);
|
||||
|
||||
extension
|
||||
.AddExpression("exp",
|
||||
@@ -336,19 +324,13 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("log",
|
||||
_("Logarithm"),
|
||||
_("Logarithm"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"log", _("Logarithm"), _("Logarithm"), "", "res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("ln",
|
||||
_("Logarithm"),
|
||||
_("Logarithm"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"ln", _("Logarithm"), _("Logarithm"), "", "res/mathfunction.png")
|
||||
.SetHidden()
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
@@ -387,11 +369,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("The exponent (n in x^n)"));
|
||||
|
||||
extension
|
||||
.AddExpression("sec",
|
||||
_("Secant"),
|
||||
_("Secant"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"sec", _("Secant"), _("Secant"), "", "res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
@@ -403,12 +382,13 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("sin",
|
||||
_("Sine"),
|
||||
_("Sine of an angle (in radian). "
|
||||
"If you want to use degrees, use`ToRad`: `sin(ToRad(45))`."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"sin",
|
||||
_("Sine"),
|
||||
_("Sine of an angle (in radian). "
|
||||
"If you want to use degrees, use`ToRad`: `sin(ToRad(45))`."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
@@ -428,12 +408,13 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("tan",
|
||||
_("Tangent"),
|
||||
_("Tangent of an angle (in radian). "
|
||||
"If you want to use degrees, use`ToRad`: `tan(ToRad(45))`."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"tan",
|
||||
_("Tangent"),
|
||||
_("Tangent of an angle (in radian). "
|
||||
"If you want to use degrees, use`ToRad`: `tan(ToRad(45))`."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
@@ -463,26 +444,28 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("x (in a+(b-a)*x)"));
|
||||
|
||||
extension
|
||||
.AddExpression("XFromAngleAndDistance",
|
||||
_("X position from angle and distance"),
|
||||
_("Compute the X position when given an angle and distance "
|
||||
"relative to the origin (0;0). This is also known as "
|
||||
"getting the cartesian coordinates of a 2D vector, using "
|
||||
"its polar coordinates."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"XFromAngleAndDistance",
|
||||
_("X position from angle and distance"),
|
||||
_("Compute the X position when given an angle and distance "
|
||||
"relative to the origin (0;0). This is also known as "
|
||||
"getting the cartesian coordinates of a 2D vector, using "
|
||||
"its polar coordinates."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddParameter("expression", _("Angle, in degrees"))
|
||||
.AddParameter("expression", _("Distance"));
|
||||
|
||||
extension
|
||||
.AddExpression("YFromAngleAndDistance",
|
||||
_("Y position from angle and distance"),
|
||||
_("Compute the Y position when given an angle and distance "
|
||||
"relative to the origin (0;0). This is also known as "
|
||||
"getting the cartesian coordinates of a 2D vector, using "
|
||||
"its polar coordinates."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"YFromAngleAndDistance",
|
||||
_("Y position from angle and distance"),
|
||||
_("Compute the Y position when given an angle and distance "
|
||||
"relative to the origin (0;0). This is also known as "
|
||||
"getting the cartesian coordinates of a 2D vector, using "
|
||||
"its polar coordinates."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddParameter("expression", _("Angle, in degrees"))
|
||||
.AddParameter("expression", _("Distance"));
|
||||
|
||||
@@ -497,7 +480,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
extension
|
||||
.AddExpression("lerpAngle",
|
||||
_("Lerp (Linear interpolation) between two angles"),
|
||||
_("Linearly interpolates between two angles (in degrees) by taking the shortest direction around the circle."),
|
||||
_("Linearly interpolates between two angles (in degrees) "
|
||||
"by taking the shortest direction around the circle."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddParameter("expression", _("Starting angle, in degrees"))
|
||||
|
@@ -818,7 +818,7 @@ module.exports = {
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('yesorno', _('Treat as bullet?'), '', false)
|
||||
.addParameter('yesorno', _('Treat as bullet'), '', false)
|
||||
.setDefaultValue('false')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setBullet');
|
||||
@@ -852,7 +852,7 @@ module.exports = {
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('yesorno', _('Fixed rotation?'), '', false)
|
||||
.addParameter('yesorno', _('Fixed rotation'), '', false)
|
||||
.setDefaultValue('false')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setFixedRotation');
|
||||
@@ -886,7 +886,7 @@ module.exports = {
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('yesorno', _('Can sleep?'), '', false)
|
||||
.addParameter('yesorno', _('Can sleep'), '', false)
|
||||
.setDefaultValue('true')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setSleepingAllowed');
|
||||
@@ -1296,7 +1296,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Layer (1 - 16)'))
|
||||
.addParameter('yesorno', _('Enable?'), '', false)
|
||||
.addParameter('yesorno', _('Enable'), '', false)
|
||||
.setDefaultValue('true')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enableLayer');
|
||||
@@ -1332,7 +1332,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Mask (1 - 16)'))
|
||||
.addParameter('yesorno', _('Enable?'), '', false)
|
||||
.addParameter('yesorno', _('Enable'), '', false)
|
||||
.setDefaultValue('true')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enableMask');
|
||||
@@ -2409,7 +2409,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Joint ID'))
|
||||
.addParameter('yesorno', _('Enable?'))
|
||||
.addParameter('yesorno', _('Enable'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enableRevoluteJointLimits');
|
||||
|
||||
@@ -2488,7 +2488,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Joint ID'))
|
||||
.addParameter('yesorno', _('Enable?'))
|
||||
.addParameter('yesorno', _('Enable'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enableRevoluteJointMotor');
|
||||
|
||||
@@ -2727,7 +2727,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Joint ID'))
|
||||
.addParameter('yesorno', _('Enable?'))
|
||||
.addParameter('yesorno', _('Enable'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enablePrismaticJointLimits');
|
||||
|
||||
@@ -2806,7 +2806,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Joint ID'))
|
||||
.addParameter('yesorno', _('Enable?'))
|
||||
.addParameter('yesorno', _('Enable'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enablePrismaticJointMotor');
|
||||
|
||||
@@ -3486,7 +3486,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Joint ID'))
|
||||
.addParameter('yesorno', _('Enable?'))
|
||||
.addParameter('yesorno', _('Enable'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enableWheelJointMotor');
|
||||
|
||||
|
@@ -274,7 +274,7 @@ module.exports = {
|
||||
.setLabel('Fixed Rotation')
|
||||
.setDescription(
|
||||
_(
|
||||
"If enabled, the object won't rotate and will stay at the same angle. Useful for characters for example."
|
||||
"If enabled, the object won't rotate and will stay at the same angle."
|
||||
)
|
||||
)
|
||||
.setGroup(_('Movement'));
|
||||
@@ -845,7 +845,7 @@ module.exports = {
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
|
||||
.addParameter('yesorno', _('Treat as bullet?'), '', false)
|
||||
.addParameter('yesorno', _('Treat as bullet'), '', false)
|
||||
.setDefaultValue('false')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setBullet');
|
||||
@@ -870,7 +870,7 @@ module.exports = {
|
||||
'SetFixedRotation',
|
||||
_('Fixed rotation'),
|
||||
_(
|
||||
"Enable or disable an object fixed rotation. If enabled the object won't be able to rotate."
|
||||
"Enable or disable an object fixed rotation. If enabled the object won't be able to rotate. This action has no effect on characters."
|
||||
),
|
||||
_('Set _PARAM0_ fixed rotation: _PARAM2_'),
|
||||
_('Dynamics'),
|
||||
@@ -879,7 +879,7 @@ module.exports = {
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
|
||||
.addParameter('yesorno', _('Fixed rotation?'), '', false)
|
||||
.addParameter('yesorno', _('Fixed rotation'), '', false)
|
||||
.setDefaultValue('false')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setFixedRotation');
|
||||
@@ -1054,7 +1054,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
|
||||
.addParameter('expression', _('Layer (1 - 8)'))
|
||||
.addParameter('yesorno', _('Enable?'), '', false)
|
||||
.addParameter('yesorno', _('Enable'), '', false)
|
||||
.setDefaultValue('true')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enableLayer');
|
||||
@@ -1090,7 +1090,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
|
||||
.addParameter('expression', _('Mask (1 - 8)'))
|
||||
.addParameter('yesorno', _('Enable?'), '', false)
|
||||
.addParameter('yesorno', _('Enable'), '', false)
|
||||
.setDefaultValue('true')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enableMask');
|
||||
@@ -1270,7 +1270,7 @@ module.exports = {
|
||||
.addParameter('expression', _('Application point on Z axis'))
|
||||
.setParameterLongDescription(
|
||||
_(
|
||||
'Use `MassCenterX` and `MassCenterY` expressions to avoid any rotation.'
|
||||
'Use `MassCenterX`, `MassCenterY` and `MassCenterZ` expressions to avoid any rotation.'
|
||||
)
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
@@ -1544,6 +1544,19 @@ module.exports = {
|
||||
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('getMassCenterY');
|
||||
|
||||
aut
|
||||
.addExpression(
|
||||
'MassCenterZ',
|
||||
_('Mass center Z'),
|
||||
_('Mass center Z'),
|
||||
'',
|
||||
'JsPlatform/Extensions/physics3d.svg'
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('getMassCenterZ');
|
||||
}
|
||||
// Collision
|
||||
extension
|
||||
|
@@ -927,9 +927,7 @@ namespace gdjs {
|
||||
const angularVelocityY = angularVelocity.GetY();
|
||||
const angularVelocityZ = angularVelocity.GetZ();
|
||||
|
||||
let bodyID = this._body.GetID();
|
||||
bodyInterface.RemoveBody(bodyID);
|
||||
bodyInterface.DestroyBody(bodyID);
|
||||
this.bodyUpdater.destroyBody();
|
||||
this._contactsEndedThisFrame.length = 0;
|
||||
this._contactsStartedThisFrame.length = 0;
|
||||
this._currentContacts.length = 0;
|
||||
@@ -938,7 +936,7 @@ namespace gdjs {
|
||||
if (!this._body) {
|
||||
return;
|
||||
}
|
||||
bodyID = this._body.GetID();
|
||||
const bodyID = this._body.GetID();
|
||||
bodyInterface.SetLinearVelocity(
|
||||
bodyID,
|
||||
this.getVec3(linearVelocityX, linearVelocityY, linearVelocityZ)
|
||||
|
@@ -733,7 +733,7 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
setWheelOffsetZ(wheelOffsetZ: float): void {
|
||||
this._wheelOffsetY = wheelOffsetZ;
|
||||
this._wheelOffsetZ = wheelOffsetZ;
|
||||
this._updateWheels();
|
||||
}
|
||||
|
||||
@@ -783,11 +783,11 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
hasFrontWheelDrive(): boolean {
|
||||
return this._hasBackWheelDrive;
|
||||
return this._hasFrontWheelDrive;
|
||||
}
|
||||
|
||||
setFrontWheelDrive(hasFrontWheelDrive: boolean): void {
|
||||
this._hasBackWheelDrive = hasFrontWheelDrive;
|
||||
this._hasFrontWheelDrive = hasFrontWheelDrive;
|
||||
this.invalidateShape();
|
||||
}
|
||||
|
||||
|
@@ -80,6 +80,8 @@ type Props = {
|
||||
isAutoProcessingFunctionCalls: boolean,
|
||||
setAutoProcessFunctionCalls: boolean => void,
|
||||
onStartNewChat: () => void,
|
||||
onRestoreInitialProject?: () => Promise<void>,
|
||||
isCloudProject?: boolean,
|
||||
|
||||
onProcessFunctionCalls: (
|
||||
functionCalls: Array<AiRequestMessageAssistantFunctionCall>,
|
||||
@@ -234,6 +236,8 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
onSendMessage,
|
||||
onSendFeedback,
|
||||
onStartNewChat,
|
||||
onRestoreInitialProject,
|
||||
isCloudProject,
|
||||
quota,
|
||||
increaseQuotaOffering,
|
||||
lastSendError,
|
||||
@@ -249,6 +253,7 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
}: Props,
|
||||
ref
|
||||
) => {
|
||||
const [isRestoring, setIsRestoring] = React.useState(false);
|
||||
// TODO: store the default mode in the user preferences?
|
||||
const [newAiRequestMode, setNewAiRequestMode] = React.useState<
|
||||
'chat' | 'agent'
|
||||
@@ -634,6 +639,41 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
})}
|
||||
>
|
||||
<ScrollView ref={scrollViewRef} style={styles.chatScrollView}>
|
||||
{aiRequest &&
|
||||
aiRequest.mode === 'agent' &&
|
||||
aiRequest.initialProjectVersionId &&
|
||||
onRestoreInitialProject &&
|
||||
isCloudProject && (
|
||||
<Paper background="dark" variant="outlined" style={{ marginBottom: 8 }}>
|
||||
<Column>
|
||||
<LineStackLayout
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<Text size="body" color="secondary" noMargin>
|
||||
<Trans>Click here to restore the project as it was at the beginning</Trans>
|
||||
</Text>
|
||||
<RaisedButton
|
||||
size="small"
|
||||
color="secondary"
|
||||
label={<Trans>Restore project</Trans>}
|
||||
disabled={isRestoring}
|
||||
onClick={async () => {
|
||||
if (!onRestoreInitialProject) return;
|
||||
setIsRestoring(true);
|
||||
try {
|
||||
await onRestoreInitialProject();
|
||||
} catch (error) {
|
||||
console.error('Error in restore button:', error);
|
||||
} finally {
|
||||
setIsRestoring(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</LineStackLayout>
|
||||
</Column>
|
||||
</Paper>
|
||||
)}
|
||||
<ChatMessages
|
||||
aiRequest={aiRequest}
|
||||
onSendFeedback={onSendFeedback}
|
||||
|
@@ -19,6 +19,8 @@ import AuthenticatedUserContext from '../Profile/AuthenticatedUserContext';
|
||||
import { Toolbar } from './Toolbar';
|
||||
import { AskAiHistory } from './AskAiHistory';
|
||||
import { makeSimplifiedProjectBuilder } from '../EditorFunctions/SimplifiedProject/SimplifiedProject';
|
||||
import { type MessageDescriptor } from '../Utils/i18n/MessageDescriptor.flow';
|
||||
import { t } from '@lingui/macro';
|
||||
import {
|
||||
canUpgradeSubscription,
|
||||
hasValidSubscriptionPlan,
|
||||
@@ -454,6 +456,13 @@ type Props = {|
|
||||
storageProvider: ?StorageProvider,
|
||||
setToolbar: (?React.Node) => void,
|
||||
i18n: I18nType,
|
||||
onSave?: () => Promise<void>,
|
||||
onOpenCloudProjectOnSpecificVersion?: ({|
|
||||
fileMetadata: FileMetadata,
|
||||
versionId: string,
|
||||
ignoreUnsavedChanges: boolean,
|
||||
openingMessage: MessageDescriptor,
|
||||
|}) => Promise<void>,
|
||||
onCreateEmptyProject: (newProjectSetup: NewProjectSetup) => Promise<void>,
|
||||
onCreateProjectFromExample: (
|
||||
exampleShortHeader: ExampleShortHeader,
|
||||
@@ -509,6 +518,8 @@ export const AskAiEditor = React.memo<Props>(
|
||||
fileMetadata,
|
||||
storageProvider,
|
||||
i18n,
|
||||
onSave,
|
||||
onOpenCloudProjectOnSpecificVersion,
|
||||
onCreateEmptyProject,
|
||||
onCreateProjectFromExample,
|
||||
onOpenLayout,
|
||||
@@ -520,8 +531,9 @@ export const AskAiEditor = React.memo<Props>(
|
||||
const editorCallbacks: EditorCallbacks = React.useMemo(
|
||||
() => ({
|
||||
onOpenLayout,
|
||||
onSave,
|
||||
}),
|
||||
[onOpenLayout]
|
||||
[onOpenLayout, onSave]
|
||||
);
|
||||
|
||||
const {
|
||||
@@ -554,6 +566,34 @@ export const AskAiEditor = React.memo<Props>(
|
||||
[setSelectedAiRequestId]
|
||||
);
|
||||
|
||||
const onRestoreInitialProject = React.useCallback(
|
||||
async () => {
|
||||
if (!selectedAiRequest || !selectedAiRequest.initialProjectVersionId || !fileMetadata || !onOpenCloudProjectOnSpecificVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only restore for cloud projects
|
||||
if (!storageProvider || storageProvider.internalName !== 'Cloud') {
|
||||
console.warn('Project restoration is only available for cloud projects');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await onOpenCloudProjectOnSpecificVersion({
|
||||
fileMetadata,
|
||||
versionId: selectedAiRequest.initialProjectVersionId,
|
||||
ignoreUnsavedChanges: true,
|
||||
openingMessage: i18n._(t`Restoring project to initial state...`),
|
||||
});
|
||||
|
||||
console.info('Project restored to initial version');
|
||||
} catch (error) {
|
||||
console.error('Error restoring project to initial version:', error);
|
||||
}
|
||||
},
|
||||
[selectedAiRequest, fileMetadata, onOpenCloudProjectOnSpecificVersion, storageProvider, i18n]
|
||||
);
|
||||
|
||||
const onOpenHistory = React.useCallback(() => {
|
||||
setIsHistoryOpen(true);
|
||||
}, []);
|
||||
@@ -695,6 +735,20 @@ export const AskAiEditor = React.memo<Props>(
|
||||
|
||||
// Request is now ready to be started.
|
||||
try {
|
||||
// For agent mode on cloud projects, save the project and store initial version
|
||||
let initialProjectVersionId = null;
|
||||
if (mode === 'agent' && project && onSave && fileMetadata && storageProvider?.internalName === 'Cloud') {
|
||||
try {
|
||||
// Save the project first to create a version
|
||||
await onSave();
|
||||
// Store the current version ID for restoration
|
||||
initialProjectVersionId = fileMetadata.version || null;
|
||||
} catch (error) {
|
||||
console.error('Error saving project before starting AI agent:', error);
|
||||
// Continue anyway, but without initial version storage
|
||||
}
|
||||
}
|
||||
|
||||
const simplifiedProjectBuilder = makeSimplifiedProjectBuilder(gd);
|
||||
const simplifiedProjectJson = project
|
||||
? JSON.stringify(
|
||||
@@ -728,7 +782,13 @@ export const AskAiEditor = React.memo<Props>(
|
||||
|
||||
console.info('Successfully created a new AI request:', aiRequest);
|
||||
setSendingAiRequest(null, false);
|
||||
updateAiRequest(aiRequest.id, aiRequest);
|
||||
|
||||
// Add the initial project version to the AI request for local storage
|
||||
const aiRequestWithInitialVersion = {
|
||||
...aiRequest,
|
||||
initialProjectVersionId,
|
||||
};
|
||||
updateAiRequest(aiRequest.id, aiRequestWithInitialVersion);
|
||||
|
||||
// Select the new AI request just created - unless the user switched to another one
|
||||
// in the meantime.
|
||||
@@ -1027,6 +1087,8 @@ export const AskAiEditor = React.memo<Props>(
|
||||
i18n={i18n}
|
||||
editorCallbacks={editorCallbacks}
|
||||
onStartNewChat={onStartNewChat}
|
||||
onRestoreInitialProject={onRestoreInitialProject}
|
||||
isCloudProject={storageProvider?.internalName === 'Cloud'}
|
||||
/>
|
||||
</div>
|
||||
</Paper>
|
||||
@@ -1068,6 +1130,8 @@ export const renderAskAiEditorContainer = (
|
||||
storageProvider={props.storageProvider}
|
||||
setToolbar={props.setToolbar}
|
||||
isActive={props.isActive}
|
||||
onSave={props.onSave}
|
||||
onOpenCloudProjectOnSpecificVersion={props.onOpenCloudProjectOnSpecificVersion}
|
||||
onCreateEmptyProject={props.onCreateEmptyProject}
|
||||
onCreateProjectFromExample={props.onCreateProjectFromExample}
|
||||
onOpenLayout={props.onOpenLayout}
|
||||
|
@@ -110,6 +110,7 @@ export type EditorCallbacks = {|
|
||||
| 'none',
|
||||
|}
|
||||
) => void,
|
||||
onSave?: () => Promise<void>,
|
||||
|};
|
||||
|
||||
/**
|
||||
|
@@ -4028,6 +4028,7 @@ const MainFrame = (props: Props) => {
|
||||
hotReloadPreviewButtonProps,
|
||||
resourceManagementProps,
|
||||
onSave: saveProject,
|
||||
onOpenCloudProjectOnSpecificVersion,
|
||||
canSave,
|
||||
onCreateEventsFunction,
|
||||
openInstructionOrExpression,
|
||||
|
@@ -88,6 +88,7 @@ export type AiRequest = {
|
||||
|
||||
lastUserMessagePriceInCredits?: number | null,
|
||||
totalPriceInCredits?: number | null,
|
||||
initialProjectVersionId?: string | null,
|
||||
};
|
||||
|
||||
export type AiGeneratedEventStats = {
|
||||
|
Reference in New Issue
Block a user