diff --git a/src/lib/ai/mutations.ts b/src/lib/ai/mutations.ts index a249b65..7c71a80 100644 --- a/src/lib/ai/mutations.ts +++ b/src/lib/ai/mutations.ts @@ -131,6 +131,26 @@ export function mutateCreateLayer( layer.contentOffset = input.contentOffset; } + // Set enter/exit transitions if provided + if (input.enterTransition) { + const preset = getPresetById(input.enterTransition.presetId); + if (preset) { + layer.enterTransition = { + presetId: input.enterTransition.presetId, + duration: input.enterTransition.duration + }; + } + } + if (input.exitTransition) { + const preset = getPresetById(input.exitTransition.presetId); + if (preset) { + layer.exitTransition = { + presetId: input.exitTransition.presetId, + duration: input.exitTransition.duration + }; + } + } + // Mutate project ctx.project.layers.push(layer); @@ -252,6 +272,34 @@ export function mutateEditLayer(ctx: MutationContext, input: EditLayerInput): Ed layer.contentOffset = Math.max(0, input.contentOffset); } + // Update enter/exit transitions + if (input.enterTransition !== undefined) { + if (input.enterTransition) { + const preset = getPresetById(input.enterTransition.presetId); + if (preset) { + layer.enterTransition = { + presetId: input.enterTransition.presetId, + duration: input.enterTransition.duration + }; + } + } else { + layer.enterTransition = undefined; + } + } + if (input.exitTransition !== undefined) { + if (input.exitTransition) { + const preset = getPresetById(input.exitTransition.presetId); + if (preset) { + layer.exitTransition = { + presetId: input.exitTransition.presetId, + duration: input.exitTransition.duration + }; + } + } else { + layer.exitTransition = undefined; + } + } + return { success: true, layerId: resolvedId, diff --git a/src/lib/ai/schemas.ts b/src/lib/ai/schemas.ts index 21bd425..8ea98f7 100644 --- a/src/lib/ai/schemas.ts +++ b/src/lib/ai/schemas.ts @@ -45,6 +45,19 @@ const TimingFieldsSchema = z.object({ .describe('Start offset for trimming media content (seconds)') }); +// ============================================ +// Layer Transition (enter/exit preset) +// ============================================ + +const LayerTransitionFieldSchema = z + .object({ + presetId: AnimationPresetIdSchema.describe('Animation preset ID to apply as transition'), + duration: z.number().positive().describe('Transition duration in seconds (typically 0.3-0.8)') + }) + .describe( + 'Automatic animation applied at layer enter/exit. Position presets are relative offsets from the layer base position. Scale/opacity are factors (0→1 means invisible→visible).' + ); + // ============================================ // Layer Type + Props Union // ============================================ @@ -123,9 +136,17 @@ export const CreateLayerInputSchema = z // Layer type and properties layer: LayerTypePropsUnion, - // Animation (preset OR custom keyframes) + // Transitions (automatic enter/exit animations, no keyframes created) + enterTransition: LayerTransitionFieldSchema.optional().describe( + 'Auto-play animation when layer enters (e.g., fade-in, slide-in-left). Applied as offset/factor on base transform.' + ), + exitTransition: LayerTransitionFieldSchema.optional().describe( + 'Auto-play animation when layer exits (e.g., fade-out, slide-out-right). Applied as offset/factor on base transform.' + ), + + // Animation (preset OR custom keyframes - baked as keyframes) animation: CreateLayerAnimationSchema.optional().describe( - 'Animation: preset OR custom keyframes' + 'Animation as keyframes: preset (baked at startTime) OR custom keyframes' ) }) .refine( @@ -199,7 +220,15 @@ export const EditLayerInputSchema = z .describe('Layer-specific properties to update (merged with existing)'), // Timing fields - ...TimingFieldsSchema.shape + ...TimingFieldsSchema.shape, + + // Transitions (automatic enter/exit animations) + enterTransition: LayerTransitionFieldSchema.optional().describe( + 'Set enter transition preset. Omit to keep existing, set to null to remove.' + ), + exitTransition: LayerTransitionFieldSchema.optional().describe( + 'Set exit transition preset. Omit to keep existing, set to null to remove.' + ) }) .refine( (data) => { @@ -295,11 +324,12 @@ export const animationTools = { - Position, rotation, scale, anchor point - Layer-specific props (text content, colors, sizes, etc.) - Style (opacity, blur, filters, drop shadow) -- Animation via preset OR custom keyframes +- Enter/exit transitions (automatic preset animations at layer boundaries) +- Animation via preset (baked as keyframes) OR custom keyframes - Timing (enter/exit times, content duration/offset) -Example: Create a text layer with animation: -{ "layer": { "type": "text", "props": { "content": "Hello World", "fontSize": 48, "fill": "#ffffff" } }, "transform": { "position": { "x": 0, "y": -200 } }, "animation": { "preset": { "id": "fade-in", "startTime": 0, "duration": 0.5 } } }`, +Example with enter transition: +{ "layer": { "type": "text", "props": { "content": "Hello", "fontSize": 48, "fill": "#fff" } }, "transform": { "position": { "x": 0, "y": -200 } }, "enterTransition": { "presetId": "fade-in", "duration": 0.5 }, "exitTransition": { "presetId": "fade-out", "duration": 0.3 } }`, inputSchema: CreateLayerInputSchema }), @@ -308,6 +338,7 @@ Example: Create a text layer with animation: - Provide only the fields you want to change - transform/style sections replace entire object if provided - props are merged with existing props +- enterTransition/exitTransition: set automatic enter/exit animations - Use layer ID from create_layer response or layer name`, inputSchema: EditLayerInputSchema }), diff --git a/src/lib/ai/system-prompt.ts b/src/lib/ai/system-prompt.ts index 8e0eb37..dc7587a 100644 --- a/src/lib/ai/system-prompt.ts +++ b/src/lib/ai/system-prompt.ts @@ -3,9 +3,11 @@ * * Tools: create_layer, edit_layer, remove_layer, configure_project, group_layers, ungroup_layers * Animation: can use preset AND/OR custom keyframes in create_layer. + * Transitions: can set enterTransition/exitTransition on layers via edit_layer. */ import type { Project } from '$lib/types/animation'; import { projectDataSchema } from '$lib/schemas/animation'; +import { getPresetsSummaryForAI } from '$lib/engine/presets'; import goodExampleRaw from '$lib/good-example.json'; const exampleProject = projectDataSchema.parse(goodExampleRaw); @@ -29,12 +31,47 @@ Steps: present plan (if full video) → execute tool calls → conclude with a f ## Tools -- **create_layer**: Layer type + props + transform + style + animation + timing -- **edit_layer**: Modify existing layer (provide fields to change) +- **create_layer**: Layer type + props + transform + style + animation + timing + enterTransition/exitTransition +- **edit_layer**: Modify existing layer (provide fields to change, including enterTransition/exitTransition) - **remove_layer**: Delete layer by ID or name - **configure_project**: Project settings (name, dimensions, duration, background) - **group_layers** / **ungroup_layers**: Group/ungroup layers +## Animation System + +Two complementary ways to animate layers: + +### 1. Transitions (enterTransition / exitTransition) +Automatic enter/exit animations applied at runtime (no keyframes created). +Set via create_layer or edit_layer. Position presets are relative offsets from the layer's base position. +Scale and opacity presets are applied as factors (e.g., scale 0→1 means from invisible to full size). +When the transition finishes, the layer returns to its base values. + +\`\`\`json +{ "enterTransition": { "presetId": "fade-in", "duration": 0.5 } } +{ "exitTransition": { "presetId": "slide-out-left", "duration": 0.3 } } +\`\`\` + +### 2. Keyframe Presets (animation.preset in create_layer) +Bakes preset keyframes onto the layer at a specific time. Good for emphasis effects or custom timing. +Position values are added as offsets to the layer's current base position. + +\`\`\`json +{ "animation": { "preset": { "id": "pulse", "startTime": 1.0, "duration": 0.5 } } } +\`\`\` + +### 3. Custom Keyframes (animation.keyframes in create_layer) +Full control over individual property animations. Can be combined with presets. + +### When to use which: +- **Transitions**: Best for entrance/exit effects. No keyframes cluttering the timeline. Automatically tied to layer enter/exit time. +- **Keyframe presets**: Best for emphasis effects (pulse, shake, bounce) at specific moments. +- **Custom keyframes**: Best for unique, hand-crafted animations. + +## Available Presets + +${getPresetsSummaryForAI()} + ## Graphic Style - MANDATORY DIRECTIVES ### Backgrounds @@ -106,8 +143,8 @@ ${JSON.stringify(exampleProject)} When the user gives no specific animation directions, apply at minimum: -- **Entrances/Exits**: Fade in from opacity:0 + slight scale (0.95→1) + blur (filter.blur 8→0). Add a gentle slide (20-40px) from a direction. Reverse for exits. -- **Text layers**: At minimum, appear from opacity:0 with a subtle slide-up on Y (~20px). Titles can add scale+blur for more impact. +- **Entrances/Exits**: Use enterTransition/exitTransition with appropriate presets (fade-in, slide-in-*, scale-in for entrances; fade-out, slide-out-*, scale-out for exits). Default duration 0.3-0.6s. +- **Text layers**: At minimum, use enterTransition with fade-in or slide-in-bottom (duration 0.4s). Titles can use pop or bounce-in for more impact. - **Flat backgrounds**: If a background feels too plain, add 1-2 decorative circle shapes (≈200x200, high blur ≈150, low opacity ≈0.15-0.25) as soft ambient blobs with gentle position drift. These are sensible defaults — override freely when the user provides specific creative direction. diff --git a/src/lib/components/ai/model-selector.svelte b/src/lib/components/ai/model-selector.svelte index 8db0d44..e366f1d 100644 --- a/src/lib/components/ai/model-selector.svelte +++ b/src/lib/components/ai/model-selector.svelte @@ -1,5 +1,5 @@ + + + + + + +
+ {#each options as preset (preset.id)} + {@const isSelected = value === preset.id} + {@const isHovered = hoveredPresetId === preset.id} + + {/each} +
+
+
diff --git a/src/lib/components/editor/panels/properties/groups/animation-presets-group.svelte b/src/lib/components/editor/panels/properties/groups/animation-presets-group.svelte index 393839b..a344159 100644 --- a/src/lib/components/editor/panels/properties/groups/animation-presets-group.svelte +++ b/src/lib/components/editor/panels/properties/groups/animation-presets-group.svelte @@ -1,11 +1,13 @@ - -