Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 24 additions & 42 deletions src/lib/ai/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import type {
RemoveKeyframeInput,
RemoveKeyframeOutput
} from './schemas';
import type { ProjectData } from '$lib/schemas/animation';
import type { Layer, ProjectData } from '$lib/schemas/animation';

/**
* Context for resolving layer IDs (e.g. "layer_0" -> "actual-uuid")
Expand Down Expand Up @@ -122,7 +122,7 @@ export function mutateCreateLayer(
try {
const layer = createLayer(input.type, {
props: input.props,
trasform: input.position,
transform: input.transform,
projectDimensions: {
width: ctx.project.width,
height: ctx.project.height
Expand Down Expand Up @@ -290,31 +290,18 @@ export function mutateEditLayer(ctx: MutationContext, input: EditLayerInput): Ed
const layer = ctx.project.layers[layerIndex];

try {
if (input.updates.name !== undefined) layer.name = input.updates.name;
if (input.updates.visible !== undefined) layer.visible = input.updates.visible;
if (input.updates.locked !== undefined) layer.locked = input.updates.locked;

if (input.updates.position) {
layer.transform.x = input.updates.position.x ?? layer.transform.x;
layer.transform.y = input.updates.position.y ?? layer.transform.y;
layer.transform.z = input.updates.position.z ?? layer.transform.z;
if (input.updates.name !== undefined) {
layer.name = input.updates.name;
}

if (input.updates.scale) {
layer.transform.scaleX = input.updates.scale.x ?? layer.transform.scaleX;
layer.transform.scaleY = input.updates.scale.y ?? layer.transform.scaleY;
if (input.updates.visible !== undefined) {
layer.visible = input.updates.visible;
}

if (input.updates.rotation !== undefined) {
layer.transform.rotationZ = (input.updates.rotation * Math.PI) / 180;
if (input.updates.locked !== undefined) {
layer.locked = input.updates.locked;
}

// Handle 3D rotation (rotationX and rotationY in degrees)
if (input.updates.rotationX !== undefined) {
layer.transform.rotationX = (input.updates.rotationX * Math.PI) / 180;
}
if (input.updates.rotationY !== undefined) {
layer.transform.rotationY = (input.updates.rotationY * Math.PI) / 180;
if (input.updates.transform) {
layer.transform = { ...layer.transform, ...input.updates.transform };
}

// Handle anchor point
Expand Down Expand Up @@ -445,19 +432,14 @@ export function mutateGroupLayers(
const groupId = nanoid();

// Create the group layer
const groupLayer = {
const groupLayer: Layer = {
id: groupId,
name: input.name ?? 'Group',
type: 'group' as const,
transform: {
x: 0,
y: 0,
z: 0,
rotationX: 0,
rotationY: 0,
rotationZ: 0,
scaleX: 1,
scaleY: 1,
position: { x: 0, y: 0, z: 0 },
rotation: { x: 0, y: 0, z: 0 },
scale: { x: 1, y: 1 },
anchor: 'center' as const
},
style: { opacity: 1 },
Expand Down Expand Up @@ -528,14 +510,14 @@ export function mutateUngroupLayers(
for (const layer of ctx.project.layers) {
if (layer.parentId === resolvedId) {
layer.parentId = undefined;
layer.transform.x += gt.x;
layer.transform.y += gt.y;
layer.transform.z += gt.z;
layer.transform.rotationX += gt.rotationX;
layer.transform.rotationY += gt.rotationY;
layer.transform.rotationZ += gt.rotationZ;
layer.transform.scaleX *= gt.scaleX;
layer.transform.scaleY *= gt.scaleY;
layer.transform.position.x += gt.position.x;
layer.transform.position.y += gt.position.y;
layer.transform.position.z += gt.position.z;
layer.transform.rotation.x += gt.rotation.x;
layer.transform.rotation.y += gt.rotation.y;
layer.transform.rotation.z += gt.rotation.z;
layer.transform.scale.x *= gt.scale.x;
layer.transform.scale.y *= gt.scale.y;
layer.style.opacity *= gs.opacity;
}
}
Expand Down Expand Up @@ -732,9 +714,9 @@ function applyPresetToProject(

let value = kf.value;
if (kf.property === 'position.x' && typeof kf.value === 'number') {
value = layer.transform.x + kf.value;
value = layer.transform.position.x + kf.value;
} else if (kf.property === 'position.y' && typeof kf.value === 'number') {
value = layer.transform.y + kf.value;
value = layer.transform.position.y + kf.value;
}

addKeyframeToProject(project, layerId, {
Expand Down
38 changes: 5 additions & 33 deletions src/lib/ai/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
* Each layer type has its own creation tool (create_text_layer, create_icon_layer, etc.)
*/
import { z } from 'zod';
import { InterpolationSchema } from '$lib/schemas/animation';
import { InterpolationSchema, TransformSchema } from '$lib/schemas/animation';
import { getPresetIds } from '$lib/engine/presets';
import { tool, type InferUITools, type Tool } from 'ai';
import { layerRegistry, getAvailableLayerTypes } from '$lib/layers/registry';
import { extractDefaultValues } from '$lib/layers/base';
import { AnchorPointSchema, extractDefaultValues } from '$lib/layers/base';

// ============================================
// Helper Functions
Expand Down Expand Up @@ -123,7 +123,7 @@ const CreateLayerInputSchema = z
name: z.string().optional().describe('Layer name for identification'),
visible: z.boolean().optional().default(true).describe('Layer visibility (default: true)'),
locked: z.boolean().optional().default(false).describe('Layer locked state (default: false)'),
position: PositionSchema,
transform: TransformSchema,
animation: AnimationSchema
})
.extend(TimingFieldsSchema.shape)
Expand Down Expand Up @@ -241,36 +241,8 @@ export const EditLayerInputSchema = z.object({
name: z.string().optional(),
visible: z.boolean().optional(),
locked: z.boolean().optional(),
position: z
.object({
x: z.number().optional(),
y: z.number().optional(),
z: z.number().optional().describe('Z position (depth)')
})
.optional(),
scale: z
.object({
x: z.number().optional(),
y: z.number().optional()
})
.optional(),
rotation: z.number().optional().describe('Rotation around Z axis (degrees)'),
rotationX: z.number().optional().describe('Rotation around X axis (degrees)'),
rotationY: z.number().optional().describe('Rotation around Y axis (degrees)'),
anchor: z
.enum([
'top-left',
'top-center',
'top-right',
'center-left',
'center',
'center-right',
'bottom-left',
'bottom-center',
'bottom-right'
])
.optional()
.describe('Anchor point for transformations'),
transform: TransformSchema.optional(),
anchor: AnchorPointSchema.optional().describe('Anchor point for transformations'),
opacity: z.number().min(0).max(1).optional(),
props: z.record(z.string(), z.unknown()).optional()
})
Expand Down
10 changes: 5 additions & 5 deletions src/lib/ai/system-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ export function buildSystemPrompt(project: Project): string {

## Transform Capabilities

- **Position**: x, y (horizontal/vertical), z (depth)
- **Scale**: x, y (independent or use matching values for proportional scaling)
- **Rotation**: rotation (Z-axis in degrees), rotationX, rotationY (3D rotation in degrees)
- **Position**: position.x, position.y (horizontal/vertical), position.z (depth)
- **Scale**: scale.x, scale.y (independent or use matching values for proportional scaling)
- **Rotation**: rotation.z (Z-axis in radians), rotation.x, rotation.y (3D rotation in radians)
- **Anchor**: Set anchor point (top-left, top-center, top-right, center-left, center, center-right, bottom-left, bottom-center, bottom-right) to control which point of the layer is positioned at the transform coordinates

## Keyframe Management
Expand Down Expand Up @@ -115,15 +115,15 @@ function buildCanvasState(project: Project): string {
const kfList = kfs
.sort((a, b) => a.time - b.time)
.map(
(kf) => `t=${kf.time}s: ${JSON.stringify(kf.value)} (${kf.interpolation.strategy})`
(kf) => `t=${kf.time}s: ${JSON.stringify(kf.value)} (${kf.interpolation?.strategy})`
)
.join(', ');
keyframesDetail += `\n ${prop}: [${kfList}]`;
}
}

return `${index}. "${layer.name}" (id: "${layer.id}", type: ${layer.type})
pos: (${layer.transform.x}, ${layer.transform.y}) | scale: (${layer.transform.scaleX}, ${layer.transform.scaleY}) | rotation: ${layer.transform.rotationZ} rad | opacity: ${layer.style.opacity}
pos: (${layer.transform.position.x}, ${layer.transform.position.y}) | scale: (${layer.transform.scale.x}, ${layer.transform.scale.y}) | rotation: ${layer.transform.rotation.z} rad | opacity: ${layer.style.opacity}
props: {${propsPreview || 'none'}}${keyframesDetail || '\n keyframes: none'}`;
})
.join('\n\n');
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/ai/ai-chat.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
name: input.name,
visible: input.visible,
locked: input.locked,
position: input.position,
transform: input.transform,
props: input.props || {},
animation: input.animation,
enterTime: input.enterTime,
Expand Down
Loading